Giter Site home page Giter Site logo

adobe / aem-experimentation Goto Github PK

View Code? Open in Web Editor NEW
6.0 16.0 6.0 247 KB

A lightweight Franklin plugin for experimentation and segmentation.

License: Apache License 2.0

JavaScript 91.00% CSS 9.00%
audiences campaigns experimentation franklin franklin-plugin segmentation experience-decisioning

aem-experimentation's Introduction

AEM Edge Delivery Services Experimentation

The AEM Experimentation plugin helps you quickly set up experimentation and segmentation on your AEM project. It is currently available to customers in collaboration with AEM Engineering via co-innovation VIP Projects. To implement experimentation or personalization use-cases, please reach out to the AEM Engineering team in the Slack channel dedicated to your project.

Features

The AEM Experimentation plugin supports:

  • ๐Ÿ”’ privacy-first, as it doesn't use any, nor persists any, end-user data that could lead to their identification. No end-user opt-in nor cookie consent is required when using the default configuration that uses AEM Edge Delivery Services Real User Monitoring.*
  • ๐Ÿ‘ฅ serving different content variations to different audiences, including custom audience definitions for your project that can be either resolved directly in-browser or against a trusted backend API.
  • ๐Ÿ’ธ serving different content variations based on marketing campaigns you are running, so that you can easily track email and/or social campaigns.
  • ๐Ÿ“ˆ running A/B test experiments on a set of variants to measure and improve the conversion on your site. This works particularly with our ๐Ÿ’น RUM conversion tracking plugin.
  • ๐Ÿš€ easy simulation of each experience and basic reporting leveraging in-page overlays.

* Bringing additional marketing technology such as visitor-based analytics or personalization to a project will cancel this privacy-first principle.

Installation

Add the plugin to your AEM project by running:

git subtree add --squash --prefix plugins/experimentation [email protected]:adobe/aem-experimentation.git main

If you later want to pull the latest changes and update your local copy of the plugin

git subtree pull --squash --prefix plugins/experimentation [email protected]:adobe/aem-experimentation.git main

If you prefer using https links you'd replace [email protected]:adobe/aem-experimentation.git in the above commands by https://github.com/adobe/aem-experimentation.git.

If the subtree pull command is failing with an error like:

fatal: can't squash-merge: 'plugins/experimentation' was never added

you can just delete the folder and re-add the plugin via the git subtree add command above.

Project instrumentation

โš ๏ธ 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 adobe/aem-lib#23 and adobe/aem-boilerplate#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:

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:

  1. at the start of the file:
    const AUDIENCES = {
      mobile: () => window.innerWidth < 600,
      desktop: () => window.innerWidth >= 600,
      // define your custom audiences here as needed
    };
    
    /**
     * Gets all the metadata elements that are in the given scope.
     * @param {String} scope The scope/prefix for the metadata
     * @returns an array of HTMLElement nodes that match the given scope
     */
    export function getAllMetadata(scope) {
      return [...document.head.querySelectorAll(`meta[property^="${scope}:"],meta[name^="${scope}-"]`)]
        .reduce((res, meta) => {
          const id = toClassName(meta.name
            ? meta.name.substring(scope.length + 1)
            : meta.getAttribute('property').split(':')[1]);
          res[id] = meta.getAttribute('content');
          return res;
        }, {});
    }
  2. if this is the first plugin you add to your project, you'll also need to add:
    // Define an execution context
    const pluginContext = {
      getAllMetadata,
      getMetadata,
      loadCSS,
      loadScript,
      sampleRUM,
      toCamelCase,
      toClassName,
    };
  3. Early in the loadEager method you'll need to add:
    async function loadEager(doc) {
      โ€ฆ
      // Add below snippet early in the eager phase
      if (getMetadata('experiment')
        || Object.keys(getAllMetadata('campaign')).length
        || Object.keys(getAllMetadata('audience')).length) {
        // eslint-disable-next-line import/no-relative-packages
        const { loadEager: runEager } = await import('../plugins/experimentation/src/index.js');
        await runEager(document, { audiences: AUDIENCES }, pluginContext);
      }
      โ€ฆ
    }
    This needs to be done as early as possible since this will be blocking the eager phase and impacting your LCP, so we want this to execute as soon as possible.
  4. Finally at the end of the loadLazy method you'll have to add:
    async function loadLazy(doc) {
      โ€ฆ
      // Add below snippet at the end of the lazy phase
      if ((getMetadata('experiment')
        || Object.keys(getAllMetadata('campaign')).length
        || Object.keys(getAllMetadata('audience')).length)) {
        // eslint-disable-next-line import/no-relative-packages
        const { loadLazy: runLazy } = await import('../plugins/experimentation/src/index.js');
        await runLazy(document, { audiences: AUDIENCES }, pluginContext);
      }
    }
    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.

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). You have already seen the audiences option in the examples above, but here is the full list we support:

