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

Change subject: Banner history logger campaign mixin
......................................................................


Banner history logger campaign mixin

Provides a campaign-associated mixin for storing a history of banner display
or banner hide events for a given campaign. The log is stored on the client
using CentralNotice's KV store (which uses LocalStorage). For a random sample
of page views, a summary of recent log entries is sent to the server using
EventLogging.

EventLogging calls are made with as small a payload as possible, and payload
size is checked before the EL call to ensure we're within the limit imposed
by curent WMF infrastructure.

Also provides an API for manually sending recent entries in the banner history
log. A temporary, non-persistent unique log ID is generated in this case, only
for the purpose of flagging which histories led to donations.

Requires the CentralNoticeBannerHistory EventLogging schema be deployed.

Bug: T90918
Change-Id: I698cc00d08f3cc644516ad837bc09ada8df0cf5b
---
M CentralNotice.hooks.php
M CentralNotice.modules.php
M CentralNotice.php
M i18n/en.json
M i18n/qqq.json
A resources/subscribing/ext.centralNotice.bannerHistoryLogger.js
M resources/subscribing/ext.centralNotice.display.hide.js
M resources/subscribing/ext.centralNotice.display.js
M resources/subscribing/ext.centralNotice.display.state.js
D resources/subscribing/ext.centralNotice.placeholderCampaignMixin.js
10 files changed, 415 insertions(+), 72 deletions(-)

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



diff --git a/CentralNotice.hooks.php b/CentralNotice.hooks.php
index f6ff4f9..7fd2244 100644
--- a/CentralNotice.hooks.php
+++ b/CentralNotice.hooks.php
@@ -118,6 +118,7 @@
        // TODO: replace ef- global functions with static methods in 
CentralNoticeHooks
        $wgHooks['ResourceLoaderTestModules'][] = 
'efCentralNoticeResourceLoaderTestModules';
        $wgHooks['UnitTestsList'][] = 'efCentralNoticeUnitTests';
+       $wgHooks['EventLoggingRegisterSchemas'][] = 
'efCentralNoticeEventLoggingRegisterSchemas';
 
        // If CentralNotice banners should be shown on this wiki, load the 
components we need for
        // showing banners. For discussion of banner loading strategies, see
@@ -479,3 +480,15 @@
 
        return true;
 }
+
+/**
+ * EventLoggingRegisterSchemas hook handler.
+ *
+ * @param array $schemas The schemas currently registered with the EventLogging
+ *  extension
+ * @return bool Always true
+ */
+function efCentralNoticeEventLoggingRegisterSchemas( &$schemas ) {
+       $schema['CentralNoticeBannerHistory'] = 13172419;
+       return true;
+}
\ No newline at end of file
diff --git a/CentralNotice.modules.php b/CentralNotice.modules.php
index 074272e..50f14a1 100644
--- a/CentralNotice.modules.php
+++ b/CentralNotice.modules.php
@@ -135,10 +135,9 @@
 
                // Messages used for campaign mixin parameter labels (labelMsg).
                // See CentralNotice.php.
-               'centralnotice-placeholder-campaign-mixin-string-placeholder',
-               'centralnotice-placeholder-campaign-mixin-integer-placeholder',
-               'centralnotice-placeholder-campaign-mixin-float-placeholder',
-               'centralnotice-placeholder-campaign-mixin-boolean-placeholder'
+               'centralnotice-banner-history-logger-rate',
+               'centralnotice-banner-history-logger-max-entry-age',
+               'centralnotice-banner-history-logger-max-entries'
        )
 );
 
@@ -228,13 +227,14 @@
 
 // This mixin module is just to make campaign mixins smoke testable. It'll be
 // removed before merging the campaign_mixnis feature branch to master.
