@@ -164,11 +165,7 @@ export default async function decorate(block) {
block.querySelector('.tab-wrapper').addEventListener('click', (event) => {
switchTab(event.target);
})
- block.querySelector('.back-button').addEventListener('click', () => {
- const host = location.origin + getBaseConfigPath();
- const listPage = blockConfig.listpage;
- document.location.href = host + `/${listPage}`;
- })
+ enableBackBtn(block, blockConfig);
block.querySelectorAll('.read-more').forEach((button) => {
button.addEventListener('click', (event) => {
const readMore = event.target;
@@ -187,6 +184,35 @@ export default async function decorate(block) {
buildStatus(program.status);
}
+function enableBackBtn(block, blockConfig) {
+ block.querySelector('.back-button').addEventListener('click', () => {
+ const host = location.origin + getBaseConfigPath();
+ const listPage = blockConfig.listpage;
+ document.location.href = host + `/${listPage}`;
+ })
+}
+
+function buildHeader(program, queryVars) {
+ const headerWrapper = document.createElement('div');
+ headerWrapper.classList.add('details-header-wrapper');
+ const date = program && program.launchDate ? `` : "";
+ const programName = program ? program.programName : queryVars.programName;
+ const campaignName = program && program.campaignName ? ' ': "";
+ headerWrapper.innerHTML = `
+
+
+ `
+ return headerWrapper;
+}
+
/**
* Extracts unique values from a specified property within an array of objects.
*
@@ -333,8 +359,7 @@ function buildArtifactLinks(program) {
async function buildStatus(status) {
const statusDiv = document.createElement('div');
statusDiv.classList.add('campaign-status');
- const statusArray = await resolveMappings("getStatusList");
- const statusMatch = filterArray(statusArray, 'value', status);
+ const statusMatch = filterArray(statusMapping, 'value', status);
const statusText = statusMatch ? statusMatch[0].text : status;
const statusHex = statusMatch[0]["color-code"];
statusDiv.textContent = statusText;
@@ -375,13 +400,14 @@ function formatDate(dateString) {
async function buildTable(jsonResponse) {
const deliverableList = jsonResponse.data.deliverableList.items;
const programKpi = jsonResponse.data.programList?.items.primaryKpi;
- const rows = document.createElement('div');
+ let rows = document.createElement('div');
// we want the 'null' deliverableType to be part of this set for filtering
const uniqueCatSet = new Set();
deliverableList.forEach(object => { uniqueCatSet.add(object['deliverableType']) })
const uniqueCategories = Array.from(uniqueCatSet);
+ const sortedCategories = sortDeliverableTypes(uniqueCategories);
let emptyCategory = false;
- uniqueCategories.forEach(async (category) => {
+ sortedCategories.forEach(async (category) => {
// build header row
let headerRow;
const matchingCampaigns = deliverableList.filter(deliverable => deliverable.deliverableType === category);
@@ -425,6 +451,19 @@ function dateSort(parent) {
})
}
+function sortDeliverableTypes(arr) {
+ return arr.sort((a, b) => {
+ // If a is null and b is not null, a should come after b
+ if (a === null && b !== null) return 1;
+ // If b is null and a is not null, b should come after a
+ if (a !== null && b === null) return -1;
+ // If both a and b are null, they are equal in terms of sorting
+ if (a === null && b === null) return 0;
+ // If neither a nor b are null, sort them alphabetically
+ return a.localeCompare(b);
+ });
+}
+
async function lookupType(rawText, mappingType) {
const mappings = (mappingType === 'deliverable-type') ? await deliverableMappings : await platformMappings;
const typeMatch = mappings.filter(item => item.value === rawText);
@@ -473,9 +512,10 @@ async function buildTableRow(deliverableJson, kpi, createHidden) {
@@ -495,14 +535,6 @@ async function buildTableRow(deliverableJson, kpi, createHidden) {
${checkBlankString(deliverableJson.driver)}
`;
- if (!(deliverableJson.linkedFolderLink == null)) {
- const finalAssetLink = document.createElement('a');
- finalAssetLink.href = deliverableJson.linkedFolderLink;
- finalAssetLink.classList.add('campaign-link');
- finalAssetLink.target = '_blank';
- finalAssetLink.textContent = "Final Asset";
- dataRow.querySelector('.column5').appendChild(finalAssetLink);
- }
createPlatformString(deliverableJson.platforms, dataRow);
return dataRow;
}
@@ -558,3 +590,25 @@ function attachListener(htmlElement) {
})
})
}
+
+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'
+ }
+ }
+}
\ No newline at end of file
diff --git a/blocks/gmo-program-header/gmo-program-header.js b/blocks/gmo-program-header/gmo-program-header.js
index d7a9e46d..ab99e4f3 100644
--- a/blocks/gmo-program-header/gmo-program-header.js
+++ b/blocks/gmo-program-header/gmo-program-header.js
@@ -1,6 +1,6 @@
import { decorateIcons } from '../../scripts/lib-franklin.js';
-import { graphqlQueryNameList, graphqlCampaignByName } from '../../scripts/graphql.js';
-import { statusMapping, productList } from '../../scripts/shared-program.js';
+import { graphqlCampaignByName } from '../../scripts/graphql.js';
+import { statusMapping, productList, getMappingArray } from '../../scripts/shared-program.js';
export default async function decorate(block) {
block.innerHTML = `
@@ -132,12 +132,12 @@ export default async function decorate(block) {
async function initializeDropdowns() {
// Business Line List
- graphqlQueryNameList('getBusinessLine').then((response) => {
+ getMappingArray('businessLine').then((response) => {
populateDropdown(response, 'dropdownBusinessOptions', 'businessLine');
});
// Geo List
- graphqlQueryNameList('getGeoList').then((response) => {
+ getMappingArray('geoList').then((response) => {
populateDropdown(response, 'dropdownGeoOptions', 'p0TargetGeo');
});
@@ -191,8 +191,7 @@ function populateDropdown(response, dropdownId, type) {
// Function to filter products based on selected business line
function filterProductsByBusinessLine(businessLine) {
- const products = productList.data.jsonByPath.item.json.options;
- const filteredProducts = products.filter(product =>
+ const filteredProducts = productList.filter(product =>
product['business-line'].includes(businessLine)
);
populateDropdown(filteredProducts, 'dropdownProductOptions', 'productOffering');
diff --git a/blocks/gmo-program-list/gmo-program-list.js b/blocks/gmo-program-list/gmo-program-list.js
index 4899cfe8..04a15a3a 100644
--- a/blocks/gmo-program-list/gmo-program-list.js
+++ b/blocks/gmo-program-list/gmo-program-list.js
@@ -228,8 +228,7 @@ async function buildCampaignList(campaigns, numPerPage) {
function buildStatus(statusWrapper, campaign) {
const campaignStatus = document.createElement('div');
const statusStr = checkBlankString(campaign.node.status);
- const statusArray = statusMapping.data.jsonByPath.item.json.options;
- const statusMatch = statusArray.filter(item => item.value === statusStr);
+ const statusMatch = statusMapping.filter(item => item.value === statusStr);
let statusText, statusColor;
if (statusMatch.length > 0) {
diff --git a/contenthub/hydration/hydration-utils.js b/contenthub/hydration/hydration-utils.js
index d2c3912f..f0b128da 100644
--- a/contenthub/hydration/hydration-utils.js
+++ b/contenthub/hydration/hydration-utils.js
@@ -115,60 +115,92 @@ export function getMetadataSchema(facetOptions){
element: 'dropdown',
multipleSelection: true,
dropdownOptions: [
- { name: 'N/A', id: 'na' },
- { name: 'Acrobat Export PDF', id: 'acrobat-export-pdf' },
- { name: 'Acrobat PDF Pack', id: 'acrobat-pdf-pack' },
- { name: 'Acrobat Pro', id: 'acrobat-pro' },
- { name: 'Acrobat Reader', id: 'acrobat-reader' },
- { name: 'Acrobat Sign Mobile', id: 'acrobat-sign-mobile' },
- { name: 'Acrobat Sign', id: 'acrobat-sign' },
- { name: 'Acrobat standard', id: 'acrobat-standard' },
- { name: 'Adobe Color', id: 'adobe-color' },
- { name: 'Adobe Express', id: 'adobe-express' },
- { name: 'Adobe Fonts', id: 'adobe-fonts' },
- { name: 'Adobe Scan', id: 'adobe-scan' },
- { name: 'Aero', id: 'aero' },
- { name: 'After Effects', id: 'after-effects' },
- { name: 'Animate', id: 'animate' },
- { name: 'Audition', id: 'audition' },
- { name: 'Behance', id: 'behance' },
- { name: 'Bridge', id: 'bridge' },
- { name: 'Capture', id: 'capture' },
- { name: 'Character Animator', id: 'character-animator' },
- { name: 'Cloud Service', id: 'cloud-service' },
- { name: 'Content Server', id: 'content-server' },
- { name: 'Creative Cloud All Apps', id: 'creative-cloud-all-apps' },
- { name: 'Digital Editions', id: 'digital-editions' },
- { name: 'Dreamweaver', id: 'dreamweaver' },
- { name: 'Fill Sign', id: 'fill-sign' },
- { name: 'Firefly', id: 'firefly' },
- { name: 'Frame.io', id: 'frame-io' },
- { name: 'Fresco', id: 'fresco' },
- { name: 'Http Dynamic Streaming', id: 'http-dynamic-streaming' },
- { name: 'Illustrator', id: 'illustrator' },
- { name: 'InCopy', id: 'incopy' },
- { name: 'InDesign Server', id: 'indesign-server' },
- { name: 'InDesign', id: 'indesign' },
- { name: 'Lightroom Classic', id: 'lightroom-classic' },
- { name: 'Lightroom', id: 'lightroom' },
- { name: 'Media Encoder', id: 'media-encoder' },
- { name: 'Media Server 5 Extended', id: 'media-server-5-extended' },
- { name: 'Media Server 5 on Amazon Web Services', id: 'media-server-5-on-amazon-web-services' },
- { name: 'Media Server 5 Professional', id: 'media-server-5-professional' },
- { name: 'Media Server 5 Standard', id: 'media-server-5-standard' },
- { name: 'Mixamo', id: 'mixamo' },
- { name: 'Photoshop Express', id: 'photoshop-express' },
- { name: 'Photoshop', id: 'photoshop' },
- { name: 'Portfolio', id: 'portfolio' },
- { name: 'Premiere Elements', id: 'premiere-elements' },
- { name: 'Premiere Pro', id: 'premiere-pro' },
- { name: 'Premiere Rush', id: 'premiere-rush' },
- { name: 'Stock', id: 'stock' },
- { name: 'Substance 3D Designer', id: 'substance-3d-designer' },
- { name: 'Substance 3D Modeler', id: 'substance-3d-modeler' },
- { name: 'Substance 3D Painter', id: 'substance-3d-painter' },
- { name: 'Substance 3D Sampler', id: 'substance-3d-sampler' },
- { name: 'Substance 3D Stager', id: 'substance-3d-stager' },
+ { name: 'N/A', id: 'N/A' },
+ { name: 'Acrobat', id: 'Acrobat' },
+ { name: 'Acrobat Export PDF', id: 'Acrobat Export PDF' },
+ { name: 'Acrobat PDF Pack', id: 'Acrobat PDF Pack' },
+ { name: 'Acrobat Pro', id: 'Acrobat Pro' },
+ { name: 'Acrobat Reader', id: 'Acrobat Reader' },
+ { name: 'Acrobat Sign', id: 'Acrobat Sign' },
+ { name: 'Acrobat Sign (Mobile)', id: 'Acrobat Sign (Mobile)' },
+ { name: 'Acrobat Standard', id: 'Acrobat Standard' },
+ { name: 'Adobe Color', id: 'Adobe Color' },
+ { name: 'Adobe Express', id: 'Adobe Express' },
+ { name: 'Adobe Fonts', id: 'Adobe Fonts' },
+ { name: 'Adobe Fresco', id: 'Adobe Fresco' },
+ { name: 'Adobe Scan', id: 'Adobe Scan' },
+ { name: 'Adobe Stock', id: 'Adobe Stock' },
+ { name: 'Advertising', id: 'Advertising' },
+ { name: 'AEC', id: 'AEC' },
+ { name: 'AEM Assets', id: 'AEM Assets' },
+ { name: 'AEM Forms', id: 'AEM Forms' },
+ { name: 'AEM Other (retired)', id: 'AEM Other (retired)' },
+ { name: 'AEM Sites', id: 'AEM Sites' },
+ { name: 'AEP', id: 'AEP' },
+ { name: 'Aero', id: 'Aero' },
+ { name: 'After Effects', id: 'After Effects' },
+ { name: 'Analytics', id: 'Analytics' },
+ { name: 'Animate', id: 'Animate' },
+ { name: 'Audience Manager', id: 'Audience Manager' },
+ { name: 'Audition', id: 'Audition' },
+ { name: 'Behance', id: 'Behance' },
+ { name: 'Bizible', id: 'Bizible' },
+ { name: 'Bridge', id: 'Bridge' },
+ { name: 'Campaign', id: 'Campaign' },
+ { name: 'Capture', id: 'Capture' },
+ { name: 'Character Animator', id: 'Character Animator' },
+ { name: 'Cloud Service', id: 'Cloud Service' },
+ { name: 'Commerce', id: 'Commerce' },
+ { name: 'Connect', id: 'Connect' },
+ { name: 'Content Server', id: 'Content Server' },
+ { name: 'Creative', id: 'Creative' },
+ { name: 'Creative Cloud All Apps', id: 'Creative Cloud All Apps' },
+ { name: 'Digital Editions', id: 'Digital Editions' },
+ { name: 'Dreamweaver', id: 'Dreamweaver' },
+ { name: 'DX General', id: 'DX General' },
+ { name: 'DX Video', id: 'DX Video' },
+ { name: 'Fill & Sign', id: 'Fill & Sign' },
+ { name: 'Firefly', id: 'Firefly' },
+ { name: 'Frame.io', id: 'Frame.io' },
+ { name: 'HTTP Dynamic Streaming', id: 'HTTP Dynamic Streaming' },
+ { name: 'Illustrator', id: 'Illustrator' },
+ { name: 'InCopy', id: 'InCopy' },
+ { name: 'InDesign', id: 'InDesign' },
+ { name: 'InDesign Server', id: 'InDesign Server' },
+ { name: 'Journey Analytics', id: 'Journey Analytics' },
+ { name: 'Journey Optimizer', id: 'Journey Optimizer' },
+ { name: 'Lightroom', id: 'Lightroom' },
+ { name: 'Lightroom Classic', id: 'Lightroom Classic' },
+ { name: 'Magento OpenSource', id: 'Magento OpenSource' },
+ { name: 'Marketo', id: 'Marketo' },
+ { name: 'Media Encoder', id: 'Media Encoder' },
+ { name: 'Media Server 5 Extended', id: 'Media Server 5 Extended' },
+ { name: 'Media Server 5 on Amazon Web Services', id: 'Media Server 5 on Amazon Web Services' },
+ { name: 'Media Server 5 Professional', id: 'Media Server 5 Professional' },
+ { name: 'Media Server 5 Standard', id: 'Media Server 5 Standard' },
+ { name: 'Mixamo', id: 'Mixamo' },
+ { name: 'Multi-product', id: 'Multi-product' },
+ { name: 'Photoshop', id: 'Photoshop' },
+ { name: 'Photoshop Express', id: 'Photoshop Express' },
+ { name: 'Portfolio', id: 'Portfolio' },
+ { name: 'PPBU', id: 'PPBU' },
+ { name: 'PPBU (Primetime)', id: 'PPBU (Primetime)' },
+ { name: 'Premiere Elements', id: 'Premiere Elements' },
+ { name: 'Premiere Pro', id: 'Premiere Pro' },
+ { name: 'Premier Support', id: 'Premier Support' },
+ { name: 'RT CDP', id: 'RT CDP' },
+ { name: 'Sensei (retired)', id: 'Sensei (retired)' },
+ { name: 'Services - Digital Performance (retired)', id: 'Services - Digital Performance (retired)' },
+ { name: 'Services - Other Consulting Services (retired)', id: 'Services - Other Consulting Services (retired)' },
+ { name: 'Sign', id: 'Sign' },
+ { name: 'Stock', id: 'Stock' },
+ { name: 'Substance 3D Designer', id: 'Substance 3D Designer' },
+ { name: 'Substance 3D Modeler', id: 'Substance 3D Modeler' },
+ { name: 'Substance 3D Painter', id: 'Substance 3D Painter' },
+ { name: 'Substance 3D Sampler', id: 'Substance 3D Sampler' },
+ { name: 'Substance 3D Stager', id: 'Substance 3D Stager' },
+ { name: 'Target', id: 'Target' },
+ { name: 'Workfront', id: 'Workfront' },
],
},
{
diff --git a/scripts/graphql.js b/scripts/graphql.js
index fd20c366..f2e5ca5d 100644
--- a/scripts/graphql.js
+++ b/scripts/graphql.js
@@ -5,31 +5,6 @@ import { logError } from './scripts.js';
const baseApiUrl = `${await getGraphqlEndpoint()}/graphql/execute.json`;
const projectId = 'gmo';
-export async function graphqlQueryNameList(queryNameList) {
- const queryName = queryNameList;
- //persisted query URLs have to be encoded together with the first semicolon
- const graphqlEndpoint = `${baseApiUrl}/${projectId}/${queryName}`;
- const jwtToken = await getBearerToken();
-
- // Return the fetch promise chain so that it can be awaited outside
- return fetch(graphqlEndpoint, {
- method: 'GET',
- headers: {
- Authorization: jwtToken,
- },
- }).then(response => {
- if (!response.ok) {
- throw new Error(`HTTP error! Status: ${response.status}`);
- }
- return response.json();
- }).then(data => {
- return data; // Make sure to return the data so that the promise resolves with it
- }).catch(error => {
- console.error('Error fetching data: ', error);
- throw error; // Rethrow or handle error as appropriate
- });
-}
-
export async function graphqlCampaignCount(filter = {}) {
const queryName = 'getTotalPrograms';
const encodedSemiColon = encodeURIComponent(';');
diff --git a/scripts/polaris.js b/scripts/polaris.js
index 1b081b40..d94f6a20 100644
--- a/scripts/polaris.js
+++ b/scripts/polaris.js
@@ -101,13 +101,10 @@ export async function authorizeURL(url) {
const response = await fetch(url, options);
+
if (!response.ok) {
- // Handle specific HTTP errors
- if (response.status === 404) {
- throw new Error('Not Found (404)');
- } else {
- throw new Error(`HTTP error! Status: ${response.status}`);
- }
+ // Return the full URL to the asset icon if the response is not OK, as the asset was not found
+ return new URL('/icons/asset.svg', window.location.origin).href;
}
const imageBlob = await response.blob();
diff --git a/scripts/security.js b/scripts/security.js
index edbe7458..c74c8023 100644
--- a/scripts/security.js
+++ b/scripts/security.js
@@ -71,15 +71,22 @@ export async function getUserProfile() {
async function getCCCollabProfile() {
const bearerToken = await getBearerToken();
const url = await getCcCollabUrl();
- return await fetchCached(
- `https://${url}`,
- {
- method: 'GET',
- headers: {
- Authorization: bearerToken,
+
+ //If this fails, we want to return something. fetchCached will halt.
+ try{
+ return await fetchCached(
+ `https://${url}`,
+ {
+ method: 'GET',
+ headers: {
+ Authorization: bearerToken,
+ },
},
- },
- );
+ );
+ } catch (error) {
+ // fetchCached will log the error if it fails
+ return null;
+ }
}
export async function getAvatarUrl() {
diff --git a/scripts/shared-program.js b/scripts/shared-program.js
index e71afcf0..c76dda30 100644
--- a/scripts/shared-program.js
+++ b/scripts/shared-program.js
@@ -1,18 +1,10 @@
-import { graphqlQueryNameList } from "./graphql.js";
-import { getProductIconMapping, getBaseConfigPath } from './site-config.js';
+import { executeQuery } from "./graphql.js";
+import { getProductIconMapping, getBaseConfigPath, getQueryPaths } from './site-config.js';
let iconMapping;
-export let statusMapping = await graphqlQueryNameList('getStatusList');
-export let productList = await graphqlQueryNameList('getProductList');
-
-/*
-* Executes graphql query for 'friendly' labels and returns array of the results
-*/
-export async function resolveMappings(mappingType) {
- const response = await graphqlQueryNameList(mappingType);
- const mappingArray = response.data.jsonByPath.item.json.options;
- return mappingArray;
-}
+const cfMapping = getQueryPaths();
+export let statusMapping = await getMappingArray('status');
+export let productList = await getMappingArray('products');
/**
* Filter provided array based on provided key/value pair
@@ -32,9 +24,8 @@ export async function getProductMapping(product) {
}
const icon = iconMatch ? configPath + iconMatch[0]['Icon-path'] : defaultIcon;
- if (productList == undefined) productList = await graphqlQueryNameList('getProductList');
- const productsArray = productList.data.jsonByPath.item.json.options;
- const productsMatch = filterArray(productsArray, 'value', product);
+ if (productList == undefined) productList = await getMappingArray('products');
+ const productsMatch = filterArray(productList, 'value', product);
const productsText = productsMatch ? productsMatch[0].text : product;
return {
@@ -57,4 +48,18 @@ export function checkBlankString(string, notAvailableText = 'Not Available') {
export function dateFormat(dateString) {
const formattedDate = dateString ? dateString.split('T')[0] : 'Not Available';
return formattedDate;
+}
+
+function getCFPath(cfArray, type) {
+ const cfMatch = cfArray.filter(item => item['type'] === type);
+ const cfPath = cfMatch.length > 0 ? cfMatch[0].path : null;
+ return cfPath;
+}
+
+export async function getMappingArray(type) {
+ const mappingCf = getCFPath(await cfMapping, type);
+ const mappings = executeQuery(`getMappings${encodeURIComponent(';')}path=${encodeURIComponent(mappingCf)}`).then((response) => {
+ return response.data.jsonByPath.item.json.options;
+ })
+ return mappings;
}
\ No newline at end of file
diff --git a/scripts/site-config.js b/scripts/site-config.js
index 6c9694ff..e4c86b94 100644
--- a/scripts/site-config.js
+++ b/scripts/site-config.js
@@ -379,4 +379,19 @@ export async function getProductIconMapping() {
console.log("Unable to retrieve site-config.json");
}
return iconArray;
+}
+
+/**
+ * @returns {Array} with mapping-type and the path to its content fragment.
+ */
+export async function getQueryPaths() {
+ let mapping = [];
+ const response = await getConfig('site-config.json');
+ for (const entry of response['query-fragments'].data || []) {
+ mapping.push({
+ type: entry['mapping-Type'],
+ path: entry['path']
+ });
+ }
+ return mapping;
}
\ No newline at end of file