Skip to content

Commit

Permalink
chore: update experimentation plugin to version 1.0.3 with semantic r…
Browse files Browse the repository at this point in the history
…elease setup and various bug fixes
  • Loading branch information
fnhipster committed Jan 15, 2025
1 parent 4d736e8 commit dd32a65
Show file tree
Hide file tree
Showing 17 changed files with 7,897 additions and 1,746 deletions.
1 change: 0 additions & 1 deletion plugins/experimentation/.eslintignore

This file was deleted.

35 changes: 35 additions & 0 deletions plugins/experimentation/.github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Release
on:
push:
branches:
- main

permissions:
contents: read

jobs:
release:
name: Release
runs-on: ubuntu-latest
permissions:
contents: write
issues: write
pull-requests: write
id-token: write
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: "lts/*"
- name: Install dependencies
run: npm ci
- name: Verify the integrity of provenance attestations and registry signatures for installed dependencies
run: npm audit signatures
- name: Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: npx semantic-release
14 changes: 14 additions & 0 deletions plugins/experimentation/.releaserc
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"branches": ["main"],
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
"@semantic-release/changelog",
"@semantic-release/npm",
["@semantic-release/git", {
"assets": ["CHANGELOG.md", "package.json"],
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
}]
]

}
20 changes: 20 additions & 0 deletions plugins/experimentation/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
## [1.0.3](https://github.com/adobe/aem-experimentation/compare/v1.0.2...v1.0.3) (2024-10-06)


### Bug Fixes

