jenkins-bot has submitted this change and it was merged.
Change subject: Use BannerChoiceDataProvider in allocation display
......................................................................
Use BannerChoiceDataProvider in allocation display
Move allocation algorithm from BannerChooser into
BannerAllocationCalculator. Calculates both and
hides new allocation with css. TODO: toggle visibility
with checkbox.
Change-Id: Ida4e62ea0ad2434246a923fbcc879b4b50e95489
---
M CentralNotice.hooks.php
A includes/BannerAllocationCalculator.php
M includes/BannerChoiceDataProvider.php
M includes/BannerChooser.php
M modules/ext.centralNotice.adminUi/adminui.common.css
M special/SpecialBannerAllocation.php
6 files changed, 193 insertions(+), 77 deletions(-)
Approvals:
Awight: Looks good to me, approved
jenkins-bot: Verified
diff --git a/CentralNotice.hooks.php b/CentralNotice.hooks.php
index 21e0645..8f5fad9 100644
--- a/CentralNotice.hooks.php
+++ b/CentralNotice.hooks.php
@@ -72,6 +72,7 @@
$wgAutoloadClasses[ 'BannerLoaderException' ] = $specialDir .
'SpecialBannerLoader.php';
$wgAutoloadClasses[ 'Banner' ] = $includeDir . 'Banner.php';
+ $wgAutoloadClasses[ 'BannerAllocationCalculator' ] = $includeDir .
'BannerAllocationCalculator.php';
$wgAutoloadClasses[ 'BannerDataException' ] = $includeDir .
'Banner.php';
$wgAutoloadClasses[ 'BannerContentException' ] = $includeDir .
'Banner.php';
$wgAutoloadClasses[ 'BannerExistenceException' ] = $includeDir .
'Banner.php';
diff --git a/includes/BannerAllocationCalculator.php
b/includes/BannerAllocationCalculator.php
new file mode 100644
index 0000000..7fa88f9
--- /dev/null
+++ b/includes/BannerAllocationCalculator.php
@@ -0,0 +1,123 @@
+<?php
+/**
+ * Wikimedia Foundation
+ *
+ * LICENSE
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+/**
+ * Calculates banner allocation percentages
+ */
+class BannerAllocationCalculator {
+ /**
+ * Calculate banner allocations given a list of banners filtered to a
single
+ * device and bucket
+ *
+ * @param array $banners each banner should have the following keys
+ * 'campaign' is the campaign name
+ * 'campaign_throttle' is the total traffic limit for the campaign
+ * 'weight' is the banner's weight within the campaign
+ * 'campaign_z_index' is the campaign priority
+ * 'allocation' is set by this function to a number between 0 and 1,
+ * indicating the fraction of the time this banner will be chosen.
+ */
+ public static function calculateAllocations( $banners ) {
+ // Normalize banners to a proportion of the total campaign
weight.
+ $campaignTotalWeights = array();
+ foreach ( $banners as $banner ) {
+ if ( empty( $campaignTotalWeights[$banner['campaign']]
) ) {
+ $campaignTotalWeights[$banner['campaign']] = 0;
+ }
+ $campaignTotalWeights[$banner['campaign']] +=
$banner['weight'];
+ }
+ foreach ( $banners as &$banner ) {
+ // Adjust the maximum allocation for the banner
according to
+ // campaign throttle settings. The max_allocation
would be
+ // this banner's allocation if only one campaign were
present.
+ $banner['max_allocation'] = ( $banner['weight'] /
$campaignTotalWeights[$banner['campaign']] )
+ * ( $banner['campaign_throttle'] / 100.0 );
+ }
+
+ // Collect banners by priority level, and determine total
desired
+ // allocation for each level.
+ $priorityTotalAllocations = array();
+ $priorityBanners = array();
+ foreach ( $banners as &$banner ) {
+ $priority = $banner['campaign_z_index'];
+ $priorityBanners[$priority][] = &$banner;
+
+ if ( empty( $priorityTotalAllocations[$priority] ) ) {
+ $priorityTotalAllocations[$priority] = 0;
+ }
+ $priorityTotalAllocations[$priority] +=
$banner['max_allocation'];
+ }
+
+ // Distribute allocation by priority.
+ $remainingAllocation = 1.0;
+ // Order by priority, descending.
+ krsort( $priorityBanners );
+ foreach ( $priorityBanners as $priority => $bannerSet ) {
+ if ( $remainingAllocation <= 0.01 ) {
+ // Don't show banners at lower priority levels
if we've used up
+ // the full 100% already.
+ foreach ( $bannerSet as &$banner ) {
+ $banner['allocation'] = 0;
+ }
+ continue;
+ }
+
+ if ( $priorityTotalAllocations[$priority] >
$remainingAllocation ) {
+ $scaling = $remainingAllocation /
$priorityTotalAllocations[$priority];
+ $remainingAllocation = 0;
+ } else {
+ $scaling = 1;
+ $remainingAllocation -=
$priorityTotalAllocations[$priority];
+ }
+ foreach ( $bannerSet as &$banner ) {
+ $banner['allocation'] =
$banner['max_allocation'] * $scaling;
+ }
+ }
+ return $banners;
+ }
+
+ /**
+ * Allocation helper. Maps an array of campaigns with banners to a
flattened
+ * list of banners, omitting those not in the specified device and
bucket.
+ *
+ * @param array $campaigns campaigns with banners as returned by
+ * @see BannerChoiceDataProvider::getChoicesForCountry
+ * @param string $device target device code
+ * @param integer $bucket target bucket number
+ * @return array banners with properties suitable for
+ * @see BannerAllocationCalculator::calculateAllocations
+ */
+ static function filterAndTransformBanners( $campaigns, $device, $bucket
) {
+ $banners = array();
+ foreach( $campaigns as $campaign ) {
+ foreach ( $campaign['banners'] as $banner ) {
+ if ( !in_array( $device, $banner['devices'] ) )
{
+ continue;
+ }
+ if ( $bucket % $campaign['bucket_count'] !=
$banner['bucket'] ) {
+ continue;
+ }
+ $banner['campaign'] = $campaign['name'];
+ $banner['campaign_throttle'] =
$campaign['throttle'];
+ $banner['campaign_z_index'] =
$campaign['preferred'];
+ $banners[] = $banner;
+ }
+ }
+ return $banners;
+ }
+}
diff --git a/includes/BannerChoiceDataProvider.php
b/includes/BannerChoiceDataProvider.php
index c4aebd3..210d81b 100644
--- a/includes/BannerChoiceDataProvider.php
+++ b/includes/BannerChoiceDataProvider.php
@@ -298,4 +298,24 @@
return $choices;
}
+
+ /**
+ * Gets banner choices filtered by country
+ *
+ * @param string $country Country of interest
+ * @return array An array of the form returned by @see
BannerChoiceDataProvider::getChoices
+ * Omits all geotargetted campaigns not aimed at the given country
+ */
+ public function getChoicesForCountry( $country ) {
+ $choices = $this->getChoices();
+
+ $filteredChoices = array();
+ // Remove campaigns that are geotargetted but not to selected
country
+ foreach( $choices as $campaign ) {
+ if ( !$campaign['geotargetted'] || in_array( $country,
$campaign['countries'] ) ) {
+ $filteredChoices[] = $campaign;
+ }
+ }
+ return $filteredChoices;
+ }
}
diff --git a/includes/BannerChooser.php b/includes/BannerChooser.php
index 0f0dff5..66ec490 100644
--- a/includes/BannerChooser.php
+++ b/includes/BannerChooser.php
@@ -36,7 +36,8 @@
$this->banners = Banner::getCampaignBanners(
$this->campaigns );
}
$this->filterBanners();
- $this->allocate();
+ $this->banners =
BannerAllocationCalculator::calculateAllocations( $this->banners );
+ $this->quantizeAllocationToSlots();
}
/**
@@ -138,70 +139,6 @@
return ( $banner[$key] === $value );
}
);
- }
-
- /**
- * Calculate allocation proportions and store them in the banners.
- */
- protected function allocate() {
- // Normalize banners to a proportion of the total campaign
weight.
- $campaignTotalWeights = array();
- foreach ( $this->banners as $banner ) {
- if ( empty( $campaignTotalWeights[$banner['campaign']]
) ) {
- $campaignTotalWeights[$banner['campaign']] = 0;
- }
- $campaignTotalWeights[$banner['campaign']] +=
$banner['weight'];
- }
- foreach ( $this->banners as &$banner ) {
- // Adjust the maximum allocation for the banner
according to
- // campaign throttle settings. The max_allocation
would be
- // this banner's allocation if only one campaign were
present.
- $banner['max_allocation'] = ( $banner['weight'] /
$campaignTotalWeights[$banner['campaign']] )
- * ( $banner['campaign_throttle'] / 100.0 );
- }
-
- // Collect banners by priority level, and determine total
desired
- // allocation for each level.
- $priorityTotalAllocations = array();
- $priorityBanners = array();
- foreach ( $this->banners as &$banner ) {
- $priorityBanners[$banner['campaign_z_index']][] =
&$banner;
-
- if ( empty(
$priorityTotalAllocations[$banner['campaign_z_index']] ) ) {
-
$priorityTotalAllocations[$banner['campaign_z_index']] = 0;
- }
- $priorityTotalAllocations[$banner['campaign_z_index']]
+= $banner['max_allocation'];
- }
-
- // Distribute allocation by priority.
- $remainingAllocation = 1.0;
- // Order by priority, descending.
- krsort( $priorityBanners );
- foreach ( $priorityBanners as $z_index => $banners ) {
- if ( $remainingAllocation <= 0.01 ) {
- // Don't show banners at lower priority levels
if we've used up
- // the full 100% already.
- foreach ( $banners as &$banner ) {
- $banner[self::ALLOCATION_KEY] = 0;
- }
- continue;
- }
-
- if ( $priorityTotalAllocations[$z_index] >
$remainingAllocation ) {
- $scaling = $remainingAllocation /
$priorityTotalAllocations[$z_index];
- $remainingAllocation = 0;
- } else {
- $scaling = 1;
- $remainingAllocation -=
$priorityTotalAllocations[$z_index];
- }
- foreach ( $banners as &$banner ) {
- $banner[self::ALLOCATION_KEY] =
$banner['max_allocation'] * $scaling;
- }
-
- }
-
- // To be deprecated by continuous allocation:
- $this->quantizeAllocationToSlots();
}
/**
diff --git a/modules/ext.centralNotice.adminUi/adminui.common.css
b/modules/ext.centralNotice.adminUi/adminui.common.css
index e322e4a..e037a8f 100644
--- a/modules/ext.centralNotice.adminUi/adminui.common.css
+++ b/modules/ext.centralNotice.adminUi/adminui.common.css
@@ -71,7 +71,9 @@
#mw-htmlform-banner-list .mw-label {
display: none;
}
-
+tr.mw-centralnotice-row-allocation-calculator, input#alogrithm-selector {
+ display: none;
+}
/* --- Archival --- */
.cn-archived-item {
diff --git a/special/SpecialBannerAllocation.php
b/special/SpecialBannerAllocation.php
index 9b70f87..1c0fb43 100644
--- a/special/SpecialBannerAllocation.php
+++ b/special/SpecialBannerAllocation.php
@@ -203,6 +203,24 @@
// Begin Allocation list fieldset
$htmlOut .= Html::openElement( 'fieldset', array( 'class' =>
'prefsection' ) );
+ // Add a box to select which algorithm's allocation to display
+ $htmlOut .= Html::openElement( 'input', array(
+ 'type' => 'checkbox',
+ 'id' => 'alogrithm-selector'
+ ) );
+
+ // Given our project and language combination, get banner
choice data
+ // for logged in and anonymous users.
+ $login_statuses = array(
+ BannerChoiceDataProvider::ANONYMOUS,
+ BannerChoiceDataProvider::LOGGED_IN
+ );
+
+ foreach ( $login_statuses as $provider_status ) {
+ $provider = new BannerChoiceDataProvider( $project,
$language, $provider_status );
+ $choice_data[$provider_status] =
$provider->getChoicesForCountry( $country );
+ }
+
// Iterate through each possible device type and get allocation
information
$devices = CNDeviceTarget::getAvailableDevices();
foreach( $devices as $device_id => $device_data ) {
@@ -253,13 +271,21 @@
);
if ( $target['anonymous'] === 'true' ) {
$label = $this->msg(
'centralnotice-banner-anonymous' )->text();
+ $provider_campaigns =
$choice_data[BannerChoiceDataProvider::ANONYMOUS];
} else {
$label = $this->msg(
'centralnotice-banner-logged-in' )->text();
+ $provider_campaigns =
$choice_data[BannerChoiceDataProvider::LOGGED_IN];
}
$label .= ' -- ' . $this->msg(
'centralnotice-bucket-letter' )->
rawParams( chr( $target['bucket'] + 65
) )->text();
- $htmlOut .= $this->getTable( $label, $banners );
+ $provider_banners =
BannerAllocationCalculator::filterAndTransformBanners(
+ $provider_campaigns,
+ $device_data['header'],
+ intval( $target['bucket'] )
+ );
+ $provider_banners =
BannerAllocationCalculator::calculateAllocations( $provider_banners );
+ $htmlOut .= $this->getTable( $label, $banners,
$provider_banners );
}
$htmlOut .= Html::closeElement( 'div' );
@@ -274,20 +300,30 @@
/**
* Generate the HTML for an allocation table
* @param $type string The title for the table
- * @param $banners array The banners to list
+ * @param $banners array The banners allocated by BannerChooser
+ * @param $providerBanners array The banners as allocated by
BannerAllocationCalculator
* @return string HTML for the table
*/
- public function getTable( $type, $banners ) {
- $viewCampaign = $this->getTitleFor( 'CentralNotice' );
-
+ public function getTable( $type, $banners, $providerBanners ) {
$htmlOut = Html::openElement( 'table',
array ( 'cellpadding' => 9, 'class' => 'wikitable
sortable', 'style' => 'margin: 1em 0;' )
);
$htmlOut .= Html::element( 'h4', array(), $type );
+ $htmlOut .= $this->createRows( $banners,
'mw-centralnotice-row-banner-chooser' );
+ $htmlOut .= $this->createRows( $providerBanners,
'mw-centralnotice-row-allocation-calculator' );
+
+ $htmlOut .= Html::closeElement( 'table' );
+
+ return $htmlOut;
+ }
+
+ public function createRows( $banners, $rowClass ) {
+ $viewCampaign = $this->getTitleFor( 'CentralNotice' );
+ $htmlOut = '';
if (count($banners) > 0) {
- $htmlOut .= Html::openElement( 'tr' );
+ $htmlOut .= Html::openElement( 'tr', array( 'class' =>
$rowClass ) );
$htmlOut .= Html::element( 'th', array( 'width' => '5%'
),
$this->msg( 'centralnotice-percentage'
)->text() );
$htmlOut .= Html::element( 'th', array( 'width' =>
'30%' ),
@@ -300,7 +336,7 @@
$percentage = sprintf( "%0.2f", round(
$banner['allocation'] * 100, 2 ) );
// Row begin
- $htmlOut .= Html::openElement( 'tr', array(
'class'=>'mw-sp-centralnotice-allocationrow' ) );
+ $htmlOut .= Html::openElement( 'tr', array(
'class'=>'mw-sp-centralnotice-allocationrow ' . $rowClass ) );
// Percentage
$htmlOut .= Html::openElement( 'td', array(
'align' => 'right' ) );
@@ -340,13 +376,10 @@
}
} else {
- $htmlOut .= Html::openElement('tr');
+ $htmlOut .= Html::openElement('tr', array( 'class' =>
$rowClass ) );
$htmlOut .= Html::openElement('td');
$htmlOut .= Xml::tags( 'p', null, $this->msg(
'centralnotice-no-allocation' )->text() );
}
-
- $htmlOut .= Html::closeElement( 'table' );
-
return $htmlOut;
}
}
--
To view, visit https://gerrit.wikimedia.org/r/172914
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: Ida4e62ea0ad2434246a923fbcc879b4b50e95489
Gerrit-PatchSet: 13
Gerrit-Project: mediawiki/extensions/CentralNotice
Gerrit-Branch: master
Gerrit-Owner: Ejegg <[email protected]>
Gerrit-Reviewer: Awight <[email protected]>
Gerrit-Reviewer: Mwalker <[email protected]>
Gerrit-Reviewer: Ssmith <[email protected]>
Gerrit-Reviewer: jenkins-bot <>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits