Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ASSETS-88904 : Update Asset Thumbnail for Campaign List Entries #85

Merged
merged 8 commits into from
May 16, 2024
16 changes: 12 additions & 4 deletions blocks/gmo-campaign-details/gmo-campaign-details.css
Original file line number Diff line number Diff line change
Expand Up @@ -86,14 +86,22 @@ body {
margin-left: 10px;
}
}

.campaign-img {
width: 80px;
height: 80px;
color: darkgray;
border: 1px solid black;
border-radius: 20px;
margin-right: 14px;
background: #e2e2e2;
border-radius: 18px;
}

.campaign-img img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 18px;
}

.tab-wrapper {
display: flex;
justify-content: space-between;
Expand Down Expand Up @@ -417,4 +425,4 @@ body {
&:not(:last-child) {
margin-right: 85px;
}
}
}
24 changes: 22 additions & 2 deletions blocks/gmo-campaign-details/gmo-campaign-details.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { getProgramInfo } from '../../scripts/graphql.js';
import { checkBlankString } from '../gmo-campaign-list/gmo-campaign-list.js'
import { statusMappings, productMappings } from '../../scripts/shared-campaigns.js';
import { getBaseConfigPath } from '../../scripts/site-config.js';
import { searchAsset } from '../../scripts/assets.js';

let blockConfig;

