From d8c040b6af33f7192074b9f74161ba235f00548e Mon Sep 17 00:00:00 2001 From: TyroneAEM <147942284+TyroneAEM@users.noreply.github.com> Date: Fri, 28 Jun 2024 11:50:12 -0500 Subject: [PATCH] ASSETS-88922 : Review the Deliverable page load time (#130) * Improve page load time by parallelizing API calls and adding debug logs - Updated `decorate` function to fetch program details and deliverables in parallel using `Promise.all`. - Added console logs before and after API calls to help with debugging. - Maintained existing functionality while optimizing performance by reducing sequential API calls. * Remove logging messages * feat: Improve performance of gmo-program-details.js - Implement parallel GraphQL queries to reduce load times. - Lazy load images using the `loading="lazy"` attribute to improve initial load performance. - Batch DOM updates using `document.createDocumentFragment()` to minimize reflows and repaints. - Debounce event listeners to prevent performance issues caused by rapid firing of events. These optimizations should enhance the overall performance and responsiveness of the page. * Optimize header rendering and asynchronous data fetching - Initiate parallel fetching of program details and deliverables. - Render a placeholder header immediately to improve perceived load time. - Update header with actual data once program details are fetched. - Ensure images are loaded asynchronously without blocking header rendering. * feat: Inject additional HTML content into main-body-wrapper after header update - Added HTML content including tab-wrapper, overview, and deliverables sections to the main-body-wrapper div after the header. - Updated the main-body-wrapper div to include newly provided HTML content for improved user interface and navigation. - Ensured the content injection occurs after the header update to maintain correct rendering order. * Added back displaying the back button with arrow icon and program name not available message and no data available message when program * Updated logic to display the total asset count for an image * Reverted back to const programData = await executeQuery(programQueryString); const deliverables = await executeQuery(deliverableQueryString); * Restored constant variables for Marketing Goal, KPIs, Target Market, Audiences const p0TargetMarketArea = program.p0TargetMarketArea; const p1TargetMarketArea = program.p1TargetMarketArea; const kpis = buildKPIList(program).outerHTML; const targetMarketAreas = buildTargetMarketAreaList(p0TargetMarketArea,p1TargetMarketArea).outerHTML; const audiences = buildAudienceList(program).outerHTML; const artifactLinks = buildArtifactLinks(program).outerHTML; To make the code more readable, they were removed when the structure of the code was refactored to incrementally build the block.innerHTML, to make the page appear to load faster. --- .../gmo-program-details.js | 132 +++++++++++------- 1 file changed, 82 insertions(+), 50 deletions(-) diff --git a/blocks/gmo-program-details/gmo-program-details.js b/blocks/gmo-program-details/gmo-program-details.js index e493bcd7..8feafeb6 100644 --- a/blocks/gmo-program-details/gmo-program-details.js +++ b/blocks/gmo-program-details/gmo-program-details.js @@ -14,12 +14,47 @@ const platformMappings = getMappingArray('platforms'); export default async function decorate(block) { const encodedSemi = encodeURIComponent(';'); const encodedProgram = encodeURIComponent(programName); + + blockConfig = readBlockConfig(block); + const programQueryString = `getProgramDetails${encodedSemi}programName=${encodedProgram}${encodedSemi}programID=${encodeURIComponent(programID)}`; + const deliverableQueryString = `getProgramDeliverables${encodedSemi}programName=${encodedProgram}${encodedSemi}programID=${encodeURIComponent(programID)}`; + + // Immediately render a placeholder header + block.innerHTML = ` +
+ + Back +
+
+
Loading program details...
+
+ `; + + // Wait for program data to render the actual header const programData = await executeQuery(programQueryString); const program = programData.data.programList.items[0]; - blockConfig = readBlockConfig(block); const header = buildHeader(program, queryVars).outerHTML; - if (!program) { + + // Update the header with the actual data + block.querySelector('.placeholder-header').outerHTML = header; + + let imageObject = null; + let totalassets = 0; + if (program) { + try { + imageObject = await searchAsset(program.programName, program.campaignName); + if (imageObject) { + insertImageIntoCampaignImg(block, imageObject); + totalassets = imageObject.assetCount; + } + } catch (error) { + console.error("Failed to load campaign image:", error); + } + decorateIcons(block); + } + else + { block.innerHTML = `
@@ -35,30 +70,15 @@ export default async function decorate(block) { return; } - const deliverableQueryString = `getProgramDeliverables${encodedSemi}programName=${encodedProgram}${encodedSemi}programID=${encodeURIComponent(programID)}`; - const deliverables = await executeQuery(deliverableQueryString); - const p0TargetMarketArea = program.p0TargetMarketArea; const p1TargetMarketArea = program.p1TargetMarketArea; - - // Extract unique deliverable types - const uniqueDeliverableTypes = getUniqueItems(programData.data.deliverableList.items, 'deliverableType'); - // Extract unique platforms (flattened from arrays within each item) - const uniquePlatforms = getUniqueItems(programData.data.deliverableList.items, 'platforms'); const kpis = buildKPIList(program).outerHTML; - const targetMarketAreas = buildTargetMarketAreaList(p0TargetMarketArea,p1TargetMarketArea).outerHTML; - const audiences = buildAudienceList(program).outerHTML; const artifactLinks = buildArtifactLinks(program).outerHTML; - - block.innerHTML = ` -
- - Back -
-
- ${header} + + // Inject the additional HTML content + block.querySelector('.main-body-wrapper').innerHTML += `
Overview
Deliverables
@@ -127,7 +147,7 @@ export default async function decorate(block) { ${artifactLinks}
Total Approved Assets
- + ${totalassets} To view the assets, go to the "All Asset" search page and use Program and Campaign name facet to filter the assets
@@ -146,26 +166,43 @@ export default async function decorate(block) {
- `; + + // Wait for deliverables data + const deliverables = await executeQuery(deliverableQueryString); + + const uniqueDeliverableTypes = getUniqueItems(programData.data.deliverableList.items, 'deliverableType'); + const uniquePlatforms = getUniqueItems(programData.data.deliverableList.items, 'platforms'); + buildProductCard(program); - try { - const imageObject = await searchAsset(program.programName, program.campaignName); - if (imageObject){ - insertImageIntoCampaignImg(block,imageObject); - document.getElementById('totalassets').textContent = imageObject.assetCount; - } - else - { - document.getElementById('totalassets').textContent = 0; - } - } catch (error) { - console.error("Failed to load campaign image:", error); - } + buildFieldScopes('deliverable-type', uniqueDeliverableTypes, block); + buildFieldScopes('platforms', uniquePlatforms, block); + + const table = await buildTable(await deliverables).then(async (rows) => { + return rows; + }); - block.querySelector('.tab-wrapper').addEventListener('click', (event) => { + // Batch Dom Updates + const tableRoot = block.querySelector('.table-content'); + const fragment = document.createDocumentFragment(); + fragment.appendChild(table); + tableRoot.appendChild(fragment); + + buildStatus(program.status); + + // Optimize Event Listeners: Added debouncing to event listeners to prevent performance issues. + const debounce = (func, delay) => { + let timeout; + return (...args) => { + clearTimeout(timeout); + timeout = setTimeout(() => func(...args), delay); + }; + }; + + block.querySelector('.tab-wrapper').addEventListener('click', debounce((event) => { switchTab(event.target); - }) + }, 300)); + enableBackBtn(block, blockConfig); block.querySelectorAll('.read-more').forEach((button) => { button.addEventListener('click', (event) => { @@ -175,14 +212,7 @@ export default async function decorate(block) { }); }); decorateIcons(block); - buildFieldScopes('deliverable-type',uniqueDeliverableTypes, block); - buildFieldScopes('platforms',uniquePlatforms, block); - const table = await buildTable(await deliverables).then(async (rows) => { - return rows; - }) - const tableRoot = block.querySelector('.table-content'); - tableRoot.appendChild(table); - buildStatus(program.status); + } function enableBackBtn(block, blockConfig) { @@ -248,9 +278,11 @@ function getUniqueItems(items, property) { )]; } -function insertImageIntoCampaignImg(block,imageObject) { +function insertImageIntoCampaignImg(block, imageObject) { const campaignImgDiv = block.querySelector('.campaign-img'); const imgElement = document.createElement('img'); + //Lazy load images + imgElement.loading = 'lazy'; imgElement.src = imageObject.imageUrl; imgElement.alt = imageObject.imageAltText; campaignImgDiv.appendChild(imgElement); @@ -259,7 +291,7 @@ function insertImageIntoCampaignImg(block,imageObject) { function switchTab(tab) { if (tab.classList.contains('active') || tab.classList.contains('tab-wrapper')) { return; - } + } document.querySelector('.tabBtn.active').classList.toggle('active'); document.querySelector(`.tab:not(.inactive)`).classList.toggle('inactive'); const targetTab = tab.dataset.target; @@ -412,7 +444,7 @@ function formatDate(dateString) { // Formatting the date into mm/dd/yyyy format const formattedDate = mm + '/' + dd + '/' + yyyy; - + return formattedDate; } @@ -502,7 +534,7 @@ async function buildHeaderRow(category, headerType, isInactive, matchCount) { const headerRow = document.createElement('div'); headerRow.classList.add('row', 'collapsible', 'header'); let divopen; - if (headerType === 'subcategory') { + if (headerType === 'subcategory') { headerRow.classList.add('subheader'); divopen = '
'; } else { @@ -579,7 +611,7 @@ function sortRows(rows) { nodes.sort((a, b) => { var classA = a.classList ? a.classList.contains('datarow') : false; var classB = b.classList ? b.classList.contains('datarow') : false; - + if (classA && !classB) { return 1; } else if (!classA && classB) {