jenkins-bot has submitted this change and it was merged.

Change subject: Mixed storage for buckets
......................................................................


Mixed storage for buckets

Allows fallback to cookies for users without LocalStorage, in any campaign.

Migrates legacy 'CN' cookies, and removes support for legacy legacy
'centralnotice_buckets_by_campaign' cookies.

Bug: T132639
Change-Id: Icb0012a8daa2fa513a3f954507c0d7adb4ce4ae7
---
M CentralNotice.modules.php
M resources/subscribing/ext.centralNotice.display.bucketer.js
2 files changed, 100 insertions(+), 59 deletions(-)

Approvals:
  Awight: Checked; Looks good to me, approved
  jenkins-bot: Verified



diff --git a/CentralNotice.modules.php b/CentralNotice.modules.php
index 2f3ec05..cde693e 100644
--- a/CentralNotice.modules.php
+++ b/CentralNotice.modules.php
@@ -222,6 +222,7 @@
        'styles'        => 'subscribing/ext.centralNotice.display.css',
        'dependencies'  => array(
                'ext.centralNotice.geoIP',
+               'ext.centralNotice.kvStore',
                'jquery.cookie',
                'json',
                'mediawiki.Uri',
diff --git a/resources/subscribing/ext.centralNotice.display.bucketer.js 
b/resources/subscribing/ext.centralNotice.display.bucketer.js
index fabda8d..8ed7351 100644
--- a/resources/subscribing/ext.centralNotice.display.bucketer.js
+++ b/resources/subscribing/ext.centralNotice.display.bucketer.js
@@ -2,9 +2,10 @@
  * Storage, retrieval and other processing of buckets. Provides
  * cn.internal.bucketer.
  *
- * Bucket assignments are stored in a cookie named simply 'CN', to maximize
- * concision. It consists of '*'-separated campaigns, each of which is made up
- * for '!'-separated fields. The format is:
+ * Bucket assignments are stored using the kvStore, in LocalStorage or a 
cookie.
+ * To maximize concision for users that fall back to cookies, the value 
consists
+ * of '*'-separated campaigns, each of which is made up of '!'-separated 
fields.
+ * The format is:
  *
  *  NAME!START!END!VALUE[*NAME!START!END!VALUE..]
  *
@@ -14,26 +15,32 @@
  *
  * For example:
  *
- *  CN=WikiConference_USA!39942400!3729600
+ * 'WikiConference_USA!39942400!3729600!2'
  *
  * ...would be deserialized to:
  *
- * { WikiConference_USA: { start: 1439942400, end: 1443672000 } }
+ * { WikiConference_USA: { start: 1439942400, end: 1443672000, val: 2 } }
  *
  */
 ( function ( $, mw ) {
 
-               // Name of the old (pre-I2b39d153b) cookie for CentralNotice 
buckets.
-               // Its value is a JSON-encoded object, mapping campaign names 
to plain
-               // objects with 'start', 'end', and 'val' parameters.
-       var LEGACY_COOKIE = 'centralnotice_buckets_by_campaign',
-
-               // Bucket objects by campaign; properties are campaign names.
-               // Retrieved from bucket cookie, if available.
-               buckets = null,
+       // Bucket objects by campaign; properties are campaign names.
+       // Retrieved from kvStore (which uses LocalStorage or a fallback cookie)
+       // or from a legacy cookie.
+       var buckets = null,
 
                // The campaign we're working with.
-               campaign = null;
+               campaign = null,
+
+               kvStore = mw.centralNotice.kvStore,
+               multiStorageOption,
+
+               // Name of the legacy cookie for CentralNotice buckets. Its 
value is
+               // a compact serialization of buckets in the the same format as 
is
+               // currently used here.
+               LEGACY_COOKIE = 'CN',
+
+               STORAGE_KEY = 'buckets';
 
        /**
         * Escape '*' and '!' in a campaign name to make it safe for 
serialization.
@@ -53,36 +60,11 @@
                } );
        }
 
-       /**
-        * Attempt to get buckets from the bucket cookie. If there is no
-        * bucket cookie, check for a 'legacy cookie' (i.e., a cookie with
-        * the name and format used prior to I2b39d153b); if there is one,
-        * migrate it to the newer cookie format. If neither cookie exists,
-        * set buckets to an empty object.
-        */
-       function loadBuckets() {
-               var cookieVal = $.cookie( 'CN' );
+       function parseSerializedBuckets( serialized ) {
 
-               buckets = {};
+               var parsedBuckets = {};
 
-               if ( !cookieVal ) {
-                       // Prior to I2b39d153be, the campaign cookie had a 
different
-                       // (longer) name and used JSON encoding. If the user 
has such
-                       // a cookie, migrate it to the new format.
-                       cookieVal = $.cookie( LEGACY_COOKIE );
-                       if ( cookieVal ) {
-                               $.removeCookie( LEGACY_COOKIE, { path: '/' } );
-                               try {
-                                       $.extend( buckets, JSON.parse( 
cookieVal ) );
-                               } catch ( e ) {}
-                               if ( !$.isEmptyObject( buckets ) ) {
-                                       storeBuckets();
-                               }
-                       }
-                       return;
-               }
-
-               $.each( cookieVal.split( '*' ), function ( idx, strBucket ) {
+               $.each( serialized.split( '*' ), function ( idx, strBucket ) {
                        var parts = strBucket.split( '!' ),
                                key = decodeCampaignName( parts[0] ),
                                start = parseInt( parts[1], 10 ) + 14e8,
@@ -90,22 +72,65 @@
                                val = parseInt( parts[3], 10 );
 
                        if ( key && start && end && !isNaN( val ) ) {
-                               buckets[ key ] = {
+                               parsedBuckets[ key ] = {
                                        start: start,
                                        end: end,
                                        val: val
                                };
                        }
                } );
+
+               return parsedBuckets;
        }
 
        /**
-        * Store buckets in the bucket cookie. The cookie will be set to expire
-        * after the all the buckets it contains do.
+        * Check legacy bucket cookie, and try to migrate. If a legacy cookie is
+        * found, load buckets from there.
+        *
+        * @returns {boolean} true if a legacy cookie was migrated, false if not
+        */
+       function possiblyLoadAndMigrateLegacyBuckets() {
+
+               var cookieVal = $.cookie( LEGACY_COOKIE );
+
+               if ( cookieVal ) {
+
+                       // We need to deserialize and store again to determine 
ttl
+                       buckets = parseSerializedBuckets( cookieVal );
+                       storeBuckets();
+                       $.removeCookie( LEGACY_COOKIE, { path: '/' } );
+                       return true;
+               }
+
+               return false;
+       }
+
+       /**
+        * Attempt to get buckets from the storage. If no stored buckets are
+        * found, set buckets to an empty object.
+        */
+       function loadBuckets() {
+
+               var val = kvStore.getItem(
+                       STORAGE_KEY,
+                       kvStore.contexts.GLOBAL,
+                       multiStorageOption
+               );
+
+               buckets = ( val ? parseSerializedBuckets( val ) : {} );
+       }
+
+       /**
+        * Store buckets using the kvStore. The storage item will be set to
+        * expire after the all the buckets it contains do.
+        *
+        * Though compact serialization is no longer needed for the majority
+        * of users, who'll get LocalStorage, it's useful for those who fall
+        * back to cookies, and it seems preferable to keep things consistent.
         */
        function storeBuckets() {
                var expires = Math.ceil( ( new Date() ) / 1000 ),
-                       cookieVal = $.map( buckets, function ( opts, key ) {
+                       serialized = $.map( buckets, function ( opts, key ) {
                                var parts = [
                                        escapeCampaignName( key ),
                                        Math.floor( opts.start - 14e8 ),
@@ -120,11 +145,14 @@
                                return parts.join( '!' );
                        } ).join( '*' );
 
-               // Store the buckets in the cookie
-               $.cookie( 'CN', cookieVal, {
-                       expires: new Date( expires * 1000 ),
-                       path: '/'
-               } );
+               kvStore.setItem(
+                       STORAGE_KEY,
+                       serialized,
+                       kvStore.contexts.GLOBAL,
+                       // Convert expires to ttl in days
+                       Math.ceil( ( expires - ( new Date() ) / 1000 ) / 86400 
),
+                       multiStorageOption
+               );
        }
 
        /**
@@ -141,11 +169,11 @@
 
        /**
         * Do all things bucket:
-        * - Get buckets from the cookie, if available.
+        * - Get buckets from the kvStore or legacy cookie, if available.
         * - If necessary, generate a random bucket for the campaign.
         * - Ensure the stored end date for this campaign is up-to-date.
         * - Go through all the buckets, purging expired buckets.
-        * - Store the updated bucket data in the cookie.
+        * - Store the updated bucket data using the kvStore.
         *
         * This should be called before a bucket is requested but after
         * setCampaign() has been called.
@@ -168,17 +196,29 @@
                bucketEndDate.setTime( campaign.end * 1000 );
                bucketEndDate.setUTCDate( bucketEndDate.getUTCDate() + 
extension );
 
-               loadBuckets();
+               // Check if and how we can store buckets. Allow cookie fallback 
in all
+               // cases in which localStorage isn't available.
+
+               // If we have no storage options (cookies and localStorage 
disabled),
+               // loading and storing buckets will be no-ops, and a new bucket 
will be
+               // chosen every time.
+               multiStorageOption = kvStore.getMultiStorageOption( true );
+
+               // In all cases, check for a legacy cookie and try to migrate 
if one
+               // was found. Otherwise, load normally.
+               if ( !possiblyLoadAndMigrateLegacyBuckets() ) {
+                       loadBuckets();
+               }
+
                bucket = buckets[campaignName];
 
                // If we have a valid bucket, just check and possibly update its
                // expiry.
 
-               // Note that buckets that are expired but that are found in
-               // the cookie (because they didn't have the chance to get
-               // purged) are not considered valid. In that case, for
-               // consistency, we choose a new random bucket, just as if
-               // no bucket had been found.
+               // Note that buckets that are expired but that were retrieved 
from
+               // storage (because they didn't have the chance to get purged) 
are
+               // not considered valid. In that case, for consistency, we 
choose a
+               // new random bucket, just as if no bucket had been found.
 
                if ( bucket && bucketEndDate > now ) {
 

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

Gerrit-MessageType: merged
Gerrit-Change-Id: Icb0012a8daa2fa513a3f954507c0d7adb4ce4ae7
Gerrit-PatchSet: 7
Gerrit-Project: mediawiki/extensions/CentralNotice
Gerrit-Branch: master
Gerrit-Owner: AndyRussG <andrew.green...@gmail.com>
Gerrit-Reviewer: AndyRussG <andrew.green...@gmail.com>
Gerrit-Reviewer: Awight <awi...@wikimedia.org>
Gerrit-Reviewer: Cdentinger <cdentin...@wikimedia.org>
Gerrit-Reviewer: Ejegg <eeggles...@wikimedia.org>
Gerrit-Reviewer: Ssmith <ssm...@wikimedia.org>
Gerrit-Reviewer: XenoRyet <dkozlow...@wikimedia.org>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to