From 853b9e9d24706e050df9b5a6e2e9cd03f01ad31a Mon Sep 17 00:00:00 2001
From: mdickson-adbe <95774602+mdickson-adbe@users.noreply.github.com>
Date: Fri, 26 Apr 2024 14:19:45 -0400
Subject: [PATCH 1/2] 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
---
.../gmo-campaign-details.css | 68 ++++++-
.../gmo-campaign-details.js | 189 ++++++++++++++----
2 files changed, 220 insertions(+), 37 deletions(-)
diff --git a/blocks/gmo-campaign-details/gmo-campaign-details.css b/blocks/gmo-campaign-details/gmo-campaign-details.css
index c524497c..d0bae026 100644
--- a/blocks/gmo-campaign-details/gmo-campaign-details.css
+++ b/blocks/gmo-campaign-details/gmo-campaign-details.css
@@ -144,6 +144,9 @@ body {
}
}
.use-cases-wrapper {
+ margin-bottom: 30px;
+}
+.use-cases-wrapper, .channel-scope-wrapper {
display: flex;
flex-direction: column;
margin-top: 30px;
@@ -152,7 +155,7 @@ body {
flex-direction: row;
margin-top: 10px;
}
- & .use-case-tag {
+ & .use-case-tag, .scope-tag {
font: normal normal normal 14px/17px Adobe Clean;
color: #505050;
border: 1px solid #D3D3D3;
@@ -164,6 +167,11 @@ body {
}
}
}
+.main-message-wrapper {
+ display: flex;
+ flex-direction: column;
+ margin-top: 20px;
+}
.links-wrapper {
margin-top: 30px;
& .links {
@@ -195,6 +203,12 @@ body {
margin-top: 10px;
margin-bottom: 10px;
}
+ &.scope {
+ font: normal normal normal 14px/21px Adobe Clean;
+ & > ul {
+ padding-left: 15px;
+ }
+ }
}
.milestone, .card-content {
font: normal normal normal 14px/21px Adobe Clean;
@@ -263,6 +277,14 @@ body {
}
&.datarow {
display: flex;
+ height: 88px;
+ font-weight: 200;
+ & .property {
+ line-height: 88px;
+ }
+ &:first-child, &:nth-child(2) {
+ border-top: 2px solid #F4F4F4;
+ }
}
&.collapsible.header {
height: unset;
@@ -288,6 +310,11 @@ body {
& .property {
line-height: 56px;
}
+ & .justify-center {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ }
}
.subheader {
&:nth-child(2) {
@@ -297,6 +324,43 @@ body {
border-top: 2px solid #F4F4F4;
}
}
+.status-wrapper {
+ height: 56px;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ & > .status-heading {
+ display: flex;
+ justify-content: space-between;
+ height: 12px;
+ font: normal normal normal 12px/15px Adobe Clean;
+
+ }
+ & > .status-bar-wrapper {
+ position: relative;
+ & > .status-bar-underlay {
+ background-color: #D3D3D3;
+ width: 100%;
+ margin-top: 15px;
+ height: 6px;
+ margin-bottom: 5px;
+ left: 0;
+ top: 0;
+ position: absolute;
+ border-radius: 3px;
+ }
+ & > .status-bar {
+ margin-top: 15px;
+ height: 6px;
+ background-color: #2680EB;
+ margin-bottom: 5px;
+ z-index: 1;
+ position: relative;
+ border-radius: 3px;
+ }
+ }
+
+}
.column1 {
margin-left: 45px;
}
@@ -316,7 +380,7 @@ body {
width: 100px;
}
.column9 {
- width: 90px;
+ width: 150px;
}
.table-column {
&:not(:last-child) {
diff --git a/blocks/gmo-campaign-details/gmo-campaign-details.js b/blocks/gmo-campaign-details/gmo-campaign-details.js
index 8ab24d33..03babcda 100644
--- a/blocks/gmo-campaign-details/gmo-campaign-details.js
+++ b/blocks/gmo-campaign-details/gmo-campaign-details.js
@@ -12,7 +12,9 @@ import { decorateIcons } from '../../scripts/lib-franklin.js';
'finalAssetName': 'Final Asset',
'finalAssetHref': '#',
'kpi': 'KPI',
- 'statusUpdate': '57'
+ 'statusUpdate': '57',
+ 'dueDate': '03/07/2024',
+ 'driver': 'Linda Tan'
},
{
'category': 'Awareness',
@@ -25,7 +27,9 @@ import { decorateIcons } from '../../scripts/lib-franklin.js';
'finalAssetName': 'Final Asset',
'finalAssetHref': '#',
'kpi': 'KPI',
- 'statusUpdate': '86'
+ 'statusUpdate': '86',
+ 'dueDate': '03/07/2024',
+ 'driver': 'Linda Tan'
},
{
'category': 'Awareness',
@@ -38,7 +42,9 @@ import { decorateIcons } from '../../scripts/lib-franklin.js';
'finalAssetName': 'Final Asset',
'finalAssetHref': '#',
'kpi': 'KPI',
- 'statusUpdate': '57'
+ 'statusUpdate': '57',
+ 'dueDate': '03/07/2024',
+ 'driver': 'Linda Tan'
},
{
'category': 'Awareness',
@@ -51,7 +57,9 @@ import { decorateIcons } from '../../scripts/lib-franklin.js';
'finalAssetName': 'Final Asset',
'finalAssetHref': '#',
'kpi': 'KPI',
- 'statusUpdate': '17'
+ 'statusUpdate': '17',
+ 'dueDate': '03/07/2024',
+ 'driver': 'Aina Tchoshanova'
},
{
'category': 'Awareness',
@@ -64,7 +72,9 @@ import { decorateIcons } from '../../scripts/lib-franklin.js';
'finalAssetName': 'Final Asset',
'finalAssetHref': '#',
'kpi': 'KPI',
- 'statusUpdate': '0%'
+ 'statusUpdate': '0%',
+ 'dueDate': '03/17/2024',
+ 'driver': 'Aina Tchoshanova'
},
{
'category': 'Education',
@@ -77,7 +87,9 @@ import { decorateIcons } from '../../scripts/lib-franklin.js';
'finalAssetName': 'Final Asset',
'finalAssetHref': '#',
'kpi': 'KPI',
- 'statusUpdate': '57'
+ 'statusUpdate': '57',
+ 'dueDate': '04/12/2024',
+ 'driver': 'Leah Walker'
},
{
'category': 'Education',
@@ -90,7 +102,9 @@ import { decorateIcons } from '../../scripts/lib-franklin.js';
'finalAssetName': 'Final Asset',
'finalAssetHref': '#',
'kpi': 'KPI',
- 'statusUpdate': '86'
+ 'statusUpdate': '86',
+ 'dueDate': '04/12/2024',
+ 'driver': 'Leah Walker'
},
{
'category': 'Education',
@@ -103,7 +117,9 @@ import { decorateIcons } from '../../scripts/lib-franklin.js';
'finalAssetName': 'Final Asset',
'finalAssetHref': '#',
'kpi': 'KPI',
- 'statusUpdate': '57'
+ 'statusUpdate': '57',
+ 'dueDate': '05/09/2024',
+ 'driver': 'Benjamin Lee'
},
{
'category': 'Conversion',
@@ -116,7 +132,9 @@ import { decorateIcons } from '../../scripts/lib-franklin.js';
'finalAssetName': 'Final Asset',
'finalAssetHref': '#',
'kpi': 'KPI',
- 'statusUpdate': '12'
+ 'statusUpdate': '12',
+ 'dueDate': '05/09/2024',
+ 'driver': 'Benjamin Lee'
},
{
'category': 'Conversion',
@@ -129,7 +147,9 @@ import { decorateIcons } from '../../scripts/lib-franklin.js';
'finalAssetName': 'Final Asset',
'finalAssetHref': '#',
'kpi': 'KPI',
- 'statusUpdate': '5'
+ 'statusUpdate': '5',
+ 'dueDate': '05/09/2024',
+ 'driver': 'Benjamin Lee'
},
{
'category': 'Conversion',
@@ -142,7 +162,9 @@ import { decorateIcons } from '../../scripts/lib-franklin.js';
'finalAssetName': 'Final Asset',
'finalAssetHref': '#',
'kpi': 'KPI',
- 'statusUpdate': '77'
+ 'statusUpdate': '77',
+ 'dueDate': '05/23/2024',
+ 'driver': 'Benjamin Lee'
},
{
'category': 'Conversion',
@@ -155,7 +177,9 @@ import { decorateIcons } from '../../scripts/lib-franklin.js';
'finalAssetName': 'Final Asset',
'finalAssetHref': '#',
'kpi': 'KPI',
- 'statusUpdate': '97%'
+ 'statusUpdate': '97%',
+ 'dueDate': '06/17/2024',
+ 'driver': 'Benjamin Lee'
},
{
'category': 'Conversion',
@@ -168,7 +192,9 @@ import { decorateIcons } from '../../scripts/lib-franklin.js';
'finalAssetName': 'Final Asset',
'finalAssetHref': '#',
'kpi': 'KPI',
- 'statusUpdate': '100'
+ 'statusUpdate': '100',
+ 'dueDate': '07/11/2024',
+ 'driver': 'Rufus Green'
},
{
'category': 'Conversion',
@@ -181,7 +207,9 @@ import { decorateIcons } from '../../scripts/lib-franklin.js';
'finalAssetName': 'Final Asset',
'finalAssetHref': '#',
'kpi': 'KPI',
- 'statusUpdate': '26'
+ 'statusUpdate': '26',
+ 'dueDate': '08/01/2024',
+ 'driver': 'Rufus Green'
},
{
'category': 'Conversion',
@@ -194,7 +222,9 @@ import { decorateIcons } from '../../scripts/lib-franklin.js';
'finalAssetName': 'Final Asset',
'finalAssetHref': '#',
'kpi': 'KPI',
- 'statusUpdate': '67'
+ 'statusUpdate': '67',
+ 'dueDate': '08/01/2024',
+ 'driver': 'Rufus Green'
},
{
'category': 'Conversion',
@@ -207,7 +237,9 @@ import { decorateIcons } from '../../scripts/lib-franklin.js';
'finalAssetName': 'Final Asset',
'finalAssetHref': '#',
'kpi': 'KPI',
- 'statusUpdate': '89'
+ 'statusUpdate': '89',
+ 'dueDate': '09/21/2024',
+ 'driver': 'Marcus Webber'
},
{
'category': 'Use',
@@ -220,7 +252,9 @@ import { decorateIcons } from '../../scripts/lib-franklin.js';
'finalAssetName': 'Final Asset',
'finalAssetHref': '#',
'kpi': 'KPI',
- 'statusUpdate': '57'
+ 'statusUpdate': '57',
+ 'dueDate': '09/21/2024',
+ 'driver': 'Marcus Webber'
},
{
'category': 'Use',
@@ -233,7 +267,9 @@ import { decorateIcons } from '../../scripts/lib-franklin.js';
'finalAssetName': 'Final Asset',
'finalAssetHref': '#',
'kpi': 'KPI',
- 'statusUpdate': '86'
+ 'statusUpdate': '86',
+ 'dueDate': '10/09/2024',
+ 'driver': 'Marcus Webber'
},
{
'category': 'Use',
@@ -246,7 +282,9 @@ import { decorateIcons } from '../../scripts/lib-franklin.js';
'finalAssetName': 'Final Asset',
'finalAssetHref': '#',
'kpi': 'KPI',
- 'statusUpdate': '57'
+ 'statusUpdate': '57',
+ 'dueDate': '11/29/2024',
+ 'driver': 'Sean Smith'
},
{
'category': 'Use',
@@ -259,7 +297,9 @@ import { decorateIcons } from '../../scripts/lib-franklin.js';
'finalAssetName': 'Final Asset',
'finalAssetHref': '#',
'kpi': 'KPI',
- 'statusUpdate': '17'
+ 'statusUpdate': '17',
+ 'dueDate': '11/29/2024',
+ 'driver': 'Sean Smith'
},
{
'category': 'Use',
@@ -272,7 +312,9 @@ import { decorateIcons } from '../../scripts/lib-franklin.js';
'finalAssetName': 'Final Asset',
'finalAssetHref': '#',
'kpi': 'KPI',
- 'statusUpdate': '57'
+ 'statusUpdate': '57',
+ 'dueDate': '11/29/2024',
+ 'driver': 'Sean Smith'
},
{
'category': 'Use',
@@ -285,7 +327,9 @@ import { decorateIcons } from '../../scripts/lib-franklin.js';
'finalAssetName': 'Final Asset',
'finalAssetHref': '#',
'kpi': 'KPI',
- 'statusUpdate': '17'
+ 'statusUpdate': '17',
+ 'dueDate': '11/29/2024',
+ 'driver': 'Sean Smith'
},
{
'category': 'Use',
@@ -298,7 +342,9 @@ import { decorateIcons } from '../../scripts/lib-franklin.js';
'finalAssetName': 'Final Asset',
'finalAssetHref': '#',
'kpi': 'KPI',
- 'statusUpdate': '57'
+ 'statusUpdate': '57',
+ 'dueDate': '11/29/2024',
+ 'driver': 'Sean Smith'
},
{
'category': 'Use',
@@ -311,7 +357,9 @@ import { decorateIcons } from '../../scripts/lib-franklin.js';
'finalAssetName': 'Final Asset',
'finalAssetHref': '#',
'kpi': 'KPI',
- 'statusUpdate': '17'
+ 'statusUpdate': '17',
+ 'dueDate': '11/29/2024',
+ 'driver': 'Sean Smith'
},
{
'category': '',
@@ -324,7 +372,9 @@ import { decorateIcons } from '../../scripts/lib-franklin.js';
'finalAssetName': 'Final Asset',
'finalAssetHref': '#',
'kpi': 'KPI',
- 'statusUpdate': '35'
+ 'statusUpdate': '35',
+ 'dueDate': '11/29/2024',
+ 'driver': 'Sean Smith'
},
{
'category': '',
@@ -337,7 +387,9 @@ import { decorateIcons } from '../../scripts/lib-franklin.js';
'finalAssetName': 'Final Asset',
'finalAssetHref': '#',
'kpi': 'KPI',
- 'statusUpdate': '35'
+ 'statusUpdate': '35',
+ 'dueDate': '11/29/2024',
+ 'driver': 'Sean Smith'
},
{
'subcategory': 'No Category',
@@ -349,7 +401,9 @@ import { decorateIcons } from '../../scripts/lib-franklin.js';
'finalAssetName': 'Final Asset',
'finalAssetHref': '#',
'kpi': 'KPI',
- 'statusUpdate': '35'
+ 'statusUpdate': '85',
+ 'dueDate': '11/29/2024',
+ 'driver': 'Sean Smith'
},
{
'category': '',
@@ -361,12 +415,15 @@ import { decorateIcons } from '../../scripts/lib-franklin.js';
'finalAssetName': 'Final Asset',
'finalAssetHref': '#',
'kpi': 'KPI',
- 'statusUpdate': '35'
+ 'statusUpdate': '35',
+ 'dueDate': '11/29/2024',
+ 'driver': 'Sean Smith'
}
];
export default async function decorate(block) {
- const rows = buildTable(testData);
+ //const rows = buildTable(testData);
+ const rows = buildTableNoGroups(testData);
block.innerHTML = `
+
+ Main Message
+
+ A major genAI release of the Photoshop beta app that delivers new and enhanced generative AI capabilities.
+
+
+
Links to Important Artifacts
@@ -431,7 +502,7 @@ export default async function decorate(block) {
-
+
Milestones
@@ -453,12 +524,36 @@ export default async function decorate(block) {
Adobe Express Mobile App
+
+
Feature Scope
+
+ - Text to image
+ - Generative fill
+ - Text effects
+
+
Audiences
Existing Express Users
+
+
+ Prospects with priority on communicators
+
+
+
+ CC entitled members who have not used Express
+
+
+
+ CC free unentitled members on mobile (PsX, LR)
+
+
+
+ K12
+
@@ -487,7 +582,7 @@ export default async function decorate(block) {
KPI
Status Update
Completion Date
- Lead / Driver
+ Project Owner
@@ -562,6 +657,15 @@ function buildTable(data) {
return rows;
}
+function buildTableNoGroups(data) {
+ const rows = document.createElement('div');
+ data.forEach((campaign) => {
+ const tableRow = buildTableRow(campaign, false);
+ rows.appendChild(tableRow);
+ })
+ return rows;
+}
+
function getUniqueValues(array, filterValue) {
const uniqueValues = new Set();
array.forEach(obj => {
@@ -596,7 +700,7 @@ function buildHeaderRow(category, headerType, isInactive) {
}
function buildTableRow(campaignJson, createHidden) {
- console.log(campaignJson);
+ //console.log(campaignJson);
const dataRow = document.createElement('div');
dataRow.classList.add('row', 'datarow');
if (createHidden) dataRow.classList.add('inactive');
@@ -604,10 +708,25 @@ function buildTableRow(campaignJson, createHidden) {
${campaignJson.name}
${campaignJson.type}
${campaignJson.channel}
- ${campaignJson.reviewLinkName}
- ${campaignJson.finalAssetName}
+
+
${campaignJson.kpi}
- ${campaignJson.statusUpdate}
+
+
+
+
Progress
+
${campaignJson.statusUpdate}%
+
+
+
+
${campaignJson.dueDate}
${campaignJson.driver}
`
From 27e2467338e964b10fc43b410d008e4033f80e3c Mon Sep 17 00:00:00 2001
From: TyroneAEM <147942284+TyroneAEM@users.noreply.github.com>
Date: Thu, 2 May 2024 15:34:20 -0500
Subject: [PATCH 2/2] 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
---
.../gmo-campaign-header.css | 36 ++++
.../gmo-campaign-header.js | 186 +++++++++++++-----
blocks/gmo-campaign-list/gmo-campaign-list.js | 67 +++++--
scripts/graphql.js | 116 +++++++++--
4 files changed, 325 insertions(+), 80 deletions(-)
diff --git a/blocks/gmo-campaign-header/gmo-campaign-header.css b/blocks/gmo-campaign-header/gmo-campaign-header.css
index 00343bc9..ac30d581 100644
--- a/blocks/gmo-campaign-header/gmo-campaign-header.css
+++ b/blocks/gmo-campaign-header/gmo-campaign-header.css
@@ -15,6 +15,7 @@
border-radius: 4px;
display: flex;
align-items: center;
+ position: relative; /* Added to be the anchor for absolute positioning for autocomplete feature*/
& > .icon {
background-color: #FFF;
height: 14px;
@@ -161,3 +162,38 @@
}
}
}
+
+/* Add the following for autocomplete styling */
+
+.autocomplete-items {
+ position: absolute;
+ top: 100%;
+ left: 0;
+ right: 0;
+ border: 1px solid #cccccc;
+ border-top: none;
+ z-index: 10;
+ background: #ffffff;
+ overflow-y: auto;
+ max-height: 200px;
+ border-bottom-left-radius: 4px;
+ border-bottom-right-radius: 4px;
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1);
+}
+
+.autocomplete-items div {
+ padding: 10px 15px;
+ cursor: pointer;
+ border-bottom: 1px solid #f0f0f0;
+ font-size: 14px; /* Adjust the font-size as needed */
+ color: #333; /* Adjust the text color as needed */
+ line-height: 1.4; /* Adjust line-height for better readability if necessary */
+}
+
+.autocomplete-items div:hover {
+ background-color: #e9e9e9;
+}
+
+.autocomplete-items div:last-child {
+ border-bottom: none;
+}
diff --git a/blocks/gmo-campaign-header/gmo-campaign-header.js b/blocks/gmo-campaign-header/gmo-campaign-header.js
index d9961bdd..6e4e96cb 100644
--- a/blocks/gmo-campaign-header/gmo-campaign-header.js
+++ b/blocks/gmo-campaign-header/gmo-campaign-header.js
@@ -1,4 +1,5 @@
import { decorateIcons } from '../../scripts/lib-franklin.js';
+import { graphqlQueryNameList, graphqlCampaignByName } from '../../scripts/graphql.js';
export default async function decorate(block) {
block.innerHTML = `
@@ -6,21 +7,19 @@ export default async function decorate(block) {
-
Categories
-
+
Business Line
+
@@ -32,7 +31,7 @@ export default async function decorate(block) {
-
+
Option 1
Option 2
Option 3
@@ -41,23 +40,7 @@ export default async function decorate(block) {
-
+
Products
@@ -66,29 +49,12 @@ export default async function decorate(block) {
-
-
-
-
-
Other (TBD)
-
@@ -100,6 +66,110 @@ export default async function decorate(block) {
`;
+
+ // autocomplete feature
+ const autocompleteList = document.getElementById('autocomplete-list');
+ // Get the input element by its ID
+ const searchInput = document.getElementById('campaign-search');
+ searchInput.addEventListener('input', async function() {
+ const value = this.value;
+ if (value)
+ {
+ const graphqlData = await graphqlCampaignByName(value);
+ //Get unique values
+ const searchItems = Array.from(new Set(graphqlData.data.programList.items.map(item => item.campaignName)));
+ autocomplete(value, searchItems);
+ }
+ else
+ {
+ //The value has been cleard so trigger the gmo-campaign-list block from the gmo-campaign-header
+ sendGmoCampaignListBlockEvent();
+ }
+ });
+
+ function autocomplete(value, items) {
+ clearAutocomplete();
+ if (!value) return;
+ const filteredItems = items.filter(item => item.toLowerCase().includes(value.toLowerCase()));
+ filteredItems.forEach(item => {
+ const entry = document.createElement('div');
+ entry.innerHTML = item;
+ entry.addEventListener('click', function() {
+ searchInput.value = this.innerText;
+ clearAutocomplete();
+ });
+ autocompleteList.appendChild(entry);
+ });
+ }
+
+ function clearAutocomplete() {
+ while (autocompleteList.firstChild) {
+ autocompleteList.removeChild(autocompleteList.firstChild);
+ }
+ }
+
+ // Listen for click events on the autocomplete list
+ autocompleteList.addEventListener('click', function(event) {
+ //Trigger the gmo-campaign-list block from the gmo-campaign-header
+ sendGmoCampaignListBlockEvent();
+ });
+
+ // Listen for change events on the autocomplete list
+ autocompleteList.addEventListener('change', function(event) {
+ //Trigger the gmo-campaign-list block from the gmo-campaign-header
+ sendGmoCampaignListBlockEvent();
+ });
+
+ //Status List
+ const statusResponse = await graphqlQueryNameList('getStatusList');
+ const statuses = statusResponse.data.programList.items;
+
+ // Extract unique statuses
+ const uniqueStatuses = Array.from(new Set(statuses.map(item => item.status)));
+ let dropdownContent = document.getElementById('dropdownStatusOptions');
+ // Clear existing options
+ dropdownContent.innerHTML = '';
+ // Append new options
+ uniqueStatuses.forEach((status, index) => {
+ // Create a new anchor element for each status
+ var anchor = document.createElement('a');
+ anchor.href = "#";
+ anchor.id = "option" + (index + 1); // increment index for 1-based id
+ //anchor.dataset.value = "option" + (index + 1);
+ anchor.dataset.value = status;
+ anchor.dataset.type = "status";
+ anchor.className = "dropoption";
+ anchor.textContent = status; // using the status as the text
+ // Append to the dropdown
+ dropdownContent.appendChild(anchor);
+ });
+
+ //Product List
+ const productResponse = await graphqlQueryNameList('getProductList');
+ const products = productResponse.data.programList.items;
+
+ // Extract unique statuses
+ const uniqueProducts = Array.from(new Set(products.map(item => item.productOffering)));
+ let dropdownProductContent = document.getElementById('dropdownProductOptions');
+ // Clear existing options
+ dropdownProductContent.innerHTML = '';
+ // Append new options
+ uniqueProducts.forEach((product, index) => {
+ // Create a new anchor element for each status
+ var anchor = document.createElement('a');
+ anchor.href = "#";
+ anchor.id = "option" + (index + 1); // increment index for 1-based id
+ //anchor.dataset.value = "option" + (index + 1);
+ anchor.dataset.value = product;
+ anchor.dataset.type = "productOffering";//field in graphQL
+ anchor.className = "dropoption";
+ anchor.textContent = product; // using the status as the text
+ // Append to the dropdown
+ dropdownProductContent.appendChild(anchor);
+ });
+
+ //End product dropdown
+
document.querySelectorAll('.dropdown-button').forEach((button) => {
button.addEventListener('click', (event) => {
toggleDropdown(event.target);
@@ -108,6 +178,8 @@ export default async function decorate(block) {
document.querySelectorAll('.dropoption').forEach((button) => {
button.addEventListener('click', (event) => {
toggleOption(event.target.dataset.value, event.target.dataset.type);
+ //Closes the dropdown list
+ toggleDropdown(event.target);
});
});
document.querySelector('.reset-filters').addEventListener('click', () => {
@@ -160,9 +232,16 @@ function handleSelectedFilter(option) {
} else {
filterTagRoot.removeChild(document.querySelector(`.selected-filter[data-value='${filterValue}'][data-type='${filterType}']`));
}
+
+ //Trigger the gmo-campaign-list block from the gmo-campaign-header
+ sendGmoCampaignListBlockEvent();
}
function resetAllFilters() {
+ //Clear the campaignName search field
+ const searchInput = document.getElementById('campaign-search');
+ searchInput.value = '';
+
const selectedFilters = document.querySelectorAll('.dropoption.selected');
selectedFilters.forEach((element) => {
element.classList.toggle('selected');
@@ -170,6 +249,8 @@ function resetAllFilters() {
const filterTagRoot = document.querySelector('.selected-filters-list');
filterTagRoot.replaceChildren();
checkResetBtn();
+ //Trigger the gmo-campaign-list block from the gmo-campaign-header
+ sendGmoCampaignListBlockEvent();
}
function checkResetBtn() {
@@ -181,3 +262,8 @@ function checkResetBtn() {
resetFiltersBtn.classList.add('inactive');
}
}
+
+function sendGmoCampaignListBlockEvent() {
+ const blockEvent = new CustomEvent('gmoCampaignListBlock');
+ document.dispatchEvent(blockEvent);
+}
diff --git a/blocks/gmo-campaign-list/gmo-campaign-list.js b/blocks/gmo-campaign-list/gmo-campaign-list.js
index 36b89b3b..6894173c 100644
--- a/blocks/gmo-campaign-list/gmo-campaign-list.js
+++ b/blocks/gmo-campaign-list/gmo-campaign-list.js
@@ -1,8 +1,6 @@
import { readBlockConfig } from '../../scripts/lib-franklin.js';
import { decorateIcons } from '../../scripts/lib-franklin.js';
-import { graphqlAllCampaigns, graphqlCampaignCount } from '../../scripts/graphql.js';
-
-const icon = 'https://delivery-p108396-e1046543.adobeaemcloud.com/adobe/assets/deliver/urn:aaid:aem:acdaa42f-00ae-42f4-97e5-8309c42d9076/marketing-hub-102023-lockup-video.png'
+import { graphqlAllCampaignsFilter, graphqlCampaignCount, generateFilterJSON } from '../../scripts/graphql.js';
const headerConfig = [
{
@@ -36,17 +34,45 @@ const headerConfig = [
let currentPageInfo = {};
let cursorArray = [];
let currentPage = 1;
-let currentNumberPerPage = 4
+let currentNumberPerPage = 4;
//Get Campaign Count for pagination
-const campaignCount = await graphqlCampaignCount();
+let campaignCount = await graphqlCampaignCount();
+
+//Custom event gmoCampaignListBlock to allow the gmo-campaign-header to trigger the gmo-campaign-list to update
+document.addEventListener('gmoCampaignListBlock', async function() {
+ //Build graphq filter that is passed to the graphql persisted queries
+ const graphQLFilterArray = getFilterValues();
+ const searchInputValue = document.getElementById('campaign-search').value;
+ if (searchInputValue!=='')
+ {
+ graphQLFilterArray.push({type:'campaignName', value:searchInputValue, operator:'='})
+ }
+ const graphqlFilter = generateFilterJSON(graphQLFilterArray);
+ const block = document.querySelector('.gmo-campaign-list.block');
+ //Get Campaign Count for pagination
+ campaignCount = await graphqlCampaignCount(graphqlFilter);
+ //Trigger loading the gmo-campaign-block
+ //Reset page variables
+ currentPageInfo = {};
+ cursorArray = [];
+ currentPage = 1;
+ currentNumberPerPage = 4;
+ decorate( block, currentNumberPerPage, '', false, false, graphqlFilter);
+});
+
-export default async function decorate(block, numPerPage = currentNumberPerPage, cursor = '', previousPage = false, nextPage = false) {
+export default async function decorate(block, numPerPage = currentNumberPerPage, cursor = '', previousPage = false, nextPage = false, graphQLFilter = {}) {
- const campaignPaginatedResponse = await graphqlAllCampaigns(numPerPage, cursor);
- const campaigns = campaignPaginatedResponse.data.campaignPaginated.edges;
- currentPageInfo = campaignPaginatedResponse.data.campaignPaginated.pageInfo;
+ const campaignPaginatedResponse = await graphqlAllCampaignsFilter(numPerPage, cursor,graphQLFilter);
+ const campaigns = campaignPaginatedResponse.data.programPaginated.edges;
+ currentPageInfo = campaignPaginatedResponse.data.programPaginated.pageInfo;
+ //Current cursor used in previous page logic
+ currentPageInfo.currentCursor = cursor;
+ //Next Page
+ if (currentPageInfo.hasNextPage){
+ currentPageInfo.nextCursor = campaigns[campaigns.length - 1].cursor;
+ }
- currentPageInfo.nextCursor = campaigns[campaigns.length - 1].cursor;
if (!previousPage && !nextPage)
{
cursorArray = campaigns.map(item => item.cursor);
@@ -89,9 +115,23 @@ export default async function decorate(block, numPerPage = currentNumberPerPage,
{
footerNext.classList.remove('active');
}
-
decorateIcons(block);
+}
+
+function getFilterValues(){
+ // Select all elements with the class 'selected-filter'
+ const filters = document.querySelectorAll('.selected-filter');
+ // Create an array to hold the data-type and data-value attributes
+ const filterAttributes = [];
+ // Loop through each filter element and extract both 'data-type' and 'data-value' attributes
+ filters.forEach(filter => {
+ const dataType = filter.getAttribute('data-type');
+ const dataValue = filter.getAttribute('data-value');
+ filterAttributes.push({ type: dataType, value: dataValue, operator : "=" });
+ });
+
+ return filterAttributes;
}
function buildCampaignList(campaigns, numPerPage) {
@@ -329,10 +369,11 @@ function nextPage(nextBtn) {
function prevPage(prevBtn) {
if (currentPageInfo.hasPreviousPage) {
currentPage--;
-
const block = document.querySelector('.gmo-campaign-list.block');
+ const currentCursor = currentPageInfo.nextCursor || currentPageInfo.currentCursor;
//Calculate cursor for previous page
- const indexCursor = cursorArray.indexOf(currentPageInfo.nextCursor) - currentPageInfo.itemCount - currentNumberPerPage;
+ const indexCursor = cursorArray.indexOf(currentCursor) - currentPageInfo.itemCount - currentNumberPerPage;
+
decorate(block, currentNumberPerPage, cursorArray[indexCursor], true, false);
if (!(prevBtn.classList.contains('active'))) {
return;
diff --git a/scripts/graphql.js b/scripts/graphql.js
index abbb0986..35243c75 100644
--- a/scripts/graphql.js
+++ b/scripts/graphql.js
@@ -1,13 +1,42 @@
import { getBearerToken } from './security.js';
import { getAdminConfig } from './site-config.js';
+import { logError } from './scripts.js';
-export async function graphqlCampaignCount() {
- const baseApiUrl = `${await getGraphqlEndpoint()}/graphql/execute.json`;
- const projectId = 'gmo';
- const queryName = 'campaign-names';
+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 = 'getCampaignNameFilter';
+ const encodedSemiColon = encodeURIComponent(';');
+ const encodedFilter = encodeURIComponent(JSON.stringify(filter));
+ const graphqlEndpoint = `${baseApiUrl}/${projectId}/${queryName}${encodedSemiColon}filter=${encodedFilter}`;
+ const jwtToken = await getBearerToken();
+
try {
const options = {
method: 'GET',
@@ -19,7 +48,7 @@ export async function graphqlCampaignCount() {
// Handle response codes
if (response.status === 200) {
const responseBody = await response.json();
- return responseBody.data.campaignList.items.length;
+ return responseBody.data.programList.items.length;
} if (response.status === 404) {
// Handle 404 error
const errorResponse = await response.json();
@@ -36,15 +65,13 @@ export async function graphqlCampaignCount() {
}
-export async function graphqlAllCampaigns(first,cursor) {
- const baseApiUrl = `${await getGraphqlEndpoint()}/graphql/execute.json`;
- const projectId = 'gmo';
+export async function graphqlAllCampaignsFilter(first,cursor,filter) {
const queryName = 'getAllCampaigns';
const encodedFirst = encodeURIComponent(first);
const encodedSemiColon = encodeURIComponent(';');
const encodedCursor = encodeURIComponent(cursor);
-
- const graphqlEndpoint = `${baseApiUrl}/${projectId}/${queryName}${encodedSemiColon}first=${encodedFirst}${encodedSemiColon}cursor=${encodedCursor}`;
+ const encodedFilter = encodeURIComponent(JSON.stringify(filter));
+ const graphqlEndpoint = `${baseApiUrl}/${projectId}/${queryName}${encodedSemiColon}first=${encodedFirst}${encodedSemiColon}cursor=${encodedCursor}${encodedSemiColon}filter=${encodedFilter}`;
const jwtToken = await getBearerToken();
try {
@@ -62,24 +89,21 @@ export async function graphqlAllCampaigns(first,cursor) {
} if (response.status === 404) {
// Handle 404 error
const errorResponse = await response.json();
- throw new Error(`Failed to get graphqlCampaignPaginated (404): ${errorResponse.detail}`);
+ throw new Error(`Failed to get graphqlAllCampaignsFilter (404): ${errorResponse.detail}`);
} else {
// Handle other response codes
- throw new Error(`Failed to retrieve graphqlCampaignPaginated: ${response.status} ${response.statusText}`);
+ throw new Error(`Failed to retrieve graphqlAllCampaignsFilter: ${response.status} ${response.statusText}`);
}
} catch (error) {
// Handle network or other errors
- logError('graphqlCampaignPaginated', error);
+ logError('graphqlAllCampaignsFilter', error);
throw error;
}
}
export async function graphqlCampaignByName(campaignName) {
-
- const baseApiUrl = `${await getGraphqlEndpoint()}/graphql/execute.json`;
- const projectId = 'gmo';
- const queryName = 'campaign-name-in-param';
+ const queryName = 'getCampaignNames';
const encodedCampaignName = encodeURIComponent(campaignName);
const encodedSemiColon = encodeURIComponent(';');
//persisted query URLs have to be encoded together with the first semicolon
@@ -139,3 +163,61 @@ async function getGraphqlEndpoint() {
const result = await getAdminConfig();
return result.aemGraphqlEndpoint;
}
+
+
+
+/**
+ * Generates GraphQL Filter String from an array filterParams
+ * @param {Array of parameter objects} filterParams -
+ * Expected parameter format
+ * [
+ * {type:"campignName", value:"Photo" ,operator : "CONTAINS"},
+ * {type:"status", value:"Current" ,operator : "="}
+ * ]
+ * operator : "CONTAINS" generates
+ * "_expressions": [
+ * {
+ * "value": "",
+ * "_operator": "CONTAINS",
+ * "_ignoreCase": true
+ * }
+ * ]
+ * operator : "=" generates
+ * "_expressions": [
+ * {
+ * "value": "",
+ * }
+ * ]
+ * @returns {string} GraphQL Filter JSON Object.
+ */
+
+export function generateFilterJSON(filterParams) {
+
+ // Initialize an empty object to hold the final structure
+ const result = {};
+
+ // Iterate over each item in the filterParams array
+ filterParams.forEach(param => {
+ // Initialize the object for each parameter name if it doesn't already exist
+ if (!result[param.type]) {
+ result[param.type] = { _expressions: [] };
+ }
+
+ // Create the expression object based on the operator
+ const expression = { value: param.value };
+ if (param.operator === "CONTAINS") {
+ expression._operator = "CONTAINS";
+ expression._ignoreCase = true;
+ }
+
+ // Add the expression object to the _expressions array for the corresponding parameter name
+ result[param.type]._expressions.push(expression);
+ });
+
+ // Convert the result object to JSON
+ const jsonResult = JSON.stringify(result,null,4);
+ // Logging the JSON to see the output
+ console.debug('Graphql filter',jsonResult);
+ console.debug('result', result);
+ return result;
+}