Expand Down Expand Up @@ -160,6 +161,12 @@ export default async function decorate(block) {
</div>
`;

try {
const imageObject = await searchAsset(program.programName, program.campaignName);
insertImageIntoCampaignImg(block,imageObject);
} catch (error) {
console.error("Failed to load campaign image:", error);
}

block.querySelector('.tab-wrapper').addEventListener('click', (event) => {
switchTab(event.target);
Expand All @@ -181,6 +188,13 @@ export default async function decorate(block) {
buildDeliverablesTable(await deliverables, block);
}

function insertImageIntoCampaignImg(block,imageObject) {
const campaignImgDiv = block.querySelector('.campaign-img');
const imgElement = document.createElement('img');
imgElement.src = imageObject.imageUrl;
imgElement.alt = imageObject.imageAltText;
campaignImgDiv.appendChild(imgElement);
}
async function buildDeliverablesTable(deliverables, block) {
const rows = buildTableNoGroups(deliverables);
const tableRoot = block.querySelector('.table-content');
Expand Down Expand Up @@ -242,9 +256,15 @@ function createKPI(kpi) {
}

function buildProductList(program) {
const product = checkBlankString(program.productOffering);
let product = checkBlankString(program.productOffering);
const productList = document.createElement('div');
productList.classList.add('product', 'card-content');

// Ensure the product exists in the productMappings, otherwise use 'Not Available'
if (!productMappings[product]) {
product = 'Not Available';
}

const productName = productMappings[product].name;
const productLabel = productMappings[product].icon;
productList.innerHTML = `
Expand Down Expand Up @@ -456,4 +476,4 @@ function attachListener(htmlElement) {
if (child.classList.contains('row')) child.classList.toggle('inactive');
})
})
}
}
1 change: 1 addition & 0 deletions blocks/gmo-campaign-header/gmo-campaign-header.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ export default async function decorate(block) {

async function initializeDropdowns() {
// Business Line List

const businessLineResponse = await graphqlQueryNameList('getBusinessLine');
const businessLines = businessLineResponse.data.jsonByPath.item.json.options;
populateDropdown(businessLines, 'dropdownBusinessOptions', 'businessLine');
Expand Down
10 changes: 9 additions & 1 deletion blocks/gmo-campaign-list/gmo-campaign-list.css
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,14 @@ body {
background: #e2e2e2;
border-radius: 18px;
}

.campaign-icon img {
width: 100%; /* Ensure the image fills the entire width of its container */
height: 100%; /* Ensure the image fills the entire height of its container */
object-fit: cover; /* This will cover the area without distorting the aspect ratio */
border-radius: 18px; /* Match the border-radius of the container */
}

.campaign-name-wrapper {
width: 220px;
}
Expand Down Expand Up @@ -240,4 +248,4 @@ select {
display: flex;
flex-direction: column;
justify-content: center;
}
}
52 changes: 41 additions & 11 deletions blocks/gmo-campaign-list/gmo-campaign-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { decorateIcons } from '../../scripts/lib-franklin.js';
import { graphqlAllCampaignsFilter, graphqlCampaignCount, generateFilterJSON } from '../../scripts/graphql.js';
import { productMappings, statusMappings } from '../../scripts/shared-campaigns.js'
import { getBaseConfigPath } from '../../scripts/site-config.js';
import { searchAsset } from '../../scripts/assets.js';

const headerConfig = [
{
Expand Down Expand Up @@ -40,6 +41,7 @@ let currentNumberPerPage = 4;
//Get Campaign Count for pagination
let campaignCount = await graphqlCampaignCount();
let blockConfig;
let initialBlockCall = true;

//Custom event gmoCampaignListBlock to allow the gmo-campaign-header to trigger the gmo-campaign-list to update
document.addEventListener('gmoCampaignListBlock', async function() {
Expand All @@ -65,7 +67,11 @@ document.addEventListener('gmoCampaignListBlock', async function() {


export default async function decorate(block, numPerPage = currentNumberPerPage, cursor = '', previousPage = false, nextPage = false, graphQLFilter = {}) {
blockConfig = readBlockConfig(block);
//Only populate blockConfig on the first call the decorate function
if (initialBlockCall) {
blockConfig = readBlockConfig(block);
initialBlockCall = false;
}
const campaignPaginatedResponse = await graphqlAllCampaignsFilter(numPerPage, cursor,graphQLFilter);
const campaigns = campaignPaginatedResponse.data.programPaginated.edges;
currentPageInfo = campaignPaginatedResponse.data.programPaginated.pageInfo;
Expand All @@ -89,7 +95,7 @@ export default async function decorate(block, numPerPage = currentNumberPerPage,
currentPageInfo.itemCount = campaigns.length;

const listHeaders = buildListHeaders(headerConfig);
const listItems = buildCampaignList(campaigns, numPerPage);
const listItems = await buildCampaignList(campaigns, numPerPage);
const listFooter = buildListFooter(campaignCount, numPerPage);

block.innerHTML = `
Expand Down Expand Up @@ -143,48 +149,71 @@ function getFilterValues(){
return filterAttributes;
}

function buildCampaignList(campaigns, numPerPage) {
async function buildCampaignList(campaigns, numPerPage) {
const listWrapper = document.createElement('div');
listWrapper.classList.add('list-items');
listWrapper.dataset.totalresults = campaigns.length;
const host = location.origin + getBaseConfigPath();
const detailsPage = blockConfig.detailspage;
campaigns.forEach((campaign, index) => {

for (const campaign of campaigns) {
const index = campaigns.indexOf(campaign);
const campaignRow = document.createElement('div');
campaignRow.classList.add('campaign-row');
if ((index + 1) > numPerPage) campaignRow.classList.add('hidden');

const campaignInfoWrapper = document.createElement('div');
campaignInfoWrapper.classList.add('campaign-info-wrapper','column-1');
campaignInfoWrapper.classList.add('campaign-info-wrapper', 'column-1');

const campaignIconLink = document.createElement('a');
campaignIconLink.href = host + `/${detailsPage}?programName=${campaign.node.programName}`
campaignIconLink.href = host + `/${detailsPage}?programName=${campaign.node.programName}`;

const campaignIcon = document.createElement('div');
campaignIcon.classList.add('campaign-icon');
campaignIcon.dataset.programname = campaign.node.programName;
campaignIcon.dataset.campaignname = campaign.node.campaignName;
//Add Icon Image
const iconImage = document.createElement('img');
try {
const imageObject = await searchAsset(campaign.node.programName, campaign.node.campaignName);
iconImage.src = imageObject.imageUrl;
iconImage.alt = imageObject.imageAltText;
} catch (error) {
console.error("No campaign image found:", error);
}
// Append the image to the campaignIcon div
campaignIcon.appendChild(iconImage);
campaignIconLink.appendChild(campaignIcon);
const campaignName = document.createElement('div');
campaignName.classList.add('campaign-name-wrapper', 'vertical-center');
campaignName.innerHTML = `
<div class='campaign-name-label'>${checkBlankString(campaign.node.campaignName)}</div>
<div class='campaign-name' data-property='campaign'>${checkBlankString(campaign.node.programName)}</div>
`
`;

campaignInfoWrapper.appendChild(campaignIconLink);
campaignInfoWrapper.appendChild(campaignName);

const campaignOverviewWrapper = document.createElement('div');
campaignOverviewWrapper.classList.add('column-2', 'campaign-description-wrapper','vertical-center');
campaignOverviewWrapper.classList.add('column-2', 'campaign-description-wrapper', 'vertical-center');

const campaignOverview = document.createElement('div');
campaignOverview.textContent = checkBlankString(campaign.node.marketingGoal.plaintext);
campaignOverview.classList.add('campaign-description');
campaignOverview.dataset.property = 'description';
campaignOverviewWrapper.appendChild(campaignOverview);

const campaignLaunch = document.createElement('div');
campaignLaunch.textContent = checkBlankString(campaign.node.launchDate);
campaignLaunch.classList.add('column-3', 'campaign-launch-date', 'vertical-center');
campaignLaunch.dataset.property = 'launch';

const campaignProducts = buildProductsList(checkBlankString(campaign.node.productOffering));
campaignProducts.classList.add('column-4', 'vertical-center');

const campaignStatusWrapper = document.createElement('div');
campaignStatusWrapper.classList.add('status-wrapper', 'column-6','vertical-center');
campaignStatusWrapper.classList.add('status-wrapper', 'column-6', 'vertical-center');

const campaignStatus = document.createElement('div');
const statusStr = checkBlankString(campaign.node.status);
const statusString = statusMappings[statusStr].label;
Expand All @@ -193,14 +222,15 @@ function buildCampaignList(campaigns, numPerPage) {
campaignStatus.classList.add('status');
campaignStatus.dataset.property = 'status';
campaignStatusWrapper.appendChild(campaignStatus);

campaignRow.appendChild(campaignInfoWrapper);
campaignRow.appendChild(campaignOverviewWrapper);
campaignRow.appendChild(campaignLaunch);
campaignRow.appendChild(campaignProducts);
campaignRow.appendChild(campaignStatusWrapper);

listWrapper.appendChild(campaignRow);
});
}
return listWrapper;
}

Expand All @@ -219,7 +249,7 @@ function buildProduct(product) {
if (!productMappings[product]) {
product = 'Not Available';
}

const productLabel = productMappings[product].name;
const productIcon = productMappings[product].icon;

Expand Down
100 changes: 100 additions & 0 deletions scripts/assets.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { getBearerToken } from './security.js';

import {
getAssetHandlerApiKey,
getDeliveryEnvironment,
getBackendApiKey,
getSearchIndex,
initDeliveryEnvironment,
getOptimizedDeliveryUrl
} from './polaris.js';

import { getAdminConfig } from './site-config.js';

import { createSearchEndpoint, logError } from './scripts.js';

async function getRequestHeadersSearchAssets() {
TyroneAEM marked this conversation as resolved.
Show resolved Hide resolved
const token = await getBearerToken();
return {
'Content-Type': 'application/json',
'x-api-key': getBackendApiKey(),
Authorization: token,
'x-adobe-accept-experimental': '1',
'x-adp-request': 'search',
};
}

const getFilters = () => {
const currentDate = new Date();
const currentEpoch = Math.floor(currentDate.getTime() / 1000);
// Algolia does not support filters based on mixed types; for example boolean & numeric field types
// is_pur-expirationDate is a boolean type; but aloglia's engine treats boolean false as 0
// so we can use that to our advantage for numberic filters
return `is_pur-expirationDate = 0 OR pur-expirationDate > ${currentEpoch}`;
};


/**
* Search Asset for programName and campaignName parameters.
*
* @param {string} - Program Name.
* @param {string} - Campaign Name.
* @returns {Image <object>} Resolves with image object.
* @throws {Error} If an HTTP error or network error occurs.
*/

export async function searchAsset(programName, campaignName, imageWidth = 80) {
const adminConfig = await getAdminConfig();
const deliveryURL = await initDeliveryEnvironment();

const indexName = await getSearchIndex();

// Initialize the facetFilters array
const facetFilters = [];
if (programName) { // Check if programName is not null
facetFilters.push('gmo-programName :'+programName);
}
if (campaignName) { // Check if campaignName is not null
facetFilters.push('gmo-campaignName :'+ campaignName);
}
const data = {
requests: [
{
indexName: indexName,
params: {
facetFilters: facetFilters,
filters: getFilters(),
highlightPostTag: '__/ais-highlight__',
highlightPreTag: '__ais-highlight__',
hitsPerPage: 1,
page: 0,
query: '',
tagFilters: ''
}
}
]
};

const options = {
method: 'POST',
headers: await getRequestHeadersSearchAssets(),
body: JSON.stringify(data),
};

try {
const response = await fetch(createSearchEndpoint(), options);
// Handle response codes
if (response.status === 200) {
// Asset retrieved successfully
const responseBody = await response.json();
const assetData = responseBody.results[0].hits[0];
const thumbnailURL = await getOptimizedDeliveryUrl(assetData.assetId, assetData['repo-name'], imageWidth);
return {imageUrl : thumbnailURL, imageAltText: assetData['repo-name']};
}
// Handle other response codes
throw new Error(`Failed to search asset: ${response.status} ${response.statusText}`);
} catch (error) {
logError('searchAsset', error);
throw error;
}
}
2 changes: 1 addition & 1 deletion scripts/scripts.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ async function loadScript(url, attrs) {
});
}

function createSearchEndpoint() {
export function createSearchEndpoint() {
return `${getDeliveryEnvironment()}/adobe/assets/search`;
}

Expand Down
Loading