diff --git a/core/DataTable/Row.php b/core/DataTable/Row.php index 973974033c9600c269ce48aa65d536aebf1bb53a..ad8a850ca06016821ab91b1e6e89a9515fabd096 100644 --- a/core/DataTable/Row.php +++ b/core/DataTable/Row.php @@ -479,7 +479,7 @@ class Row implements \ArrayAccess, \IteratorAggregate } if ($enableCopyMetadata) { - $this->sumRowMetadata($rowToSum); + $this->sumRowMetadata($rowToSum, $aggregationOperations); } } @@ -506,6 +506,19 @@ class Row implements \ArrayAccess, \IteratorAggregate case 'sum': $newValue = $this->sumRowArray($thisColumnValue, $columnToSumValue); break; + case 'uniquearraymerge': + if (is_array($thisColumnValue) && is_array($columnToSumValue)) { + foreach ($columnToSumValue as $columnSum) { + if (!in_array($columnSum, $thisColumnValue)) { + $thisColumnValue[] = $columnSum; + } + } + } elseif (!is_array($thisColumnValue) && is_array($columnToSumValue)) { + $thisColumnValue = $columnToSumValue; + } + + $newValue = $thisColumnValue; + break; default: throw new Exception("Unknown operation '$operation'."); } @@ -516,12 +529,29 @@ class Row implements \ArrayAccess, \IteratorAggregate * Sums the metadata in `$rowToSum` with the metadata in `$this` row. * * @param Row $rowToSum + * @param array $aggregationOperations */ - public function sumRowMetadata($rowToSum) + public function sumRowMetadata($rowToSum, $aggregationOperations = array()) { if (!empty($rowToSum->metadata) && !$this->isSummaryRow() ) { + $aggregatedMetadata = array(); + + if (is_array($aggregationOperations)) { + // we need to aggregate value before value is overwritten by maybe another row + foreach ($aggregationOperations as $columnn => $operation) { + $thisMetadata = $this->getMetadata($columnn); + $sumMetadata = $rowToSum->getMetadata($columnn); + + if ($thisMetadata === false && $sumMetadata === false) { + continue; + } + + $aggregatedMetadata[$columnn] = $this->getColumnValuesMerged($operation, $thisMetadata, $sumMetadata); + } + } + // We shall update metadata, and keep the metadata with the _most visits or pageviews_, rather than first or last seen $visits = max($rowToSum->getColumn(Metrics::INDEX_PAGE_NB_HITS) || $rowToSum->getColumn(Metrics::INDEX_NB_VISITS), // Old format pre-1.2, @see also method doSumVisitsMetrics() @@ -532,6 +562,11 @@ class Row implements \ArrayAccess, \IteratorAggregate $this->maxVisitsSummed = $visits; $this->metadata = $rowToSum->metadata; } + + foreach ($aggregatedMetadata as $column => $value) { + // we need to make sure aggregated value is used, and not metadata from $rowToSum + $this->setMetadata($column, $value); + } } } diff --git a/plugins/CoreAdminHome/Menu.php b/plugins/CoreAdminHome/Menu.php index b4621e2f5e356e4fedc86531c03ade9666e7bd0a..a149baa39981dd131871b805fdfc7554703cc03b 100644 --- a/plugins/CoreAdminHome/Menu.php +++ b/plugins/CoreAdminHome/Menu.php @@ -61,7 +61,7 @@ class Menu extends \Piwik\Plugin\Menu if (!Piwik::isUserIsAnonymous()) { $menu->addManageItem('CoreAdminHome_TrackingCode', $this->urlForAction('trackingCodeGenerator'), - $order = 10); + $order = 20); if (SettingsManager::hasUserPluginsSettingsForCurrentUser()) { $menu->addPersonalItem('CoreAdminHome_PluginSettings', diff --git a/plugins/CustomVariables/Archiver.php b/plugins/CustomVariables/Archiver.php index 4a53ab78df307d527e23b98061385a44224cf1d6..39aa9e6bb735e25196c78d24dccb4c6ab03ae321 100644 --- a/plugins/CustomVariables/Archiver.php +++ b/plugins/CustomVariables/Archiver.php @@ -11,6 +11,7 @@ namespace Piwik\Plugins\CustomVariables; use Piwik\Config; use Piwik\DataAccess\LogAggregator; use Piwik\DataArray; +use Piwik\DataTable; use Piwik\Metrics; use Piwik\Tracker\GoalManager; use Piwik\Tracker; @@ -52,7 +53,7 @@ class Archiver extends \Piwik\Plugin\Archiver public function aggregateMultipleReports() { - $columnsAggregationOperation = null; + $columnsAggregationOperation = array('slots' => 'uniquearraymerge'); $this->getProcessor()->aggregateDataTableRecords( self::CUSTOM_VARIABLE_RECORD_NAME, diff --git a/plugins/CustomVariables/CustomVariables.php b/plugins/CustomVariables/CustomVariables.php index 03238212f160ddfb78e76ddcefb5d8de8a1d2e8f..24525983cdba551dd94cddb02634325d206c60d5 100644 --- a/plugins/CustomVariables/CustomVariables.php +++ b/plugins/CustomVariables/CustomVariables.php @@ -151,7 +151,7 @@ class CustomVariables extends \Piwik\Plugin { $translationKeys[] = 'CustomVariables_CustomVariables'; $translationKeys[] = 'CustomVariables_ManageDescription'; - $translationKeys[] = 'CustomVariables_Scope'; + $translationKeys[] = 'CustomVariables_ScopeX'; $translationKeys[] = 'CustomVariables_Index'; $translationKeys[] = 'CustomVariables_Usages'; $translationKeys[] = 'CustomVariables_Unused'; diff --git a/plugins/CustomVariables/Menu.php b/plugins/CustomVariables/Menu.php index 80e1d78193d471ca114c2682c02e3296d46f8198..b97aa1bb015177c7d7d5831466b6ee028069965a 100644 --- a/plugins/CustomVariables/Menu.php +++ b/plugins/CustomVariables/Menu.php @@ -27,7 +27,7 @@ class Menu extends \Piwik\Plugin\Menu $idSite = Common::getRequestVar('idSite', $default, 'int'); if (Piwik::isUserHasAdminAccess($idSite)) { - $menu->addManageItem('Custom Variables', $this->urlForAction('manage'), $orderId = 30); + $menu->addManageItem('Custom Variables', $this->urlForAction('manage'), $orderId = 15); } } } diff --git a/plugins/CustomVariables/angularjs/manage-custom-vars/manage-custom-vars.controller.js b/plugins/CustomVariables/angularjs/manage-custom-vars/manage-custom-vars.controller.js index 4b6b1c213dc4ef736dc3a992da26e5977a27d193..4cbc8dc3e890cd2a81996247112ec0be49ecb4c5 100644 --- a/plugins/CustomVariables/angularjs/manage-custom-vars/manage-custom-vars.controller.js +++ b/plugins/CustomVariables/angularjs/manage-custom-vars/manage-custom-vars.controller.js @@ -13,23 +13,7 @@ manageCustomVarsModel.fetchUsages(); this.model = manageCustomVarsModel; - this.createCustomVariableSlot = function () { - var highestIndex = 5; - angular.forEach(manageCustomVarsModel.customVariables, function (customVar) { - if (customVar.index > highestIndex) { - highestIndex = customVar.index; - } - }); - - var translate = $filter('translate'); - - var command = './console customvariables:set-max-custom-variables ' + (highestIndex + 1); - var text = translate('CustomVariables_CreatingCustomVariableTakesTime'); - text += '<br /><br />' + translate('CustomVariables_CurrentAvailableCustomVariables', '<strong>' + highestIndex + '</strong>'); - text += '<br /><br />' + translate('CustomVariables_ToCreateCustomVarExecute'); - text += '<br /><br /><code>' + command + '</code>'; - - piwik.helper.modalConfirm('<div class="ui-confirm" title="' + translate('CustomVariables_CreateNewSlot') + '">' + text + '<br /><br /></div>'); - } + this.siteName = piwik.siteName; + this.scopes = ['visit', 'page']; } })(); \ No newline at end of file diff --git a/plugins/CustomVariables/angularjs/manage-custom-vars/manage-custom-vars.directive.html b/plugins/CustomVariables/angularjs/manage-custom-vars/manage-custom-vars.directive.html index 8cc7c60e600648391e079f8ebf75658b0bfc6238..2870295847705590d6b7101fbdbcdd9ab3732555 100644 --- a/plugins/CustomVariables/angularjs/manage-custom-vars/manage-custom-vars.directive.html +++ b/plugins/CustomVariables/angularjs/manage-custom-vars/manage-custom-vars.directive.html @@ -1,41 +1,50 @@ <div class="manageCustomVars"> - <h2 piwik-enriched-headline>{{ 'CustomVariables_CustomVariables'|translate }}</h2> + <h2 piwik-enriched-headline help-url="http://piwik.org/docs/custom-variables/">{{ 'CustomVariables_CustomVariables'|translate }}</h2> - {{ 'CustomVariables_ManageDescription'|translate }} + <p> + <span ng-bind-html="'CustomVariables_ManageDescription'|translate:manageCustomVars.siteName"></span> + </p> - <br /> + <div ng-repeat="scope in manageCustomVars.scopes"> + <h2 class="secondary">{{ 'CustomVariables_ScopeX'|translate:(scope|ucfirst) }}</h2> + <table class="dataTable entityTable"> + <thead> + <tr> + <th>{{'CustomVariables_Index'|translate }}</th> + <th>{{'CustomVariables_Usages'|translate }}</th> + </tr> + </thead> + <tbody> + <tr> + <td colspan="3" ng-show="manageCustomVars.model.isLoading">{{ 'General_Loading'|translate }}</td> + </tr> + <tr ng-repeat="customVariables in manageCustomVars.model.customVariables|filter:{scope: scope}"> + <td class="index">{{ customVariables.index }}</td> + <td> + <span ng-show="(customVariables.usages|length) === 0" + class="unused">{{'CustomVariables_Unused'|translate }}</span> + <span ng-show="customVariables.usages|length" ng-repeat="cvar in customVariables.usages|orderBy:'-nb_actions'"> + <span title="{{ 'CustomVariables_UsageDetails'|translate:(cvar.nb_visits ? cvar.nb_visits : 0):(cvar.nb_actions ? cvar.nb_actions : 0) }}">{{ cvar.name }}</span><span ng-show="!$last">, </span> + </span> + </td> + </tr> + </tbody> + </table> + </div> - <h3>{{ customVariables.scope }}</h3> - <table class="dataTable entityTable" style="max-width: 900px;"> - <thead> - <tr> - <th>{{'CustomVariables_Scope'|translate }}</th> - <th>{{'CustomVariables_Index'|translate }}</th> - <th>{{'CustomVariables_Usages'|translate }}</th> - </tr> - </thead> - <tbody> - <tr> - <td colspan="3" ng-show="manageCustomVars.model.isLoading">{{ 'General_Loading'|translate }}</td> - </tr> - <tr ng-repeat="customVariables in manageCustomVars.model.customVariables"> - <td class="scope">{{ customVariables.scope|ucfirst }}</td> - <td class="index">{{ customVariables.index }}</td> - <td> - <span ng-show="(customVariables.usages|length) === 0" - class="unused">{{'CustomVariables_Unused'|translate }}</span> - <span ng-show="customVariables.usages|length" ng-repeat="cvar in customVariables.usages|orderBy:'-nb_actions'"> - <span title="{{ 'CustomVariables_UsageDetails'|translate:cvar.nb_visits:cvar.nb_actions }}">{{ cvar.name }}</span><span ng-show="!$last">, </span> - </span> - </td> - </tr> - </tbody> - </table> + <h2 class="secondary" ng-show="!manageCustomVars.model.isLoading">{{ 'CustomVariables_CreateNewSlot'|translate }}</h2> - <br/> - <a class="btn addCustomVar" ng-show="!manageCustomVars.model.isLoading" value="" - ng-click="manageCustomVars.createCustomVariableSlot()" - >{{ 'CustomVariables_CreateNewSlot'|translate }} <span class="icon-info"></span></a><br/> + <p ng-show="!manageCustomVars.model.isLoading"> + {{ 'CustomVariables_CreatingCustomVariableTakesTime'|translate }} + <br /><br /> + <span ng-bind-html="'CustomVariables_CurrentAvailableCustomVariables'|translate:('<strong>'+manageCustomVars.model.numSlotsAvailable+'</strong>')"></span> + <br /> + <br /> + {{ 'CustomVariables_ToCreateCustomVarExecute'|translate }} + <br /> + <br /> + <code>./console customvariables:set-max-custom-variables {{ manageCustomVars.model.numSlotsAvailable + 1 }}</code> + </p> </div> \ No newline at end of file diff --git a/plugins/CustomVariables/angularjs/manage-custom-vars/manage-custom-vars.directive.less b/plugins/CustomVariables/angularjs/manage-custom-vars/manage-custom-vars.directive.less index 2d3e4e4f04f311774d7a0e418695dd69f1b7e3a7..3a37f84a43d59bc8d8d2f07f4814086a4341afbd 100644 --- a/plugins/CustomVariables/angularjs/manage-custom-vars/manage-custom-vars.directive.less +++ b/plugins/CustomVariables/angularjs/manage-custom-vars/manage-custom-vars.directive.less @@ -3,6 +3,10 @@ color: @color-silver; } + table, p { + width: 900px; + } + .scope, .index { width: 90px; max-width: 90px; diff --git a/plugins/CustomVariables/angularjs/manage-custom-vars/manage-custom-vars.model.js b/plugins/CustomVariables/angularjs/manage-custom-vars/manage-custom-vars.model.js index 3cc5b3d4b27960779c21a07b6db1cf6b8f30dbc9..87d3f92fe612b7714bf1f2e7723237eea1972c2e 100644 --- a/plugins/CustomVariables/angularjs/manage-custom-vars/manage-custom-vars.model.js +++ b/plugins/CustomVariables/angularjs/manage-custom-vars/manage-custom-vars.model.js @@ -15,7 +15,8 @@ customVariables : [], extractions : [], isLoading: false, - fetchUsages: fetchUsages + fetchUsages: fetchUsages, + numSlotsAvailable: 5, }; return model; @@ -27,6 +28,13 @@ piwikApi.fetch({method: 'CustomVariables.getUsagesOfSlots'}) .then(function (customVariables) { model.customVariables = customVariables; + + angular.forEach(customVariables, function (customVar) { + if (customVar.index > model.numSlotsAvailable) { + model.numSlotsAvailable = customVar.index; + } + }); + })['finally'](function () { // .finally() is not IE8 compatible see https://github.com/angular/angular.js/commit/f078762d48d0d5d9796dcdf2cb0241198677582c model.isLoading = false; }); diff --git a/plugins/CustomVariables/lang/en.json b/plugins/CustomVariables/lang/en.json index 61033b2757591e969285908364009f900b4ce00a..810cd197028afce2e0e54450d783ea33369ee970 100644 --- a/plugins/CustomVariables/lang/en.json +++ b/plugins/CustomVariables/lang/en.json @@ -7,12 +7,12 @@ "PluginDescription": "Custom Variables are (name, value) pairs that you can assign using the Javascript API to visitors or any of their action. Piwik will then report how many visits, pages, conversions for each of these custom names and values. View the detailed Custom Variables for each user and action in the Visitor Log.<br />Required to use <a href=\"http://piwik.org/docs/ecommerce-analytics/\">Ecommerce Analytics</a> feature!", "ScopePage": "scope page", "ScopeVisit": "scope visit", - "ManageDescription": "This overview shows all custom variable slots and their usages. The names within each slot are ordered by how often they were used in total.", - "Scope": "Scope", + "ManageDescription": "This overview shows all custom variable slots and their usages for website '%s'. The names within each slot are ordered by how often they were used in total.", + "ScopeX": "Scope %s", "Index": "Index", "Usages": "Usages", "Unused": "Unused", - "CreateNewSlot": "Create a new Custom Variable slot", + "CreateNewSlot": "Increase the number of available Custom Variables slots", "UsageDetails": "%s visits and %s actions since creation of this website.", "CreatingCustomVariableTakesTime": "Creating a new custom variable slot can take a long time depending on the size of your database. Therefore it is only possible to do this via a command which needs to be executed on the command line.", "CurrentAvailableCustomVariables": "Currently you can use up to %s Custom Variables per site.", diff --git a/plugins/CustomVariables/tests/UI/CustomVariables_spec.js b/plugins/CustomVariables/tests/UI/CustomVariables_spec.js index a64a1cd7ce8349cf64a968a22b4f0ccbd5fa05c2..1e247fca56ec5664eaf7781dddbec0015013a1ae 100644 --- a/plugins/CustomVariables/tests/UI/CustomVariables_spec.js +++ b/plugins/CustomVariables/tests/UI/CustomVariables_spec.js @@ -22,10 +22,4 @@ describe("CustomVariables", function () { expect.screenshot('link_in_menu').to.be.captureSelector('li:contains(Manage)', function (page) { }, done); }); - - it('should show custom variable creation command in dialog', function (done) { - expect.screenshot('create').to.be.captureSelector('.ui-dialog', function (page) { - page.click('.manageCustomVars .addCustomVar') - }, done); - }); }); \ No newline at end of file diff --git a/plugins/CustomVariables/tests/UI/expected-ui-screenshots/CustomVariables_create.png b/plugins/CustomVariables/tests/UI/expected-ui-screenshots/CustomVariables_create.png deleted file mode 100644 index ccd46cfa9b282160d74b6e3a68f096ca63a02d21..0000000000000000000000000000000000000000 Binary files a/plugins/CustomVariables/tests/UI/expected-ui-screenshots/CustomVariables_create.png and /dev/null differ diff --git a/plugins/CustomVariables/tests/UI/expected-ui-screenshots/CustomVariables_manage.png b/plugins/CustomVariables/tests/UI/expected-ui-screenshots/CustomVariables_manage.png index c520cde1bf581d24ea6dd7993c69e6ba9dfe6a20..c03b7e379504e7703ca6ba08ff1dcb19094e6a45 100644 Binary files a/plugins/CustomVariables/tests/UI/expected-ui-screenshots/CustomVariables_manage.png and b/plugins/CustomVariables/tests/UI/expected-ui-screenshots/CustomVariables_manage.png differ diff --git a/tests/PHPUnit/Unit/DataTable/RowTest.php b/tests/PHPUnit/Unit/DataTable/RowTest.php index e18806d2d55069e75db48f1acd37100fdcaf0fee..bc9c49bb845174b39cab97f634ebe69c44a7f468 100644 --- a/tests/PHPUnit/Unit/DataTable/RowTest.php +++ b/tests/PHPUnit/Unit/DataTable/RowTest.php @@ -362,6 +362,65 @@ class RowTest extends \PHPUnit_Framework_TestCase $this->assertTrue($this->row->hasColumn('test')); } + public function test_sumRowMetadata_shouldSumMetadataAccordingToAggregationOperations() + { + $this->row->setColumn('nb_visits', 10); + $this->row->setMetadata('my_sum', 5); + $this->row->setMetadata('my_max', 4); + $this->row->setMetadata('my_array', array(array('test' => 1, 'value' => 1), array('test' => 2, 'value' => 2))); + + + $row = $this->getTestRowWithNoSubDataTable(); + $row->setColumn('nb_visits', 15); + $row->setMetadata('my_sum', 7); + $row->setMetadata('my_max', 2); + $row->setMetadata('my_array', array(array('test' => 3, 'value' => 3), array('test' => 2, 'value' => 2))); + + + $aggregations = array( + 'nosuchcolumn' => 'max', // this metadata name does not exist and should be ignored + 'my_sum' => 'sum', + 'my_max' => 'max', + 'my_array' => 'uniquearraymerge' + ); + $this->row->sumRowMetadata($row, $aggregations); + + $metadata = $this->row->getMetadata(); + $expected = array( + 'my_sum' => 12, + 'my_max' => 4, + 'my_array' => array(array('test' => 1, 'value' => 1), array('test' => 2, 'value' => 2), array('test' => 3, 'value' => 3)) + ); + $this->assertSame($expected, $metadata); + } + + public function test_sumRowMetadata_uniquearraymergeShouldUseArrayFromOtherRow_IfNoMetadataForThisRowSpecified() + { + $row = $this->getTestRowWithNoSubDataTable(); + $arrayValue = array(array('test' => 3, 'value' => 3), array('test' => 2, 'value' => 2)); + $row->setMetadata('my_array', $arrayValue); + + $aggregations = array('my_array' => 'uniquearraymerge'); + + $this->row->sumRowMetadata($row, $aggregations); + + $this->assertSame(array('my_array' => $arrayValue), $this->row->getMetadata()); + } + + public function test_sumRowMetadata_uniquearraymergeShouldUseArrayFromThisRow_IfNoMetadataForOtherRowSpecified() + { + $row = $this->getTestRowWithNoSubDataTable(); + + $arrayValue = array(array('test' => 3, 'value' => 3), array('test' => 2, 'value' => 2)); + $this->row->setMetadata('my_array', $arrayValue); + + $aggregations = array('my_array' => 'uniquearraymerge'); + + $this->row->sumRowMetadata($row, $aggregations); + + $this->assertSame(array('my_array' => $arrayValue), $this->row->getMetadata()); + } + private function assertColumnSavesValue($expectedValue, $columnName, $valueToSet) { $this->row->setColumn($columnName, $valueToSet);