Skip to content

Commit

Permalink
Squashed 'plugins/experimentation/' changes from 638e0db..f7c45aa
Browse files Browse the repository at this point in the history
f7c45aa fix: Support Flexible Parsing (#37)
f29dd80 fix: failing tests following refactoring
9db77eb fix: audience checkpoint name
7dc74e1 feat: prepare data for RUMv2 collection
f6ccbcd fix: update getAllMetadata function (#33)
ae769a2 feat: Supporting Naming Variants in Page Expereimentation (#31)

git-subtree-dir: plugins/experimentation
git-subtree-split: f7c45aa70429cb111599cd5a257e22a0d336f77e
  • Loading branch information
FentPams committed Jul 23, 2024
1 parent cd18019 commit 2b9ad86
Show file tree
Hide file tree
Showing 9 changed files with 69 additions and 19 deletions.
13 changes: 13 additions & 0 deletions documentation/experiments.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,19 @@ which would essentially disable the control page.

If you need to be really granular, decimal numbers are also supported, like `33.34, 33.33, 33.33`.

#### Custom Variant Labels
This feature allows authors to specify custom labels for both page and section experiment variants via metadata. While the internal variant IDs remain in a predefined format (e.g., challenger-1, challenger-2), the labels in the overlay pill can be customized to provide more meaningful names.

To customize the labels, add a new entry in the page metadata or section metadata(below takes page metadata as an example):

| Metadata | |
|---------------------|--------------------------------------------------------------|
| Experiment | Hero Test |
| Experiment Variants | [https://{ref}--{repo}--{org}.hlx.page/my-page-variant-1](), [https://{ref}--{repo}--{org}.hlx.page/my-page-variant-2](), [https://{ref}--{repo}--{org}.hlx.page/my-page-variant-3]() |
| Experiment Names | foo1, foo2, foo3 |

The names defined will match with the corresponding variants in sequence. If the number of names provided is less than the number of variants, the default naming will be applied for the remaining variants.

#### Code-level experiments

Note that the above assumes you have different content variants to serve, but if you want to run a pure code-based A/B Test, this is also achievable via:
Expand Down
52 changes: 40 additions & 12 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,16 @@ export function toCamelCase(name) {
return toClassName(name).replace(/-([a-z])/g, (g) => g[1].toUpperCase());
}

/**
* Removes all leading hyphens from a string.
* @param {String} after the string to remove the leading hyphens from, usually is colon
* @returns {String} The string without leading hyphens
*/
export function removeLeadingHyphens(inputString) {
// Remove all leading hyphens which are converted from the space in metadata
return inputString.replace(/^(-+)/, '');
}

/**
* Retrieves the content of metadata tags.
* @param {String} name The metadata name (or property)
Expand All @@ -94,12 +104,18 @@ export function getMetadata(name) {
*/
export function getAllMetadata(scope) {
const value = getMetadata(scope);
return [...document.head.querySelectorAll(`meta[name^="${scope}-"]`)]
.reduce((res, meta) => {
const key = toCamelCase(meta.name.substring(scope.length + 1));
res[key] = meta.getAttribute('content');
return res;
}, value ? { value } : {});
const metaTags = document.head.querySelectorAll(`meta[name^="${scope}"], meta[property^="${scope}:"]`);
return [...metaTags].reduce((res, meta) => {
const key = removeLeadingHyphens(
meta.getAttribute('name')
? meta.getAttribute('name').substring(scope.length)
: meta.getAttribute('property').substring(scope.length + 1),
);

const camelCaseKey = toCamelCase(key);
res[camelCaseKey] = meta.getAttribute('content');
return res;
}, value ? { value } : {});
}

/**
Expand Down Expand Up @@ -578,14 +594,19 @@ async function getExperimentConfig(pluginOptions, metadata, overrides) {
label: 'Control',
};

// get the custom labels for the variants names
const labelNames = stringToArray(metadata.names);
pages.forEach((page, i) => {
const vname = `challenger-${i + 1}`;
// label with custom name or default
const customLabel = labelNames.length > i ? labelNames[i] : `Challenger ${i + 1}`;

variantNames.push(vname);
variants[vname] = {
percentageSplit: `${splits[i].toFixed(4)}`,
pages: [page],
blocks: [],
label: `Challenger ${i + 1}`,
label: customLabel,
};
});
inferEmptyPercentageSplits(Object.values(variants));
Expand Down Expand Up @@ -643,6 +664,7 @@ async function getExperimentConfig(pluginOptions, metadata, overrides) {

return config;
}

/**
* Parses the campaign manifest.
*/
Expand Down Expand Up @@ -670,6 +692,8 @@ async function runExperiment(document, pluginOptions) {
(el, config, result) => {
const { id, selectedVariant, variantNames } = config;
const variant = result ? selectedVariant : variantNames[0];
el.dataset.experiment = id;
el.dataset.variant = variant;
el.classList.add(`experiment-${toClassName(id)}`);
el.classList.add(`variant-${toClassName(variant)}`);
window.hlx?.rum?.sampleRUM('experiment', {
Expand Down Expand Up @@ -771,10 +795,12 @@ async function runCampaign(document, pluginOptions) {
(el, config, result) => {
const { selectedCampaign = 'default' } = config;
const campaign = result ? toClassName(selectedCampaign) : 'default';
el.dataset.audience = selectedCampaign;
el.dataset.audiences = Object.keys(pluginOptions.audiences).join(',');
el.classList.add(`campaign-${campaign}`);
window.hlx?.rum?.sampleRUM('campaign', {
source: el.className,
target: campaign,
window.hlx?.rum?.sampleRUM('audience', {
source: campaign,
target: Object.keys(pluginOptions.audiences).join(':'),
});
document.dispatchEvent(new CustomEvent('aem:experimentation', {
detail: {
Expand Down Expand Up @@ -841,6 +867,7 @@ function getUrlFromAudienceConfig(config) {
}

async function serveAudience(document, pluginOptions) {
document.body.dataset.audiences = Object.keys(pluginOptions.audiences).join(',');
return applyAllModifications(
pluginOptions.audiencesMetaTagPrefix,
pluginOptions.audiencesQueryParameter,
Expand All @@ -851,10 +878,11 @@ async function serveAudience(document, pluginOptions) {
(el, config, result) => {
const { selectedAudience = 'default' } = config;
const audience = result ? toClassName(selectedAudience) : 'default';
el.dataset.audience = audience;
el.classList.add(`audience-${audience}`);
window.hlx?.rum?.sampleRUM('audience', {
source: el.className,
target: audience,
source: audience,
target: Object.keys(pluginOptions.audiences).join(':'),
});
document.dispatchEvent(new CustomEvent('aem:experimentation', {
detail: {
Expand Down
4 changes: 2 additions & 2 deletions src/preview.js
Original file line number Diff line number Diff line change
Expand Up @@ -213,9 +213,9 @@ function createVariant(experiment, variantName, config, options) {
experimentURL.searchParams.set(options.experimentsQueryParameter, `${experiment}/${variantName}`);

return {
label: `<code>${variantName}</code>`,
label: `<code>${variant.label}</code>`,
description: `
<p>${variant.label}</p>
<p>${variantName}</p>
<p class="percentage">(${percentage} split)</p>
<p class="performance"></p>`,
actions: [{ label: 'Simulate', href: experimentURL.href }],
Expand Down
4 changes: 2 additions & 2 deletions tests/audiences.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ test.describe('Page-level audiences', () => {
expect(await page.evaluate(() => window.rumCalls)).toContainEqual([
'audience',
expect.objectContaining({
source: 'audience-foo',
target: 'foo',
source: 'foo',
target: 'foo:bar',
}),
]);
});
Expand Down
6 changes: 3 additions & 3 deletions tests/campaigns.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,10 @@ test.describe('Page-level campaigns', () => {
});
await goToAndRunCampaign(page, '/tests/fixtures/campaigns/page-level?campaign=foo');
expect(await page.evaluate(() => window.rumCalls)).toContainEqual([
'campaign',
'audience',
expect.objectContaining({
source: 'campaign-foo',
target: 'foo',
source: 'foo',
target: 'foo:bar',
}),
]);
});
Expand Down
1 change: 1 addition & 0 deletions tests/fixtures/audiences/page-level.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<head>
<meta name="audience-foo" content="/tests/fixtures/audiences/variant-1"/>
<meta name="audience-bar" content="/tests/fixtures/audiences/variant-2"/>
<meta property="audience:-foo" content="/tests/fixtures/audiences/variant-1"/>
<script>
window.AUDIENCES = {
foo: () => true,
Expand Down
6 changes: 6 additions & 0 deletions tests/fixtures/campaigns/page-level.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
<meta name="campaign-foo" content="/tests/fixtures/campaigns/variant-1"/>
<meta name="campaign-bar" content="/tests/fixtures/campaigns/variant-2"/>
<script type="module" src="/tests/fixtures/scripts.js"></script>
<script>
window.AUDIENCES = {
foo: () => true,
bar: () => true,
}
</script>
</head>
<body>
<main>
Expand Down
1 change: 1 addition & 0 deletions tests/fixtures/experiments/page-level.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<head>
<meta name="experiment" content="foo"/>
<meta name="experiment-variants" content="/tests/fixtures/experiments/page-level-v1,/tests/fixtures/experiments/page-level-v2"/>
<meta name="experiment-name" content="V1,V2"/>
<script type="module" src="/tests/fixtures/scripts.js"></script>
</head>
<body>
Expand Down
1 change: 1 addition & 0 deletions tests/fixtures/experiments/section-level.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<a href="/tests/fixtures/experiments/section-level-v2">/tests/fixtures/experiments/section-level-v2</a>
</div>
</div>
<div><div>Experiment Names</div><div>V1,V2</div></div>
</div>
</div>
</main>
Expand Down

0 comments on commit 2b9ad86

Please sign in to comment.