Skip to content

Commit

Permalink
Merge commit '97e67b7207ab00e95c47651061c2cceac4d007d9' into experime…
Browse files Browse the repository at this point in the history
…ntation-v2-integration
  • Loading branch information
FentPams committed Aug 20, 2024
2 parents 5bc6727 + 97e67b7 commit 7553799
Show file tree
Hide file tree
Showing 12 changed files with 189 additions and 2 deletions.
10 changes: 10 additions & 0 deletions plugins/experimentation/documentation/audiences.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,16 @@ The audiences are set up directly in the page metadata block as follows:

The notation is pretty flexible and authors can also use `Audience (Mobile)` or `Audience Mobile` if this is a preferred notation.

#### Page redirect

If you aim to direct your audience to a target URL instead of just replacing the content, you can do so by adding the `Audience Resolution | redirect` property to the page metadata:

| Metadata | |
|---------------------|---------------------------------------------------------------|
| Audience: Mobile | [https://{ref}--{repo}--{org}.hlx.page/my-page-for-mobile]() |
| Audience: Desktop | [https://{ref}--{repo}--{org}.hlx.page/my-page-for-desktop]() |
| Audience Resolution | redirect |

### Section-level audiences

Each section in a page can also run any number of audiences. Section-level audiences are run after the page-level audiences have run, i.e. after the variants have been processed and their markup was pulled into the main page, so the section-level audiences that will run are dictated by the document from the current page-level experiment/audience/campaign, and not necessarily just the main page.
Expand Down
10 changes: 10 additions & 0 deletions plugins/experimentation/documentation/campaigns.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,16 @@ If you wanted to additionally restrict the campaign to specific audiences, so th
If any of the listed audiences is resolved, then the campaign will run and the matching content will be served.
If you needed both audiences to be resolved, you'd define a new `mobile-iphone` audience in your project and use that in the metadata instead.

#### Page Redirect

If you aim to fully direct a campaign page to a target URL instead of just replacing the content, you can do so by adding the `Campaign Resolution | redirect` property to the page metadata:

| Metadata | |
|----------------------|-----------------------------------------------------------------|
| Campaign: Xmas | [https://{ref}--{repo}--{org}.hlx.page/my-page-for-xmas]() |
| Campaign: Halloween | [https://{ref}--{repo}--{org}.hlx.page/my-page-for-halloween]() |
| Campaign Resolution | redirect |

### Section-level audiences

Each section in a page can also run any number of campaigns. Section-level campaigns are run after the page-level campaigns have run, i.e. after the variants have been processed and their markup was pulled into the main page, so the section-level campaigns that will run are dictated by the document from the current page-level experiment/audience/campaign, and not necessarily just the main page.
Expand Down
17 changes: 17 additions & 0 deletions plugins/experimentation/documentation/experiments.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,23 @@ Start and end dates are in the flexible JS [Date Time String Format](https://tc3

So you can both use generic dates, like `2024-01-31` or `2024/01/31`, and time-specific dates like `2024-01-31T13:37` or `2024/01/31 1:37 pm`. You can even enforce a specific timezone so your experiment activates when, say, it's 2am GMT+1 by using `2024/1/31 2:00 pm GMT+1` or similar notations.

#### Redirect page experiments
For the use case that fully redirect to the target URL instead of just replacing the content (our default behavior), you could add a new property `Experiment Resolution | redirect` in page metadata:
| 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 Resolution | redirect

In this example, the Hero Test experiment will redirect to one of the specified URLs based on the selected variant.

Similarly, the redirects for audience personalization and campaign personalization could be enabled by adding:

`Audience Resolution | redirect`
`Campaign Resolution | redirect`

For more details, refer to: [audiences](./audiences.md#page-redirect), [campaigns](./campaigns.md#page-redirect)

### Section-level experiments

Each section in a page can also run 1 experiment, so you can have as many section-level experiments as you have sections.
Expand Down
2 changes: 1 addition & 1 deletion plugins/experimentation/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "1.0.1",
"main": "src/index.js",
"scripts": {
"lint:js": "eslint src",
"lint:js": "eslint src tests",
"lint:css": "stylelint src/**/*.css --allow-empty-input",
"lint": "npm run lint:js && npm run lint:css",
"start": "http-server . -p 3000",
Expand Down
29 changes: 29 additions & 0 deletions plugins/experimentation/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ export function toClassName(name) {
* @param {string} result - the URL of the served experience.
*/
function fireRUM(type, config, pluginOptions, result) {
<<<<<<< HEAD
const sampleData = {
experiment: {
source: config.id,
Expand All @@ -96,6 +97,28 @@ function fireRUM(type, config, pluginOptions, result) {

const rumType = type === "experiment" ? "experiment" : "audience";
window.hlx?.rum?.sampleRUM(rumType, sampleData[type]);
=======
const { selectedCampaign = 'default', selectedAudience = 'default' } = config;

const typeHandlers = {
experiment: () => ({
source: config.id,
target: result ? config.selectedVariant : config.variantNames[0],
}),
campaign: () => ({
source: result ? toClassName(selectedCampaign) : 'default',
target: Object.keys(pluginOptions.audiences).join(':'),
}),
audience: () => ({
source: result ? toClassName(selectedAudience) : 'default',
target: Object.keys(pluginOptions.audiences).join(':'),
}),
};

const { source, target } = typeHandlers[type]();
const rumType = type === 'experiment' ? 'experiment' : 'audience';
window.hlx?.rum?.sampleRUM(rumType, { source, target });
>>>>>>> 97e67b7207ab00e95c47651061c2cceac4d007d9
}
/**
Expand Down Expand Up @@ -348,6 +371,7 @@ function toDecisionPolicy(config) {
* Creates an instance of a modification handler that will be responsible for applying the desired
* personalized experience.
*
* @param {String} type The type of modifications to apply
* @param {Object} overrides The config overrides
* @param {Function} metadataToConfig a function that will handle the parsing of the metadata
* @param {Function} getExperienceUrl a function that returns the URL to the experience
Expand Down Expand Up @@ -376,7 +400,12 @@ function createModificationsHandler(
// Firing RUM event early since redirection will stop the rest of the JS execution
fireRUM(type, config, pluginOptions, url);
window.location.replace(url);
<<<<<<< HEAD
return null;
=======
// eslint-disable-next-line consistent-return
return;
>>>>>>> 97e67b7207ab00e95c47651061c2cceac4d007d9
}
// eslint-disable-next-line no-await-in-loop
res = await replaceInner(new URL(url, window.location.origin).pathname, el);
Expand Down
18 changes: 18 additions & 0 deletions plugins/experimentation/tests/audiences.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,24 @@ test.describe('Page-level audiences', () => {
]);
});

test('Track RUM is fired before redirect.', async ({ page }) => {
const rumCalls = [];
await page.exposeFunction('logRumCall', (...args) => rumCalls.push(args));
await page.addInitScript(() => {
window.hlx = { rum: { sampleRUM: (...args) => window.logRumCall(args) } };
});
await page.goto('/tests/fixtures/audiences/page-level--redirect');
await page.waitForURL('/tests/fixtures/audiences/variant-1');
expect(await page.evaluate(() => window.document.body.innerText)).toEqual('Hello v1!');
expect(rumCalls[0]).toContainEqual([
'audience',
{
source: 'foo',
target: 'foo:bar',
},
]);
});

test('Exposes the audiences in a JS API.', async ({ page }) => {
await goToAndRunAudience(page, '/tests/fixtures/audiences/page-level');
expect(await page.evaluate(() => window.hlx.audiences)).toContainEqual(
Expand Down
18 changes: 18 additions & 0 deletions plugins/experimentation/tests/campaigns.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,24 @@ test.describe('Page-level campaigns', () => {
]);
});

test('Track RUM is fired before redirect.', async ({ page }) => {
const rumCalls = [];
await page.exposeFunction('logRumCall', (...args) => rumCalls.push(args));
await page.addInitScript(() => {
window.hlx = { rum: { sampleRUM: (...args) => window.logRumCall(args) } };
});
await page.goto('/tests/fixtures/campaigns/page-level--redirect?campaign=bar');
await page.waitForURL('/tests/fixtures/campaigns/variant-2');
expect(await page.evaluate(() => window.document.body.innerText)).toEqual('Hello v2!');
expect(rumCalls[0]).toContainEqual([
'audience',
{
source: 'bar',
target: 'foo:bar',
},
]);
});

test('Exposes the campaign in a JS API.', async ({ page }) => {
await goToAndRunCampaign(page, '/tests/fixtures/campaigns/page-level?campaign=bar');
expect(await page.evaluate(() => window.hlx.campaigns)).toContainEqual(
Expand Down
34 changes: 34 additions & 0 deletions plugins/experimentation/tests/experiments.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,40 @@ test.describe('Page-level experiments', () => {
]);
});

test('Track RUM is fired before redirect', async ({ page }) => {
const rumCalls = [];
await page.exposeFunction('logRumCall', (...args) => rumCalls.push(args));
await page.addInitScript(() => {
window.hlx = { rum: { sampleRUM: (...args) => window.logRumCall(args) } };
});
await page.goto('/tests/fixtures/experiments/page-level--redirect');
await page.waitForFunction(() => window.hlx.rum.sampleRUM);
expect(rumCalls[0]).toContainEqual([
'experiment',
{
source: 'foo',
target: expect.stringMatching(/control|challenger-1|challenger-2/),
},
]);

const expectedContent = {
v1: 'Hello v1!',
v2: 'Hello v2!',
redirect: 'Hello World!',
};
const expectedUrlPath = {
v1: '/tests/fixtures/experiments/page-level-v1',
v2: '/tests/fixtures/experiments/page-level-v2',
redirect: '/tests/fixtures/experiments/page-level--redirect',
};
const url = new URL(page.url());
const variant = Object.keys(expectedUrlPath).find((k) => url.pathname.endsWith(k));
expect(await page.evaluate(() => window.document.body.innerText)).toMatch(
new RegExp(expectedContent[variant]),
);
expect(expectedUrlPath[variant]).toBe(url.pathname);
});

test('Exposes the experiment in a JS API.', async ({ page }) => {
await goToAndRunExperiment(page, '/tests/fixtures/experiments/page-level');
expect(await page.evaluate(() => window.hlx.experiments)).toContainEqual(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<html>
<head>
<meta name="audience-foo" content="/tests/fixtures/audiences/variant-1"/>
<meta name="audience-bar" content="/tests/fixtures/audiences/variant-2"/>
<meta name="audience-resolution" content="redirect"/>
<meta property="audience:-foo" content="/tests/fixtures/audiences/variant-1"/>
<script>
window.AUDIENCES = {
foo: () => true,
bar: () => true,
}
</script>
<script type="module" src="/tests/fixtures/scripts.js"></script>
</head>
<body>
<main>
<div>Hello World!</div>
</main>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<html>
<head>
<meta name="campaign-foo" content="/tests/fixtures/campaigns/variant-1"/>
<meta name="campaign-bar" content="/tests/fixtures/campaigns/variant-2"/>
<meta name="campaign-resolution" content="redirect"/>
<script type="module" src="/tests/fixtures/scripts.js"></script>
<script>
window.AUDIENCES = {
foo: () => true,
bar: () => true,
}
</script>
</head>
<body>
<main>
<div>Hello World!</div>
</main>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<html>
<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"/>
<meta name="experiment-resolution" content="redirect"/>
<script type="module" src="/tests/fixtures/scripts.js"></script>
</head>
<body>
<main>Hello World!</main>
</body>
</html>
2 changes: 1 addition & 1 deletion plugins/experimentation/tests/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,5 @@ export async function waitForDomEvent(page, eventName) {
document.addEventListener(name, (ev) => resolve(ev.detail));
});
}, eventName);
return async () => await window.eventPromise;
return async () => window.eventPromise;
}

0 comments on commit 7553799

Please sign in to comment.