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