* regressions from prerendering logic and experimeantation ([5b90510](https://github.com/adobe/aem-experimentation/commit/5b90510168be9f9b55fc71e9c227cadaf481b968))

## [1.0.2](https://github.com/adobe/aem-experimentation/compare/v1.0.1...v1.0.2) (2024-06-11)


### Bug Fixes

* pill css loading on localhost ([a41fafc](https://github.com/adobe/aem-experimentation/commit/a41fafc1003ada023725a451fe2215947a9bdeb9))

## [1.0.1](https://github.com/adobe/aem-experimentation/compare/v1.0.0...v1.0.1) (2024-05-24)


### Bug Fixes

* semantic release ([5040fa8](https://github.com/adobe/aem-experimentation/commit/5040fa88c7a01b032431967e230abaaf6d69f9d6))
71 changes: 40 additions & 31 deletions plugins/experimentation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,32 +37,6 @@ you can just delete the folder and re-add the plugin via the `git subtree add` c

## Project instrumentation

:warning: The plugin requires that you have a recent RUM instrumentation from the AEM boilerplate that supports `sampleRUM.always`. If you are getting errors that `.on` cannot be called on an `undefined` object, please apply the changes from https://github.com/adobe/aem-boilerplate/pull/247/files to your `lib-franklin.js`.

### On top of the plugin system

The easiest way to add the plugin is if your project is set up with the plugin system extension in the boilerplate.
You'll know you have it if `window.hlx.plugins` is defined on your page.

If you don't have it, you can follow the proposal in https://github.com/adobe/aem-lib/pull/23 and https://github.com/adobe/aem-boilerplate/pull/275 and apply the changes to your `aem.js`/`lib-franklin.js` and `scripts.js`.

Once you have confirmed this, you'll need to edit your `scripts.js` in your AEM project and add the following at the start of the file:
```js
const AUDIENCES = {
mobile: () => window.innerWidth < 600,
desktop: () => window.innerWidth >= 600,
// define your custom audiences here as needed
};

window.hlx.plugins.add('experimentation', {
condition: () => getMetadata('experiment')
|| Object.keys(getAllMetadata('campaign')).length
|| Object.keys(getAllMetadata('audience')).length,
options: { audiences: AUDIENCES },
url: '/plugins/experimentation/src/index.js',
});
```

### On top of a regular boilerplate project

Typically, you'd know you don't have the plugin system if you don't see a reference to `window.hlx.plugins` in your `scripts.js`. In that case, you can still manually instrument this plugin in your project by falling back to a more manual instrumentation. To properly connect and configure the plugin for your project, you'll need to edit your `scripts.js` in your AEM project and add the following:
Expand Down Expand Up @@ -104,6 +78,16 @@ Typically, you'd know you don't have the plugin system if you don't see a refere
toClassName,
};
```
And make sure to import any missing/undefined methods from `aem.js`/`lib-franklin.js` at the very top of the file:
```js
import {
...
getMetadata,
loadScript,
toCamelCase,
toClassName,
} from './aem.js';
```
3. Early in the `loadEager` method you'll need to add:
```js
async function loadEager(doc) {
Expand Down Expand Up @@ -136,6 +120,30 @@ Typically, you'd know you don't have the plugin system if you don't see a refere
```
This is mostly used for the authoring overlay, and as such isn't essential to the page rendering, so having it at the end of the lazy phase is good enough.

### On top of the plugin system

The easiest way to add the plugin is if your project is set up with the plugin system extension in the boilerplate.
You'll know you have it if `window.hlx.plugins` is defined on your page.
If you don't have it, you can follow the proposal in https://github.com/adobe/aem-lib/pull/23 and https://github.com/adobe/aem-boilerplate/pull/275 and apply the changes to your `aem.js`/`lib-franklin.js` and `scripts.js`.

Once you have confirmed this, you'll need to edit your `scripts.js` in your AEM project and add the following at the start of the file:
```js
const AUDIENCES = {
mobile: () => window.innerWidth < 600,
desktop: () => window.innerWidth >= 600,
// define your custom audiences here as needed
};
window.hlx.plugins.add('experimentation', {
condition: () => getMetadata('experiment')
|| Object.keys(getAllMetadata('campaign')).length
|| Object.keys(getAllMetadata('audience')).length,
options: { audiences: AUDIENCES },
url: '/plugins/experimentation/src/index.js',
});
```
### Custom options
There are various aspects of the plugin that you can configure via options you are passing to the 2 main methods above (`runEager`/`runLazy`).
Expand All @@ -160,6 +168,9 @@ runEager.call(document, {
// short durations of those campaigns/experiments
rumSamplingRate: 10,
// these metadata fields will be updated when replacing content
overrideMetadataFields: [],
// the storage type used to persist data between page views
// (for instance to remember what variant in an experiment the user was served)
storage: window.SessionStorage,
Expand All @@ -177,14 +188,12 @@ runEager.call(document, {
/* Experimentation related properties */
// See more details on the dedicated Experiments page linked below
experimentsRoot: '/experiments',
experimentsConfigFile: 'manifest.json',
experimentsMetaTag: 'experiment',
experimentsQueryParameter: 'experiment',
}, pluginContext);
```
For detailed implementation instructions on the different features, please read the dedicated pages we have on those topics:
- [Audiences](https://github.com/adobe/aem-experimentation/wiki/Audiences)
- [Campaigns](https://github.com/adobe/aem-experimentation/wiki/Campaigns)
- [Experiments](https://github.com/adobe/aem-experimentation/wiki/Experiments)
- [Audiences](/documentation/audiences.md)
- [Campaigns](/documentation/campaigns.md)
- [Experiments](/documentation/experiments.md)
75 changes: 75 additions & 0 deletions plugins/experimentation/documentation/audiences.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
With audiences you can serve different versions of your content to different groups of users based on the information you can glean from there current session. For instance, you can optimize the experience for:
- mobile vs. desktop
- Chrome vs. Firefox
- [1st vs. returning visitor](https://github.com/hlxsites/wknd/blob/main/scripts/scripts.js#L33-L34)
- [fast vs slow connections](https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation/effectiveType)
- different geographies via [Geolocation API](https://github.com/hlxsites/wknd/blob/main/scripts/scripts.js#L33-L34) or CDN workers ([Fastly](https://www.fastly.com/documentation/reference/vcl/variables/geolocation/), [Cloudflare](https://developers.cloudflare.com/workers/examples/geolocation-hello-world/))
- etc.

## Set up

First, you need to define audiences for the project. This is done directly in the project codebase. Audiences are defined as a `Map` of audience names and boolean evaluating (async) functions that check whether the given audience is resolved in the current browsing session.

You'd typically define the mapping in your AEM's `scripts.js` as follows:
```js
const geoPromise = (async () => {
const resp = await fetch(/* some geo service*/);
return resp.json();
})();

const AUDIENCES = {
mobile: () => window.innerWidth < 600,
desktop: () => window.innerWidth >= 600,
us: async () => (await geoPromise).region === 'us',
eu: async () => (await geoPromise).region === 'eu',
}
```

As you can see in the example above, functions need to return a boolean value. If the value is truthy, the audience is considered resolved, and if it's falsy then it isn't. You can also use any browser API directly, or rely on external services to resolve an audience.

:warning: Using external services will have a performance impact on the initial page load as the call will be blocking the page rendering until the async function is fully evaluated.

The audiences for the project then need to be passed to the plugin initialization as follows:

```js
const { loadEager } = await import('../plugins/experimentation/src/index.js');
await loadEager(document, { audiences: AUDIENCES }, /* plugin execution context */);
```

### Custom options

By default, the audience feature looks at the `Audience` metadata tags and `audience` query parameters, but if this clashes with your existing codebase or doesn't feel intuitive to your authors, you can adjust this by passing new options to the plugin.

For instance, here is an alternate configuration that would use `segment` instead of `audience`:
```js
const { loadEager } = await import('../plugins/experimentation/src/index.js');
await loadEager(document, {
audiences: AUDIENCES,
audiencesMetaTagPrefix: 'segment',
audiencesQueryParameter: 'segment',
}, /* plugin execution context */);
```

## Authoring

Once the audiences made it into the project codebase, your authors are ready to start using audiences for their experiences.
This is done directly in the page metadata block:

| 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]() |

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

### Simulation

Once all of this is set up, authors will have access to an overlay on `localhost` and on the stage environments (i.e. `*.hlx.page`) that lets them see what audiences have been configured for the page and switch between each to visualize the content variations accordingly.

![audience overlay](./images/audiences-overlay.png)

The simulation capabilities leverage the `audience` query parameter that is appended to the URL and forcibly let you see the specific content variant.

## Development

To help developers in designing variants for each audience, when audiences are resolved on the page it will automatically add a new CSS class named `audience-<name of the audience>` for each to the `<body>` element, i.e. `audience-mobile audience-iphone`.
60 changes: 60 additions & 0 deletions plugins/experimentation/documentation/campaigns.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
With campaigns you can send out emails or social media posts that link back to your site and that will serve specific offers or versions of your content to the targeted audience.

## Set up

The set up is pretty minimal. Once you've instrumented the experimentation plugin in your AEM website, you are essentially good to go.

Just keep in mind that if you want to only target specific audiences for that campaign, you'll also need to set up the [audiences](Audiences) accordingly for your project.

### Custom options

By default, the campaigns feature looks at the `Campaign` metadata tags and `campaign` query parameter, but if this clashes with your existing codebase or doesn't feel intuitive to your authors, you can adjust this by passing new options to the plugin.

For instance, here is an alternate configuration that would use `sale` instead of `campaign`:
```js
const { loadEager } = await import('../plugins/experimentation/src/index.js');
await loadEager(document, {
campaignsMetaTagPrefix: 'sale',
campaignsQueryParameter: 'sale',
}, /* plugin execution context */);
```

:mega: The campaign feature also supports the industry-standard Urchin Tracking Module (UTM) `utm_campaign` as query parameter. There is nothing special you need to do to get this working and it will be seamlessly handled the same way as the `campaignsQueryParameter`. This means that both:

- [https://{ref}--{repo}--{org}.hlx.page/my-page?campaign=xmas]()
- [https://{ref}--{repo}--{org}.hlx.page/my-page?utm_campaign=xmas]()

would essentially serve you the `xmas` variant of the experience.

## Authoring

Once the above steps are done, your authors are ready to start using campaigns for their experiences.
This is done directly in the page metadata block:

| 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]() |

The notation is pretty flexible and authors can also use `Campaign (Xmas)` or `Campaign Halloween` if this is a preferred notation.

If you wanted to additionally restrict the campaign to specific audiences, so that for instance your campaigns are only accessible on mobile phone or on iPhone, you'd leverage the [audiences](Audiences) feature and use the following 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 Audience | mobile, iphone |

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.

### Simulation

Once all of this is set up, authors will have access to an overlay on `localhost` and on the stage environments (i.e. `*.hlx.stage`) that lets them see what campaigns have been configured for the page and switch between each to visualize the content variations accordingly.

![audience overlay](./images/campaigns-overlay.png)

## Development

To help developers in designing variants for each campaign, when a campaign is resolved on the page it will automatically add a new CSS class named `campaign-<name of the campaign>` to the `<body>` element, i.e. `campaign-xmas`.
Loading

0 comments on commit dd32a65

Please sign in to comment.