Add ext.popups.experiment module

Changes:
* Add the ext.popups.experiment module
* Add the mw.popups.experiment.isUserInCondition function, which
  returns a promise that resolves with true if the user is in the
  experimental condition, otherwise false
* If the experiment isn't configured, i.e. wgPopupsExperimentConfig is
  null or undefined, then the user isn't entered into the experiment,
  providing a kill switch

Bug: T132604
Change-Id: I49597494273e3862711a32e4951c8598e6c8bf59
This commit is contained in:
Sam Smith 2016-05-13 13:25:39 +01:00
parent 85b27d8de8
commit 3f03d681c9
4 changed files with 211 additions and 1 deletions

View file

@ -223,8 +223,12 @@ class PopupsHooks {
'tests/qunit/ext.popups.renderer.article.test.js',
'tests/qunit/ext.popups.core.test.js',
'tests/qunit/ext.popups.settings.test.js',
'tests/qunit/ext.popups.experiment.test.js',
),
'dependencies' => array(
'ext.popups.desktop',
'ext.popups.experiment'
),
'dependencies' => array( 'ext.popups.desktop' ),
'localBasePath' => __DIR__,
'remoteExtPath' => 'Popups',
);

View file

@ -92,6 +92,21 @@
"dependencies": [
"ext.popups.core"
]
},
"ext.popups.experiment": {
"scripts": [
"resources/ext.popups.experiment.js"
],
"dependencies": [
"ext.popups.core",
"jquery.jStorage",
"mediawiki.user",
"mediawiki.storage",
"mediawiki.experiments"
],
"targets": [
"desktop"
]
}
},
"ResourceFileModulePaths": {

View file

@ -0,0 +1,79 @@
( function ( mw, $ ) {
/**
* @ignore
*/
function getToken() {
var key = 'PopupsExperimentID',
id = mw.storage.get( key );
if ( !id ) {
id = mw.user.generateRandomSessionId();
mw.storage.set( key, id );
}
return id;
}
/**
* Has the user previously enabled Popups by clicking "Enable previews" in the
* footer menu?
*
* @return {boolean}
* @ignore
*/
function hasUserEnabledFeature() {
var value = $.jStorage.get( 'mwe-popups-enabled' );
return Boolean( value ) && value !== 'false';
}
/**
* @class mw.popups.experiment
* @singleton
*/
mw.popups.experiment = {};
/**
* Gets whether or not the user has Popups enabled, i.e. whether they are in the experiment
* condition.
*
* The user is in the experiment condition if:
* * they've enabled Popups by click "Enable previews" in the footer menu, or
* * they've enabled Popups as a beta feature, or
* * they aren't in the control bucket of the experiment
*
* N.B. that the user isn't entered into the experiment, i.e. they aren't assigned or a bucket,
* if the experiment isn't configured.
*
* @return {jQuery.Promise}
*/
mw.popups.experiment.isUserInCondition = function isUserInCondition() {
var deferred = $.Deferred(),
config = mw.config.get( 'wgPopupsExperimentConfig' ),
result;
if (
hasUserEnabledFeature() ||
// Users with the beta feature enabled are already in the experimental condition.
mw.config.get( 'wgPopupsExperimentIsBetaFeatureEnabled', false )
) {
deferred.resolve( true );
} else if ( !config ) {
deferred.resolve( false );
} else {
mw.requestIdleCallback( function () {
// FIXME: mw.experiments should expose the CONTROL_BUCKET constant, e.g.
// `mw.experiments.CONTROL_BUCKET`.
result = mw.experiments.getBucket( config, getToken() ) !== 'control';
deferred.resolve( result );
} );
}
return deferred.promise();
};
}( mediaWiki, jQuery ) );

View file

@ -0,0 +1,112 @@
( function ( mw, $ ) {
QUnit.module( 'ext.popups.experiment', QUnit.newMwEnvironment( {
config: {
wgPopupsExperimentConfig: {
name: 'Popups A/B Test - May, 2016',
enabled: true,
buckets: {
control: 0.5,
A: 0.5
}
}
},
teardown: function () {
mw.storage.remove( 'PopupsExperimentID' );
}
} ) );
QUnit.test( '#isUserInCondition: user has beta feature enabled', function ( assert ) {
var done = assert.async();
mw.config.set( 'wgPopupsExperimentConfig', null );
mw.config.set( 'wgPopupsExperimentIsBetaFeatureEnabled', true );
mw.popups.experiment.isUserInCondition().then( function ( result ) {
assert.strictEqual(
result,
true,
'If the user has the beta feature enabled, then they aren\'t in the condition.'
);
done();
} );
} );
QUnit.test( '#isUserInCondition', function ( assert ) {
var getBucketSpy = this.sandbox.stub( mw.experiments, 'getBucket' ).returns( 'A' ),
config = mw.config.get( 'wgPopupsExperimentConfig' ),
done = assert.async();
mw.popups.experiment.isUserInCondition().then( function ( result ) {
var fistCallArgs = getBucketSpy.firstCall.args;
assert.deepEqual(
fistCallArgs[ 0 ],
config,
'The Popups experiment config is used when bucketing the user.'
);
assert.strictEqual(
result,
true,
'If the user isn\'t in the control bucket, then they are in the condition.'
);
done();
} );
} );
QUnit.test( '#isUserInCondition: token is persisted', function ( assert ) {
var token = '1234567890',
setSpy = this.sandbox.spy( mw.storage, 'set' ),
done = assert.async();
this.sandbox.stub( mw.user, 'generateRandomSessionId' ).returns( token );
mw.popups.experiment.isUserInCondition().then( function () {
assert.deepEqual(
setSpy.firstCall.args[ 1 ],
token,
'The token is persisted transparently.'
);
done();
} );
} );
QUnit.test( '#isUserInCondition: experiment isn\'t configured', function ( assert ) {
var done = assert.async();
mw.config.set( 'wgPopupsExperimentConfig', null );
mw.popups.experiment.isUserInCondition().then( function ( result ) {
assert.strictEqual(
result,
false,
'If the experiment isn\'t configured, then the user isn\'t in the condition.'
);
done();
} );
} );
QUnit.test( '#isUserInCondition: user has enabled the feature', function ( assert ) {
var done = assert.async();
$.jStorage.set( 'mwe-popups-enabled', 'true' );
mw.popups.experiment.isUserInCondition().then( function ( result ) {
assert.strictEqual(
result,
true,
'If the experiment has enabled the feature, then the user is in the condition.'
);
$.jStorage.deleteKey( 'mwe-popups-enabled' );
done();
} );
} );
}( mediaWiki, jQuery ) );