-$wgResourceModules[ 'ext.centralNotice.placeholderCampaignMixin' ] = array(
+$wgResourceModules[ 'ext.centralNotice.bannerHistoryLogger' ] = array(
        'localBasePath' => $dir . '/resources',
        'remoteExtPath' => 'CentralNotice/resources',
-       'scripts'       => 
'subscribing/ext.centralNotice.placeholderCampaignMixin.js',
+       'scripts'       => 
'subscribing/ext.centralNotice.bannerHistoryLogger.js',
        // campaign mixin modules need this dependency, to be sure it is loaded 
first
        'dependencies'  => array(
                'ext.centralNotice.kvStore',
+               'mediawiki.Uri',
                // Mixins must depend on display to ensure the hook they use to
                // register themselves is available when they run
                'ext.centralNotice.display',
diff --git a/CentralNotice.php b/CentralNotice.php
index d39a050..8731d55 100644
--- a/CentralNotice.php
+++ b/CentralNotice.php
@@ -252,25 +252,21 @@
 // It'll be removed before merging the campaign_mixnis feature branch to 
master.
 
 $wgCentralNoticeCampaignMixins = array(
-       'placeholderCampaignMixin' => array(
-               'module' => 'ext.centralNotice.placeholderCampaignMixin',
-               'nameMsg' => 'centralnotice-placeholder-campaign-mixin-name',
+       'bannerHistoryLogger' => array(
+               'module' => 'ext.centralNotice.bannerHistoryLogger',
+               'nameMsg' => 'centralnotice-banner-history-logger',
                'parameters' => array(
-                       'stringPlaceholder' => array(
-                               'type' => 'string',
-                               'labelMsg' => 
'centralnotice-placeholder-campaign-mixin-string-placeholder'
-                       ),
-                       'integerPlaceholder' => array(
-                               'type' => 'integer',
-                               'labelMsg' => 
'centralnotice-placeholder-campaign-mixin-integer-placeholder'
-                       ),
-                       'floatPlaceholder' => array(
+                       'rate' => array(
                                'type' => 'float',
-                               'labelMsg' => 
'centralnotice-placeholder-campaign-mixin-float-placeholder'
+                               'labelMsg' => 
'centralnotice-banner-history-logger-rate',
                        ),
-                       'booleanPlaceholder' => array(
-                               'type' => 'boolean',
-                               'labelMsg' => 
'centralnotice-placeholder-campaign-mixin-boolean-placeholder'
+                       'maxEntryAge' => array(
+                               'type' => 'integer',
+                               'labelMsg' => 
'centralnotice-banner-history-logger-max-entry-age'
+                       ),
+                       'maxEntries' => array(
+                               'type' => 'integer',
+                               'labelMsg' => 
'centralnotice-banner-history-logger-max-entries'
                        )
                )
        )
diff --git a/i18n/en.json b/i18n/en.json
index 062a9b6..60a1385 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -258,9 +258,8 @@
        "apihelp-query+centralnoticelogs-param-start": "Start time of range 
(optional).",
        "apihelp-query+centralnoticelogs-param-end": "End time of range 
(optional).",
        "apihelp-query+centralnoticelogs-example-1": "Show logs",
-       "centralnotice-placeholder-campaign-mixin-name": "Placeholder mixin",
-       "centralnotice-placeholder-campaign-mixin-string-placeholder": "String 
placeholder",
-       "centralnotice-placeholder-campaign-mixin-integer-placeholder": 
"Integer placeholder",
-       "centralnotice-placeholder-campaign-mixin-float-placeholder": "Float 
placeholder",
-       "centralnotice-placeholder-campaign-mixin-boolean-placeholder": 
"Boolean placeholder"
+       "centralnotice-banner-history-logger": "Banner history logger",
+       "centralnotice-banner-history-logger-rate": "Sample rate",
+       "centralnotice-banner-history-logger-max-entry-age": "Maximum age of 
log entries (in days)",
+       "centralnotice-banner-history-logger-max-entries": "Maximum number of 
entries to keep in log"
 }
diff --git a/i18n/qqq.json b/i18n/qqq.json
index 1750ed2..a7c5647 100644
--- a/i18n/qqq.json
+++ b/i18n/qqq.json
@@ -280,5 +280,9 @@
        "apihelp-query+centralnoticelogs-param-offset": 
"{{doc-apihelp-param|query+centralnoticelogs|offset}}",
        "apihelp-query+centralnoticelogs-param-start": 
"{{doc-apihelp-param|query+centralnoticelogs|start}}",
        "apihelp-query+centralnoticelogs-param-end": 
"{{doc-apihelp-param|query+centralnoticelogs|end}}",
-       "apihelp-query+centralnoticelogs-example-1": 
"{{doc-apihelp-example|query+centralnoticelogs}}"
+       "apihelp-query+centralnoticelogs-example-1": 
"{{doc-apihelp-example|query+centralnoticelogs}}",
+       "centralnotice-banner-history-logger": "Name of the banner history 
logger campaign mixin, for adminsitration UI control",
+       "centralnotice-banner-history-logger-rate": "Label for the banner 
history logger campaign mixin sample rate control in the administration UI",
+       "centralnotice-banner-history-logger-max-entry-age": "Label for the 
banner history logger campaign mixin maximum entry age control in the 
administration UI",
+       "centralnotice-banner-history-logger-max-entries": "Label for the 
banner history logger campaign mixin maximum number of log entries control in 
the administration UI"
 }
diff --git a/resources/subscribing/ext.centralNotice.bannerHistoryLogger.js 
b/resources/subscribing/ext.centralNotice.bannerHistoryLogger.js
new file mode 100644
index 0000000..dd2893f
--- /dev/null
+++ b/resources/subscribing/ext.centralNotice.bannerHistoryLogger.js
@@ -0,0 +1,301 @@
+/*
+ * Banner history logger mixin. Records an event every time this campaign is
+ * selected for the user (even if the banner is hidden). The log is kept in
+ * LocalStorage (via CentralNotice's kvStore). A sample of logs are sent to the
+ * server via EventLogging. Also allows triggering a call to EventLogging via
+ * cn.internal.bannerHistoryLogger.sendLog().
+ */
+( function ( $, mw ) {
+
+       var cn = mw.centralNotice,
+               mixin = new cn.Mixin( 'bannerHistoryLogger' ),
+               now = Math.round( ( new Date() ).getTime() / 1000 ),
+               log,
+
+               BANNER_HISTORY_KV_STORE_KEY = 'banner_history',
+               EVENT_LOGGING_SCHEMA = 'CentralNoticeBannerHistory';
+
+       /**
+        * Load the banner history log from KV storage
+        */
+       function loadLog() {
+
+               log = cn.getKVStorageItem(
+                       BANNER_HISTORY_KV_STORE_KEY,
+                       cn.getKVStorageContexts().GLOBAL
+               );
+
+               if ( !log ) {
+                       log = [];
+               }
+       }
+
+       /**
+        * Return a log entry about the current campaign selection event.
+        * @returns {Object}
+        */
+       function makeLogEntry() {
+
+               var data = cn.data,
+
+                       // Randomly shift timestamp +/- 0 to 10 seconds, so 
logs can't be
+                       // linked to specific Web requests. This is to 
strengthen user
+                       // privacy.
+                       randomTimeShift = Math.round( Math.random() * 20 ) - 10,
+                       time = now + randomTimeShift,
+
+                       logEntry = {
+                               language: data.uselang,
+                               country: data.country,
+                               isAnon: data.anonymous,
+                               campaign: data.campaign,
+                               campaignCategory: data.campaignCategory,
+                               bucket: data.bucket,
+                               time: time,
+                               status: data.status,
+                               statusCode: data.statusCode,
+
+                               bannerNotGuaranteedToDisplay:
+                                       data.bannerNotGuaranteedToDisplay ? 
true : false
+                       };
+
+               if ( data.banner ) {
+                       logEntry.banner = data.banner;
+               }
+
+               if ( data.bannerCanceledReason ) {
+                       logEntry.bannerCanceledReason = 
data.bannerCanceledReason;
+               }
+
+               if ( data.bannerLoadedButHiddenReason ) {
+                       logEntry.bannerLoadedButHiddenReason = 
data.bannerLoadedButHiddenReason;
+               }
+
+               return logEntry;
+       }
+
+       /**
+        * Remove log entries older than maxEntryAge (in days) and, if 
necessary,
+        * remove entries to keep the total within maxEntries.
+        * @param {number} maxEntryAge
+        * @param {number} maxEntries
+        */
+       function purgeOldLogEntries( maxEntryAge, maxEntries ) {
+               var i = 0,
+                       cutoff = now - maxEntryAge * 86400;
+
+               // If we're above the max number of entries, pare it down, 
starting
+               // with older entries
+               if ( log.length > maxEntries ) {
+                       log = log.slice( 0 - maxEntries );
+               }
+
+               // Remove any remaining entries that are older than maxEntryAge
+               while ( i < log.length && log[i].time < cutoff ) {
+                       i++;
+               }
+               log = log.slice( i );
+       }
+
+       /**
+        * Store the contents of the log variable in kvStorage
+        */
+       function storeLog() {
+               cn.setKVStorageItem(
+                       BANNER_HISTORY_KV_STORE_KEY,
+                       log,
+                       cn.getKVStorageContexts().GLOBAL
+               );
+       }
+
+       /**
+        * Return an object with data for EventLogging.
+        *
+        * We scrunch the data as small as possible due to the WMF 
infrastructure's
+        * EventLogging payload limit.
+        *
+        * @param {number} rate The sampling rate used
+        * @param {boolean} logId A unique identifier for this log. Note: this
+        *    should not be persisted anywhere on the client (see below).
+        * @returns {Object}
+        */
+       function makeEventLoggingData( rate, logId ) {
+
+               var elData = {},
+                       kvErrorLog = cn.getKVStorageErrorLog(),
+                       i, logEntry, elLogEntry;
+
+               // sample rate
+               if ( rate ) {
+                       elData.r = rate;
+               }
+
+               // log ID
+               if ( logId ) {
+                       elData.i = logId;
+               }
+
+               // if applicable, time of last kv store error
+               if ( kvErrorLog.length > 0 ) {
+                       elData.e = kvErrorLog[kvErrorLog.length - 1].time;
+               }
+
+               if ( !log ) {
+                       return elData;
+               }
+
+               // total log length
+               elData.n = log.length;
+               elData.l = [];
+
+               // Add log entries, starting with the most recent ones, until 
the EL
+               // URL is too big, or we reach the end of the log.
+               i = log.length - 1;
+
+               while ( i >= 0 ) {
+                       logEntry = log[i];
+
+                       elLogEntry = {
+                               t: logEntry.time,
+                               s: logEntry.statusCode
+                       };
+
+                       if ( logEntry.banner ) {
+                               elLogEntry.b = logEntry.banner;
+                       } else {
+                               elLogEntry.c = logEntry.campaign;
+                       }
+
+                       elData.l.unshift ( elLogEntry );
+
+                       if ( !checkEventLoggingURLSize( elData ) ) {
+                               elData.l.shift();
+                               break;
+                       }
+
+                       i--;
+               }
+
+               return elData;
+       }
+
+       /**
+        * Check the EventLogging URL we'd get from this data isn't too big. 
Here
+        * we copy some of the same processes done by ext.eventLogging.
+        *
+        * FIXME This is a temporary measure!
+        *
+        * @returns {boolean} true if the EL payload size is OK
+        */
+       function checkEventLoggingURLSize( elData ) {
+
+               var fullElData = {
+                               event    : elData,
+                               revision : 13172419, // Coordinate with 
CentralNotice.hooks.php
+                               schema   : EVENT_LOGGING_SCHEMA,
+                               webHost  : location.hostname,
+                               wiki     : mw.config.get( 'wgDBname' )
+                       },
+
+                       url = mw.eventLog.makeBeaconUrl( fullElData );
+
+               return ( url.length <= mw.eventLog.maxUrlSize );
+       }
+
+       // Set a function to run after a campaign is chosen and after a banner 
for
+       // that campaign is chosen or not
+       mixin.setPostBannerHandler( function( mixinParams ) {
+
+               // Nothing here needs to happen right away. At least, be sure 
we're not
+               // doing anything until the DOM is ready.
+               $( function() {
+
+                       var urlQuery = ( new mw.Uri() ).query,
+
+                               // URL param bannerLoggerRate can override 
rate, for debugging
+                               rate = urlQuery.bannerLoggerRate !== undefined ?
+                                       parseFloat( urlQuery.bannerLoggerRate ) 
: mixinParams.rate;
+
+                       // If KV storage works here, do our stuff
+                       if ( cn.isKVStorageAvailable( ) ) {
+
+                               loadLog();
+                               log.push( makeLogEntry() );
+
+                               purgeOldLogEntries( mixinParams.maxEntryAge,
+                                       mixinParams.maxEntries );
+
+                               storeLog();
+
+                       } else {
+
+                               // No KV storage? Just log this to the cookie
+                               cn.logKVStorageNotAvailableError();
+                               log = null;
+                       }
+
+                       // Send a sample to the server
+                       if ( Math.random() < rate ) {
+
+                               // Load the EventLogging RL module (if it's not 
already loaded).
+                               // Note that this can't work in the top RL 
queue unless DOM-
+                               // ready-wrapped (as here).
+                               mw.loader.using( [
+                                       'ext.eventLogging',
+                                       'schema.' + EVENT_LOGGING_SCHEMA
+                               ] ).done( function() {
+                                       mw.eventLog.logEvent(
+                                               EVENT_LOGGING_SCHEMA,
+                                               makeEventLoggingData( rate )
+                                       );
+                               } );
+                       }
+               } );
+       } );
+
+       // Register the mixin
+       cn.registerCampaignMixin( mixin );
+
+       // Object for access by other CentralNotice RL modules
+       cn.internal.bannerHistoryLogger = {
+
+               /**
+                * Send the banner history log to the server, with a generated 
unique
+                * log ID. Return a promise that resolves with the logId.
+                *
+                * Note: Should not be called from code in the top RL queue (or 
should
+                * be delayed until the DOM is ready).
+                *
+                * Note: this unique ID must not be stored anywhere on the 
client. It
+                * should be used only within the current browsing session to 
flag when
+                * a banner history is associated with a donation. If a user 
clicks on a
+                * banner to donate, it may be passed on to the WMF's donation 
sites via
+                * a URL parameter. Those sites should never store it on the 
client.
+                *
+                * @returns {jQuery.Promise}
+                */
+               sendLog: function() {
+
+                       var deferred = $.Deferred();
+
+                       mw.loader.using( [
+                               'ext.eventLogging',
+                               'mediawiki.user',
+                               'schema.' + EVENT_LOGGING_SCHEMA
+                       ] ).done( function() {
+
+                               var logId = mw.user.generateRandomSessionId();
+
+                               mw.eventLog.logEvent(
+                                       EVENT_LOGGING_SCHEMA,
+                                       makeEventLoggingData( null, logId )
+                               );
+
+                               deferred.resolve( logId );
+                       } );
+
+                       return deferred.promise();
+               }
+       };
+
+} )( jQuery, mediaWiki );
\ No newline at end of file
diff --git a/resources/subscribing/ext.centralNotice.display.hide.js 
b/resources/subscribing/ext.centralNotice.display.hide.js
index e5abbb2..282eaf1 100644
--- a/resources/subscribing/ext.centralNotice.display.hide.js
+++ b/resources/subscribing/ext.centralNotice.display.hide.js
@@ -8,13 +8,19 @@
                cookieName,
                shouldHide = false,
                reason,
+               reasonCode,
                durations = mw.config.get( 'wgNoticeCookieDurations' ),
 
                HIDE_COOKIE_PREFIX = 'centralnotice_hide_',
 
                REASONS = {
-                       CLOSE: 'close'
+                       CLOSE: new Reason( 'close', 1 )
                };
+
+       function Reason( key, code ) {
+               this.key = key;
+               this.code = code;
+       }
 
        function removeCookie() {
                $.cookie( cookieName, null, { path: '/' } );
@@ -60,6 +66,7 @@
                        if ( now < hideData.created + 
durations[hideData.reason] ) {
                                shouldHide = true;
                                reason = hideData.reason;
+                               reasonCode = hideData.reasonCode || '';
                        }
                },
 
@@ -67,8 +74,12 @@
                        return shouldHide;
                },
 
-               reason: function() {
+               getReason: function() {
                        return reason;
+               },
+
+               getReasonCode: function() {
+                       return reasonCode;
                },
 
                setHideWithCloseButtonCookies: function() {
@@ -77,7 +88,8 @@
                                hideData = {
                                        v: 1,
                                        created: Math.floor( date.getTime() / 
1000 ),
-                                       reason: REASONS.CLOSE
+                                       reason: REASONS.CLOSE.key,
+                                       reasonCode: REASONS.CLOSE.code
                                };
 
                        // Re-use the same date object to set the cookie's 
expiry time
diff --git a/resources/subscribing/ext.centralNotice.display.js 
b/resources/subscribing/ext.centralNotice.display.js
index 5ff5d10..ee53f3c 100644
--- a/resources/subscribing/ext.centralNotice.display.js
+++ b/resources/subscribing/ext.centralNotice.display.js
@@ -285,7 +285,7 @@
                // Check the hide cookie and possibly cancel the banner
                hide.processCookie();
                if ( hide.shouldHide() ) {
-                       cn.cancelBanner( hide.reason() );
+                       state.cancelBanner( hide.getReason(), 
hide.getReasonCode() );
                        runPostBannerMixinHooks();
                        recordImpression();
                        return;
@@ -380,9 +380,11 @@
                 * Call this from the preBannerMixinHook to prevent a banner 
from
                 * being chosen and loaded.
                 * @param {string} reason An explanation of why the banner was 
canceled.
+                * @param {number} reasonCode A code corresponding to this 
reason
+                *   (temporary measure, for use in minified banner history 
log).
                 */
-               cancelBanner: function( reason ) {
-                       cn.internal.state.cancelBanner( reason );
+               cancelBanner: function( reason, reasonCode ) {
+                       cn.internal.state.cancelBanner( reason, reasonCode );
                },
 
                /**
@@ -539,6 +541,29 @@
                                return;
                        }
                        cn.kvStore.logNotAvailableError();
+               },
+
+               /**
+                * Send the banner history log to the server, with a generated 
unique
+                * log ID. Return a promise that resolves with the logId.
+                *
+                * Note: Should not be called from code in the top RL queue (or 
should
+                * be delayed until the DOM is ready).
+                *
+                * Note: this unique ID must not be stored anywhere on the 
client. It
+                * should be used only within the current browsing session to 
flag when
+                * a banner history is associated with a donation. If a user 
clicks on a
+                * banner to donate, it may be passed on to the WMF's donation 
sites via
+                * a URL parameter. Those sites should never store it on the 
client.
+                *
+                * @returns {jQuery.Promise}
+                */
+               sendBannerHistoryLog: function() {
+                       if ( !cn.internal.bannerHistoryLogger ) {
+                               mw.log( 'bannerHistoryLogger not loaded!' );
+                               return;
+                       }
+                       return cn.internal.bannerHistoryLogger.sendLog();
                }
        };
 
diff --git a/resources/subscribing/ext.centralNotice.display.state.js 
b/resources/subscribing/ext.centralNotice.display.state.js
index bb4eaa4..931ce2a 100644
--- a/resources/subscribing/ext.centralNotice.display.state.js
+++ b/resources/subscribing/ext.centralNotice.display.state.js
@@ -8,6 +8,7 @@
                data = {},
                campaign,
                banner,
+               status,
 
                UNKNOWN_COUNTRY_CODE = 'XX',
 
@@ -27,14 +28,19 @@
                },
 
                STATUSES = {
-                       CAMPAIGN_NOT_CHOSEN:      'campaign_not_chosen',
-                       CAMPAIGN_CHOSEN:          'campaign_chosen',
-                       BANNER_CANCELED:          'banner_canceled',
-                       NO_BANNER_AVAILABLE:      'no_banner_available',
-                       BANNER_CHOSEN:            'banner_chosen',
-                       BANNER_LOADED_BUT_HIDDEN: 'banner_loaded_but_hidden',
-                       BANNER_SHOWN:             'banner_shown',
+                       CAMPAIGN_NOT_CHOSEN:      new Status( 
'campaign_not_chosen', 0 ),
+                       CAMPAIGN_CHOSEN:          new Status( 
'campaign_chosen', 1 ),
+                       BANNER_CANCELED:          new Status( 
'banner_canceled', 2 ),
+                       NO_BANNER_AVAILABLE:      new Status( 
'no_banner_available', 3 ),
+                       BANNER_CHOSEN:            new Status( 'banner_chosen', 
4 ),
+                       BANNER_LOADED_BUT_HIDDEN: new Status( 
'banner_loaded_but_hidden', 5 ),
+                       BANNER_SHOWN:             new Status( 'banner_shown', 6 
)
                };
+
+       function Status( key, code ) {
+               this.key = key;
+               this.code = code;
+       }
 
        /**
         * Get a code for the general category the user's device is in.
@@ -113,6 +119,13 @@
                data.testingBanner = true;
        }
 
+       function setStatus( s, reasonCode ) {
+               var reasonCodeStr = reasonCode ? '.' + reasonCode : '';
+               status = s;
+               data.status = s.key;
+               data.statusCode = s.code.toString() + reasonCodeStr;
+       }
+
        /**
         * State object (intended for access from within this RL module)
         */
@@ -136,7 +149,7 @@
 
                setUp: function() {
                        setInitialData();
-                       data.status = STATUSES.CAMPAIGN_NOT_CHOSEN;
+                       setStatus( STATUSES.CAMPAIGN_NOT_CHOSEN );
                },
 
                setUpForTestingBanner: function() {
@@ -147,7 +160,7 @@
 
                        // For testing, we'll set the status to what it 
normally is after
                        // a banner is chosen
-                       data.status = STATUSES.BANNER_CHOSEN;
+                       setStatus( STATUSES.BANNER_CHOSEN );
                },
 
                /**
@@ -183,7 +196,7 @@
 
                        campaign = c;
                        data.campaign = campaign.name;
-                       data.status = STATUSES.CAMPAIGN_CHOSEN;
+                       setStatus( STATUSES.CAMPAIGN_CHOSEN );
 
                        // Set the campaignCategory property if all the banners 
in this
                        // campaign have the same category. This is necessary 
so we can
@@ -215,7 +228,7 @@
                        banner = b;
                        data.banner = banner.name;
                        data.bannerCategory = banner.category;
-                       data.status = STATUSES.BANNER_CHOSEN;
+                       setStatus( STATUSES.BANNER_CHOSEN );
                },
 
                setBucket: function ( bucket ) {
@@ -226,9 +239,9 @@
                        data.bannerNotGuaranteedToDisplay = true;
                },
 
-               cancelBanner: function( reason ) {
+               cancelBanner: function( reason, reasonCode ) {
                        data.bannerCanceledReason = reason;
-                       data.status = STATUSES.BANNER_CANCELED;
+                       setStatus( STATUSES.BANNER_CANCELED, reasonCode );
 
                        // Legacy fields for Special:RecordImpression
                        data.result = 'hide';
@@ -236,20 +249,20 @@
                },
 
                isBannerCanceled: function() {
-                       return data.status === STATUSES.BANNER_CANCELED;
+                       return status === STATUSES.BANNER_CANCELED;
                },
 
                setNoBannerAvailable: function() {
-                       data.status = STATUSES.NO_BANNER_AVAILABLE;
+                       setStatus( STATUSES.NO_BANNER_AVAILABLE );
 
                        // Legacy fields for Special:RecordImpression
                        data.result = 'hide';
                        data.reason = 'empty';
                },
 
-               setBannerLoadedButHidden: function( reason ) {
-                       data.status = STATUSES.BANNER_LOADED_BUT_HIDDEN;
+               setBannerLoadedButHidden: function( reason, reasonCode ) {
                        data.bannerLoadedButHiddenReason = reason;
+                       setStatus( STATUSES.BANNER_LOADED_BUT_HIDDEN, 
reasonCode );
 
                        // Legacy fields for Special:RecordImpression
                        data.result = 'hide';
@@ -261,7 +274,7 @@
                },
 
                setBannerShown: function() {
-                       data.status = STATUSES.BANNER_SHOWN;
+                       setStatus( STATUSES.BANNER_SHOWN );
 
                        // Legacy field for Special:RecordImpression
                        data.result = 'show';
diff --git 
a/resources/subscribing/ext.centralNotice.placeholderCampaignMixin.js 
b/resources/subscribing/ext.centralNotice.placeholderCampaignMixin.js
deleted file mode 100644
index a567a8a..0000000
--- a/resources/subscribing/ext.centralNotice.placeholderCampaignMixin.js
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Placeholder mixin, for smoke testing while working on campaign mixins
- * and related infrastructure. This will be removed before merging to the
- * master branch of the CN repository.
- */
-( function ( $, mw ) {
-
-       var mixin = new mw.centralNotice.Mixin( 'placeholderCampaignMixin' );
-
-       mixin.setPreBannerHandler( function( params ) {
-               mw.log( params );
-       } );
-
-       mixin.setPostBannerHandler( function( params ) {
-               mw.log( params );
-       } );
-
-       mw.centralNotice.registerCampaignMixin( mixin );
-
-} )( jQuery, mediaWiki );
\ No newline at end of file

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

Gerrit-MessageType: merged
Gerrit-Change-Id: I698cc00d08f3cc644516ad837bc09ada8df0cf5b
Gerrit-PatchSet: 13
Gerrit-Project: mediawiki/extensions/CentralNotice
Gerrit-Branch: campaign_mixins
Gerrit-Owner: AndyRussG <[email protected]>
Gerrit-Reviewer: AndyRussG <[email protected]>
Gerrit-Reviewer: Awight <[email protected]>
Gerrit-Reviewer: Cdentinger <[email protected]>
Gerrit-Reviewer: Ejegg <[email protected]>
Gerrit-Reviewer: Siebrand <[email protected]>
Gerrit-Reviewer: Ssmith <[email protected]>
Gerrit-Reviewer: XenoRyet <[email protected]>
Gerrit-Reviewer: jenkins-bot <>

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

Reply via email to