MinervaNeue/resources/skins.minerva.scripts/AB.js

78 lines
2.1 KiB
JavaScript
Raw Normal View History

/*
* Bucketing wrapper for creating AB-tests.
*
* Given a test name, sampling rate, and session ID, provides a class that buckets users into
* predefined bucket ("control", "A", "B") and starts an AB-test.
*/
( function ( M, mwExperiments ) {
var bucket = {
CONTROL: 'control',
A: 'A',
B: 'B'
};
/**
* Buckets users based on params and exposes an `isEnabled` and `getBucket` method.
* @param {Object} config Configuration object for AB test.
* @param {string} config.testName
* @param {number} config.samplingRate Sampling rate for the AB-test.
* @param {number} config.sessionId Session ID for user bucketing.
* @constructor
*/
function AB( config ) {
var
testName = config.testName,
samplingRate = config.samplingRate,
sessionId = config.sessionId,
test = {
name: testName,
enabled: !!samplingRate,
buckets: {
control: 1 - samplingRate,
A: samplingRate / 2,
B: samplingRate / 2
}
};
/**
* Gets the users AB-test bucket.
*
* A boolean instead of an enum is usually a code smell. However, the nature of A/B testing
* is to compare an A group's performance to a B group's so a boolean seems natural, even
* in the long term, and preferable to showing bucketing encoding ("A", "B", "control") to
* callers which is necessary if getBucket(). The downside is that now two functions exist
* where one would suffice.
*
* @return {string} AB-test bucket, bucket.CONTROL_BUCKET by default, bucket.A or bucket.B
* buckets otherwise.
*/
function getBucket() {
return mwExperiments.getBucket( test, sessionId );
}
function isA() {
return getBucket() === bucket.A;
}
function isB() {
return getBucket() === bucket.B;
}
/**
* Checks whether or not a user is in the AB-test,
* @return {boolean}
*/
function isEnabled() {
return getBucket() !== bucket.CONTROL; // I.e., `isA() || isB()`;
}
return {
isA: isA,
isB: isB,
isEnabled: isEnabled
};
}
M.define( 'skins.minerva.scripts/AB', AB );
}( mw.mobileFrontend, mw.experiments ) );