Skip to content

Commit

Permalink
ASSETS-88922 : Review the Deliverable page load time (#130)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
TyroneAEM authored Jun 28, 2024
1 parent f2806bb commit 5b61d34
Showing 1 changed file with 82 additions and 50 deletions.
132 changes: 82 additions & 50 deletions blocks/gmo-program-details/gmo-program-details.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = `
<div class="back-button">
<span class="icon icon-back"></span>
<span class="back-label">Back</span>
</div>
<div class="main-body-wrapper">
<div class="placeholder-header">Loading program details...</div>
</div>
`;

// 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 = `
<div class="back-button">
<span class="icon icon-back"></span>
Expand All @@ -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 = `
<div class="back-button">
<span class="icon icon-back"></span>
<span class="back-label">Back</span>
</div>
<div class="main-body-wrapper">
${header}

// Inject the additional HTML content
block.querySelector('.main-body-wrapper').innerHTML += `
<div class="tab-wrapper">
<div id="tab1toggle" data-target="tab1" class="tabBtn active">Overview</div>
<div id="tab2toggle" data-target="tab2" class="tabBtn">Deliverables</div>
Expand Down Expand Up @@ -127,7 +147,7 @@ export default async function decorate(block) {
${artifactLinks}
<div class="total-assets total-assets-tooltip">
<div class="h3">Total Approved Assets</div>
<span id="totalassets" class="description"></span>
<span id="totalassets" class="description">${totalassets}</span>
<span class="tooltiptext">To view the assets, go to the "All Asset" search page and use Program and Campaign name facet to filter the assets</span>
</div>
</div>
Expand All @@ -146,26 +166,43 @@ export default async function decorate(block) {
</div>
</div>
</div>
</div>
`;

// 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) => {
Expand All @@ -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) {
Expand Down Expand Up @@ -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);
Expand All @@ -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;
Expand Down Expand Up @@ -412,7 +444,7 @@ function formatDate(dateString) {

// Formatting the date into mm/dd/yyyy format
const formattedDate = mm + '/' + dd + '/' + yyyy;

return formattedDate;
}

Expand Down Expand Up @@ -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 = '<div class="heading-wrapper subheading">';
} else {
Expand Down Expand Up @@ -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) {
Expand Down

0 comments on commit 5b61d34

Please sign in to comment.