runEager.call(document, {
  // Overrides the base path if the plugin was installed in a sub-directory
  basePath: '',

  // Lets you configure the prod environment.
  // (prod environments do not get the pill overlay)
  prodHost: 'www.my-website.com',
  // if you have several, or need more complex logic to toggle pill overlay, you can use
  isProd: () => window.location.hostname.endsWith('hlx.page')
    || window.location.hostname === ('localhost'),

  /* Generic properties */
  // RUM sampling rate on regular AEM pages is 1 out of 100 page views
  // but we increase this by default for audiences, campaigns and experiments
  // to 1 out of 10 page views so we can collect metrics faster of the relative
  // short durations of those campaigns/experiments
  rumSamplingRate: 10,

  // 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,

  /* Audiences related properties */
  // See more details on the dedicated Audiences page linked below
  audiences: {},
  audiencesMetaTagPrefix: 'audience',
  audiencesQueryParameter: 'audience',

  /* Campaigns related properties */
  // See more details on the dedicated Campaigns page linked below
  campaignsMetaTagPrefix: 'campaign',
  campaignsQueryParameter: 'campaign',

  /* 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:

aem-experimentation's People

Contributors

buuhuu avatar chicharr avatar ilievlad73 avatar ivanviviani avatar meryllblanchet avatar ramboz avatar

Stargazers

 avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

aem-experimentation's Issues

Gear button on pile not working

When I click the gear icon in the pile here https://main--exlm--buuhuu.hlx.page/en/dirk-demo nothing happens.

That seems to be related to this piece of code: https://github.com/adobe/aem-experimentation/blob/main/src/preview.js#L52-L75

  const actions = typeof header === 'object'
    ? (header.actions || []).map((action) => (action.href
      ? `<div class="hlx-button"><a href="${action.href}">${action.label}</a></div>`
      : `<div class="hlx-button"><a href="#">${action.label}</a></div>`))
    : [];
  // ...
  const buttons = [...popup.querySelectorAll('.hlx-popup-header-actions .hlx-button a')];
  actions.forEach((action, index) => {
    if (action.onclick) {
      buttons[index].addEventListener('click', action.onclick);
    }
  });

While the only item in header.actions has an onclick handler, the html string in the actions array does not.

Run experiment should fetch whole document, not only "plain"

Currently challengers are loaded with the plain selector, and the innerHTML of the main element is replaced with it.

That lacks support for changing metadata with the challenger, for example changing the pages template or theme.

Furthermore this is the same mechanism we load fragments with, and for AEM authoring we remove the instrumentation for fragments, leaving the challenger being not editable anymore.

I would like to suggest,

  • to load the full page,
  • parse it with a dom parser or contextual fragment,
  • then replace the main of the page with the challengers main, and
  • if we want to replace metadata that is different on the challenger as well.

Disable the pill per default, enable only on .page or locahost

This request is an ask to reverse the logic to show or not show the pill.

Currently it is not shown if:

  • on .live
  • if configured on prodHost
  • if configured isProd returns true

We should reverse this to use a white list and show it only if

  • on .page or localhost (as recommended in the README.md)
  • if configured and isProd returns false
  • if configured and not on prodHost

That way no user setting is needed when running aem-experimentation with crosswalk

Audiences Popup Dialog not appearing on page preview

Expected Behaviour

The Audiences popup dialog should appear at the bottom right of the page, when audiences are configured for it on the metadata block of the page document.

Actual Behaviour

The Audiences popup dialog is not appearing on the page.

Reproduce Scenario (including but not limited to)

Any page with Audiences configured in its metadata block.

Steps to Reproduce

  1. Configure a page document with a desktop and mobile Audience in its metadata block.
  2. View the page on the development server or alternatively preview it and view it on the preview domain.

Platform and Version

Microsoft Edge Version 121.0.2277.112 (Official build) (64-bit)

Sample Code that illustrates the problem

Logs taken while reproducing problem

TypeError: Cannot read properties of undefined (reading 'forEach')
    at createPopupDialog (preview.js:71:18)
    at createPopupButton (preview.js:81:17)
    at decorateAudiencesPill (preview.js:463:16)
    at async Module.decoratePreviewMode (preview.js:490:5)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.