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

Reply via email to