Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
  state
  hoc
  index.js
  setupTests.js
  package.json
  README.md
Size: Mime:
  README.md

ab-connector

Greenkeeper badge

Allows A/B testing in React projects.

It listens to window.postMessage() event (https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) fired inside Google Optimize and saves passed message (experiment name and variant) in the store.

It also pushes the optimize.activate event on every location change. This custom event can be used inside Google Optimize as an activation event.

Installation

Add the private registry:

echo "registry=https://npm-proxy.fury.io/mfsTqYdDz3bsKFQJuMAR/tmf/" >> .npmrc

Add the package to your package.json:

npm install --save --exact @doodle/ab-connector

Documentation

Getting started

Checkout the repo inside what you want to set A/B tests and navigate to the selected component.

Load A/B saga into your project:

import { loadAbSaga } from '@doodle/ab-connector';
export default function* rootSaga(options = {}) {
  yield all([
    call(loadAbSaga, options),
    // ... add other sagas here
  ]);
}

Load initial state:

import { createState as createAbInitialState } from '@doodle/ab-connector';
export default {
  ...createAbInitialState(),
};
import { combineReducers } from 'redux';
import { createReducer as createAbReducer } from '@doodle/ab-connector';

export default function createReducer(injectedReducers = {}) {
  return combineReducers({
    router: routerReducer,
    ...createAbReducer(),
    ...injectedReducers,
  });
}

Import Higher Order Component from @doodle/ab-connector:

import { withAbTest } from '@doodle/ab-connector';

Now you can wrap your component in it to add specific A/B test variants.

export default withAbTest(Component, "Your Test Name", variantsObject, options);

Your variantsObject should be a mapping from variant names to components representing them

const variantsObject = {
  variantA: VariantAComponent,
  variantB: VariantBComponent,
  /*...*/
  variantN: VariantNComponent,
}

Your options object may contain settings for local development:

const options = {
  isLocal: false, // Disables the load timeout if set to true, enables loading tests via console
  debug: false, // Displays log information if set to true
  experimentId: '<Optimize Experiment ID>', // See Experiment ID in Google Optimize section
  waitForUser: false, // wait for the user to be loaded / not loaded before triggering activate optimize
}

All experiment data is injected as abTestData property to a variant component. By default the abTestData object looks like:

//...
const { abTestData } = this.props;

console.log('Mounted variant', abTestData.variant);

// If custom key is defined, see Google Optimize documentation part
console.log('Custom key', abTestData.key);

Google Tag Manager

To improve A/B tests in React we introduced preloading scripts for GTM (Optimize), meaning that before the Optimize script is loaded, we set a pending flag to know if it ever will be loaded.

If this flag is not set while an A/B React component loads, the component won't wait to actually render a different variant, but rather the original wrapped one.

In GTM the tag names are:

  • Optimize Loaded (Sets window.doodleABTest.loaded to true)
  • Optimize Pending (Sets window.doodleABTest.pending to true)

Google Optimize

Experiment ID

The experiment id can be used to properly define if the original component should be rendered, or the wrapped React component should wait for an optimize event.

Experiment ID

Setup

  1. Create A/B test experiment.
  2. Inside TARGETING tab select Evaluete on custom even (and select event: optimize.activate)
  3. Create your A/B variants
  4. Inside each variant add JavaScript change:
/**
 * name {string}
 * variant {string}
 * key0 - keyN {*}
 * */
var experiment = {
  name: 'experimentName', // Mandatory
  variant: 'experimentVariant' // Mandatory,
  isGlobal: false, // Optional: Inidicates if this experiment is meant to be used by a component
  experimentId: 'id', // Optional: Used for example by svc-billing in specific API call headers
  // Optional extra data, with custom name and data, NOT the Optimize experiment ID
  key0: 'value0',
  //...
  keyN: 'valueN',
};
window.postMessage({ experiment: experiment }, 'https://doodle.com/premium/private/billing');

To properly add conditionals to Optimize, all currently activated tests are loaded into window.doodleABTest.activeExperiments as a | separated string.

Optimize is able to access this variable and work with them in its experiments.

Development

Login to private npm registry:

npm login
#Username: tmf
#Password:
#Email: (this IS public) tmf@doodle.com

After bumping version in package.json, build the dist folder

npm run build

and publish the new version (from the root project):

npm publish

Both dist and src folders will be published allowing us to do

import { withAbTest } from '@doodle/ab-connector';

but also to include a single file

import withAbTest from '@doodle/ab-connector/src/hoc/withAbTest';