From 6b961ac1e06a55d91176c18f440485b84862d365 Mon Sep 17 00:00:00 2001 From: Tyrone Tse Date: Mon, 19 Aug 2024 15:37:33 -0500 Subject: [PATCH 1/7] Added performance logging code for queries getAllCampaigns for Program List executeQuery for Program Details --- scripts/graphql.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/scripts/graphql.js b/scripts/graphql.js index f2e5ca5d..2f7d5f6b 100644 --- a/scripts/graphql.js +++ b/scripts/graphql.js @@ -47,6 +47,8 @@ export async function graphqlAllCampaignsFilter(first,cursor,filter) { const encodedCursor = encodeURIComponent(cursor); const encodedFilter = encodeURIComponent(JSON.stringify(filter)); const graphqlEndpoint = `${baseApiUrl}/${projectId}/${queryName}${encodedSemiColon}first=${encodedFirst}${encodedSemiColon}cursor=${encodedCursor}${encodedSemiColon}filter=${encodedFilter}`; + //Performance logging + const startTime = performance.now(); const jwtToken = await getBearerToken(); try { @@ -57,6 +59,10 @@ export async function graphqlAllCampaignsFilter(first,cursor,filter) { }, }; const response = await fetch(`${graphqlEndpoint}`, options); + //Performance logging + const endTime = performance.now(); + const executionTime = endTime - startTime; + console.log(`getAllCampaigns Execution Time: ${executionTime} ms`); // Handle response codes if (response.status === 200) { const responseBody = await response.json(); @@ -170,6 +176,8 @@ export async function executeQuery(queryString) { const baseApiUrl = `${await getGraphqlEndpoint()}/graphql/execute.json`; const projectId = 'gmo'; const queryEndpoint = `${baseApiUrl}/${projectId}/${queryString}`; + //Performance logging + const startTime = performance.now(); const jwtToken = await getBearerToken(); return fetch(queryEndpoint, { @@ -181,6 +189,11 @@ export async function executeQuery(queryString) { if (!response.ok) { throw new Error(`HTTP error! Status: ${response.status}`); } + //Performance logging + const endTime = performance.now(); + const executionTime = endTime - startTime; + console.log(`executeQuery for ${queryString} Execution Time: ${executionTime} ms`); + return response.json(); }).then(data => { return data; // Make sure to return the data so that the promise resolves with it From 7458a831888d9708d814ff68fdbe196ea6672d90 Mon Sep 17 00:00:00 2001 From: Tyrone Tse Date: Wed, 21 Aug 2024 10:01:50 -0500 Subject: [PATCH 2/7] gmo-program-details.js : Added the path parameter to program-details page, which now is passed to the graphql query getProgramDetails which now has the path parameter query ($programName: String, $programID:String, $path:ID) --- .../gmo-program-details.js | 33 ++++++++++++++++++- blocks/gmo-program-list/gmo-program-list.js | 6 ++-- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/blocks/gmo-program-details/gmo-program-details.js b/blocks/gmo-program-details/gmo-program-details.js index 263d34d6..1f6b82d0 100644 --- a/blocks/gmo-program-details/gmo-program-details.js +++ b/blocks/gmo-program-details/gmo-program-details.js @@ -17,8 +17,10 @@ export default async function decorate(block) { const encodedProgram = encodeURIComponent(programName); blockConfig = readBlockConfig(block); - + // Including path in the query if present + const pathParam = queryVars.path ? `&path=${encodeURIComponent(queryVars.path)}` : ''; 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 @@ -698,6 +700,7 @@ function attachListener(htmlElement) { }) } +/* old code function extractQueryVars() { const urlStr = window.location.href; const pnRegex = /.*programName=(.*?)&programID=(.*)/; @@ -719,3 +722,31 @@ function extractQueryVars() { } } } +*/ + + +function extractQueryVars() { + const urlStr = window.location.href; + const pnRegex = /[?&]programName=([^&]+)&programID=([^&]+)(&path=([^&]+))?/; + const match = urlStr.match(pnRegex); + if (match && match[1] && match[2]) { + const pName = decodeURIComponent(match[1].replace(/\+/g, ' ')); + let pID = decodeURIComponent(match[2].replace(/\+/g, ' ')); + let pPath = match[4] ? decodeURIComponent(match[4].replace(/\+/g, ' ')) : null; + if (pID.endsWith('#')) { + pID = pID.slice(0, -1); + } + return { + programName: pName, + programID: pID, + path: pPath + }; + } else { + return { + programName: 'Program Name Not Available', + programID: 'Program ID Not Available', + path: null + }; + } +} + diff --git a/blocks/gmo-program-list/gmo-program-list.js b/blocks/gmo-program-list/gmo-program-list.js index 927093f4..7a9bd3cf 100644 --- a/blocks/gmo-program-list/gmo-program-list.js +++ b/blocks/gmo-program-list/gmo-program-list.js @@ -181,6 +181,7 @@ async function buildCampaignList(campaigns, numPerPage) { const programName = campaign.node.programName; const campaignName = campaign.node.campaignName; const programID = campaign.node.programID ? campaign.node.programID : ""; + const path = campaign.node._path; campaignRow.classList.add('campaign-row'); if ((index + 1) > numPerPage) campaignRow.classList.add('hidden'); @@ -190,9 +191,9 @@ async function buildCampaignList(campaigns, numPerPage) { const campaignIconLink = document.createElement('a'); let campaignDetailsLink = host + `/${detailsPage}?programName=${programName}&`; - campaignDetailsLink += `programID=${programID}` + campaignDetailsLink += `programID=${programID}`; + campaignDetailsLink += `&path=${path}`; campaignIconLink.href = campaignDetailsLink; - const campaignIcon = document.createElement('div'); campaignIcon.classList.add('campaign-icon'); campaignIcon.dataset.programname = programName; @@ -203,7 +204,6 @@ async function buildCampaignList(campaigns, numPerPage) { const campaignNameWrapper = document.createElement('div'); campaignNameWrapper.classList.add('campaign-name-wrapper', 'vertical-center'); - campaignNameWrapper.innerHTML = `
${checkBlankString(programName)} From 62e8f761b2ee87b2d8981db60e333577d27381e0 Mon Sep 17 00:00:00 2001 From: Tyrone Tse Date: Wed, 21 Aug 2024 15:37:38 -0500 Subject: [PATCH 3/7] Fix programName extraction and conditionally add 'path' parameter to query string - Corrected the `extractQueryVars` function to properly handle and return the correct value for `programName`, ensuring that the `+` character is not mistakenly replaced with a space. - Updated the construction of `programQueryString` to include the `path` parameter only if `encodedPath` is not an empty string. - These changes improve the accuracy of parameter handling and optimize the query string construction process. --- .../gmo-program-details.js | 36 ++++--------------- 1 file changed, 6 insertions(+), 30 deletions(-) diff --git a/blocks/gmo-program-details/gmo-program-details.js b/blocks/gmo-program-details/gmo-program-details.js index 1f6b82d0..758eb5f7 100644 --- a/blocks/gmo-program-details/gmo-program-details.js +++ b/blocks/gmo-program-details/gmo-program-details.js @@ -15,11 +15,12 @@ const platformMappings = getMappingArray('platforms'); export default async function decorate(block) { const encodedSemi = encodeURIComponent(';'); const encodedProgram = encodeURIComponent(programName); + const encodedPath = queryVars.path ? `${encodeURIComponent(queryVars.path)}` : ''; blockConfig = readBlockConfig(block); // Including path in the query if present - const pathParam = queryVars.path ? `&path=${encodeURIComponent(queryVars.path)}` : ''; - const programQueryString = `getProgramDetails${encodedSemi}programName=${encodedProgram}${encodedSemi}programID=${encodeURIComponent(programID)}`; + const programQueryString = `getProgramDetails${encodedSemi}programName=${encodedProgram}${encodedSemi}programID=${encodeURIComponent(programID)}` + + (encodedPath ? `${encodedSemi}path=${encodedPath}` : ''); const deliverableQueryString = `getProgramDeliverables${encodedSemi}programName=${encodedProgram}${encodedSemi}programID=${encodeURIComponent(programID)}`; @@ -700,39 +701,14 @@ function attachListener(htmlElement) { }) } -/* old code -function extractQueryVars() { - const urlStr = window.location.href; - const pnRegex = /.*programName=(.*?)&programID=(.*)/; - const match = urlStr.match(pnRegex); - if (match && match[1] && match[2]) { - const pName = decodeURIComponent(match[1]); - let pID = decodeURIComponent(match[2]) - if (pID.endsWith('#')) { - pID = pID.slice(0, -1); - } - return { - programName: pName, - programID: pID - } - } else { - return { - programName: 'Program Name Not Available', - programID: 'Program ID Not Available' - } - } -} -*/ - - function extractQueryVars() { const urlStr = window.location.href; const pnRegex = /[?&]programName=([^&]+)&programID=([^&]+)(&path=([^&]+))?/; const match = urlStr.match(pnRegex); if (match && match[1] && match[2]) { - const pName = decodeURIComponent(match[1].replace(/\+/g, ' ')); - let pID = decodeURIComponent(match[2].replace(/\+/g, ' ')); - let pPath = match[4] ? decodeURIComponent(match[4].replace(/\+/g, ' ')) : null; + const pName = decodeURIComponent(match[1]); // Removed the replace method + let pID = decodeURIComponent(match[2]); + let pPath = match[4] ? decodeURIComponent(match[4]) : null; if (pID.endsWith('#')) { pID = pID.slice(0, -1); } From 3ecb7025a4a8bc4536478fd1a6b233e0ad2b7907 Mon Sep 17 00:00:00 2001 From: Tyrone Tse Date: Tue, 27 Aug 2024 09:26:22 -0500 Subject: [PATCH 4/7] Updated logging to use console.debug --- scripts/graphql.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/graphql.js b/scripts/graphql.js index 2f7d5f6b..58012272 100644 --- a/scripts/graphql.js +++ b/scripts/graphql.js @@ -62,7 +62,8 @@ export async function graphqlAllCampaignsFilter(first,cursor,filter) { //Performance logging const endTime = performance.now(); const executionTime = endTime - startTime; - console.log(`getAllCampaigns Execution Time: ${executionTime} ms`); + console.debug(`getAllCampaigns Execution Time: ${executionTime} ms`); + // Handle response codes if (response.status === 200) { const responseBody = await response.json(); @@ -192,7 +193,7 @@ export async function executeQuery(queryString) { //Performance logging const endTime = performance.now(); const executionTime = endTime - startTime; - console.log(`executeQuery for ${queryString} Execution Time: ${executionTime} ms`); + console.debug(`executeQuery for ${queryString} Execution Time: ${executionTime} ms`); return response.json(); }).then(data => { From 689ab74c4b6bfd862b54ad040635d0125ec4ae4f Mon Sep 17 00:00:00 2001 From: Tyrone Tse Date: Wed, 28 Aug 2024 11:51:02 -0500 Subject: [PATCH 5/7] feat(calendar): implement caching mechanism for thumbnails to optimize performance - Replaced array-based caching with an object-based caching system using a combination of `programName`, `campaignName`, and `deliverableType` as the cache key. - Updated the `addThumbnailToItem` function to utilize the new caching mechanism, reducing redundant API calls. - Converted the outer loop from `forEach` to `for...of` to ensure proper handling of asynchronous operations and to support the sequential execution of `await` calls. --- scripts/program-calendar.js | 59 ++++++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 21 deletions(-) diff --git a/scripts/program-calendar.js b/scripts/program-calendar.js index 565056a9..7dec86e3 100644 --- a/scripts/program-calendar.js +++ b/scripts/program-calendar.js @@ -8,6 +8,10 @@ const startDateProp = 'taskPlannedStartDate'; const endDateProp = 'taskPlannedEndDate'; const taskStatusMappings = await getMappingArray('taskStatus'); +// Thumbnail cache array object to store the image objects for given programName, campaignName, and deliverableType +//const thumbnailCache = []; +const thumbnailCache = {}; + // Helper function to get task status mapping function getTaskStatusMapping(taskStatus) { return taskStatusMappings.find(mapping => mapping.value === taskStatus) || {}; @@ -90,7 +94,8 @@ export async function buildCalendar(dataObj, block, type, mappingArray, period) } var groupIndex = 1; - uniqueGroups.forEach((group) => { + + for (const group of uniqueGroups) { // find all members of this group const matchedItems = deliverables.filter(item => item.deliverableType === group); @@ -132,7 +137,8 @@ export async function buildCalendar(dataObj, block, type, mappingArray, period) const itemWrapper = document.createElement('div'); itemWrapper.classList.add('group-content'); - matchedItems.forEach((item) => { + + for (const item of matchedItems) { const itemStartDate = (item[startDateProp]) ? new Date(item[startDateProp]) : viewStart; const itemEndDate = (item[endDateProp]) ? new Date(item[endDateProp]) : viewEnd; @@ -174,11 +180,10 @@ export async function buildCalendar(dataObj, block, type, mappingArray, period)
`; itemEl.style.width = itemDurationPct + '%'; - // Call the new function to fetch and add the thumbnail - addThumbnailToItem(itemEl, item.programName, item.campaignName,item.deliverableType); + // Call the new function to fetch and add the thumbnail, ensuring sequential execution + await addThumbnailToItem(itemEl, item.programName, item.campaignName, item.deliverableType); itemWrapper.appendChild(itemEl); - - }); + } const groupEl = document.createElement('div'); groupEl.classList.add('calendar-group', `color${groupIndex}`); @@ -203,8 +208,7 @@ export async function buildCalendar(dataObj, block, type, mappingArray, period) contentWrapper.appendChild(groupEl); groupIndex +=1; - - }); + } calendarEl.appendChild(contentWrapper); block.querySelector('.calendar.tab').appendChild(calendarEl); @@ -290,20 +294,33 @@ export async function buildCalendar(dataObj, block, type, mappingArray, period) } async function addThumbnailToItem(itemEl, programName, campaignName, deliverableType) { - try { - const imageObject = await searchAsset(programName, campaignName,deliverableType); - if (imageObject && imageObject.imageUrl) { - const thumbnailDiv = itemEl.querySelector('.thumbnail'); - const imgElement = document.createElement('img'); - imgElement.src = imageObject.imageUrl; - imgElement.alt = imageObject.imageAltText; - imgElement.loading = 'lazy'; - thumbnailDiv.appendChild(imgElement); - } else { - console.error("Image Object does not have a valid imageUrl"); + // Create a unique key for the cache based on the parameters + const cacheKey = `${programName}-${campaignName}-${deliverableType}`; + + // Check if the imageObject is already cached + let imageObject = thumbnailCache[cacheKey]; + + // If not cached, make the API call and store the result in the cache + if (!imageObject) { + try { + imageObject = await searchAsset(programName, campaignName, deliverableType); + thumbnailCache[cacheKey] = imageObject; // Store the result in the cache + } catch (error) { + console.error("Failed to load thumbnail image:", error); + return; // Exit the function if the API call fails } - } catch (error) { - console.error("Failed to load thumbnail image:", error); + } + + // Use the cached or newly fetched imageObject + if (imageObject && imageObject.imageUrl) { + const thumbnailDiv = itemEl.querySelector('.thumbnail'); + const imgElement = document.createElement('img'); + imgElement.src = imageObject.imageUrl; + imgElement.alt = imageObject.imageAltText; + imgElement.loading = 'lazy'; + thumbnailDiv.appendChild(imgElement); + } else { + console.error("Image Object does not have a valid imageUrl"); } } From 95bec45666cece4268f621243f18cc4fc528ccdf Mon Sep 17 00:00:00 2001 From: Tyrone Tse Date: Wed, 28 Aug 2024 15:00:13 -0500 Subject: [PATCH 6/7] Removed commented out code --- scripts/program-calendar.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/scripts/program-calendar.js b/scripts/program-calendar.js index 7dec86e3..8d057345 100644 --- a/scripts/program-calendar.js +++ b/scripts/program-calendar.js @@ -8,8 +8,7 @@ const startDateProp = 'taskPlannedStartDate'; const endDateProp = 'taskPlannedEndDate'; const taskStatusMappings = await getMappingArray('taskStatus'); -// Thumbnail cache array object to store the image objects for given programName, campaignName, and deliverableType -//const thumbnailCache = []; +// Thumbnail cache array object to store the image objects using cacheKey = `${programName}-${campaignName}-${deliverableType}`; const thumbnailCache = {}; // Helper function to get task status mapping @@ -94,7 +93,7 @@ export async function buildCalendar(dataObj, block, type, mappingArray, period) } var groupIndex = 1; - + for (const group of uniqueGroups) { // find all members of this group const matchedItems = deliverables.filter(item => item.deliverableType === group); From 79c4f38aba51a0d30ad41be8249ab17a8bccffec Mon Sep 17 00:00:00 2001 From: Tyrone Tse Date: Thu, 29 Aug 2024 09:41:53 -0500 Subject: [PATCH 7/7] Update cache key generation to conditionally include campaignName - Modified the cache key generation logic to only include campaignName if it is not null or an empty string. --- blocks/gmo-program-details/gmo-program-details.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/blocks/gmo-program-details/gmo-program-details.js b/blocks/gmo-program-details/gmo-program-details.js index ec78bef3..232e12ae 100644 --- a/blocks/gmo-program-details/gmo-program-details.js +++ b/blocks/gmo-program-details/gmo-program-details.js @@ -1025,8 +1025,8 @@ async function getTaskStatusMapping(taskStatus) { } async function addThumbnailToItem(itemEl, programName, campaignName, deliverableType) { - // Create a unique key for the cache based on the parameters - const cacheKey = `${programName}-${campaignName}-${deliverableType}`; + // Create a unique key for the cache based on the parameters, only add the campaignName in cacheKey when it is not null or empty + const cacheKey = campaignName ? `${programName}-${campaignName}-${deliverableType}` : `${programName}-${deliverableType}`; // Check if the imageObject is already cached let imageObject = thumbnailCache[cacheKey];