Ejegg has submitted this change and it was merged.

Change subject: Banner diet mixin
......................................................................


Banner diet mixin

Provides the same functionality as [1], but in a mixin.  This allows 
CentralNotice
to avoid loading the banner if it's not going to be displayed, and makes the
configuration knobs available in campaign settings.

[1] 
https://meta.wikimedia.org/wiki/MediaWiki:CentralNotice/Resources/BannerShowHideCountDate.js

TODO:
* In a followup patch, add code which migrates away from cookie to localstorage 
when available.

Bug: T112017
Change-Id: Ia27bd9e164160eab70f6d42bfee50e50640cbaae
---
M CentralNotice.modules.php
M CentralNotice.php
M i18n/en.json
M i18n/qqq.json
A resources/subscribing/ext.centralNotice.impressionDiet.js
5 files changed, 199 insertions(+), 4 deletions(-)

Approvals:
  Ejegg: Looks good to me, approved



diff --git a/CentralNotice.modules.php b/CentralNotice.modules.php
index 4e37b1e..3730b3a 100644
--- a/CentralNotice.modules.php
+++ b/CentralNotice.modules.php
@@ -150,6 +150,16 @@
                'centralnotice-set-record-impression-sample-rate',
                'centralnotice-custom-record-impression-sample-rate',
                'centralnotice-banners-not-guaranteed-to-display',
+
+               // Impression diet
+               'centralnotice-impression-diet-cookie-name',
+               'centralnotice-impression-diet-cookie-name-help',
+               'centralnotice-impression-diet-maximum-seen',
+               'centralnotice-impression-diet-maximum-seen-help',
+               'centralnotice-impression-diet-restart-cycle-delay',
+               'centralnotice-impression-diet-restart-cycle-delay-help',
+               'centralnotice-impression-diet-skip-initial',
+               'centralnotice-impression-diet-skip-initial-help',
        )
 );
 
@@ -232,6 +242,16 @@
        'targets'       => array( 'desktop', 'mobile' ),
 );
 
+$wgResourceModules[ 'ext.centralNotice.impressionDiet' ] = array(
+       'localBasePath' => $dir . '/resources',
+       'remoteExtPath' => 'CentralNotice/resources',
+       'scripts'       => 'subscribing/ext.centralNotice.impressionDiet.js',
+       'dependencies'  => array(
+               'ext.centralNotice.display',
+       ),
+       'targets'       => array( 'desktop', 'mobile' ),
+);
+
 $wgResourceModules[ 'ext.centralNotice.legacySupport' ] = array(
        'localBasePath' => $dir . '/resources',
        'remoteExtPath' => 'CentralNotice/resources',
diff --git a/CentralNotice.php b/CentralNotice.php
index ddc4371..5439675 100644
--- a/CentralNotice.php
+++ b/CentralNotice.php
@@ -289,7 +289,33 @@
                                'labelMsg' => 
'centralnotice-banners-not-guaranteed-to-display'
                        )
                )
-       )
+       ),
+       'impressionDiet' => array(
+               'module' => 'ext.centralNotice.impressionDiet',
+               'nameMsg' => 'centralnotice-impression-diet',
+               'parameters' => array(
+                       'cookieName' => array(
+                               'type' => 'string',
+                               'labelMsg' => 
'centralnotice-impression-diet-cookie-name',
+                               'helpMsg' => 
'centralnotice-impression-diet-cookie-name-help',
+                       ),
+                       'skipInitial' => array(
+                               'type' => 'integer',
+                               'labelMsg' => 
'centralnotice-impression-diet-skip-initial',
+                               'helpMsg' => 
'centralnotice-impression-diet-skip-initial-help',
+                       ),
+                       'maximumSeen' => array(
+                               'type' => 'integer',
+                               'labelMsg' => 
'centralnotice-impression-diet-maximum-seen',
+                               'helpMsg' => 
'centralnotice-impression-diet-maximum-seen-help',
+                       ),
+                       'restartCycleDelay' => array(
+                               'type' => 'integer',
+                               'labelMsg' => 
'centralnotice-impression-diet-restart-cycle-delay',
+                               'helpMsg' => 
'centralnotice-impression-diet-restart-cycle-delay-help',
+                       ),
+               ),
+       ),
 );
 
 /* Setup */
diff --git a/i18n/en.json b/i18n/en.json
index 9441d1f..bb46c4c 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -267,9 +267,18 @@
        "centralnotice-banner-history-logger-max-entry-age-help": "Log entries 
older than this will be expired and will not be sent back to the server.",
        "centralnotice-banner-history-logger-max-entries": "Maximum number of 
entries to keep in log",
        "centralnotice-banner-history-logger-max-entries-help": "If more 
history is recorded before the log is sent to the server, the oldest items will 
disappear.",
-       "centralnotice-legacy-support": "Legacy support",
+       "centralnotice-legacy-support": "Legacy hiding and impression counting 
support",
        "centralnotice-legacy-support-help": "Settings for compatibility with 
