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-166355, 166002, 159369, 156867 [MEP] Batch PR #3542

Open
wants to merge 6 commits into
base: stage
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 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
39 changes: 28 additions & 11 deletions libs/features/personalization/personalization.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ let isPostLCP = false;

export const TRACKED_MANIFEST_TYPE = 'personalization';

// Replace any non-alpha chars except comma, space, ampersand and hyphen
const RE_KEY_REPLACE = /[^a-z0-9\- _,&=]/g;
// Replace any non-alpha chars except comma, space, ampersand, colon, and hyphen
const RE_KEY_REPLACE = /[^a-z0-9\- _,&=:]/g;

const MANIFEST_KEYS = [
'action',
Expand Down Expand Up @@ -574,7 +574,7 @@ const getVariantInfo = (line, variantNames, variants, manifestPath, fTargetId) =
if (!config.mep?.preview) manifestId = false;
const { origin } = PAGE_URL;
variantNames.forEach((vn) => {
const targetManifestId = vn.startsWith(TARGET_EXP_PREFIX) ? targetId : false;
const targetManifestId = vn.includes(TARGET_EXP_PREFIX) ? targetId : false;
if (!line[vn] || line[vn].toLowerCase() === 'false') return;

const variantInfo = {
Expand Down Expand Up @@ -784,14 +784,16 @@ async function getPersonalizationVariant(manifestPath, variantNames = [], varian
}

const hasMatch = (name) => {
if (name === '') return true;
if (!name) return true;
if (name === variantLabel?.toLowerCase()) return true;
if (name.startsWith('param-')) return checkForParamMatch(name);
if (userEntitlements?.includes(name)) return true;
return PERSONALIZATION_KEYS.includes(name) && PERSONALIZATION_TAGS[name]();
};

const matchVariant = (name) => {
const matchVariant = (n) => {
// split before checks
const name = n.includes(':') ? n.split(':')[1] : n;
if (name.startsWith(TARGET_EXP_PREFIX)) return hasMatch(name);
const processedList = name.split('&').map((condition) => {
const reverse = condition.trim().startsWith(COLUMN_NOT_OPERATOR);
Expand Down Expand Up @@ -967,10 +969,11 @@ function compareExecutionOrder(a, b) {
return a.executionOrder > b.executionOrder ? 1 : -1;
}

export function cleanAndSortManifestList(manifests) {
const config = getConfig();
export function cleanAndSortManifestList(manifests, conf) {
const config = conf ?? getConfig();
const manifestObj = {};
let allManifests = manifests;
let targetManifestWinsOverServerManifest = false;
if (config.mep?.experiments) allManifests = [...manifests, ...config.mep.experiments];
allManifests.forEach((manifest) => {
try {
Expand All @@ -986,6 +989,12 @@ export function cleanAndSortManifestList(manifests) {
freshManifest.source = freshManifest.source.concat(fullManifest.source);
freshManifest.name = fullManifest.name;
freshManifest.selectedVariantName = fullManifest.selectedVariantName;
targetManifestWinsOverServerManifest = config?.env?.name === 'prod' && fullManifest.selectedVariantName.startsWith('target-');

freshManifest.variants = targetManifestWinsOverServerManifest
? fullManifest.variants
: freshManifest.variants;

freshManifest.selectedVariant = freshManifest.variants[freshManifest.selectedVariantName];
manifestObj[manifest.manifestPath] = freshManifest;
} else {
Expand All @@ -994,9 +1003,8 @@ export function cleanAndSortManifestList(manifests) {

const manifestConfig = manifestObj[manifest.manifestPath];
const { selectedVariantName, variantNames, placeholderData } = manifestConfig;

if (selectedVariantName && variantNames.includes(selectedVariantName)) {
manifestConfig.run = true;
manifestConfig.selectedVariantName = selectedVariantName;
manifestConfig.selectedVariant = manifestConfig.variants[selectedVariantName];
} else {
/* c8 ignore next 2 */
Expand Down Expand Up @@ -1078,7 +1086,12 @@ export async function applyPers(manifests) {

const pznVariants = pznList.map((r) => {
const val = r.experiment.selectedVariantName.replace(TARGET_EXP_PREFIX, '').trim().slice(0, 15);
return val === 'default' ? 'nopzn' : val;
const arr = val.split(':');
if (arr.length > 2 || arr[0]?.trim() === '' || arr[1]?.trim() === '') {
log('MEP Error: When using (optional) column nicknames, please use the following syntax: "<nickname>: <original audience>"');
}
if (!val.includes(':') || val.startsWith(':')) return val === 'default' ? 'nopzn' : val;
return arr[0].trim();
});
const pznManifests = pznList.map((r) => r.experiment.analyticsTitle);
config.mep.martech = `|${pznVariants.join('--')}|${pznManifests.join('--')}`;
Expand Down Expand Up @@ -1207,7 +1220,11 @@ async function handleMartechTargetInteraction(
performance.clearMarks();
performance.clearMeasures();
try {
window.lana.log(`target response time: ${roundedResponseTime}`, { tags: 'martech', errorType: 'i' });
window.lana.log(`target response time: ${roundedResponseTime}`, {
tags: 'martech',
errorType: 'e',
sampleRate: 0.5,
});
} catch (e) {
// eslint-disable-next-line no-console
console.error('Error logging target response time:', e);
Expand Down
1 change: 1 addition & 0 deletions libs/features/personalization/preview.css
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
font-weight: 600;
font-size: 24px;
display: block !important;
font-family: 'Adobe Clean', adobe-clean, 'Trebuchet MS', sans-serif;
}

.mep-badge {
Expand Down
12 changes: 10 additions & 2 deletions libs/martech/martech.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,11 @@ export const getTargetPersonalization = async (
window.addEventListener(ALLOY_SEND_EVENT, () => {
const responseTime = calculateResponseTime(responseStart);
try {
window.lana.log(`target response time: ${responseTime}`, { tags: 'martech', errorType: 'i' });
window.lana.log(`target response time: ${responseTime}`, {
tags: 'martech',
errorType: 'e',
sampleRate: 0.5,
});
} catch (e) {
// eslint-disable-next-line no-console
console.error('Error logging target response time:', e);
Expand All @@ -82,7 +86,11 @@ export const getTargetPersonalization = async (
const response = await waitForEventOrTimeout(ALLOY_SEND_EVENT, timeout);
if (response.error) {
try {
window.lana.log('target response time: ad blocker', { tags: 'martech', errorType: 'i' });
window.lana.log('target response time: ad blocker', {
tags: 'martech',
errorType: 'e',
sampleRate: 0.5,
});
} catch (e) {
// eslint-disable-next-line no-console
console.error('Error logging target response time for ad blocker:', e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,33 @@
}
],
"fragments": []
},
"target-test": {
"commands": [
{
"action": "append",
"selector": "section1",
"pageFilter": "",
"target": "https://main--milo--adobecom.hlx.page/drafts/vgoodrich/fragments/2024/q1/mepmanifestorder/fragments/test1",
"selectorType": "css"
}
],
"fragments": []
}
},
"variantNames": [
"all"
"all",
"target-test"
],
"manifestOverrideName": "",
"manifestType": "test",
"executionOrder": "1-2",
"run": true,
"selectedVariantName": "all",
"selectedVariantName": "target-test",
"selectedVariant": {
"commands": [
{
"action": "appendtosection",
"action": "append",
"selector": "section1",
"pageFilter": "",
"target": "https://main--milo--adobecom.hlx.page/drafts/vgoodrich/fragments/2024/q1/mepmanifestorder/fragments/test",
Expand All @@ -34,6 +47,7 @@
],
"fragments": []
},
"source": ["target"],
"name": "MILO0013 - manifest order",
"manifest": "https://main--milo--adobecom.hlx.page/drafts/vgoodrich/fragments/2024/q1/mepmanifestorder/manifests/test-normal.json",
"manifestUrl": "https://main--milo--adobecom.hlx.page/drafts/vgoodrich/fragments/2024/q1/mepmanifestorder/manifests/test-normal.json",
Expand All @@ -52,10 +66,23 @@
}
],
"fragments": []
},
"target-test": {
"commands": [
{
"action": "appendtosection",
"selector": "section1",
"pageFilter": "",
"target": "https://main--milo--adobecom.hlx.page/drafts/vgoodrich/fragments/2024/q1/mepmanifestorder/fragments/test1",
"selectorType": "css"
}
],
"fragments": []
}
},
"variantNames": [
"all"
"all",
"target-test"
],
"manifestOverrideName": "",
"manifestType": "personalization",
Expand All @@ -74,6 +101,7 @@
],
"fragments": []
},
"source": ["pzn"],
"manifest": "/drafts/vgoodrich/fragments/2024/q1/mepmanifestorder/manifests/pzn-normal.json",
"manifestUrl": "https://main--milo--adobecom.hlx.page/drafts/vgoodrich/fragments/2024/q1/mepmanifestorder/manifests/pzn-normal.json",
"manifestPath": "/drafts/vgoodrich/fragments/2024/q1/mepmanifestorder/manifests/pzn-normal.json"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"action":"replaceContent",
"selector":"#notthere",
"page filter (optional)":"",
"chrome & loggedout":"https://main--milo--adobecom.hlx.page/drafts/vgoodrich/fragments/139173-mep-and/android"
"my nickname: chrome & loggedout":"https://main--milo--adobecom.hlx.page/drafts/vgoodrich/fragments/139173-mep-and/android"
}
]
},
Expand Down
53 changes: 53 additions & 0 deletions test/features/personalization/mocks/manifestWithNicknames.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"info": {
"total": 3,
"offset": 0,
"limit": 3,
"data": [
{
"key": "manifest-type",
"value": "Personalization"
},
{
"key": "manifest-override-name",
"value": ""
},
{
"key": "manifest-execution-order",
"value": "Normal"
}
]
},
"experiences": {
"total": 1,
"offset": 0,
"limit": 1,
"data": [
{
"action": "replace",
"selector": "any-header",
"page filter (optional)": "",
"target-var1": "target-var1",
"challenger 2: target-var2": "challenger 2: target-var2",
"param-nickname=false": "param-nickname=false",
"pzn2: param-nickname=true": "pzn2: param-nickname=true",
"pzn2: param-nickname=double:": "pzn2: param-nickname=double:",
"param-nickname=end:": "param-nickname=end:",
":param-nickname=start": ":param-nickname=start"
}
]
},
"placeholders": {
"total": 0,
"offset": 0,
"limit": 0,
"data": []
},
":version": 3,
":names": [
"experiences",
"info",
"placeholders"
],
":type": "multi-sheet"
}
77 changes: 74 additions & 3 deletions test/features/personalization/personalization.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { readFile } from '@web/test-runner-commands';
import { assert, stub } from 'sinon';
import { getConfig, setConfig } from '../../../libs/utils/utils.js';
import {
handleFragmentCommand, applyPers,
handleFragmentCommand, applyPers, cleanAndSortManifestList,
init, matchGlob, createContent, combineMepSources, buildVariantInfo,
} from '../../../libs/features/personalization/personalization.js';
import mepSettings from './mepSettings.js';
Expand Down Expand Up @@ -35,6 +35,7 @@ describe('Functional Test', () => {
// Add custom keys so tests doesn't rely on real data
const config = getConfig();
config.env = { name: 'prod' };
config.locale = { ietf: 'en-US', prefix: '' };
config.consumerEntitlements = {
'11111111-aaaa-bbbb-6666-cccccccccccc': 'my-special-app',
'22222222-xxxx-bbbb-7777-cccccccccccc': 'fireflies',
Expand Down Expand Up @@ -152,11 +153,11 @@ describe('Functional Test', () => {
expect(config.mep?.martech).to.be.undefined;
});

it('should choose chrome & logged out', async () => {
it('should choose chrome & logged out (using nickname)', async () => {
await loadManifestAndSetResponse('./mocks/manifestWithAmpersand.json');
await init(mepSettings);
const config = getConfig();
expect(config.mep?.martech).to.equal('|chrome & logged|ampersand');
expect(config.mep?.martech).to.equal('|my nickname|ampersand');
});

it('should choose not firefox', async () => {
Expand All @@ -166,6 +167,52 @@ describe('Functional Test', () => {
expect(config.mep?.martech).to.equal('|not firefox|not');
});

it('should not error when nickname has multiple colons', async () => {
await loadManifestAndSetResponse('./mocks/manifestWithNicknames.json');
const tempMepSettings = {
mepParam: '/path/to/manifest.json--pzn2: param-nickname=double:',
mepHighlight: false,
mepButton: false,
pzn: '/path/to/manifest.json',
promo: false,
target: false,
};
await init(tempMepSettings);
const config = getConfig();
console.log('test: ', config);
expect(config.mep?.martech).to.equal('|pzn2|manifest');
});

it('should not error when name nickname is empty', async () => {
await loadManifestAndSetResponse('./mocks/manifestWithNicknames.json');
const tempMepSettings = {
mepParam: '/path/to/manifest.json--:param-nickname=start',
mepHighlight: false,
mepButton: false,
pzn: '/path/to/manifest.json',
promo: false,
target: false,
};
await init(tempMepSettings);
const config = getConfig();
expect(config.mep?.martech).to.equal('|:param-nickname|manifest');
});

it('should show nickname instead of original audience when using nicknames syntax', async () => {
await loadManifestAndSetResponse('./mocks/manifestWithNicknames.json');
const tempMepSettings = {
mepParam: '/path/to/manifest.json--pzn2: param-nickname=true',
mepHighlight: false,
mepButton: false,
pzn: '/path/to/manifest.json',
promo: false,
target: false,
};
await init(tempMepSettings);
const config = getConfig();
expect(config.mep?.martech).to.equal('|pzn2|manifest');
});

it('should read and use entitlement data', async () => {
setConfig(getConfig());
const config = getConfig();
Expand Down Expand Up @@ -396,4 +443,28 @@ describe('MEP Utils', () => {
expect(manifests[4].manifestPath).to.equal('/mep-param/manifest2.json');
});
});
describe('cleanAndSortManifestList', async () => {
it('chooses server manifest over target manifest if same manifest path', async () => {
const config = { env: { name: 'stage' } };
let manifests = await readFile({ path: './mocks/manifestLists/two-manifests-one-from-target.json' });
manifests = JSON.parse(manifests);
manifests[0].manifestPath = 'same path';
manifests[1].manifestPath = 'same path';
const response = cleanAndSortManifestList(manifests, config);
const result = response.find((manifest) => manifest.source.length > 1);
expect(result).to.be.not.null;
expect(result.selectedVariant.commands[0].action).to.equal('appendtosection');
});
it('chooses target manifest over server manifest if same manifest path and in production and selected audience is "target-*"', async () => {
const config = { env: { name: 'prod' } };
let manifests = await readFile({ path: './mocks/manifestLists/two-manifests-one-from-target.json' });
manifests = JSON.parse(manifests);
manifests[0].manifestPath = 'same path';
manifests[1].manifestPath = 'same path';
const response = cleanAndSortManifestList(manifests, config);
const result = response.find((manifest) => manifest.source.length > 1);
expect(result).to.be.not.null;
expect(result.selectedVariant.commands[0].action).to.equal('append');
});
});
});
Loading