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

MWPW-134799: MEP On/Off #1580

Merged
merged 8 commits into from
Dec 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions libs/features/personalization/add-preview-to-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,35 @@

export default async function addPreviewToConfig({
pageUrl,
persEnabled,
mepEnabled,
persManifests,
targetEnabled,
}) {
const { mep: mepOverride, mepHighlight, mepButton } = Object.fromEntries(pageUrl.searchParams);
const config = updateConfig({
...getConfig(),
mep: {
preview: (mepButton !== 'off' && (mepOverride !== undefined || persEnabled || targetEnabled)),
preview: (mepButton !== 'off' && (mepOverride !== undefined || mepEnabled)),
override: mepOverride ? decodeURIComponent(mepOverride) : '',
highlight: (mepHighlight !== undefined && mepHighlight !== 'false'),
},
});

if (config.mep.override !== '') {
const persManifestPaths = persManifests.map((manifest) => {
if (manifest.startsWith('/')) return manifest;
const { manifestPath } = manifest;
if (manifestPath.startsWith('/')) return manifestPath;

Check warning on line 21 in libs/features/personalization/add-preview-to-config.js

View check run for this annotation

Codecov / codecov/patch

libs/features/personalization/add-preview-to-config.js#L20-L21

Added lines #L20 - L21 were not covered by tests
try {
const url = new URL(manifest);
const url = new URL(manifestPath);

Check warning on line 23 in libs/features/personalization/add-preview-to-config.js

View check run for this annotation

Codecov / codecov/patch

libs/features/personalization/add-preview-to-config.js#L23

Added line #L23 was not covered by tests
return url.pathname;
} catch (e) {
return manifest;
return manifestPath;

Check warning on line 26 in libs/features/personalization/add-preview-to-config.js

View check run for this annotation

Codecov / codecov/patch

libs/features/personalization/add-preview-to-config.js#L26

Added line #L26 was not covered by tests
}
});

config.mep.override.split(',').forEach((manifestPair) => {
const manifestTitle = manifestPair.trim().toLowerCase().split('--')[0];
if (!persManifestPaths.includes(manifestTitle)) {
persManifests.push(manifestTitle);
const manifestPath = manifestPair.trim().toLowerCase().split('--')[0];
if (!persManifestPaths.includes(manifestPath)) {
persManifests.push({ manifestPath });

Check warning on line 33 in libs/features/personalization/add-preview-to-config.js

View check run for this annotation

Codecov / codecov/patch

libs/features/personalization/add-preview-to-config.js#L31-L33

Added lines #L31 - L33 were not covered by tests
}
});
}
Expand Down
32 changes: 25 additions & 7 deletions libs/features/personalization/personalization.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,15 @@ export const preloadManifests = ({ targetManifests = [], persManifests = [] }) =
let manifests = targetManifests;

manifests = manifests.concat(
persManifests.map((manifestPath) => ({
manifestPath: appendJsonExt(manifestPath),
manifestUrl: manifestPath,
persManifests.map((manifest) => ({
...manifest,
manifestPath: appendJsonExt(manifest.manifestPath),
manifestUrl: manifest.manifestPath,
})),
);

for (const manifest of manifests) {
if (!manifest.manifestData && manifest.manifestPath) {
if (!manifest.manifestData && manifest.manifestPath && !manifest.disabled) {
manifest.manifestPath = normalizePath(manifest.manifestPath);
loadLink(
manifest.manifestPath,
Expand Down Expand Up @@ -573,6 +574,15 @@ function cleanManifestList(manifests) {
return cleanedList;
}

const createDefaultExperiment = (manifest) => ({
disabled: manifest.disabled,
event: manifest.event,
manifest: manifest.manifestPath,
variantNames: ['all'],
selectedVariantName: 'default',
selectedVariant: { commands: [] },
});

export async function applyPers(manifests) {
const config = getConfig();

Expand All @@ -581,14 +591,22 @@ export async function applyPers(manifests) {
getEntitlements();
const cleanedManifests = cleanManifestList(manifests);

const override = config.mep?.override;
let results = [];
const experiments = [];
for (const manifest of cleanedManifests) {
results.push(await runPersonalization(manifest, config));
if (manifest.disabled && !override) {
experiments.push(createDefaultExperiment(manifest));
} else {
const result = await runPersonalization(manifest, config);
if (result) {
results.push(result);
experiments.push(result.experiment);
}
}
}
results = results.filter(Boolean);
deleteMarkedEls();

const experiments = results.map((r) => r.experiment);
updateConfig({
...config,
experiments,
Expand Down
5 changes: 5 additions & 0 deletions libs/features/personalization/preview.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,10 @@

const manifestFileName = manifestPath.split('/').pop();
const targetTitle = name ? `${name}<br><i>${manifestFileName}</i>` : manifestFileName;
const scheduled = manifest.event
? `<p>Scheduled - ${manifest.disabled ? 'inactive' : 'active'}</p>
<p>On: ${manifest.event.start?.toLocaleString()}</p>
<p>Off: ${manifest.event.end?.toLocaleString()}</p>` : '';

Check warning on line 177 in libs/features/personalization/preview.js

View check run for this annotation

Codecov / codecov/patch

libs/features/personalization/preview.js#L174-L177

Added lines #L174 - L177 were not covered by tests
let analyticsTitle = '';
if (manifestType === NON_TRACKED_MANIFEST_TYPE) {
analyticsTitle = 'N/A for this manifest type';
Expand All @@ -186,6 +190,7 @@
<a class="mep-edit-manifest" data-manifest-url="${manifestUrl}" data-manifest-path="${manifestPath}" target="_blank" title="Open manifest">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0,0,256,256" width="16px" height="16px" fill-rule="nonzero"><g transform=""><g fill="#ffffff" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none" text-anchor="none" style="mix-blend-mode: normal"><g transform="scale(8.53333,8.53333)"><path d="M22.82813,3c-0.51175,0 -1.02356,0.19544 -1.41406,0.58594l-2.41406,2.41406l5,5l2.41406,-2.41406c0.781,-0.781 0.781,-2.04713 0,-2.82812l-2.17187,-2.17187c-0.3905,-0.3905 -0.90231,-0.58594 -1.41406,-0.58594zM17,8l-11.74023,11.74023c0,0 0.91777,-0.08223 1.25977,0.25977c0.342,0.342 0.06047,2.58 0.48047,3c0.42,0.42 2.64389,0.12436 2.96289,0.44336c0.319,0.319 0.29688,1.29688 0.29688,1.29688l11.74023,-11.74023zM4,23l-0.94336,2.67188c-0.03709,0.10544 -0.05623,0.21635 -0.05664,0.32813c0,0.55228 0.44772,1 1,1c0.11177,-0.00041 0.22268,-0.01956 0.32813,-0.05664c0.00326,-0.00128 0.00652,-0.00259 0.00977,-0.00391l0.02539,-0.00781c0.00196,-0.0013 0.00391,-0.0026 0.00586,-0.00391l2.63086,-0.92773l-1.5,-1.5z"></path></g></g></g></svg>
</a>
${scheduled}

Check warning on line 193 in libs/features/personalization/preview.js

View check run for this annotation

Codecov / codecov/patch

libs/features/personalization/preview.js#L193

Added line #L193 was not covered by tests
</div>
<div class="mep-manifest-variants">${radio}</div>
</div>`;
Expand Down
36 changes: 36 additions & 0 deletions libs/features/personalization/promo-utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { getMetadata } from '../../utils/utils.js';

const GMTStringToLocalDate = (gmtString) => new Date(`${gmtString}+00:00`);

export const isDisabled = (event) => {
if (!event) return false;
const currentDate = new Date();
if ((!event.start && event.end) || (!event.end && event.start)) return true;
return Boolean(event.start && event.end
&& (currentDate < event.start || currentDate > event.end));
};

export default function getPromoManifests(manifestNames) {
const attachedManifests = manifestNames
? manifestNames.split(',')?.map((manifest) => manifest?.trim())
: [];
const schedule = getMetadata('schedule');
if (!schedule) {
return [];
}
return schedule.split(',')
.map((manifest) => {
const [name, start, end, manifestPath] = manifest.trim().split('|').map((s) => s.trim());
if (attachedManifests.includes(name)) {
const event = {
name,
start: GMTStringToLocalDate(start),
end: GMTStringToLocalDate(end),
};
const disabled = isDisabled(event);
return { manifestPath, disabled, event };
}
return null;
})
.filter((manifest) => manifest != null);
}
18 changes: 13 additions & 5 deletions libs/utils/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -790,13 +790,16 @@ async function loadMartech({ persEnabled = false, persManifests = [] } = {}) {

async function checkForPageMods() {
const persMd = getMetadata('personalization');
const promoMd = getMetadata('manifestnames');
const targetMd = getMetadata('target');
let persManifests = [];
const search = new URLSearchParams(window.location.search);
const persEnabled = persMd && persMd !== 'off' && search.get('personalization') !== 'off';
const targetEnabled = targetMd && targetMd !== 'off' && search.get('target') !== 'off';
const promoEnabled = promoMd && promoMd !== 'off';
const mepEnabled = persEnabled || targetEnabled || promoEnabled;

if (persEnabled || targetEnabled) {
if (mepEnabled) {
const { base } = getConfig();
loadLink(
`${base}/features/personalization/personalization.js`,
Expand All @@ -807,20 +810,25 @@ async function checkForPageMods() {
if (persEnabled) {
persManifests = persMd.toLowerCase()
.split(/,|(\s+)|(\\n)/g)
.filter((path) => path?.trim());
.filter((path) => path?.trim())
.map((manifestPath) => ({ manifestPath }));
}

if (promoEnabled) {
const { default: getPromoManifests } = await import('../features/personalization/promo-utils.js');
persManifests = persManifests.concat(getPromoManifests(promoMd));
}

const { env } = getConfig();
let previewOn = false;
const mep = PAGE_URL.searchParams.get('mep');
if (mep !== null || (env?.name !== 'prod' && (persEnabled || targetEnabled))) {
if (mep !== null || (env?.name !== 'prod' && mepEnabled)) {
previewOn = true;
const { default: addPreviewToConfig } = await import('../features/personalization/add-preview-to-config.js');
persManifests = await addPreviewToConfig({
pageUrl: PAGE_URL,
persEnabled,
mepEnabled,
persManifests,
targetEnabled,
});
}

Expand Down
2 changes: 2 additions & 0 deletions test/features/personalization/mocks/head-schedule.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<meta name="manifestnames" content="pre-black-friday-global,black-friday-global,cyber-monday">
<meta name="schedule" content="pre-black-friday-global | 2000-11-01T00:00:00 | 2300-12-15T00:00:00 | https://main--milo--adobecom.hlx.page/promos/2023/black-friday/pre-black-friday/manifest-global.json, black-friday-global | 2000-12-15T00:00:00 | 2000-12-31T00:00:00 | https://main--milo--adobecom.hlx.page/promos/2023/black-friday/black-friday/manifest-global.json">
18 changes: 18 additions & 0 deletions test/features/personalization/mocks/manifestScheduledActive.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"total": 5,
"offset": 0,
"limit": 5,
"data": [
{
"action": "insertContentAfter",
"selector": ".marquee",
"page filter (optional)": "",
"param-newoffer=123": "",
"chrome": "/fragments/insertafter3",
"firefox": "",
"android": "",
"ios": ""
}
],
":type": "sheet"
}
18 changes: 18 additions & 0 deletions test/features/personalization/mocks/manifestScheduledInactive.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"total": 5,
"offset": 0,
"limit": 5,
"data": [
{
"action": "insertContentAfter",
"selector": ".marquee",
"page filter (optional)": "",
"param-newoffer=123": "",
"chrome": "/fragments/insertafter4",
"firefox": "",
"android": "",
"ios": ""
}
],
":type": "sheet"
}
26 changes: 26 additions & 0 deletions test/features/personalization/personalization.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,4 +163,30 @@ describe('Functional Test', () => {
expect(fragment).to.not.be.null;
expect(fragment.parentElement.previousElementSibling.className).to.equal('marquee');
});

it('scheduled manifest should apply changes if active (bts)', async () => {
let manifestJson = await readFile({ path: './mocks/manifestScheduledActive.json' });
manifestJson = JSON.parse(manifestJson);
setFetchResponse(manifestJson);
expect(document.querySelector('a[href="/fragments/insertafter3"]')).to.be.null;
const event = { name: 'bts', start: new Date('2023-11-24T13:00:00+00:00'), end: new Date('2222-11-24T13:00:00+00:00') };
await applyPers([{ manifestPath: '/promos/bts/manifest.json', disabled: false, event }]);

const fragment = document.querySelector('a[href="/fragments/insertafter3"]');
expect(fragment).to.not.be.null;

expect(fragment.parentElement.previousElementSibling.className).to.equal('marquee');
});

it('scheduled manifest should not apply changes if not active (blackfriday)', async () => {
let manifestJson = await readFile({ path: './mocks/manifestScheduledInactive.json' });
manifestJson = JSON.parse(manifestJson);
setFetchResponse(manifestJson);
expect(document.querySelector('a[href="/fragments/insertafter4"]')).to.be.null;
const event = { name: 'blackfriday', start: new Date('2022-11-24T13:00:00+00:00'), end: new Date('2022-11-24T13:00:00+00:00') };
await applyPers([{ manifestPath: '/promos/blackfriday/manifest.json', disabled: true, event }]);

const fragment = document.querySelector('a[href="/fragments/insertafter4"]');
expect(fragment).to.be.null;
});
});
80 changes: 80 additions & 0 deletions test/features/personalization/promo-utils.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { expect } from '@esm-bundle/chai';
import { readFile } from '@web/test-runner-commands';
import getPromoManifests, { isDisabled } from '../../../libs/features/personalization/promo-utils.js';

describe('isDisabled', () => {
it('should be enabled if current time is within range', () => {
const event = {
start: new Date('2000-11-01T00:00:00'),
end: new Date('2300-11-01T00:00:00'),
};
expect(isDisabled(event)).to.be.false;
});

it('should be enabled if no event exist', () => {
expect(isDisabled(null)).to.be.false;
});

it('should be enabled if event has no dates', () => {
expect(isDisabled({})).to.be.false;
});

it('should be disabled if current time is outside range', () => {
const event = {
start: new Date('2300-11-01T00:00:00'),
end: new Date('2301-11-01T00:00:00'),
};
expect(isDisabled(event)).to.be.true;
});

it('should be disabled if no start time defined', () => {
const event = { end: new Date('2300-11-01T00:00:00') };
expect(isDisabled(event)).to.be.true;
});

it('should be disabled if no end time defined', () => {
const event = { start: new Date('2000-11-01T00:00:00') };
expect(isDisabled(event)).to.be.true;
});
});

describe('getPromoManifests', () => {
const expectedManifests = [
{
manifestPath: 'https://main--milo--adobecom.hlx.page/promos/2023/black-friday/pre-black-friday/manifest-global.json',
disabled: false,
event: {
name: 'pre-black-friday-global',
start: new Date('2000-11-01T00:00:00.000Z'),
end: new Date('2300-12-15T00:00:00.000Z'),
},
},
{
manifestPath: 'https://main--milo--adobecom.hlx.page/promos/2023/black-friday/black-friday/manifest-global.json',
disabled: true,
event: {
name: 'black-friday-global',
start: new Date('2000-12-15T00:00:00.000Z'),
end: new Date('2000-12-31T00:00:00.000Z'),
},
},
];

it('should return an array of promo manifests', async () => {
document.head.innerHTML = await readFile({ path: './mocks/head-schedule.html' });
const manifestNames = 'pre-black-friday-global,black-friday-global,cyber-monday';
expect(getPromoManifests(manifestNames)).to.deep.eq(expectedManifests);
});

it('should return an empty array if no schedule', async () => {
document.head.innerHTML = '';
const manifestNames = 'pre-black-friday-global,black-friday-global,cyber-monday';
expect(getPromoManifests(manifestNames).length).to.be.equal(0);
});

it('should return an empty array if no manifestnames', async () => {
document.head.innerHTML = await readFile({ path: './mocks/head-schedule.html' });
const manifestNames = '';
expect(getPromoManifests(manifestNames).length).to.be.equal(0);
});
});
5 changes: 5 additions & 0 deletions test/utils/mocks/head-personalization.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<meta name="personalization" content="https://main--milo--adobecom.hlx.page/products/special-offers-manifest.json">
<meta name="manifestnames" content="pre-black-friday-global,black-friday-global,cyber-monday">
<meta name="schedule" content="pre-black-friday-global | 2023-11-01T00:00:00 | 2023-12-15T00:00:00 | https://main--milo--adobecom.hlx.page/promos/2023/black-friday/pre-black-friday/manifest-global.json, black-friday-global | 2023-12-15T00:00:00 | 2023-12-31T00:00:00 | https://main--milo--adobecom.hlx.page/promos/2023/black-friday/black-friday/manifest-global.json, max | 2023-11-01T00:00:00 | 2023-11-30T00:00:00 | https://main--milo--adobecom.hlx.page/promos/2023/black-friday/cyber-monday/manifest-global.json">
<title>Document Title</title>
<link rel="icon" href="data:,">
25 changes: 25 additions & 0 deletions test/utils/utils.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -561,4 +561,29 @@ describe('Utils', () => {
expect(a.href).includes('/fragments/footer-promos/ccx-video-links');
});
});

describe('personalization', async () => {
const MANIFEST_JSON = {
info: { total: 2, offset: 0, limit: 2, data: [{ key: 'manifest-type', value: 'Personalization' }, { key: 'manifest-override-name', value: '' }, { key: 'name', value: '1' }] }, placeholders: { total: 0, offset: 0, limit: 0, data: [] }, experiences: { total: 1, offset: 0, limit: 1, data: [{ action: 'insertContentAfter', selector: '.marquee', 'page filter (optional)': '/products/special-offers', chrome: 'https://main--milo--adobecom.hlx.page/drafts/mariia/fragments/personalizationtext' }] }, ':version': 3, ':names': ['info', 'placeholders', 'experiences'], ':type': 'multi-sheet',
};
function htmlResponse() {
return new Promise((resolve) => {
resolve({
ok: true,
json: () => MANIFEST_JSON,
});
});
}

it('should process personalization manifest and save in config', async () => {
window.fetch = sinon.stub().returns(htmlResponse());
document.head.innerHTML = await readFile({ path: './mocks/head-personalization.html' });
await utils.loadArea();
const resultConfig = utils.getConfig();
const resultExperiment = resultConfig.experiments[0];
expect(resultConfig.mep.preview).to.be.true;
expect(resultConfig.experiments.length).to.equal(3);
expect(resultExperiment.manifest).to.equal('/products/special-offers-manifest.json');
});
});
});
Loading