older banners that rely on Special:RecordImpression or include JavaScript that 
hides the banner.",
        "centralnotice-set-record-impression-sample-rate": "Set sample rate for 
Special:RecordImpression",
        "centralnotice-custom-record-impression-sample-rate": 
"Special:RecordImpression sample rate (should be between 0 and 1 inclusive)",
-       "centralnotice-banners-not-guaranteed-to-display": "Banners might not 
display, as determined by in-banner JavaScript"
+       "centralnotice-banners-not-guaranteed-to-display": "Banners might not 
display, as determined by in-banner JavaScript",
+       "centralnotice-impression-diet": "Impression diet",
+       "centralnotice-impression-diet-cookie-name": "Hide cookie name",
+       "centralnotice-impression-diet-cookie-name-help": "To share an 
impression counting threshold across campaigns, specify the same cookie name 
for each campaign.  If the cookie name is changed during a campaign, all counts 
to date will be lost.",
+       "centralnotice-impression-diet-maximum-seen": "Maximum impressions any 
individual will see",
+       "centralnotice-impression-diet-maximum-seen-help": "If the cycle is 
configured to restart, this limits the number of impressions seen per cycle 
rather than the total number of impressions seen.",
+       "centralnotice-impression-diet-restart-cycle-delay": "How long to wait 
before restarting the cycle, in seconds (optional)",
+       "centralnotice-impression-diet-restart-cycle-delay-help": "Defaults to 
0, which means readers will experience a single cycle, and then we stop 
displying banners.",
+       "centralnotice-impression-diet-skip-initial": "Number of initial 
impressions to skip (optional)",
+       "centralnotice-impression-diet-skip-initial-help": "Defaults to 0, 
which will not skip any impressions. When the cycle begins, we will skip this 
many potential impressions before displaying a banner."
 }
diff --git a/i18n/qqq.json b/i18n/qqq.json
index 0ffbc04..1748f0b 100644
--- a/i18n/qqq.json
+++ b/i18n/qqq.json
@@ -1,6 +1,7 @@
 {
        "@metadata": {
                "authors": [
+                       "Adamw",
                        "Amire80",
                        "Bennylin",
                        "Darth Kule",
@@ -294,5 +295,14 @@
        "centralnotice-legacy-support-help": "Description of the legacy support 
feature",
        "centralnotice-set-record-impression-sample-rate": "Label for the 
control to activate setting the sample rate for Special:RecordImpression, for 
the legacy support campaign mixin",
        "centralnotice-custom-record-impression-sample-rate": "Label for the 
control to determine the sample rate for Special:RecordImpression, for the 
legacy support campaign mixin",
-       "centralnotice-banners-not-guaranteed-to-display": "Label for the 
control to indicate that banners might not display, for the legacy support 
campaign mixin"
+       "centralnotice-banners-not-guaranteed-to-display": "Label for the 
control to indicate that banners might not display, for the legacy support 
campaign mixin",
+       "centralnotice-impression-diet-cookie-name": "Label for the form 
control to set diet cookie name.",
+       "centralnotice-impression-diet-cookie-name-help": "Help text for form 
control.",
+       "centralnotice-impression-diet-count-group": "Label for the form 
control to set count group.",
+       "centralnotice-impression-diet-count-group-help": "Help text for form 
control.",
+       "centralnotice-impression-diet-maximum-seen": "Label for the form 
control to set maximum seen count.",
+       "centralnotice-impression-diet-maximum-seen-help": "Help text for form 
control.",
+       "centralnotice-impression-diet-restart-cycle-delay": "Label for the 
form control to set cycle restart delay",
+       "centralnotice-impression-diet-skip-initial": "Label for the form 
control to skip initial impressions.",
+       "centralnotice-impression-diet-skip-initial-help": "Help text for form 
control."
 }
diff --git a/resources/subscribing/ext.centralNotice.impressionDiet.js 
b/resources/subscribing/ext.centralNotice.impressionDiet.js
new file mode 100644
index 0000000..9306636
--- /dev/null
+++ b/resources/subscribing/ext.centralNotice.impressionDiet.js
@@ -0,0 +1,130 @@
+/*
+ * Impression diet mixin.  Provides knobs to cap the number of impressions
+ * each user will see.
+
+ * Compatible with 
MediaWiki:CentralNotice/Resources/BannerShowHideCountDate.js,
+ * for the moment.  Campaigns using the onwiki showhide code can be migrated
+ * without donors seeing any extra impressions.
+ *
+ * Determine if a banner should be hidden or displayed based on the
+ * contents of the cookie named mixinParams.cookieName and
+ * mixinParams.cookieName + `-wait`.
+ *
+ * Showing a banner entails that a certain number of impressions have
+ * already occured in a time period.
+ *
+ * Parameter documentation is provided in the CentralNotice campaign 
management UI.
+ *
+ * Banner may be forced if URL parameter force = 1
+ * Counters may be reset if URL parameter reset = 1
+ *
+ * Flow chart: 
https://commons.wikimedia.org/wiki/File:CentralNotice_-_wait_cookie_code_flow.png
+ * (FIXME: update ^^ with new parameter names)
+ */
+( function ( $, mw ) {
+       'use strict';
+
+       var cn = mw.centralNotice,
+               mixin = new cn.Mixin( 'impressionDiet' );
+
+       mixin.setPreBannerHandler( function( mixinParams ) {
+               var counts = getCounts( mixinParams.cookieName ),
+                       forceFlag = mw.util.getParamValue( 'force' ),
+                       hide = null;
+
+               // Compare counts against campaign settings and decide whether 
to show a banner
+
+               var pastDate = counts.waitUntil < new Date().getTime(),
+                       waitForHideImps = counts.waitCount < 
mixinParams.skipInitial,
+                       waitForShowImps = counts.waitSeenCount < 
mixinParams.maximumSeen;
+
+               if ( forceFlag ) {
+                       // URL forced a banner.
+                       hide = false;
+               } else if ( !pastDate ) {
+                       // We're still waiting for the next cycle to begin.
+                       hide = 'waitdate';
+                       counts.waitCount += 1;
+               } else if ( pastDate && waitForHideImps ) {
+                       // We're still skipping initial impressions.
+                       hide = 'waitimps';
+                       counts.waitCount += 1;
+               } else if ( pastDate && !waitForHideImps && waitForShowImps ) {
+                       // Show a banner!
+                       hide = false;
+                       counts.waitSeenCount += 1;
+                       counts.seenCount += 1;
+
+                       if ( counts.waitSeenCount >= mixinParams.maximumSeen ) {
+                               // We just completed a cycle.  Wait to restart.
+                               counts.waitCount = 0;
+                               counts.waitSeenCount = 0;
+                               counts.waitUntil = new Date().getTime() + ( 
mixinParams.restartCycleDelay * 1000 );
+                       }
+               }
+
+               if ( hide === null ) {
+                       // All bets are off!
+                       hide = 'waiterr';
+                       counts.waitCount = 0;
+                       counts.waitSeenCount = 0;
+                       counts.waitUntil = new Date().getTime() + ( 
mixinParams.restartCycleDelay * 1000 );
+               }
+
+               // Hide based on the results.
+               if ( hide ) {
+                       cn.internal.state.cancelBanner( hide );
+               }
+
+               // Bookkeeping.
+               storeCounts( mixinParams.cookieName, counts );
+       } );
+
+       /*
+        * Get running impression counts from cookie storage
+        *
+        * TODO: Deprecate cookies and replace with campaign-linked KV storage.
+        */
+       function getCounts( cookieName ) {
+               // Reset counters on demand
+               if ( mw.util.getParamValue( 'reset' ) === '1' ) {
+                       return {
+                               seenCount: 0,
+                               waitCount: 0,
+                               waitUntil: 0,
+                               waitSeenCount: 0
+                       };
+               }
+
+               // Parse the count cookies, if available.
+               var cookieCount = parseInt( $.cookie( cookieName ) ) || 0,
+                       waitData = ($.cookie( cookieName + '-wait' ) || '' 
).split( /[|]/ );
+
+               return {
+                       /** Total number of impressions seen by this user */
+                       seenCount: cookieCount,
+                       /** This cycle's count of how many impressions we've 
waited for */
+                       waitCount: parseInt( waitData[0] ) || 0,
+                       /** Timestamp (ms) until we can show another banner */
+                       waitUntil: parseInt( waitData[1] ) || 0,
+                       /** Number of impressions seen this cycle */
+                       waitSeenCount: parseInt( waitData[2] ) || 0
+               };
+       }
+
+       /*
+        * Store updated counts
+        *
+        * TODO: Deprecate legacy storage in favor of campaign-linked KV 
storage.
+        */
+       function storeCounts( cookieName, counts ) {
+               var waitData = counts.waitCount + '|' + counts.waitUntil + '|' 
+ counts.waitSeenCount;
+
+               // Finish up and store results
+               $.cookie( cookieName, counts.seenCount, { expires: 365, path: 
'/' } );
+               $.cookie( cookieName + '-wait', waitData, { expires: 365, path: 
'/' } );
+       }
+
+       // Register the mixin
+       cn.registerCampaignMixin( mixin );
+} )( jQuery, mediaWiki );

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

Gerrit-MessageType: merged
Gerrit-Change-Id: Ia27bd9e164160eab70f6d42bfee50e50640cbaae
Gerrit-PatchSet: 19
Gerrit-Project: mediawiki/extensions/CentralNotice
Gerrit-Branch: master
Gerrit-Owner: Awight <[email protected]>
Gerrit-Reviewer: AndyRussG <[email protected]>
Gerrit-Reviewer: Awight <[email protected]>
Gerrit-Reviewer: Cdentinger <[email protected]>
Gerrit-Reviewer: Ejegg <[email protected]>
Gerrit-Reviewer: Pcoombe <[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