Phuedx has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/193083

Change subject: Four minute abs
......................................................................

Four minute abs

* Allow experiments to be configured server-side and run client-side
** Experiments are all contained in the wgMFExperiments configuration
   variable
** You enter a user into an experiment and get the bucket they've been
assigned to by calling

    M.require( 'experiments' ).getBucket( experiment );

Here an experiment is a set of buckets and associated probabilities,
e.g. the user has a 50% likelihood of being assigned to the control
bucket, etc.

DISCUSSION

The `experiments` module simply wraps the `mw.user.bucket` method, which
has been deprecated since 1.23. We should either revert the deprecation
or add a similar method to the `user` module.

Change-Id: I309e52d9d85735b904830b5b15cada63a5947bb7
---
M includes/Config.php
M includes/MobileFrontend.hooks.php
M includes/Resources.php
A includes/config/Experiments.php
A javascripts/experiments.js
A tests/qunit/test_experiments.js
6 files changed, 141 insertions(+), 1 deletion(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/MobileFrontend 
refs/changes/83/193083/1

diff --git a/includes/Config.php b/includes/Config.php
index 64442c9..6c13fc9 100644
--- a/includes/Config.php
+++ b/includes/Config.php
@@ -3,6 +3,7 @@
        require_once __DIR__ . "/config/Analytics.php";
        require_once __DIR__ . "/config/Editing.php";
        require_once __DIR__ . "/config/Experimental.php";
+       require_once __DIR__ . "/config/Experiments.php";
        require_once __DIR__ . "/config/Legacy.php";
        require_once __DIR__ . "/config/Nearby.php";
        require_once __DIR__ . "/config/Site.php";
diff --git a/includes/MobileFrontend.hooks.php 
b/includes/MobileFrontend.hooks.php
index baaee6c..d963d6c 100644
--- a/includes/MobileFrontend.hooks.php
+++ b/includes/MobileFrontend.hooks.php
@@ -354,7 +354,7 @@
         * @return boolean
         */
        public static function onResourceLoaderGetConfigVars( &$vars ) {
-               global $wgMFNearbyEndpoint, $wgMFContentNamespace;
+               global $wgMFNearbyEndpoint, $wgMFContentNamespace, 
$wgMFExperiments;
                $vars['wgMFNearbyEndpoint'] = $wgMFNearbyEndpoint;
                $vars['wgMFThumbnailSizes'] = array(
                        'tiny' =>  MobilePage::TINY_IMAGE_WIDTH,
@@ -369,6 +369,7 @@
                // Set the licensing agreement that is displayed in the 
uploading interface.
                $wgMFUploadLicenseLink = SkinMinerva::getLicenseLink( 'upload' 
);
                $vars['wgMFUploadLicenseLink'] = $wgMFUploadLicenseLink;
+               $vars['wgMFExperiments'] = $wgMFExperiments;
                return true;
        }
 
diff --git a/includes/Resources.php b/includes/Resources.php
index 77ce766..6941323 100644
--- a/includes/Resources.php
+++ b/includes/Resources.php
@@ -306,6 +306,7 @@
                        'mobile.user',
                        'mediawiki.api',
                        'mobile.redlinks',
+                       'mobile.experiments',
                ),
                'templates' => array(
                        'icon.hogan' => 'templates/icon.hogan',
@@ -1599,6 +1600,11 @@
                        'mobile.toc',
                ),
        ),
