jenkins-bot has submitted this change and it was merged.
Change subject: WikiGrok A/B test dry run
......................................................................
WikiGrok A/B test dry run
* Create the wikiGrokUser class, which encapsulates the state of a user
that might be entered into the test
* Create the WikiGrokAbTest class, which controls whether or not a user
is eligible to enter the test and, if so, which version of WikiGrok
they'll see
Change-Id: If038f131a78e171479db6a18c311940d79c42277
---
M includes/Resources.php
M includes/skins/SkinMinerva.php
A javascripts/modules/wikigrok/WikiGrokAbTest.js
M javascripts/modules/wikigrok/wikigrok.js
A javascripts/modules/wikigrok/wikigrokuser.js
A tests/qunit/modules/wikigrok/test_WikiGrokAbTest.js
6 files changed, 206 insertions(+), 79 deletions(-)
Approvals:
Kaldari: Looks good to me, approved
jenkins-bot: Verified
diff --git a/includes/Resources.php b/includes/Resources.php
index 272be02..7b693a2 100644
--- a/includes/Resources.php
+++ b/includes/Resources.php
@@ -606,10 +606,21 @@
),
),
+ 'mobile.wikigrok.abTest' => $wgMFResourceFileModuleBoilerplate + array(
+ 'dependencies' => array(
+ 'mobile.user',
+ 'jquery.cookie',
+ ),
+ 'scripts' => array(
+ 'javascripts/modules/wikigrok/wikigrokuser.js',
+ 'javascripts/modules/wikigrok/WikiGrokAbTest.js',
+ ),
+ ),
+
'mobile.wikigrok' => $wgMFResourceFileModuleBoilerplate + array(
'dependencies' => array(
'mobile.startup',
- 'mobile.user',
+ 'mobile.wikigrok.abTest',
'mobile.loggingSchemas',
),
'scripts' => array(
diff --git a/includes/skins/SkinMinerva.php b/includes/skins/SkinMinerva.php
index 4d806a7..ad075ad 100644
--- a/includes/skins/SkinMinerva.php
+++ b/includes/skins/SkinMinerva.php
@@ -809,7 +809,8 @@
$wgMFAnonymousEditing,
$wgMFPhotoUploadEndpoint, $wgMFPhotoUploadAppendToDesc,
$wgMFCollapseSectionsByDefault, $wgMFShowRedLinksAnon,
- $wgMFShowRedLinks;
+ $wgMFShowRedLinks,
+ $wgMFWikiGrokAbTestStartDate,
$wgMFWikiGrokAbTestEndDate;
$title = $this->getTitle();
$user = $this->getUser();
@@ -843,6 +844,8 @@
if ( $this->isMobileMode ) {
$vars['wgImagesDisabled'] =
$this->mobileContext->imagesDisabled();
$vars['wgUserCanUpload'] =
$this->mobileContext->userCanUpload();
+ $vars['wgMFWikiGrokAbTestStartDate'] =
$wgMFWikiGrokAbTestStartDate;
+ $vars['wgMFWikiGrokAbTestEndDate'] =
$wgMFWikiGrokAbTestEndDate;
}
return $vars;
diff --git a/javascripts/modules/wikigrok/WikiGrokAbTest.js
b/javascripts/modules/wikigrok/WikiGrokAbTest.js
new file mode 100644
index 0000000..5d3f786
--- /dev/null
+++ b/javascripts/modules/wikigrok/WikiGrokAbTest.js
@@ -0,0 +1,61 @@
+( function ( M ) {
+
+ var Class = M.require( 'Class' ),
+
+ /**
+ * Represents the WikiGrok A/B test.
+ *
+ * @class WikiGrokAbTest
+ * @extends Class
+ */
+ WikiGrokAbTest = Class.extend( {
+
+ /**
+ * Initialises a new instance of the WikiGrokAbTest
class.
+ *
+ * @param {Number} startDate The date the test starts,
specified as a Unix
+ * timestamp
+ * @param {Number} endDate The date that the test ends,
specified as a Unix
+ * timestamp
+ */
+ initialize: function ( startDate, endDate ) {
+ var now = new Date().getTime() / 1000;
+
+ this.isEnabled = startDate && endDate && (
startDate <= now && now <= endDate );
+ },
+
+ /**
+ * Gets the version of WikiGrok to show to the user.
+ *
+ * @param {Object} wikiGrokUser The WikiGrok user
+ * @return {String} `'A'` or `'B'`
+ */
+ getVersion: function ( wikiGrokUser ) {
+ var lastCharacter =
wikiGrokUser.getToken().slice( -1 );
+
+ return lastCharacter > 'U' ? 'B' : 'A';
+ }
+ } );
+
+ /**
+ * Creates a new instance of the WikiGrokAbTest using
+ * `wfMFWikiGrokAbTestStartDate` and `wgMFWikiGrokAbTestEndDate` as the
`startDate`
+ * and `endDate` parameters respectively.
+ *
+ * @return {WikiGrokAbTest}
+ */
+ WikiGrokAbTest.newFromMwConfig = function () {
+ var config = mw.config.get( [
+ 'wgMFWikiGrokAbTestStartDate',
+ 'wgMFWikiGrokAbTestEndDate'
+ ] );
+
+ return new WikiGrokAbTest(
+ config.wgMFWikiGrokAbTestStartDate,
+ config.wgMFWikiGrokAbTestEndDate
+ );
+ };
+
+ M.define( 'WikiGrokAbTest', WikiGrokAbTest );
+
+} ( mw.mobileFrontend, mw ) );
diff --git a/javascripts/modules/wikigrok/wikigrok.js
b/javascripts/modules/wikigrok/wikigrok.js
index 6167119..a69dae6 100644
--- a/javascripts/modules/wikigrok/wikigrok.js
+++ b/javascripts/modules/wikigrok/wikigrok.js
@@ -1,10 +1,13 @@
// Determine whether or not it is appropriate to load WikiGrok, and if so,
load it.
( function ( M, $ ) {
+ // Only run in alpha or beta mode
+ M.assertMode( [ 'beta', 'alpha' ] );
+
var wikidataID = mw.config.get( 'wgWikibaseItemId' ),
errorSchema = M.require(
'loggingSchemas/mobileWebWikiGrokError' ),
permittedOnThisDevice = mw.config.get(
'wgMFEnableWikiGrokOnAllDevices' ) || !M.isWideScreen(),
idOverride,
- versions = {
+ versionConfigs = {
A: {
module: 'mobile.wikigrok.dialog',
view: 'modules/wikigrok/WikiGrokDialog',
@@ -16,97 +19,52 @@
name: 'b'
}
},
- version,
- DEFAULT_VERSION = 'A';
+ versionConfig,
+ WikiGrokAbTest = M.require( 'WikiGrokAbTest' ),
+ wikiGrokUser = M.require( 'wikiGrokUser' );
/*
- * Gets the version of wikigrok to use.
- *
- * If logged in:
- * * If Alpha, use B
- * * Otherwise use A
- * If anonymous:
- * * If it had any particular version assigned, use that one.
- * * Else, assign randomly a wikigrok version to use.
+ * Gets the configuration for the version of WikiGrok to use.
*
* The `wikigrokversion` query parameter can be used to override this
logic,
* `wikigrokversion=a` means that A will always be used. If the override
* version doesn't exist, then the default version (currently A) will
be used.
*
- * @return {Object}
+ * If the user is eligible to enter the WikiGrok AB test, then the test
+ * determines which version to use.
+ *
+ * @return {Object|null}
*/
- function getWikiGrokVersion() {
- var cookieName = mw.config.get( 'wgCookiePrefix' ) +
'-wikiGrokAnonymousVersion',
- anonVersion = $.cookie( cookieName ),
- versionOverride;
+ function getWikiGrokConfig() {
+ var versionOverride,
+ versionConfig = null,
+ wikiGrokAbTest = WikiGrokAbTest.newFromMwConfig();
+ // See if there is a query string override
if ( M.query.wikigrokversion ) {
versionOverride = M.query.wikigrokversion.toUpperCase();
- if ( versions.hasOwnProperty( versionOverride ) ) {
- return versions[versionOverride];
+ if ( versionConfigs.hasOwnProperty( versionOverride ) )
{
+ versionConfig = versionConfigs[versionOverride];
}
-
- return versions[DEFAULT_VERSION];
+ // Otherwise, see if A/B test is running, and if so, choose a
version.
+ } else if ( wikiGrokAbTest.isEnabled ) {
+ versionConfig =
versionConfigs[wikiGrokAbTest.getVersion( wikiGrokUser )];
}
- if ( !mw.user.isAnon() ) {
- if ( M.isAlphaGroupMember() ) {
- return versions.B;
- } else {
- return versions.A;
- }
- } else {
- if ( anonVersion ) {
- return versions[anonVersion];
- } else {
- anonVersion = Math.round( Math.random() ) ? 'A'
: 'B';
- $.cookie( cookieName, anonVersion, {
- expires: 90, // (days)
- path: '/'
- } );
- return versions[anonVersion];
- }
- }
+ return versionConfig;
}
- /*
- * Gets the user's token from 'cookie prefix' + "-wikiGrokUserToken"
- * cookie. If the cookie isn't set, then a token is generated,
- * stored in the cookie for 90 days, and then returned.
- *
- * @return {string}
- */
- function getUserToken() {
- var cookieName = mw.config.get( 'wgCookiePrefix' ) +
'-wikiGrokUserToken',
- storedToken = $.cookie( cookieName ),
- generatedToken;
-
- if ( storedToken ) {
- return storedToken;
- }
-
- generatedToken = mw.user.generateRandomSessionId();
-
- $.cookie( cookieName, generatedToken, {
- expires: 90, // (days)
- path: '/'
- } );
-
- return generatedToken;
- }
+ versionConfig = getWikiGrokConfig();
// Allow query string override for testing, for example,
'?wikidataid=Q508703'
if ( !wikidataID ) {
- idOverride = window.location.search.match( /wikidataid=([^&]*)/
);
+ idOverride = M.query.wikidataid;
if ( idOverride ) {
- mw.config.set( 'wgWikibaseItemId', idOverride[1] );
- wikidataID = idOverride[1];
+ mw.config.set( 'wgWikibaseItemId', idOverride );
+ wikidataID = idOverride;
}
}
-
- // Only run in alpha mode
- M.assertMode( [ 'beta', 'alpha' ] );
if (
// WikiGrok is enabled
@@ -120,20 +78,20 @@
// Wikibase is active and this page has an item ID
wikidataID &&
// We're in Main namespace,
- mw.config.get( 'wgNamespaceNumber' ) === 0
+ mw.config.get( 'wgNamespaceNumber' ) === 0 &&
+ versionConfig
) {
// Load the required module and view based on the version for
the user
- version = getWikiGrokVersion();
- mw.loader.using( version.module ).done( function () {
- var WikiGrokDialog = M.require( version.view );
+ mw.loader.using( versionConfig.module ).done( function () {
+ var WikiGrokDialog = M.require( versionConfig.view );
// Initialize the dialog and insert it into the page
(but don't display yet)
function init() {
var dialog = new WikiGrokDialog( {
itemId: wikidataID,
title: mw.config.get( 'wgTitle' ),
- userToken: getUserToken(),
+ userToken: wikiGrokUser.getToken(),
testing: ( idOverride ) ? true : false
} );
@@ -148,10 +106,10 @@
} ).fail( function () {
var data = {
error: 'no-impression-cannot-load-interface',
- taskType: 'version ' + version.name,
+ taskType: 'version ' + versionConfig.name,
taskToken: mw.user.generateRandomSessionId(),
- userToken: getUserToken(),
- isLoggedIn: !mw.user.isAnon()
+ userToken: wikiGrokUser.getToken(),
+ isLoggedIn: !wikiGrokUser.isAnon()
};
if ( idOverride ) {
data.testing = true;
diff --git a/javascripts/modules/wikigrok/wikigrokuser.js
b/javascripts/modules/wikigrok/wikigrokuser.js
new file mode 100644
index 0000000..900ae68
--- /dev/null
+++ b/javascripts/modules/wikigrok/wikigrokuser.js
@@ -0,0 +1,45 @@
+( function ( M, $, mw ) {
+
+ var user = M.require( 'user' ),
+
+ /**
+ * The `wikiGrokUser` object encapsulates the WikiGrok A/B test
specific
+ * state of the user, e.g. whether or not they have seen
WikiGrok while
+ * browsing anonymously, whereas the `user` object encapsulates
their
+ * general state, e.g. their ID and name.
+ *
+ * @class wikiGrokUser
+ * @singleton
+ */
+ wikiGrokUser = $.extend( {
+
+ /**
+ * Gets the user's token from the "-wikiGrokUserToken"
cookie. If the cookie
+ * isn't set, then a token is generated and then stored
in the cookie for 90
+ * days, and then returned.
+ *
+ * @return {String}
+ */
+ getToken: function () {
+ var cookieName = mw.config.get(
'wgCookiePrefix' ) + '-wikiGrokUserToken',
+ storedToken = $.cookie( cookieName ),
+ generatedToken;
+
+ if ( storedToken ) {
+ return storedToken;
+ }
+
+ generatedToken =
mw.user.generateRandomSessionId();
+
+ $.cookie( cookieName, generatedToken, {
+ expires: 90, // (days)
+ path: '/'
+ } );
+
+ return generatedToken;
+ }
+ }, user );
+
+ M.define( 'wikiGrokUser', wikiGrokUser );
+
+} ( mw.mobileFrontend, jQuery, mw ) );
diff --git a/tests/qunit/modules/wikigrok/test_WikiGrokAbTest.js
b/tests/qunit/modules/wikigrok/test_WikiGrokAbTest.js
new file mode 100644
index 0000000..5946e7e
--- /dev/null
+++ b/tests/qunit/modules/wikigrok/test_WikiGrokAbTest.js
@@ -0,0 +1,49 @@
+( function ( M, $ ) {
+
+ var WikiGrokAbTest = M.require( 'WikiGrokAbTest' ),
+ wikiGrokUser = M.require( 'wikiGrokUser' ),
+ now = new Date().getTime() / 1000,
+ enabledTest = new WikiGrokAbTest( now - 86400, now + 86400 );
+
+ QUnit.module( 'MobileFrontend: modules/wikigrok/WikiGrokAbTest', {
+ } );
+
+ QUnit.test( 'isEnabled returns false when the experiment isn\'t
enabled', 2, function ( assert ) {
+ var dataProvider = [
+ [now + 86400, now + 86401], // startDate is in
the future
+ [now - 86400, now - 86401], // endDate in the
past
+ ],
+ test;
+
+ $.each( dataProvider, function ( i, data ) {
+ test = new WikiGrokAbTest( data[0], data[1] );
+
+ assert.strictEqual( test.isEnabled, false );
+ } );
+ } );
+
+ QUnit.test( 'isEnabled returns true when the test is active', 1,
function ( assert ) {
+ assert.strictEqual( enabledTest.isEnabled, true );
+ } );
+
+ QUnit.test( 'getVersion()', 62, function ( assert ) {
+
+ // A map of expected version to last character of the user's
token
+ var dataProvider = {
+ 'A': '0123456789ABCDEFGHIJKLMNOPQRSTU'.split( '' ),
+ 'B': 'VWXYZabcdefghijklmnopqrstuvwxyz'.split( '' )
+ };
+
+ this.stub( wikiGrokUser, 'getToken' );
+
+ $.each( dataProvider, function ( expectedVersion, tokens ) {
+ $.each( tokens, function ( i, token ) {
+ wikiGrokUser.getToken.returns( token );
+
+ assert.strictEqual(
enabledTest.getVersion( wikiGrokUser ), expectedVersion );
+ } );
+ } );
+
+ } );
+
+} ( mw.mobileFrontend, jQuery ) );
--
To view, visit https://gerrit.wikimedia.org/r/171630
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: If038f131a78e171479db6a18c311940d79c42277
Gerrit-PatchSet: 10
Gerrit-Project: mediawiki/extensions/MobileFrontend
Gerrit-Branch: master
Gerrit-Owner: Phuedx <[email protected]>
Gerrit-Reviewer: Awjrichards <[email protected]>
Gerrit-Reviewer: Bmansurov <[email protected]>
Gerrit-Reviewer: Jdlrobson <[email protected]>
Gerrit-Reviewer: Jhernandez <[email protected]>
Gerrit-Reviewer: Kaldari <[email protected]>
Gerrit-Reviewer: Phuedx <[email protected]>
Gerrit-Reviewer: Robmoen <[email protected]>
Gerrit-Reviewer: jenkins-bot <>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits