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

Reply via email to