From 23b578bd0e2d1a1e70bb19820e9c0013302e9b76 Mon Sep 17 00:00:00 2001 From: mdickson-adbe <95774602+mdickson-adbe@users.noreply.github.com> Date: Thu, 13 Jun 2024 12:24:41 -0400 Subject: [PATCH] Holistic campaign view dashboard MVP (#120) * Added mapping for uuid (#74) Co-authored-by: Mathieu Lessard * Removed Prefix from Displayed UUID Value on Assets (#76) * Modified formatAssetMetadata() to allow custom dc-format Labels (#79) * Removed "required" and custom values for fields (#86) Co-authored-by: Mathieu Lessard * Added getBaseConfigPath to Collections Back Button (#89) * Releasing HCV Dashboard code (#106) * ASSETS-88894 GRAPHQL Persisted Query Code (#53) * GRAPHQL POC Demo code to call a Persisted GraphQL query for Content Fragments * Updated the code call the GraphQL Persisted query from the QA AEM Author server, and also to authenticate using the JWT Bearer token * Renamed file test-graphql.js to graphql.js Added function graphqlAllCampaign() to get call GraphQL persisted query for All Campains Added function graphqlCampaignByName(campaignName) to get call GraphQL persisted query to get campaign by campaign name. Added function getGraphqlEndpoint() to get the value of property aemGraphqlEndpoint from the config file admin-config.json * Changed queryName to getAllCampaings * Rollback to 3/15/24 Commit a519a5155c3dd57f0c54ea8bd336cd0a231cffab before adding GraphQL test code * Delete test graphql code * ASSETS-88895 : Show HCV report pages to limited users (#55) * security.js updated function checkUserAccess for users that are members of the imsUserGroup, that if a page has the property reporting-access then access to the page is only granted if the user is member of te group defined by the property imsReportingGroup in the file admin-config.xlsx site-config.js : Updated the function getQuickLinkConfig to show the quick link to users who are members of the new column Group in shared-quicklinks tab of file site-config.xlsx * securiity.js : Deleted function isReportingAccessPage() as it is not needed any more. Updated function checkUserAccess() : Update code for non public pages, check if the current page path is in the pages returned by the function getQuickLinks. If it does not exist in quick links, then the user does not have access to the page. The functionQuickLinks already has logic to check if a page is only accessible by users of a page group. Renamed function checkGroupAccess(adminConfigGroupPropertyName) to checkPageGroupAccess(adminConfigGroupPropertyName) * Campaign List block for Marketing Dashboard page (#56) * initial commit, new blocks/files * rename files, start implementing * continuing to implement/adjust styling * numerous changes - finish building bones of list component - build bones of pagination/footer - add icons for products (will likely need more) - build javascript for pagination (cleanup needed) - build javascript for sorting list --------- Co-authored-by: Michael Dickson * updated hydration-utils.js (#54) MH: Added Firefly product to AA Modal 'Product' Field List * Assets 98990 - Dynamic campaign list (#61) * initial commit, new blocks/files * rename files, start implementing * continuing to implement/adjust styling * numerous changes - finish building bones of list component - build bones of pagination/footer - add icons for products (will likely need more) - build javascript for pagination (cleanup needed) - build javascript for sorting list * finish pagination * Updates to css/js to better align to mockup * ASSETS-88895 : Updated function getQuickLinkConfig() to still work if the row.Group is undefined or does not exist (#60) * security.js updated function checkUserAccess for users that are members of the imsUserGroup, that if a page has the property reporting-access then access to the page is only granted if the user is member of te group defined by the property imsReportingGroup in the file admin-config.xlsx site-config.js : Updated the function getQuickLinkConfig to show the quick link to users who are members of the new column Group in shared-quicklinks tab of file site-config.xlsx * securiity.js : Deleted function isReportingAccessPage() as it is not needed any more. Updated function checkUserAccess() : Update code for non public pages, check if the current page path is in the pages returned by the function getQuickLinks. If it does not exist in quick links, then the user does not have access to the page. The functionQuickLinks already has logic to check if a page is only accessible by users of a page group. Renamed function checkGroupAccess(adminConfigGroupPropertyName) to checkPageGroupAccess(adminConfigGroupPropertyName) * Updated function getQuickLinkConfig() to still work if the row.Group is undefined or does not exist * Added function export async function graphqlFilterOnMarketingInitiative(marketingInitiative) (#64) Which calls the persisted query gmo/filter-on-marketing-initiative with example parameters { "marketingInitiative": "FY24Q1-Q2_AdobeExpress_level Up" } https://author-p108396-e1046543.adobeaemcloud.com/graphql/execute.json/gmo/filter-on-marketing-initiative%3BmarketingInitiative=FY24Q1-Q2_AdobeExpress_level%20Up * Campaign List filters display (#65) * initial html structure setup * refactor from select to div implementation Select with multiselect displays as a list, not a dropdown * finish refactor, enable visual functionality - added all javascript needed for visual functionality - finished refactoring structure * Assets 98990 (#66) * initial commit, new blocks/files * rename files, start implementing * continuing to implement/adjust styling * numerous changes - finish building bones of list component - build bones of pagination/footer - add icons for products (will likely need more) - build javascript for pagination (cleanup needed) - build javascript for sorting list * finish pagination * Updates to css/js to better align to mockup * minor updates - update graphql query to have hardcoded offset/limit (to resolve error) - remove comments - add placeholder 'refresh date' message and css - move css for main body to accomodate above message * add firefly icon --------- Co-authored-by: Tyrone Tse * ASSETS-88899 : [Issue] Collection Detail Page is Redirecting to No-Access Page (#68) * Added function hideQuickLinks() * Added the hide field to the shared-quicklinks config/worksheet /blocks/adp-header/adp-header.js : Updated the code to only show links if hide!=='true' /scripts/security.js : Updated function checkUserAccess to get the current URL without the parameters for /collection/ and /share/ /scripts/site-config.js : Updated function to include the hide field * Refactored the If statement logic * Assets 98992- Campaign details/overview (#69) * initial block structure * start mocking up structure * many changes - finish overview tab mockup - (mostly) finish deliverables tab * fix css- add top/bottom borders to rows * delete invalid metadata from new svgs * updates for mvp - Rename and hide various elements of page - Refactor tablebuilding function to deal with items that are missing categorization properties * hide total assets * ASSETS-88900 : Update Pagination for gmo-campiagn-list Block (#70) * gmo-campagin-list refactored to use persisted query function function graphqlCampaignPaginated(first,cursor) to get a page of Campaigns at a time * Refactored the Navigation code for Previous Page, Next Page, Select Number of Items on a Page. Refactored the code for Repaginate for when the number of items page is changed Show current page status * graphql.js : Updated function graphqlAllCampaigns(first,cursor) to call getAllCampaigns persisted query with parameters first and cursor Deleted function graphqlCampaignPaginated(first,cursor) Updated gmo-campaign-list/gmo-campaign-list.js : To use persisted query graphqlAllCampaigns(first,cursor) and refactored the pagination to call the decorate function to call the graphql query each time to get the next or previous page * Fixed logic in calculating the nextCursor * Removed all debug console.log statements * Removed testCampaigns array of test campaign data, which now has been replaced by data from graphql * Renamed the variable testConfig to headerConfig * Campaign details block (#71) * initial block structure * start mocking up structure * many changes - finish overview tab mockup - (mostly) finish deliverables tab * fix css- add top/bottom borders to rows * delete invalid metadata from new svgs * updates for mvp - Rename and hide various elements of page - Refactor tablebuilding function to deal with items that are missing categorization properties * hide total assets * add status bar display Also adjusted demo data slightly to include due date and lead * update css/html to align with mockup and mvp * resolve border bug * implemented requested changes * ASSETS-88901 : (Backend) Campaign Page: Filters (#72) * graphql.js : Added functions graphqlProductList and graphqlStatusList gmo-campaign-header.js : Updated code to build Status dropdown from function graphqlStatusList Updated code to build Product dropdown from function graphqlProductList * /blocks/gmo-campaign-list/gmo-campaign-list.js => Added custom event listener for custom event gmoCampaignListBlock which allows the event gmoCampaignListBlock to be called from the block component gmo-campaign-header.js /blocks/gmo-campaign-header/gmo-campaign-header.js : Created new function sendGmoCampaignListBlockEvent() that calls the custom event gmoCampaignListBlock from the gmo-campaign-header. This will trigger the custom event in the /blocks/gmo-campaign-list to call the graphql persisted query getAllCampaigns with a filter based on the selected values from the dropdown lists for Business Line Status and Product * graphql.js : Added test function graphAllCampaignsFilterfirst,cursor,filter) : Which has the filter parameter which is the graphQL filter object Added function generateFilterJSON(filterParams) which generates the graphQL filter object from an Array of parameters. /blocks/gmo-campaign-header/gmo-campaign-header.js : Added event on the campaign-search field, to trigger the sendGmoCampaignBlockEvent after 3 characters are typed, later this will be replaced by a Campaign Suggested List generated as the user types /blocks/gmo-campaign-list/gmo-campaign-list.js : Added code build the graphQL filter object from the Campaign Search fields and Drop Downs Updated the function decorate to have new parameter graphQLFilter So that the function graphqlAllCampaignsFilter(numPerPage, cursor,graphQLFilter) can be called * Adde autocomplete list CSS, JS and HMTL for the campaign search field. * graphql.js Update function graphCampaignByName to use persisted query getCampaignNames used for autocomplete list for Campaign Name search gmo-campaign-list.js : Changed campaignName search to use filter operater : '=' /gmo-campaign-header.js : Updated campaign search autocompleteList to trigger sendCampaignListBlockEvent to make autocomplete search work * gmo-campaign-header.js : Updated function resetAllFilters() to call function sendGmoCampaignListBlockEvent(); gmo-campaign-list.js : Updated campaignCount to call function graphqlCampaignCount(graphqlFilter) with graphqlFilter graphql.js : Updated function graphCampaignCount : Added filter parameter, and call persisted query getCampaignNameFilter * Removed console.log messages * Fixed bug with currentPageInfo.nextCursor * Fixed bug in calculation of cursor to use for the previous page logic * graphql.js : Deleted function graphqlAllCampaigns(first,cursor) , replaced function graphqlStatusList() and graphqlProductList() with function graphqlQueryNameList(queryNameList) Variables baseApiUrl and projectId are now global/class level variables. gmo-campaign-header.js : Close dropdown list when a value is selected * update campaign with program --------- Co-authored-by: Shivani gupta * ASSETS-88901 : Campaign Header: Dropdown List Only Allow Single Value to Be Selected (#73) * graphql.js : Added functions graphqlProductList and graphqlStatusList gmo-campaign-header.js : Updated code to build Status dropdown from function graphqlStatusList Updated code to build Product dropdown from function graphqlProductList * /blocks/gmo-campaign-list/gmo-campaign-list.js => Added custom event listener for custom event gmoCampaignListBlock which allows the event gmoCampaignListBlock to be called from the block component gmo-campaign-header.js /blocks/gmo-campaign-header/gmo-campaign-header.js : Created new function sendGmoCampaignListBlockEvent() that calls the custom event gmoCampaignListBlock from the gmo-campaign-header. This will trigger the custom event in the /blocks/gmo-campaign-list to call the graphql persisted query getAllCampaigns with a filter based on the selected values from the dropdown lists for Business Line Status and Product * graphql.js : Added test function graphAllCampaignsFilterfirst,cursor,filter) : Which has the filter parameter which is the graphQL filter object Added function generateFilterJSON(filterParams) which generates the graphQL filter object from an Array of parameters. /blocks/gmo-campaign-header/gmo-campaign-header.js : Added event on the campaign-search field, to trigger the sendGmoCampaignBlockEvent after 3 characters are typed, later this will be replaced by a Campaign Suggested List generated as the user types /blocks/gmo-campaign-list/gmo-campaign-list.js : Added code build the graphQL filter object from the Campaign Search fields and Drop Downs Updated the function decorate to have new parameter graphQLFilter So that the function graphqlAllCampaignsFilter(numPerPage, cursor,graphQLFilter) can be called * Adde autocomplete list CSS, JS and HMTL for the campaign search field. * graphql.js Update function graphCampaignByName to use persisted query getCampaignNames used for autocomplete list for Campaign Name search gmo-campaign-list.js : Changed campaignName search to use filter operater : '=' /gmo-campaign-header.js : Updated campaign search autocompleteList to trigger sendCampaignListBlockEvent to make autocomplete search work * gmo-campaign-header.js : Updated function resetAllFilters() to call function sendGmoCampaignListBlockEvent(); gmo-campaign-list.js : Updated campaignCount to call function graphqlCampaignCount(graphqlFilter) with graphqlFilter graphql.js : Updated function graphCampaignCount : Added filter parameter, and call persisted query getCampaignNameFilter * Removed console.log messages * Fixed bug with currentPageInfo.nextCursor * Fixed bug in calculation of cursor to use for the previous page logic * graphql.js : Deleted function graphqlAllCampaigns(first,cursor) , replaced function graphqlStatusList() and graphqlProductList() with function graphqlQueryNameList(queryNameList) Variables baseApiUrl and projectId are now global/class level variables. gmo-campaign-header.js : Close dropdown list when a value is selected * update campaign with program * Updated function toggleOption : To only allow a single option to be selected in the dropdown list --------- Co-authored-by: Shivani gupta * Assets 98993 (#75) * many updates * finish dynamic properties - all properties should be dynamic based on graphql data - refactored some common lookups to a shared javascript file - updated overflow for overview/description - made status, products more presentable - more elegant handling of empty audience and kpi lists * test rename svg to resolve issue * finish updating icon names and mapping * resolve pr comments * Release 05.09.2024 (#78) * Added mapping for uuid (#74) Co-authored-by: Mathieu Lessard * Removed Prefix from Displayed UUID Value on Assets (#76) --------- Co-authored-by: Mathieu Lessard Co-authored-by: Christopher Heintzman * ASSETS-88902 : Add Target Geo Filter in the Landing Page (update all dropdown lists to use updated graphql queries) (#77) * Add hardcoded Geo(graphy) dropdown list filter to Campaign Header * Fixed Previous Page pagination logic for calculating the cursor for the Previous page * Updated Products and Status Dropdown Lists code to use updated graphql persisted queries * Assets 98994 (#81) * many updates * finish dynamic properties - all properties should be dynamic based on graphql data - refactored some common lookups to a shared javascript file - updated overflow for overview/description - made status, products more presentable - more elegant handling of empty audience and kpi lists * test rename svg to resolve issue * finish updating icon names and mapping * initial changeover from static to dynamic data * adjustments based on feedback * additional changes based on feedback * minor bugfix, null checks * squash final bug with read more * final touches * combine two graphql functions with duplicated code * bugfixes per pr review * ASSETS-88902 : Make the Business Line Dropdown List filter values in the Product Dropdown List filter (#82) * Add hardcoded Geo(graphy) dropdown list filter to Campaign Header * Fixed Previous Page pagination logic for calculating the cursor for the Previous page * Updated Products and Status Dropdown Lists code to use updated graphql persisted queries * Updated Business Line dropdown list to be populated by graphql persisted query getBusinessLine. Updated Geo dropdown list to be populated by graphql persisted query getGeoList * Updated the Business Line dropdown list to filter the Products List when a Business Line is selected. When a Business Line option is deselected then the Products List shows all products. * Reduced the sample dropdown list options * make links configurable, remove extraneous logs (#83) * ASSETS-88903 : [Issue] Product Name and Label are Undefined (#84) * Refactored the function buildProduct(product) to handle the condition when a product is not defined in the JSON object productMappings[product] which is defined in /scripts/shared-campaigns.js * Removed comment * ASSETS-88904 : Update Asset Thumbnail for Campaign List Entries (#85) * Refactored the function buildProduct(product) to handle the condition when a product is not defined in the JSON object productMappings[product] which is defined in /scripts/shared-campaigns.js * Added function searchAsset(programName, campaignName) to get the asset URL * Changed alt text to use assets repo-name property * Renamed/Moved AssetsDatasource.js to /scripts/assets gmo-campaign-list.js : Added logic to only allow blockConfig to be set on initial call to function decorate(block ... otherwise the values from blockConfig are overwritten when paginating to next page. gmo-campaign-details.js : Added campaign Image gmo-campaign-details.css : Updated CSS to display campaign Image * gmo-campaign-list.js : Deleted comment /scripts/assets.js : Updated to use createSearchEndpoint /scripts/scripts.js : Added export to export function createSearchEndpoint() { * Fixed bug when product is not defined in productMappings in /scripts/shared-campaigns.js * gmo-campaign-list.js : Removed iconImage.alt = "Failed to load image"; /scripts/assets.js : Updated facetFilters to an Array of Stings instead of Array of objects * Assets 98996 (#88) * initial grouping functionality * add sorting, label mappings * readd expand/collapse chevrons, add counts * add sort by date in groups * add important links dynamic generation * resolve bug with detailpage link * adjust width on links in deliverables tab * merge 12024 in and adjust width for links * update property name for revised completion date * implement graphql query for status mapping * ASSETS-88905 : Add Fields to the Overview Tab (#87) * Added Target Market Area lists function function createKPI(kpi) is renamed createLI(li) Added function buildTargetMarketAreaList(p0TargetMarketArea,p1TargetMarketArea) { * gmo-campaign-details.css : CSS fixes for scope-tag to not wrap gmo-campaign-details.js : Added Target Market Area and Platforms refactored async function buildChannelScope(scopeTypeId, scopes, block) to be able to display data for data based for a specified CSS ID * Added function getUniqueItems(items, property) to get unique values for deliverableType and platforms * Renamed function async function buildChannelScope(scopeTypeId, scopes, block) to async function buildFieldScopes(scopeTypeId, scopes, block) * Added global variable globalGraphFliter (#92) Previous and Next Buttons now call function decorate(block, numPerPage = currentNumberPerPage, cursor = '', previousPage = false, nextPage = false, graphQLFilter = {}) with graphFilter = currentGraphqlFilter * Assets 98996 (#93) * initial grouping functionality * add sorting, label mappings * readd expand/collapse chevrons, add counts * add sort by date in groups * add important links dynamic generation * resolve bug with detailpage link * adjust width on links in deliverables tab * merge 12024 in and adjust width for links * update property name for revised completion date * implement graphql query for status mapping * add deliverabletype graphql - todo: refactor, some superfluous function(s) can be removed * refactor product list to use graphql * fix deliverable type tags, fix multiline text * fix sort on marketing moments column * removing merge artifact * resolve PR comments * platforms mapping with graphql (#94) - also cleaned up extraneous/defunct code * Refactor sort icons (#95) * platforms mapping with graphql - also cleaned up extraneous/defunct code * refactor chevrons for column sort * add title attribs * ASSETS-88908 : Remove Console Error in Marketing Dashboard page and Campaign Details when no image is found (#96) * assets.js : Refactored code to eliminate console.log error gmo-campaign-list.js : Removed console.error("No campaign image found:", error); * Eliminated JavaScript errors when image is not found * Display total assets = 0 when the campaign image does not exist * Code cleanup (#98) * platforms mapping with graphql - also cleaned up extraneous/defunct code * refactor chevrons for column sort * add title attribs * remove kpi column * rename blocks * cleaning up redundant code * continuing cleanup * fix pagination bug * fix css bug in audience card * Updated product mapping label to text "Not Available" when the product mapping label is null (#99) * Code cleanup part 2 (#100) * platforms mapping with graphql - also cleaned up extraneous/defunct code * refactor chevrons for column sort * add title attribs * remove kpi column * rename blocks * cleaning up redundant code * continuing cleanup * fix pagination bug * fix css bug in audience card * cleanup, readd missing row - remove commented code - remove commented html - readd campaign name to overview tab if available * resolve undone null check for product label * handle bad response from product icon map * Added function closeAllDropDowns() (#101) Added function handleClickOutside(event) Updated function attachEventListeners() // Add event listener for clicks outside of dropdowns document.addEventListener('click', handleClickOutside); Added function resetProductsDropDown(); Updated function resetAllFilters() to call function resetProductsDropDown(); * Sticky deliverables header, update graphql endpoints (#102) * platforms mapping with graphql - also cleaned up extraneous/defunct code * refactor chevrons for column sort * add title attribs * remove kpi column * rename blocks * cleaning up redundant code * continuing cleanup * fix pagination bug * fix css bug in audience card * cleanup, readd missing row - remove commented code - remove commented html - readd campaign name to overview tab if available * resolve undone null check for product label * handle bad response from product icon map * update query endpoints, css tweak on deliverables * Added tool tip for Program Name and Campaign Name (#103) * remove duplicate icons --------- Co-authored-by: TyroneAEM <147942284+TyroneAEM@users.noreply.github.com> Co-authored-by: Michael Dickson Co-authored-by: Samruddhi <150183547+staware30@users.noreply.github.com> Co-authored-by: mdickson-adbe <95774602+mdickson-adbe@users.noreply.github.com> Co-authored-by: Tyrone Tse Co-authored-by: Shivani gupta Co-authored-by: mathieu-lessard Co-authored-by: Mathieu Lessard Co-authored-by: Christopher Heintzman Co-authored-by: Shivani gupta * ASSETS-88912 : [Issue] Last page number is not matching with total number of pages (#105) * ASSETS-88894 GRAPHQL Persisted Query Code (#53) * GRAPHQL POC Demo code to call a Persisted GraphQL query for Content Fragments * Updated the code call the GraphQL Persisted query from the QA AEM Author server, and also to authenticate using the JWT Bearer token * Renamed file test-graphql.js to graphql.js Added function graphqlAllCampaign() to get call GraphQL persisted query for All Campains Added function graphqlCampaignByName(campaignName) to get call GraphQL persisted query to get campaign by campaign name. Added function getGraphqlEndpoint() to get the value of property aemGraphqlEndpoint from the config file admin-config.json * Changed queryName to getAllCampaings * Rollback to 3/15/24 Commit a519a5155c3dd57f0c54ea8bd336cd0a231cffab before adding GraphQL test code * Delete test graphql code * ASSETS-88895 : Show HCV report pages to limited users (#55) * security.js updated function checkUserAccess for users that are members of the imsUserGroup, that if a page has the property reporting-access then access to the page is only granted if the user is member of te group defined by the property imsReportingGroup in the file admin-config.xlsx site-config.js : Updated the function getQuickLinkConfig to show the quick link to users who are members of the new column Group in shared-quicklinks tab of file site-config.xlsx * securiity.js : Deleted function isReportingAccessPage() as it is not needed any more. Updated function checkUserAccess() : Update code for non public pages, check if the current page path is in the pages returned by the function getQuickLinks. If it does not exist in quick links, then the user does not have access to the page. The functionQuickLinks already has logic to check if a page is only accessible by users of a page group. Renamed function checkGroupAccess(adminConfigGroupPropertyName) to checkPageGroupAccess(adminConfigGroupPropertyName) * Campaign List block for Marketing Dashboard page (#56) * initial commit, new blocks/files * rename files, start implementing * continuing to implement/adjust styling * numerous changes - finish building bones of list component - build bones of pagination/footer - add icons for products (will likely need more) - build javascript for pagination (cleanup needed) - build javascript for sorting list --------- Co-authored-by: Michael Dickson * updated hydration-utils.js (#54) MH: Added Firefly product to AA Modal 'Product' Field List * Assets 98990 - Dynamic campaign list (#61) * initial commit, new blocks/files * rename files, start implementing * continuing to implement/adjust styling * numerous changes - finish building bones of list component - build bones of pagination/footer - add icons for products (will likely need more) - build javascript for pagination (cleanup needed) - build javascript for sorting list * finish pagination * Updates to css/js to better align to mockup * ASSETS-88895 : Updated function getQuickLinkConfig() to still work if the row.Group is undefined or does not exist (#60) * security.js updated function checkUserAccess for users that are members of the imsUserGroup, that if a page has the property reporting-access then access to the page is only granted if the user is member of te group defined by the property imsReportingGroup in the file admin-config.xlsx site-config.js : Updated the function getQuickLinkConfig to show the quick link to users who are members of the new column Group in shared-quicklinks tab of file site-config.xlsx * securiity.js : Deleted function isReportingAccessPage() as it is not needed any more. Updated function checkUserAccess() : Update code for non public pages, check if the current page path is in the pages returned by the function getQuickLinks. If it does not exist in quick links, then the user does not have access to the page. The functionQuickLinks already has logic to check if a page is only accessible by users of a page group. Renamed function checkGroupAccess(adminConfigGroupPropertyName) to checkPageGroupAccess(adminConfigGroupPropertyName) * Updated function getQuickLinkConfig() to still work if the row.Group is undefined or does not exist * Added function export async function graphqlFilterOnMarketingInitiative(marketingInitiative) (#64) Which calls the persisted query gmo/filter-on-marketing-initiative with example parameters { "marketingInitiative": "FY24Q1-Q2_AdobeExpress_level Up" } https://author-p108396-e1046543.adobeaemcloud.com/graphql/execute.json/gmo/filter-on-marketing-initiative%3BmarketingInitiative=FY24Q1-Q2_AdobeExpress_level%20Up * Campaign List filters display (#65) * initial html structure setup * refactor from select to div implementation Select with multiselect displays as a list, not a dropdown * finish refactor, enable visual functionality - added all javascript needed for visual functionality - finished refactoring structure * Assets 98990 (#66) * initial commit, new blocks/files * rename files, start implementing * continuing to implement/adjust styling * numerous changes - finish building bones of list component - build bones of pagination/footer - add icons for products (will likely need more) - build javascript for pagination (cleanup needed) - build javascript for sorting list * finish pagination * Updates to css/js to better align to mockup * minor updates - update graphql query to have hardcoded offset/limit (to resolve error) - remove comments - add placeholder 'refresh date' message and css - move css for main body to accomodate above message * add firefly icon --------- Co-authored-by: Tyrone Tse * ASSETS-88899 : [Issue] Collection Detail Page is Redirecting to No-Access Page (#68) * Added function hideQuickLinks() * Added the hide field to the shared-quicklinks config/worksheet /blocks/adp-header/adp-header.js : Updated the code to only show links if hide!=='true' /scripts/security.js : Updated function checkUserAccess to get the current URL without the parameters for /collection/ and /share/ /scripts/site-config.js : Updated function to include the hide field * Refactored the If statement logic * Assets 98992- Campaign details/overview (#69) * initial block structure * start mocking up structure * many changes - finish overview tab mockup - (mostly) finish deliverables tab * fix css- add top/bottom borders to rows * delete invalid metadata from new svgs * updates for mvp - Rename and hide various elements of page - Refactor tablebuilding function to deal with items that are missing categorization properties * hide total assets * ASSETS-88900 : Update Pagination for gmo-campiagn-list Block (#70) * gmo-campagin-list refactored to use persisted query function function graphqlCampaignPaginated(first,cursor) to get a page of Campaigns at a time * Refactored the Navigation code for Previous Page, Next Page, Select Number of Items on a Page. Refactored the code for Repaginate for when the number of items page is changed Show current page status * graphql.js : Updated function graphqlAllCampaigns(first,cursor) to call getAllCampaigns persisted query with parameters first and cursor Deleted function graphqlCampaignPaginated(first,cursor) Updated gmo-campaign-list/gmo-campaign-list.js : To use persisted query graphqlAllCampaigns(first,cursor) and refactored the pagination to call the decorate function to call the graphql query each time to get the next or previous page * Fixed logic in calculating the nextCursor * Removed all debug console.log statements * Removed testCampaigns array of test campaign data, which now has been replaced by data from graphql * Renamed the variable testConfig to headerConfig * Campaign details block (#71) * initial block structure * start mocking up structure * many changes - finish overview tab mockup - (mostly) finish deliverables tab * fix css- add top/bottom borders to rows * delete invalid metadata from new svgs * updates for mvp - Rename and hide various elements of page - Refactor tablebuilding function to deal with items that are missing categorization properties * hide total assets * add status bar display Also adjusted demo data slightly to include due date and lead * update css/html to align with mockup and mvp * resolve border bug * implemented requested changes * ASSETS-88901 : (Backend) Campaign Page: Filters (#72) * graphql.js : Added functions graphqlProductList and graphqlStatusList gmo-campaign-header.js : Updated code to build Status dropdown from function graphqlStatusList Updated code to build Product dropdown from function graphqlProductList * /blocks/gmo-campaign-list/gmo-campaign-list.js => Added custom event listener for custom event gmoCampaignListBlock which allows the event gmoCampaignListBlock to be called from the block component gmo-campaign-header.js /blocks/gmo-campaign-header/gmo-campaign-header.js : Created new function sendGmoCampaignListBlockEvent() that calls the custom event gmoCampaignListBlock from the gmo-campaign-header. This will trigger the custom event in the /blocks/gmo-campaign-list to call the graphql persisted query getAllCampaigns with a filter based on the selected values from the dropdown lists for Business Line Status and Product * graphql.js : Added test function graphAllCampaignsFilterfirst,cursor,filter) : Which has the filter parameter which is the graphQL filter object Added function generateFilterJSON(filterParams) which generates the graphQL filter object from an Array of parameters. /blocks/gmo-campaign-header/gmo-campaign-header.js : Added event on the campaign-search field, to trigger the sendGmoCampaignBlockEvent after 3 characters are typed, later this will be replaced by a Campaign Suggested List generated as the user types /blocks/gmo-campaign-list/gmo-campaign-list.js : Added code build the graphQL filter object from the Campaign Search fields and Drop Downs Updated the function decorate to have new parameter graphQLFilter So that the function graphqlAllCampaignsFilter(numPerPage, cursor,graphQLFilter) can be called * Adde autocomplete list CSS, JS and HMTL for the campaign search field. * graphql.js Update function graphCampaignByName to use persisted query getCampaignNames used for autocomplete list for Campaign Name search gmo-campaign-list.js : Changed campaignName search to use filter operater : '=' /gmo-campaign-header.js : Updated campaign search autocompleteList to trigger sendCampaignListBlockEvent to make autocomplete search work * gmo-campaign-header.js : Updated function resetAllFilters() to call function sendGmoCampaignListBlockEvent(); gmo-campaign-list.js : Updated campaignCount to call function graphqlCampaignCount(graphqlFilter) with graphqlFilter graphql.js : Updated function graphCampaignCount : Added filter parameter, and call persisted query getCampaignNameFilter * Removed console.log messages * Fixed bug with currentPageInfo.nextCursor * Fixed bug in calculation of cursor to use for the previous page logic * graphql.js : Deleted function graphqlAllCampaigns(first,cursor) , replaced function graphqlStatusList() and graphqlProductList() with function graphqlQueryNameList(queryNameList) Variables baseApiUrl and projectId are now global/class level variables. gmo-campaign-header.js : Close dropdown list when a value is selected * update campaign with program --------- Co-authored-by: Shivani gupta * ASSETS-88901 : Campaign Header: Dropdown List Only Allow Single Value to Be Selected (#73) * graphql.js : Added functions graphqlProductList and graphqlStatusList gmo-campaign-header.js : Updated code to build Status dropdown from function graphqlStatusList Updated code to build Product dropdown from function graphqlProductList * /blocks/gmo-campaign-list/gmo-campaign-list.js => Added custom event listener for custom event gmoCampaignListBlock which allows the event gmoCampaignListBlock to be called from the block component gmo-campaign-header.js /blocks/gmo-campaign-header/gmo-campaign-header.js : Created new function sendGmoCampaignListBlockEvent() that calls the custom event gmoCampaignListBlock from the gmo-campaign-header. This will trigger the custom event in the /blocks/gmo-campaign-list to call the graphql persisted query getAllCampaigns with a filter based on the selected values from the dropdown lists for Business Line Status and Product * graphql.js : Added test function graphAllCampaignsFilterfirst,cursor,filter) : Which has the filter parameter which is the graphQL filter object Added function generateFilterJSON(filterParams) which generates the graphQL filter object from an Array of parameters. /blocks/gmo-campaign-header/gmo-campaign-header.js : Added event on the campaign-search field, to trigger the sendGmoCampaignBlockEvent after 3 characters are typed, later this will be replaced by a Campaign Suggested List generated as the user types /blocks/gmo-campaign-list/gmo-campaign-list.js : Added code build the graphQL filter object from the Campaign Search fields and Drop Downs Updated the function decorate to have new parameter graphQLFilter So that the function graphqlAllCampaignsFilter(numPerPage, cursor,graphQLFilter) can be called * Adde autocomplete list CSS, JS and HMTL for the campaign search field. * graphql.js Update function graphCampaignByName to use persisted query getCampaignNames used for autocomplete list for Campaign Name search gmo-campaign-list.js : Changed campaignName search to use filter operater : '=' /gmo-campaign-header.js : Updated campaign search autocompleteList to trigger sendCampaignListBlockEvent to make autocomplete search work * gmo-campaign-header.js : Updated function resetAllFilters() to call function sendGmoCampaignListBlockEvent(); gmo-campaign-list.js : Updated campaignCount to call function graphqlCampaignCount(graphqlFilter) with graphqlFilter graphql.js : Updated function graphCampaignCount : Added filter parameter, and call persisted query getCampaignNameFilter * Removed console.log messages * Fixed bug with currentPageInfo.nextCursor * Fixed bug in calculation of cursor to use for the previous page logic * graphql.js : Deleted function graphqlAllCampaigns(first,cursor) , replaced function graphqlStatusList() and graphqlProductList() with function graphqlQueryNameList(queryNameList) Variables baseApiUrl and projectId are now global/class level variables. gmo-campaign-header.js : Close dropdown list when a value is selected * update campaign with program * Updated function toggleOption : To only allow a single option to be selected in the dropdown list --------- Co-authored-by: Shivani gupta * Assets 98993 (#75) * many updates * finish dynamic properties - all properties should be dynamic based on graphql data - refactored some common lookups to a shared javascript file - updated overflow for overview/description - made status, products more presentable - more elegant handling of empty audience and kpi lists * test rename svg to resolve issue * finish updating icon names and mapping * resolve pr comments * Release 05.09.2024 (#78) * Added mapping for uuid (#74) Co-authored-by: Mathieu Lessard * Removed Prefix from Displayed UUID Value on Assets (#76) --------- Co-authored-by: Mathieu Lessard Co-authored-by: Christopher Heintzman * ASSETS-88902 : Add Target Geo Filter in the Landing Page (update all dropdown lists to use updated graphql queries) (#77) * Add hardcoded Geo(graphy) dropdown list filter to Campaign Header * Fixed Previous Page pagination logic for calculating the cursor for the Previous page * Updated Products and Status Dropdown Lists code to use updated graphql persisted queries * Assets 98994 (#81) * many updates * finish dynamic properties - all properties should be dynamic based on graphql data - refactored some common lookups to a shared javascript file - updated overflow for overview/description - made status, products more presentable - more elegant handling of empty audience and kpi lists * test rename svg to resolve issue * finish updating icon names and mapping * initial changeover from static to dynamic data * adjustments based on feedback * additional changes based on feedback * minor bugfix, null checks * squash final bug with read more * final touches * combine two graphql functions with duplicated code * bugfixes per pr review * ASSETS-88902 : Make the Business Line Dropdown List filter values in the Product Dropdown List filter (#82) * Add hardcoded Geo(graphy) dropdown list filter to Campaign Header * Fixed Previous Page pagination logic for calculating the cursor for the Previous page * Updated Products and Status Dropdown Lists code to use updated graphql persisted queries * Updated Business Line dropdown list to be populated by graphql persisted query getBusinessLine. Updated Geo dropdown list to be populated by graphql persisted query getGeoList * Updated the Business Line dropdown list to filter the Products List when a Business Line is selected. When a Business Line option is deselected then the Products List shows all products. * Reduced the sample dropdown list options * make links configurable, remove extraneous logs (#83) * ASSETS-88903 : [Issue] Product Name and Label are Undefined (#84) * Refactored the function buildProduct(product) to handle the condition when a product is not defined in the JSON object productMappings[product] which is defined in /scripts/shared-campaigns.js * Removed comment * ASSETS-88904 : Update Asset Thumbnail for Campaign List Entries (#85) * Refactored the function buildProduct(product) to handle the condition when a product is not defined in the JSON object productMappings[product] which is defined in /scripts/shared-campaigns.js * Added function searchAsset(programName, campaignName) to get the asset URL * Changed alt text to use assets repo-name property * Renamed/Moved AssetsDatasource.js to /scripts/assets gmo-campaign-list.js : Added logic to only allow blockConfig to be set on initial call to function decorate(block ... otherwise the values from blockConfig are overwritten when paginating to next page. gmo-campaign-details.js : Added campaign Image gmo-campaign-details.css : Updated CSS to display campaign Image * gmo-campaign-list.js : Deleted comment /scripts/assets.js : Updated to use createSearchEndpoint /scripts/scripts.js : Added export to export function createSearchEndpoint() { * Fixed bug when product is not defined in productMappings in /scripts/shared-campaigns.js * gmo-campaign-list.js : Removed iconImage.alt = "Failed to load image"; /scripts/assets.js : Updated facetFilters to an Array of Stings instead of Array of objects * Assets 98996 (#88) * initial grouping functionality * add sorting, label mappings * readd expand/collapse chevrons, add counts * add sort by date in groups * add important links dynamic generation * resolve bug with detailpage link * adjust width on links in deliverables tab * merge 12024 in and adjust width for links * update property name for revised completion date * implement graphql query for status mapping * ASSETS-88905 : Add Fields to the Overview Tab (#87) * Added Target Market Area lists function function createKPI(kpi) is renamed createLI(li) Added function buildTargetMarketAreaList(p0TargetMarketArea,p1TargetMarketArea) { * gmo-campaign-details.css : CSS fixes for scope-tag to not wrap gmo-campaign-details.js : Added Target Market Area and Platforms refactored async function buildChannelScope(scopeTypeId, scopes, block) to be able to display data for data based for a specified CSS ID * Added function getUniqueItems(items, property) to get unique values for deliverableType and platforms * Renamed function async function buildChannelScope(scopeTypeId, scopes, block) to async function buildFieldScopes(scopeTypeId, scopes, block) * Added global variable globalGraphFliter (#92) Previous and Next Buttons now call function decorate(block, numPerPage = currentNumberPerPage, cursor = '', previousPage = false, nextPage = false, graphQLFilter = {}) with graphFilter = currentGraphqlFilter * Assets 98996 (#93) * initial grouping functionality * add sorting, label mappings * readd expand/collapse chevrons, add counts * add sort by date in groups * add important links dynamic generation * resolve bug with detailpage link * adjust width on links in deliverables tab * merge 12024 in and adjust width for links * update property name for revised completion date * implement graphql query for status mapping * add deliverabletype graphql - todo: refactor, some superfluous function(s) can be removed * refactor product list to use graphql * fix deliverable type tags, fix multiline text * fix sort on marketing moments column * removing merge artifact * resolve PR comments * platforms mapping with graphql (#94) - also cleaned up extraneous/defunct code * Refactor sort icons (#95) * platforms mapping with graphql - also cleaned up extraneous/defunct code * refactor chevrons for column sort * add title attribs * ASSETS-88908 : Remove Console Error in Marketing Dashboard page and Campaign Details when no image is found (#96) * assets.js : Refactored code to eliminate console.log error gmo-campaign-list.js : Removed console.error("No campaign image found:", error); * Eliminated JavaScript errors when image is not found * Display total assets = 0 when the campaign image does not exist * Code cleanup (#98) * platforms mapping with graphql - also cleaned up extraneous/defunct code * refactor chevrons for column sort * add title attribs * remove kpi column * rename blocks * cleaning up redundant code * continuing cleanup * fix pagination bug * fix css bug in audience card * Updated product mapping label to text "Not Available" when the product mapping label is null (#99) * Code cleanup part 2 (#100) * platforms mapping with graphql - also cleaned up extraneous/defunct code * refactor chevrons for column sort * add title attribs * remove kpi column * rename blocks * cleaning up redundant code * continuing cleanup * fix pagination bug * fix css bug in audience card * cleanup, readd missing row - remove commented code - remove commented html - readd campaign name to overview tab if available * resolve undone null check for product label * handle bad response from product icon map * Added function closeAllDropDowns() (#101) Added function handleClickOutside(event) Updated function attachEventListeners() // Add event listener for clicks outside of dropdowns document.addEventListener('click', handleClickOutside); Added function resetProductsDropDown(); Updated function resetAllFilters() to call function resetProductsDropDown(); * Sticky deliverables header, update graphql endpoints (#102) * platforms mapping with graphql - also cleaned up extraneous/defunct code * refactor chevrons for column sort * add title attribs * remove kpi column * rename blocks * cleaning up redundant code * continuing cleanup * fix pagination bug * fix css bug in audience card * cleanup, readd missing row - remove commented code - remove commented html - readd campaign name to overview tab if available * resolve undone null check for product label * handle bad response from product icon map * update query endpoints, css tweak on deliverables * Added tool tip for Program Name and Campaign Name (#103) * remove duplicate icons * Change default items per page from 4 to 8 Changed undefined message for Campaign to Marketing Moment Not Available * Remove debugging from graphql.js * Deleted function debug_console() --------- Co-authored-by: Shivani Gupta <61603050+shiv-gup@users.noreply.github.com> Co-authored-by: Michael Dickson Co-authored-by: Samruddhi <150183547+staware30@users.noreply.github.com> Co-authored-by: mdickson-adbe <95774602+mdickson-adbe@users.noreply.github.com> Co-authored-by: Shivani gupta Co-authored-by: mathieu-lessard Co-authored-by: Mathieu Lessard Co-authored-by: Christopher Heintzman Co-authored-by: Shivani gupta * Performance refactoring (#107) * performance refactoring - improve thumbnail performance by makin async - format date property to yyyy-mm-dd - fix margin issue in program header - refactor function that builds header dropdown for increased performance * resolve bug with event listeners not attaching * add programID parameter to deliverable query (#109) * add programID parameter to deliverable query * quick null check for status color * remove console log * Update query names, handle formatting (#110) * update query names * handle null deliverable types * make dateFormat function available to detail block * remove unused function * remove console log messages * fix typo in graphql query name (#111) * ASSETS-88914 : Fix Pagination Frontend Code (#108) * Changed the default number items per page to 4 Added function debounce(func, wait) to delay the Previous and Next page to wait 0.5 seconds between each click, to stop the user clicking too fast and breaking pagination * Decrease debounce timeout to 200 milliseconds (0.2 secs) * Added check that next page cannot go past last page Added check that prev page cannot go back past page 1 * fix(pagination): Ensure proper handling of next/prev buttons and page boundaries - Added totalPages calculation and checks to ensure "Next" button is disabled on the last page - Updated nextPage function to correctly enable/disable pagination buttons - Improved prevPage function to ensure it navigates back to the first page correctly - Applied debounce to prevent rapid clicking issues - Refactored logic for enabling/disabling pagination buttons based on current page state * - Changed default items per page to 8 - Removed 4 items per page from filter - Removed cusorArray, and replaced it with currentPageInfo.previousCursor = currentPageInfo.currentCursor; - Set next cursor to data.programPaginated.pageInfo.endCursor; - Function prevPage now uses currentPage.previousCursor as the cursor parameter value, and calculating the cursor value to use from cursorArray is no longer needed. * Increased the click delay to 500 milliseconds * Disabled the Previous and Next Button as soon as they are clicked, to prevent the user multiple clicking the button. * Restored previous logic using cursorArray to manage the calculation of the cursor for the previous page. (#113) * Adjust height of deliverables tab on program-details block (#114) * minor css changes for program details * adjust deliverables table height * add min height to deliverables table * remove commented css * Sort deliverable type array before rows are made * DXI-26587- Modify landing page for v3 Hub, so Signin goes to CH/v4 (#116) * Replace throwing 404 error when the asset is not found, with asset.svg icon (#119) * Make content fragment path for WF mappings configurable (#118) * refactoring to use single graphql endpoint for map * refactor 'mapping' functions - refactor mapping retrieval functions in all three hcv blocks * Remove comments * remove unused function stub * resolve bug in header filter refresh * remove unused import * Fixed issue where header didn't load if the user didn't have a profile yet (#121) Co-authored-by: Mathieu Lessard * add checks for null 'review link' and 'final asset' (#122) * Refactor header and query variable extraction (#123) * add checks for null 'review link' and 'final asset' * Add msg for programs with no data available * refactor header so it can be used with no data * wrap msg with div for styling * refactor query variable extraction - had to account for ampersand in program name * Updated Product List (#117) * Product list updated * N/A value and label now match * Fixed formatting --------- Co-authored-by: Mathieu Lessard --------- Co-authored-by: mathieu-lessard Co-authored-by: Mathieu Lessard Co-authored-by: Christopher Heintzman Co-authored-by: Shivani Gupta <61603050+shiv-gup@users.noreply.github.com> Co-authored-by: TyroneAEM <147942284+TyroneAEM@users.noreply.github.com> Co-authored-by: Samruddhi <150183547+staware30@users.noreply.github.com> Co-authored-by: Tyrone Tse Co-authored-by: Shivani gupta Co-authored-by: Shivani gupta Co-authored-by: shivanigupta --- blocks/gmo-landing-page/gmo-landing-page.js | 2 +- .../gmo-program-details.css | 10 +- .../gmo-program-details.js | 146 ++++++++++++------ .../gmo-program-header/gmo-program-header.js | 11 +- blocks/gmo-program-list/gmo-program-list.js | 3 +- contenthub/hydration/hydration-utils.js | 140 ++++++++++------- scripts/graphql.js | 25 --- scripts/polaris.js | 9 +- scripts/security.js | 23 ++- scripts/shared-program.js | 37 +++-- scripts/site-config.js | 15 ++ 11 files changed, 255 insertions(+), 166 deletions(-) diff --git a/blocks/gmo-landing-page/gmo-landing-page.js b/blocks/gmo-landing-page/gmo-landing-page.js index b6da5a47..d2060aec 100644 --- a/blocks/gmo-landing-page/gmo-landing-page.js +++ b/blocks/gmo-landing-page/gmo-landing-page.js @@ -44,7 +44,7 @@ export default async function decorate(block) { const host = location.origin; const signInMsg = getSignInMsg(block); const config = readBlockConfig(block); - const redirect = host + config?.mainpage; + const redirect = config?.redirect ?? (host + config?.mainpage); block.innerHTML=`
diff --git a/blocks/gmo-program-details/gmo-program-details.css b/blocks/gmo-program-details/gmo-program-details.css index c653aafd..fb2c1b9f 100644 --- a/blocks/gmo-program-details/gmo-program-details.css +++ b/blocks/gmo-program-details/gmo-program-details.css @@ -1,3 +1,6 @@ +:root { + border-bottom: none; +} body { background-color: rgb(247, 246, 246); } @@ -43,8 +46,10 @@ body { border-radius: 6px; box-shadow: 0px 3px 6px #0000000D; margin-top: 20px; - min-height: 800px; padding: 20px; + & > .no-data-msg { + margin-top: 20px; + } } } @@ -319,7 +324,8 @@ body { } } .table-content { - height: 45vh; + max-height: 45vh; + min-height: 7vh; overflow-y: auto; &::-webkit-scrollbar { display: none; diff --git a/blocks/gmo-program-details/gmo-program-details.js b/blocks/gmo-program-details/gmo-program-details.js index ff05b20f..ededa381 100644 --- a/blocks/gmo-program-details/gmo-program-details.js +++ b/blocks/gmo-program-details/gmo-program-details.js @@ -1,63 +1,64 @@ import { decorateIcons, readBlockConfig } from '../../scripts/lib-franklin.js'; -import { getQueryVariable } from '../../scripts/shared.js'; import { executeQuery } from '../../scripts/graphql.js'; -import { resolveMappings, filterArray, getProductMapping, checkBlankString, dateFormat } from '../../scripts/shared-program.js'; +import { filterArray, getProductMapping, checkBlankString, dateFormat, statusMapping, getMappingArray } from '../../scripts/shared-program.js'; import { getBaseConfigPath } from '../../scripts/site-config.js'; import { searchAsset } from '../../scripts/assets.js'; let blockConfig; -const programName = getQueryVariable('programName'); -const programID = getQueryVariable('programID'); -const deliverableMappings = resolveMappings("getDeliverableTypeMapping"); -const platformMappings = resolveMappings("getPlatformsMapping"); +const queryVars = extractQueryVars(); +const programName = queryVars.programName; +const programID = queryVars.programID; +const deliverableMappings = getMappingArray('deliverableType'); +const platformMappings = getMappingArray('platforms'); export default async function decorate(block) { - const encodedSemi = encodeURIComponent(';'); const encodedProgram = encodeURIComponent(programName); const programQueryString = `getProgramDetails${encodedSemi}programName=${encodedProgram}${encodedSemi}programID=${encodeURIComponent(programID)}`; const programData = await executeQuery(programQueryString); + const program = programData.data.programList.items[0]; + blockConfig = readBlockConfig(block); + const header = buildHeader(program, queryVars).outerHTML; + if (!program) { + block.innerHTML = ` +
+ + Back +
+
+ ${header} +
No data available.
+
+ ` + decorateIcons(block); + enableBackBtn(block, blockConfig); + return; + } + const deliverableQueryString = `getProgramDeliverables${encodedSemi}programName=${encodedProgram}${encodedSemi}programID=${encodeURIComponent(programID)}`; const deliverables = await executeQuery(deliverableQueryString); - const p0TargetMarketArea = programData.data.programList.items[0].p0TargetMarketArea; - const p1TargetMarketArea = programData.data.programList.items[0].p1TargetMarketArea; + 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 program = programData.data.programList.items[0]; const kpis = buildKPIList(program).outerHTML; const targetMarketAreas = buildTargetMarketAreaList(p0TargetMarketArea,p1TargetMarketArea).outerHTML; const audiences = buildAudienceList(program).outerHTML; - const date = formatDate(program.launchDate); const artifactLinks = buildArtifactLinks(program).outerHTML; - blockConfig = readBlockConfig(block); + block.innerHTML = `
Back
-
-
-
-
-
- ${program.programName} -
- ${program.campaignName ? '
' + program.campaignName + '
': ""} -
- - Launch date - ${date} -
-
-
+ ${header}
Overview
Deliverables
@@ -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 ? `
` + + `Launch date${formatDate(program.launchDate)}
` : ""; + const programName = program ? program.programName : queryVars.programName; + const campaignName = program && program.campaignName ? '
' + program.campaignName + '
': ""; + headerWrapper.innerHTML = ` +
+
+
+
+ ${programName} +
+ ${campaignName} + ${date} +
+ ` + 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) {
${checkBlankString(typeLabel)}
+ ${deliverableJson.linkedFolderLink ? 'Final Asset ': "Not Available"}
@@ -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