+       'mobile.experiments' => $wgMFResourceFileModuleBoilerplate + array(
+               'scripts' => array(
+                       'javascripts/experiments.js',
+               ),
+       ),
 );
 
 $wgResourceModules = array_merge( $wgResourceModules,
diff --git a/includes/config/Experiments.php b/includes/config/Experiments.php
new file mode 100644
index 0000000..c62a56d
--- /dev/null
+++ b/includes/config/Experiments.php
@@ -0,0 +1,36 @@
+<?php
+// Needs to be called within MediaWiki; not standalone
+if ( !defined( 'MEDIAWIKI' ) ) {
+       die( 'Not an entry point.' );
+}
+
+/**
+ * @var array A set of experiments.
+ *
+ * Consider the following example:
+ *
+ * <code>
+ * $wgMFExperiments = array(
+ *     'wikigrok' => array(
+ *         'enabled' => true,
+ *         'buckets' => array(
+ *             'control' => 0.33,
+ *             'A' => 0.33,
+ *             'B' => 0.33,
+ *         ),
+ *         'version' => 1,
+ *         'expiry' => 14, // 14 days
+ *     ),
+ * );
+ * </code>
+ *
+ * The wikigrok experiment has three buckets: control, A, and B. The user has 
a 33% chance of being
+ * being assigned to each bucket. Note well that if the experiment were 
disabled, then the user is
+ * always assigned to the control bucket.
+ *
+ * This is the first version of the experiment.
+ *
+ * The user will be rebucketed in 14 days unless the experiment is updated, 
which would require
+ * changing the version, or is disabled.
+ */
+$wgMFExperiments = array();
diff --git a/javascripts/experiments.js b/javascripts/experiments.js
new file mode 100644
index 0000000..f840756
--- /dev/null
+++ b/javascripts/experiments.js
@@ -0,0 +1,37 @@
+( function ( mw, M ) {
+
+       var CONTROL_BUCKET = 'control';
+
+       M.define( 'experiments', {
+
+               /**
+                * Gets the bucket for the experiment.
+                *
+                * Defers to `mw.user.bucket` to do the bucketing.
+                *
+                * @param {String} experiment
+                * @throws Error If the experiment hasn't been defined in the 
`$wgMFExperiments`
+                *  configuration variable
+                * @returns {String}
+                */
+               getBucket: function ( experiment ) {
+                       var experiments = mw.config.get( 'wgMFExperiments' ) || 
{},
+                               options;
+
+                       if ( !experiments.hasOwnProperty( experiment ) ) {
+
+                               // TODO: Should errors like this be translated?
+                               throw new Error( 'The experiment "' + 
experiment + '" hasn\'t been defined.' );
+                       }
+
+                       options = experiments[experiment];
+
+                       if ( !options.enabled ) {
+                               return CONTROL_BUCKET;
+                       }
+
+                       return mw.user.bucket( experiment, options );
+               }
+       } );
+
+} ( mw, mw.mobileFrontend ) );
diff --git a/tests/qunit/test_experiments.js b/tests/qunit/test_experiments.js
new file mode 100644
index 0000000..c94ef4f
--- /dev/null
+++ b/tests/qunit/test_experiments.js
@@ -0,0 +1,59 @@
+( function ( mw, M ) {
+
+       QUnit.module( 'MobileFrontend Experiments', {
+               setup: function () {
+                       this.wgMFExperiments = {
+                               foo: {
+                                       enabled: true,
+                                       buckets: {
+                                               control: 50,
+                                               'wikigrok-version-a': 25,
+                                               'wikigrok-version-b': 25
+                                       },
+                                       version: 1,
+                                       expires: 14 // 14 days
+                               },
+                               bar: {
+                                       enabled: false,
+                                       buckets: {
+                                               control: 84,
+                                               'should-see-wikigrok-roulette': 
16
+                                       }
+                               }
+                       };
+                       mw.config.set( 'wgMFExperiments', this.wgMFExperiments 
);
+
+                       this.bucketStub = this.stub( mw.user, 'bucket' );
+
+                       this.experiments = M.require( 'experiments' );
+               }
+       } );
+
+       QUnit.test( 'it should throw when the experiment hasn\'t been defined', 
1, function ( assert ) {
+               assert.throws( function () {
+                       this.experiments.getBucket( 'baz' );
+               } );
+       } );
+
+       QUnit.test( 'it should defer to mw.user.bucket to perform the 
bucketing', 1, function ( assert ) {
+               this.experiments.getBucket( 'foo' );
+
+               assert.strictEqual( true, this.bucketStub.calledWith( 'foo', 
this.wgMFExperiments.foo ) );
+       } );
+
+       QUnit.test( 'it should get the bucket if the experiment has been 
defined', 1, function ( assert ) {
+               var bucket = 'wikigrok-version-a';
+
+               this.bucketStub.returns( bucket );
+
+               assert.strictEqual( bucket, this.experiments.getBucket( 'foo' ) 
);
+       } );
+
+       QUnit.test( 'it should always return "control" if the experiment has 
been defined as disabled', 2, function ( assert ) {
+               var bucket = this.experiments.getBucket( 'bar' );
+
+               assert.strictEqual( 'control', bucket );
+               assert.strictEqual( false, this.bucketStub.called );
+       } );
+
+} ( mw, mw.mobileFrontend ) );

-- 
To view, visit https://gerrit.wikimedia.org/r/193083
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I309e52d9d85735b904830b5b15cada63a5947bb7
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/MobileFrontend
Gerrit-Branch: master
Gerrit-Owner: Phuedx <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to