Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/matomo-org/matomo.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Steur <thomas.steur@gmail.com>2015-04-08 08:00:41 +0300
committerThomas Steur <thomas.steur@gmail.com>2015-04-21 06:20:26 +0300
commit04b931a2e03d0ac2d157bc35c7b8e6f20cd0624b (patch)
tree711ae97dac7a8810605bed20a2bef67efd5ed2b7
parent1cfdd56b16ced85c96eadb60d71b9156b4ae0a65 (diff)
improve performance of all websites dashboard when having thousands of websites
-rw-r--r--CHANGELOG.md1
-rw-r--r--core/Archive.php28
-rw-r--r--core/Archive/DataCollection.php43
-rw-r--r--core/Archive/DataTableFactory.php207
-rw-r--r--core/DataAccess/ArchiveSelector.php14
-rw-r--r--core/DataTable.php18
-rw-r--r--core/DataTable/Filter/ColumnCallbackAddMetadata.php54
-rw-r--r--core/DataTable/Manager.php10
-rw-r--r--core/Site.php69
-rw-r--r--lang/en.json2
-rw-r--r--plugins/MultiSites/.gitignore1
-rwxr-xr-xplugins/MultiSites/API.php167
-rw-r--r--plugins/MultiSites/Columns/Metrics/EcommerceOnlyEvolutionMetric.php12
-rw-r--r--plugins/MultiSites/Controller.php33
-rw-r--r--plugins/MultiSites/Dashboard.php294
-rw-r--r--plugins/MultiSites/MultiSites.php2
-rw-r--r--plugins/MultiSites/angularjs/dashboard/dashboard-group.filter.js67
-rw-r--r--plugins/MultiSites/angularjs/dashboard/dashboard-model.service.js290
-rw-r--r--plugins/MultiSites/angularjs/dashboard/dashboard.controller.js16
-rw-r--r--plugins/MultiSites/angularjs/dashboard/dashboard.directive.html39
-rw-r--r--plugins/MultiSites/angularjs/dashboard/dashboard.directive.less1
-rw-r--r--plugins/MultiSites/tests/Fixtures/ManySitesWithVisits.php83
-rw-r--r--plugins/MultiSites/tests/Integration/ControllerTest.php126
-rw-r--r--plugins/MultiSites/tests/Integration/DashboardTest.php401
-rw-r--r--tests/PHPUnit/Integration/Archive/DataTableFactoryTest.php360
-rw-r--r--tests/PHPUnit/System/expected/test_BackwardsCompatibility1XTest__MultiSites.getAll_day.xml4
-rw-r--r--tests/PHPUnit/System/expected/test_ImportLogs__MultiSites.getAll_month.xml4
-rw-r--r--tests/PHPUnit/System/expected/test_ImportLogs_withEnhancedAndLast7__MultiSites.getAll_month.xml50
-rw-r--r--tests/PHPUnit/System/expected/test_OneVisitorTwoVisits__MultiSites.getAll_day.xml2
-rw-r--r--tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions_MultiSites.getAll_firstSite_lastN__API.getProcessedReport_day.xml16
-rw-r--r--tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions__MultiSites.getAll_day.xml16
-rw-r--r--tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions__MultiSites.getAll_month.xml4
-rw-r--r--tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions__MultiSites.getAll_week.xml8
-rw-r--r--tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions__MultiSites.getAll_year.xml4
-rw-r--r--tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions_idSiteOne___MultiSites.getAll_day.xml16
-rw-r--r--tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions_idSiteOne___MultiSites.getAll_month.xml4
-rw-r--r--tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions_idSiteOne___MultiSites.getAll_week.xml8
-rw-r--r--tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions_idSiteOne___MultiSites.getAll_year.xml4
-rw-r--r--tests/PHPUnit/System/expected/test_oneVisitor_oneWebsite_severalDays_DateRange_IndexedByDate__MultiSites.getAll_day.xml4
-rw-r--r--tests/PHPUnit/System/expected/test_oneVisitor_oneWebsite_severalDays_DateRange_MultipleDatesNotSupported__MultiSites.getAll_day.xml10
-rw-r--r--tests/PHPUnit/System/expected/test_oneVisitor_oneWebsite_severalDays_DateRange__MultiSites.getAll_range.xml4
-rw-r--r--tests/PHPUnit/Unit/Archive/DataCollectionTest.php284
-rw-r--r--tests/UI/specs/MultiSites_spec.js47
-rw-r--r--tests/UI/specs/UIIntegration_spec.js6
44 files changed, 2187 insertions, 646 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ce013f131c..382fd9d4d5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@ This is a changelog for Piwik platform developers. All changes for our HTTP API'
### Breaking Changes
* The API method `Live.getLastVisitsDetails` does no longer support the API parameter `filter_sort_column` to prevent possible memory issues when `filter_offset` is large.
+* The Event `Site.setSite` was removed as it causes performance problems.
### New commands
* There is now a `diagnostic:run` command to run the system check from the command line.
diff --git a/core/Archive.php b/core/Archive.php
index 5178718e73..8026fc0f5b 100644
--- a/core/Archive.php
+++ b/core/Archive.php
@@ -357,6 +357,20 @@ class Archive
}
/**
+ * Similar to {@link getDataTableFromNumeric()} but merges all children on the created DataTable.
+ *
+ * This is the same as doing `$this->getDataTableFromNumeric()->mergeChildren()` but this way it is much faster.
+ *
+ * @return DataTable|DataTable\Map
+ */
+ public function getDataTableFromNumericAndMergeChildren($names)
+ {
+ $data = $this->get($names, 'numeric');
+ $resultIndexes = $this->getResultIndices();
+ return $data->getMergedDataTable($resultIndexes);
+ }
+
+ /**
* Queries and returns one or more reports as DataTable instances.
*
* This method will query blob data that is a serialized array of of {@link DataTable\Row}'s and
@@ -616,21 +630,19 @@ class Archive
$archiveData = ArchiveSelector::getArchiveData($archiveIds, $archiveNames, $archiveDataType, $idSubtable);
+ $isNumeric = $archiveDataType == 'numeric';
+
foreach ($archiveData as $row) {
// values are grouped by idsite (site ID), date1-date2 (date range), then name (field name)
- $idSite = $row['idsite'];
- $periodStr = $row['date1'] . "," . $row['date2'];
+ $periodStr = $row['date1'] . ',' . $row['date2'];
- if ($archiveDataType == 'numeric') {
+ if ($isNumeric) {
$row['value'] = $this->formatNumericValue($row['value']);
} else {
- $result->addMetadata($idSite, $periodStr, 'ts_archived', $row['ts_archived']);
+ $result->addMetadata($row['idsite'], $periodStr, 'ts_archived', $row['ts_archived']);
}
- $resultRow = & $result->get($idSite, $periodStr);
-
- // one blob per datatable or subtable
- $resultRow[$row['name']] = $row['value'];
+ $result->set($row['idsite'], $periodStr, $row['name'], $row['value']);
}
return $result;
diff --git a/core/Archive/DataCollection.php b/core/Archive/DataCollection.php
index efd63cd925..7973d01481 100644
--- a/core/Archive/DataCollection.php
+++ b/core/Archive/DataCollection.php
@@ -138,6 +138,21 @@ class DataCollection
}
/**
+ * Set data for a specific site & period. If there is no data for the given site ID & period,
+ * it is set to the default row.
+ *
+ * @param int $idSite
+ * @param string $period eg, '2012-01-01,2012-01-31'
+ * @param string $name eg 'nb_visits'
+ * @param string $value eg 5
+ */
+ public function set($idSite, $period, $name, $value)
+ {
+ $row = & $this->get($idSite, $period);
+ $row[$name] = $value;
+ }
+
+ /**
* Adds a new metadata to the data for specific site & period. If there is no
* data for the given site ID & period, it is set to the default row.
*
@@ -214,6 +229,23 @@ class DataCollection
}
/**
+ * See {@link DataTableFactory::makeMerged()}
+ *
+ * @param array $resultIndices
+ * @return DataTable|DataTable\Map
+ * @throws Exception
+ */
+ public function getMergedDataTable($resultIndices)
+ {
+ $dataTableFactory = new DataTableFactory(
+ $this->dataNames, $this->dataType, $this->sitesId, $this->periods, $this->defaultRow);
+
+ $index = $this->getIndexedArray($resultIndices);
+
+ return $dataTableFactory->makeMerged($index, $resultIndices);
+ }
+
+ /**
* Returns archive data as a DataTable indexed by metadata. Indexed data will
* be represented by Map instances. Each DataTable will have
* its subtable IDs set.
@@ -303,9 +335,14 @@ class DataCollection
$indexKeyValues = array_keys($this->periods);
}
- foreach ($indexKeyValues as $key) {
- $result[$key] = $this->createOrderedIndex($metadataNamesToIndexBy);
+ if (empty($metadataNamesToIndexBy)) {
+ $result = array_fill_keys($indexKeyValues, array());
+ } else {
+ foreach ($indexKeyValues as $key) {
+ $result[$key] = $this->createOrderedIndex($metadataNamesToIndexBy);
+ }
}
+
}
return $result;
@@ -321,7 +358,7 @@ class DataCollection
foreach ($metadataNamesToIndexBy as $metadataName) {
if ($metadataName == DataTableFactory::TABLE_METADATA_SITE_INDEX) {
$key = $idSite;
- } else if ($metadataName == DataTableFactory::TABLE_METADATA_PERIOD_INDEX) {
+ } elseif ($metadataName == DataTableFactory::TABLE_METADATA_PERIOD_INDEX) {
$key = $period;
} else {
$key = $row[self::METADATA_CONTAINER_ROW_KEY][$metadataName];
diff --git a/core/Archive/DataTableFactory.php b/core/Archive/DataTableFactory.php
index cfc1937813..36e985e6dc 100644
--- a/core/Archive/DataTableFactory.php
+++ b/core/Archive/DataTableFactory.php
@@ -146,6 +146,11 @@ class DataTableFactory
$this->idSubtable = $idSubtable;
}
+ private function isNumericDataType()
+ {
+ return $this->dataType == 'numeric';
+ }
+
/**
* Creates a DataTable|Set instance using an index of
* archive data.
@@ -157,21 +162,128 @@ class DataTableFactory
*/
public function make($index, $resultIndices)
{
+ $keyMetadata = $this->getDefaultMetadata();
+
if (empty($resultIndices)) {
// for numeric data, if there's no index (and thus only 1 site & period in the query),
// we want to display every queried metric name
if (empty($index)
- && $this->dataType == 'numeric'
+ && $this->isNumericDataType()
) {
$index = $this->defaultRow;
}
- $dataTable = $this->createDataTable($index, $keyMetadata = array());
+ $dataTable = $this->createDataTable($index, $keyMetadata);
} else {
- $dataTable = $this->createDataTableMapFromIndex($index, $resultIndices, $keyMetadata = array());
+ $dataTable = $this->createDataTableMapFromIndex($index, $resultIndices, $keyMetadata);
+ }
+
+ return $dataTable;
+ }
+
+ /**
+ * Creates a merged DataTable|Map instance using an index of archive data similar to {@link make()}.
+ *
+ * Whereas {@link make()} creates a Map for each result index (period and|or site), this will only create a Map
+ * for a period result index and move all site related indices into one dataTable. This is the same as doing
+ * `$dataTableFactory->make()->mergeChildren()` just much faster. It is mainly useful for reports across many sites
+ * eg `MultiSites.getAll`. Was done as part of https://github.com/piwik/piwik/issues/6809
+ *
+ * @param array $index @see DataCollection
+ * @param array $resultIndices an array mapping metadata names with pretty metadata labels.
+ *
+ * @return DataTable|DataTable\Map
+ * @throws \Exception
+ */
+ public function makeMerged($index, $resultIndices)
+ {
+ if (!$this->isNumericDataType()) {
+ throw new \Exception('This method is supposed to work with non-numeric data types but it is not tested. To use it, remove this exception and write tests to be sure it works.');
+ }
+
+ $metadata = array(DataTableFactory::TABLE_METADATA_PERIOD_INDEX => reset($this->periods));
+
+ $firstIdSite = reset($this->sitesId);
+ $isNumeric = $this->isNumericDataType();
+ $numResultIndices = count($resultIndices);
+
+ $firstResultIndex = null;
+ if ($numResultIndices >= 1) {
+ reset($resultIndices);
+ $firstResultIndex = key($resultIndices);
+ }
+
+ $useSimpleDataTable = !array_key_exists(self::TABLE_METADATA_SITE_INDEX, $resultIndices) && $isNumeric;
+
+ if ($numResultIndices === 1 && $firstResultIndex === self::TABLE_METADATA_PERIOD_INDEX) {
+ $index = array($firstIdSite => $index);
+ $numResultIndices = 2;
+ } elseif ($numResultIndices === 0) {
+ $index = array($firstIdSite => $index);
}
- $this->transformMetadata($dataTable);
+ $defaultRow = $this->defaultRow;
+
+ if ($numResultIndices === 2) {
+ $tables = array();
+
+ $dataTable = new DataTable\Map();
+ $dataTable->setKeyName($resultIndices[self::TABLE_METADATA_PERIOD_INDEX]);
+
+ foreach ($this->periods as $range => $period) {
+ $metadata[self::TABLE_METADATA_PERIOD_INDEX] = $period;
+ if ($useSimpleDataTable) {
+ $table = new DataTable\Simple();
+ } else {
+ $table = new DataTable();
+ }
+
+ $table->setAllTableMetadata($metadata);
+ $dataTable->addTable($table, $this->prettifyIndexLabel(self::TABLE_METADATA_PERIOD_INDEX, $range));
+
+ $tables[$range] = $table;
+ }
+
+ foreach ($index as $idsite => $table) {
+ $rowMeta = array('idsite' => $idsite);
+
+ foreach ($table as $range => $row) {
+ if (!empty($row)) {
+ $tables[$range]->addRow(new Row(array(
+ Row::COLUMNS => $row,
+ Row::METADATA => $rowMeta)
+ ));
+ } elseif ($isNumeric) {
+ $tables[$range]->addRow(new Row(array(
+ Row::COLUMNS => $defaultRow,
+ Row::METADATA => $rowMeta)
+ ));
+ }
+ }
+ }
+
+ } else {
+ if ($useSimpleDataTable) {
+ $dataTable = new DataTable\Simple();
+ } else {
+ $dataTable = new DataTable();
+ }
+ $dataTable->setAllTableMetadata($metadata);
+
+ foreach ($index as $idsite => $row) {
+ if (!empty($row)) {
+ $dataTable->addRow(new Row(array(
+ Row::COLUMNS => $row,
+ Row::METADATA => array('idsite' => $idsite))
+ ));
+ } elseif ($isNumeric) {
+ $dataTable->addRow(new Row(array(
+ Row::COLUMNS => $defaultRow,
+ Row::METADATA => array('idsite' => $idsite))
+ ));
+ }
+ }
+ }
return $dataTable;
}
@@ -190,16 +302,16 @@ class DataTableFactory
* @param array $blobRow
* @return DataTable|DataTable\Map
*/
- private function makeFromBlobRow($blobRow)
+ private function makeFromBlobRow($blobRow, $keyMetadata)
{
if ($blobRow === false) {
return new DataTable();
}
if (count($this->dataNames) === 1) {
- return $this->makeDataTableFromSingleBlob($blobRow);
+ return $this->makeDataTableFromSingleBlob($blobRow, $keyMetadata);
} else {
- return $this->makeIndexedByRecordNameDataTable($blobRow);
+ return $this->makeIndexedByRecordNameDataTable($blobRow, $keyMetadata);
}
}
@@ -211,7 +323,7 @@ class DataTableFactory
* @param array $blobRow
* @return DataTable
*/
- private function makeDataTableFromSingleBlob($blobRow)
+ private function makeDataTableFromSingleBlob($blobRow, $keyMetadata)
{
$recordName = reset($this->dataNames);
if ($this->idSubtable !== null) {
@@ -225,7 +337,7 @@ class DataTableFactory
}
// set table metadata
- $table->setMetadataValues(DataCollection::getDataRowMetadata($blobRow));
+ $table->setAllTableMetadata(array_merge(DataCollection::getDataRowMetadata($blobRow), $keyMetadata));
if ($this->expandDataTable) {
$table->enableRecursiveFilters();
@@ -242,12 +354,12 @@ class DataTableFactory
* @param array $blobRow
* @return DataTable\Map
*/
- private function makeIndexedByRecordNameDataTable($blobRow)
+ private function makeIndexedByRecordNameDataTable($blobRow, $keyMetadata)
{
$table = new DataTable\Map();
$table->setKeyName('recordName');
- $tableMetadata = DataCollection::getDataRowMetadata($blobRow);
+ $tableMetadata = array_merge(DataCollection::getDataRowMetadata($blobRow), $keyMetadata);
foreach ($blobRow as $name => $blob) {
$newTable = DataTable::fromSerializedArray($blob);
@@ -267,23 +379,26 @@ class DataTableFactory
* @param array $keyMetadata The metadata to add to the table when it's created.
* @return DataTable\Map
*/
- private function createDataTableMapFromIndex($index, $resultIndices, $keyMetadata = array())
+ private function createDataTableMapFromIndex($index, $resultIndices, $keyMetadata)
{
- $resultIndexLabel = reset($resultIndices);
+ $result = new DataTable\Map();
+ $result->setKeyName(reset($resultIndices));
$resultIndex = key($resultIndices);
array_shift($resultIndices);
- $result = new DataTable\Map();
- $result->setKeyName($resultIndexLabel);
+ $hasIndices = !empty($resultIndices);
foreach ($index as $label => $value) {
- $keyMetadata[$resultIndex] = $label;
-
- if (empty($resultIndices)) {
- $newTable = $this->createDataTable($value, $keyMetadata);
- } else {
+ if ($resultIndex === DataTableFactory::TABLE_METADATA_SITE_INDEX) {
+ $keyMetadata[$resultIndex] = new Site($label);
+ } elseif ($resultIndex === DataTableFactory::TABLE_METADATA_PERIOD_INDEX) {
+ $keyMetadata[$resultIndex] = $this->periods[$label];
+ }
+ if ($hasIndices) {
$newTable = $this->createDataTableMapFromIndex($value, $resultIndices, $keyMetadata);
+ } else {
+ $newTable = $this->createDataTable($value, $keyMetadata);
}
$result->addTable($newTable, $this->prettifyIndexLabel($resultIndex, $label));
@@ -302,11 +417,11 @@ class DataTableFactory
private function createDataTable($data, $keyMetadata)
{
if ($this->dataType == 'blob') {
- $result = $this->makeFromBlobRow($data);
+ $result = $this->makeFromBlobRow($data, $keyMetadata);
} else {
- $result = $this->makeFromMetricsArray($data);
+ $result = $this->makeFromMetricsArray($data, $keyMetadata);
}
- $this->setTableMetadata($keyMetadata, $result);
+
return $result;
}
@@ -359,17 +474,12 @@ class DataTableFactory
}
}
- /**
- * Converts site IDs and period string ranges into Site instances and
- * Period instances in DataTable metadata.
- */
- private function transformMetadata(DataTableInterface $table)
+ private function getDefaultMetadata()
{
- $periods = $this->periods;
- $table->filter(function (DataTable $table) use ($periods) {
- $table->setMetadata(DataTableFactory::TABLE_METADATA_SITE_INDEX, new Site($table->getMetadata(DataTableFactory::TABLE_METADATA_SITE_INDEX)));
- $table->setMetadata(DataTableFactory::TABLE_METADATA_PERIOD_INDEX, $periods[$table->getMetadata(DataTableFactory::TABLE_METADATA_PERIOD_INDEX)]);
- });
+ return array(
+ DataTableFactory::TABLE_METADATA_SITE_INDEX => new Site(reset($this->sitesId)),
+ DataTableFactory::TABLE_METADATA_PERIOD_INDEX => reset($this->periods),
+ );
}
/**
@@ -388,38 +498,15 @@ class DataTableFactory
}
/**
- * @param $keyMetadata
- * @param $result
- */
- private function setTableMetadata($keyMetadata, DataTableInterface $result)
- {
- if (!isset($keyMetadata[DataTableFactory::TABLE_METADATA_SITE_INDEX])) {
- $keyMetadata[DataTableFactory::TABLE_METADATA_SITE_INDEX] = reset($this->sitesId);
- }
-
- if (!isset($keyMetadata[DataTableFactory::TABLE_METADATA_PERIOD_INDEX])) {
- reset($this->periods);
- $keyMetadata[DataTableFactory::TABLE_METADATA_PERIOD_INDEX] = key($this->periods);
- }
-
- // Note: $result can be a DataTable\Map
- $result->filter(function (DataTable $table) use ($keyMetadata) {
- foreach ($keyMetadata as $name => $value) {
- $table->setMetadata($name, $value);
- }
- });
- }
-
- /**
* @param $data
* @return DataTable\Simple
*/
- private function makeFromMetricsArray($data)
+ private function makeFromMetricsArray($data, $keyMetadata)
{
$table = new DataTable\Simple();
if (!empty($data)) {
- $table->setAllTableMetadata(DataCollection::getDataRowMetadata($data));
+ $table->setAllTableMetadata(array_merge(DataCollection::getDataRowMetadata($data), $keyMetadata));
DataCollection::removeMetadataFromDataRow($data);
@@ -431,11 +518,13 @@ class DataTableFactory
// w/o this code, an empty array would be created, and other parts of Piwik
// would break.
if (count($this->dataNames) == 1
- && $this->dataType == 'numeric'
+ && $this->isNumericDataType()
) {
$name = reset($this->dataNames);
$table->addRow(new Row(array(Row::COLUMNS => array($name => 0))));
}
+
+ $table->setAllTableMetadata($keyMetadata);
}
$result = $table;
diff --git a/core/DataAccess/ArchiveSelector.php b/core/DataAccess/ArchiveSelector.php
index faeef35b88..a500ef66e6 100644
--- a/core/DataAccess/ArchiveSelector.php
+++ b/core/DataAccess/ArchiveSelector.php
@@ -153,9 +153,13 @@ class ArchiveSelector
throw new \Exception("Website IDs could not be read from the request, ie. idSite=");
}
+ foreach ($siteIds as $index => $siteId) {
+ $siteIds[$index] = (int) $siteId;
+ }
+
$getArchiveIdsSql = "SELECT idsite, name, date1, date2, MAX(idarchive) as idarchive
FROM %s
- WHERE idsite IN (" . Common::getSqlStringFieldsArray($siteIds) . ")
+ WHERE idsite IN (" . implode(',', $siteIds) . ")
AND " . self::getNameCondition($plugins, $segment) . "
AND %s
GROUP BY idsite, date1, date2";
@@ -172,7 +176,7 @@ class ArchiveSelector
foreach ($monthToPeriods as $table => $periods) {
$firstPeriod = reset($periods);
- $bind = array_values($siteIds);
+ $bind = array();
if ($firstPeriod instanceof Range) {
$dateCondition = "period = ? AND date1 = ? AND date2 = ?";
@@ -203,12 +207,10 @@ class ArchiveSelector
// get the archive IDs
foreach ($archiveIds as $row) {
- $archiveName = $row['name'];
-
//FIXMEA duplicate with Archive.php
- $dateStr = $row['date1'] . "," . $row['date2'];
+ $dateStr = $row['date1'] . ',' . $row['date2'];
- $result[$archiveName][$dateStr][] = $row['idarchive'];
+ $result[$row['name']][$dateStr][] = $row['idarchive'];
}
}
diff --git a/core/DataTable.php b/core/DataTable.php
index a9be60d98c..d67e0984cb 100644
--- a/core/DataTable.php
+++ b/core/DataTable.php
@@ -1133,12 +1133,12 @@ class DataTable implements DataTableInterface, \IteratorAggregate, \ArrayAccess
}
if (is_null($limit)) {
- $spliced = array_splice($this->rows, $offset);
+ array_splice($this->rows, $offset);
} else {
- $spliced = array_splice($this->rows, $offset, $limit);
+ array_splice($this->rows, $offset, $limit);
}
- $countDeleted = count($spliced);
- return $countDeleted;
+
+ return $count - $this->getRowsCount();
}
/**
@@ -1415,11 +1415,10 @@ class DataTable implements DataTableInterface, \IteratorAggregate, \ArrayAccess
return;
}
- // we define an exception we may throw if at one point we notice that we cannot handle the data structure
- $e = new Exception(" Data structure returned is not convertible in the requested format." .
+ $exceptionText = " Data structure returned is not convertible in the requested format." .
" Try to call this method with the parameters '&format=original&serialize=1'" .
"; you will get the original php data structure serialized." .
- " The data structure looks like this: \n \$data = " . var_export($array, true) . "; ");
+ " The data structure looks like this: \n \$data = %s; ";
// first pass to see if the array has the structure
// array(col1_name => val1, col2_name => val2, etc.)
@@ -1460,12 +1459,13 @@ class DataTable implements DataTableInterface, \IteratorAggregate, \ArrayAccess
// it cannot be lost during the conversion. Because we are not able to handle properly
// this key, we throw an explicit exception.
if (is_string($key)) {
- throw $e;
+ // we define an exception we may throw if at one point we notice that we cannot handle the data structure
+ throw new Exception(sprintf($exceptionText, var_export($array, true)));
}
// if any of the sub elements of row is an array we cannot handle this data structure...
foreach ($row as $subRow) {
if (is_array($subRow)) {
- throw $e;
+ throw new Exception(sprintf($exceptionText, var_export($array, true)));
}
}
$row = new Row(array(Row::COLUMNS => $row));
diff --git a/core/DataTable/Filter/ColumnCallbackAddMetadata.php b/core/DataTable/Filter/ColumnCallbackAddMetadata.php
index 805a5db5ab..cb2af14262 100644
--- a/core/DataTable/Filter/ColumnCallbackAddMetadata.php
+++ b/core/DataTable/Filter/ColumnCallbackAddMetadata.php
@@ -45,10 +45,6 @@ class ColumnCallbackAddMetadata extends BaseFilter
{
parent::__construct($table);
- if (!is_array($columnsToRead)) {
- $columnsToRead = array($columnsToRead);
- }
-
$this->columnsToRead = $columnsToRead;
$this->functionToApply = $functionToApply;
$this->functionParameters = $functionParameters;
@@ -69,23 +65,45 @@ class ColumnCallbackAddMetadata extends BaseFilter
$rows = $table->getRowsWithoutSummaryRow();
}
- foreach ($rows as $key => $row) {
+ if (is_array($this->columnsToRead)) {
- $parameters = array();
- foreach ($this->columnsToRead as $columnsToRead) {
- $parameters[] = $row->getColumn($columnsToRead);
- }
+ // I know it is duplicated code, I did extract the inner part into a method but when executing this on 100k
+ // rows it actually makes a difference having this code duplicated instead of having an inner function call
+ foreach ($rows as $key => $row) {
- if (!is_null($this->functionParameters)) {
- $parameters = array_merge($parameters, $this->functionParameters);
- }
- if (!is_null($this->functionToApply)) {
- $newValue = call_user_func_array($this->functionToApply, $parameters);
- } else {
- $newValue = $parameters[0];
+ $parameters = array();
+ foreach ($this->columnsToRead as $columnsToRead) {
+ $parameters[] = $row->getColumn($columnsToRead);
+ }
+
+ if (!is_null($this->functionParameters)) {
+ $parameters = array_merge($parameters, $this->functionParameters);
+ }
+ if (!is_null($this->functionToApply)) {
+ $newValue = call_user_func_array($this->functionToApply, $parameters);
+ } else {
+ $newValue = $parameters[0];
+ }
+ if ($newValue !== false) {
+ $row->setMetadata($this->metadataToAdd, $newValue);
+ }
}
- if ($newValue !== false) {
- $row->addMetadata($this->metadataToAdd, $newValue);
+ } else {
+ foreach ($rows as $key => $row) {
+
+ $parameters = array($row->getColumn($this->columnsToRead));
+
+ if (!is_null($this->functionParameters)) {
+ $parameters = array_merge($parameters, $this->functionParameters);
+ }
+ if (!is_null($this->functionToApply)) {
+ $newValue = call_user_func_array($this->functionToApply, $parameters);
+ } else {
+ $newValue = $parameters[0];
+ }
+ if ($newValue !== false) {
+ $row->setMetadata($this->metadataToAdd, $newValue);
+ }
}
}
}
diff --git a/core/DataTable/Manager.php b/core/DataTable/Manager.php
index 76444a074f..c823293c76 100644
--- a/core/DataTable/Manager.php
+++ b/core/DataTable/Manager.php
@@ -24,7 +24,7 @@ class Manager extends \ArrayObject
* Id of the next inserted table id in the Manager
* @var int
*/
- protected $nextTableId = 1;
+ protected $nextTableId = 0;
private static $instance;
@@ -45,9 +45,9 @@ class Manager extends \ArrayObject
*/
public function addTable($table)
{
- $this[$this->nextTableId] = $table;
$this->nextTableId++;
- return $this->nextTableId - 1;
+ $this[$this->nextTableId] = $table;
+ return $this->nextTableId;
}
/**
@@ -75,7 +75,7 @@ class Manager extends \ArrayObject
*/
public function getMostRecentTableId()
{
- return $this->nextTableId - 1;
+ return $this->nextTableId;
}
/**
@@ -91,7 +91,7 @@ class Manager extends \ArrayObject
if ($deleteWhenIdTableGreaterThan == 0) {
$this->exchangeArray(array());
- $this->nextTableId = 1;
+ $this->nextTableId = 0;
}
}
diff --git a/core/Site.php b/core/Site.php
index 1a828fce47..7b9ed154fa 100644
--- a/core/Site.php
+++ b/core/Site.php
@@ -78,11 +78,35 @@ class Site
*/
public static function setSites($sites)
{
+ self::triggerSetSitesEvent($sites);
+
foreach($sites as $idsite => $site) {
self::setSite($idsite, $site);
}
}
+ private static function triggerSetSitesEvent(&$sites)
+ {
+ /**
+ * Triggered so plugins can modify website entities without modifying the database.
+ *
+ * This event should **not** be used to add data that is expensive to compute. If you
+ * need to make HTTP requests or query the database for more information, this is not
+ * the place to do it.
+ *
+ * **Example**
+ *
+ * Piwik::addAction('Site.setSites', function (&$sites) {
+ * foreach ($sites as &$site) {
+ * $site['name'] .= " (original)";
+ * }
+ * });
+ *
+ * @param array $sites An array of website entities. [Learn more.](/guides/persistence-and-the-mysql-backend#websites-aka-sites)
+ */
+ Piwik::postEvent('Site.setSites', array(&$sites));
+ }
+
/**
* Sets a site information in memory (statically cached).
*
@@ -99,24 +123,6 @@ class Site
throw new UnexpectedWebsiteFoundException("An unexpected website was found, check idSite in the request.");
}
- /**
- * Triggered so plugins can modify website entities without modifying the database.
- *
- * This event should **not** be used to add data that is expensive to compute. If you
- * need to make HTTP requests or query the database for more information, this is not
- * the place to do it.
- *
- * **Example**
- *
- * Piwik::addAction('Site.setSite', function ($idSite, &$info) {
- * $info['name'] .= " (original)";
- * });
- *
- * @param int $idSite The ID of the website entity that will be modified.
- * @param array $infoSite The website entity. [Learn more.](/guides/persistence-and-the-mysql-backend#websites-aka-sites)
- */
- Piwik::postEvent('Site.setSite', array($idSite, &$infoSite));
-
self::$infoSites[$idSite] = $infoSite;
}
@@ -132,6 +138,8 @@ class Site
*/
public static function setSitesFromArray($sites)
{
+ self::triggerSetSitesEvent($sites);
+
foreach ($sites as $site) {
$idSite = null;
if (!empty($site['idsite'])) {
@@ -410,21 +418,17 @@ class Site
* site with the specified ID.
*
* @param int $idsite The ID of the site whose data is being accessed.
- * @param bool|string $field The name of the field to get.
- * @return array|string
+ * @param string $field The name of the field to get.
+ * @return string
*/
- protected static function getFor($idsite, $field = false)
+ protected static function getFor($idsite, $field)
{
- $idsite = (int)$idsite;
-
if (!isset(self::$infoSites[$idsite])) {
$site = API::getInstance()->getSiteFromId($idsite);
self::setSite($idsite, $site);
}
- if ($field) {
- return self::$infoSites[$idsite][$field];
- }
- return self::$infoSites[$idsite];
+
+ return self::$infoSites[$idsite][$field];
}
/**
@@ -440,9 +444,16 @@ class Site
/**
* @ignore
*/
- public static function getSite($id)
+ public static function getSite($idsite)
{
- return self::getFor($id);
+ $idsite = (int)$idsite;
+
+ if (!isset(self::$infoSites[$idsite])) {
+ $site = API::getInstance()->getSiteFromId($idsite);
+ self::setSite($idsite, $site);
+ }
+
+ return self::$infoSites[$idsite];
}
/**
diff --git a/lang/en.json b/lang/en.json
index ed98e18273..81b093925b 100644
--- a/lang/en.json
+++ b/lang/en.json
@@ -31,7 +31,7 @@
"ChooseWebsite": "Choose website",
"ClickHere": "Click here for more information.",
"ClickToChangePeriod": "Click again to change the period.",
- "Close": "Close",
+ "ClickToSearch": "Click to search",
"ColumnActionsPerVisit": "Actions per Visit",
"ColumnActionsPerVisitDocumentation": "The average number of actions (page views, site searches, downloads or outlinks) that were performed during the visits.",
"ColumnAverageGenerationTime": "Avg. generation time",
diff --git a/plugins/MultiSites/.gitignore b/plugins/MultiSites/.gitignore
new file mode 100644
index 0000000000..c8c9480010
--- /dev/null
+++ b/plugins/MultiSites/.gitignore
@@ -0,0 +1 @@
+tests/System/processed/*xml \ No newline at end of file
diff --git a/plugins/MultiSites/API.php b/plugins/MultiSites/API.php
index c85dc94024..df92877416 100755
--- a/plugins/MultiSites/API.php
+++ b/plugins/MultiSites/API.php
@@ -14,6 +14,7 @@ use Piwik\Archive;
use Piwik\Common;
use Piwik\Container\StaticContainer;
use Piwik\DataTable;
+use Piwik\DataTable\Row;
use Piwik\Period\Range;
use Piwik\Piwik;
use Piwik\Plugins\Goals\Archiver;
@@ -81,9 +82,10 @@ class API extends \Piwik\Plugin\API
* Only used when a scheduled task is running
* @param bool|string $enhanced When true, return additional goal & ecommerce metrics
* @param bool|string $pattern If specified, only the website which names (or site ID) match the pattern will be returned using SitesManager.getPatternMatchSites
+ * @param array $showColumns If specified, only the requested columns will be fetched
* @return DataTable
*/
- public function getAll($period, $date, $segment = false, $_restrictSitesToLogin = false, $enhanced = false, $pattern = false)
+ public function getAll($period, $date, $segment = false, $_restrictSitesToLogin = false, $enhanced = false, $pattern = false, $showColumns = array())
{
Piwik::checkUserHasSomeViewAccess();
@@ -100,7 +102,8 @@ class API extends \Piwik\Plugin\API
$segment,
$_restrictSitesToLogin,
$enhanced,
- $multipleWebsitesRequested = true
+ $multipleWebsitesRequested = true,
+ $showColumns
);
}
@@ -185,7 +188,8 @@ class API extends \Piwik\Plugin\API
$segment,
$_restrictSitesToLogin,
$enhanced,
- $multipleWebsitesRequested = false
+ $multipleWebsitesRequested = false,
+ $showColumns = array()
);
}
@@ -197,7 +201,7 @@ class API extends \Piwik\Plugin\API
return $sites;
}
- private function buildDataTable($sitesToProblablyAdd, $period, $date, $segment, $_restrictSitesToLogin, $enhanced, $multipleWebsitesRequested)
+ private function buildDataTable($sitesToProblablyAdd, $period, $date, $segment, $_restrictSitesToLogin, $enhanced, $multipleWebsitesRequested, $showColumns)
{
$idSites = array();
if (!empty($sitesToProblablyAdd)) {
@@ -221,6 +225,10 @@ class API extends \Piwik\Plugin\API
$apiECommerceMetrics = array();
$apiMetrics = API::getApiMetrics($enhanced);
foreach ($apiMetrics as $metricName => $metricSettings) {
+ if (!empty($showColumns) && !in_array($metricName, $showColumns)) {
+ unset($apiMetrics[$metricName]);
+ continue;
+ }
$fieldsToGet[] = $metricSettings[self::METRIC_RECORD_NAME_KEY];
$columnNameRewrites[$metricSettings[self::METRIC_RECORD_NAME_KEY]] = $metricName;
@@ -229,25 +237,11 @@ class API extends \Piwik\Plugin\API
}
}
- // get the data
- // $dataTable instanceOf Set
- $dataTable = $archive->getDataTableFromNumeric($fieldsToGet);
-
- if ($multipleWebsitesRequested && count($idSites) === 1 && Range::isMultiplePeriod($date, $period)) {
- } else {
- $dataTable = $this->mergeDataTableMapAndPopulateLabel($idSites, $multipleWebsitesRequested, $dataTable);
- }
-
- if ($dataTable instanceof DataTable\Map) {
- foreach ($dataTable->getDataTables() as $table) {
- $this->addMissingWebsites($table, $fieldsToGet, $sitesToProblablyAdd);
- }
- } else {
- $this->addMissingWebsites($dataTable, $fieldsToGet, $sitesToProblablyAdd);
- }
+ $dataTable = $archive->getDataTableFromNumericAndMergeChildren($fieldsToGet);
- // calculate total visits/actions/revenue
- $this->setMetricsTotalsMetadata($dataTable, $apiMetrics);
+ $this->populateLabel($dataTable);
+ $totalMetrics = $this->preformatApiMetricsForTotalsCalculation($apiMetrics);
+ $this->setMetricsTotalsMetadata($dataTable, $totalMetrics);
// if the period isn't a range & a lastN/previousN date isn't used, we get the same
// data for the last period to show the evolution of visits/actions/revenue
@@ -263,23 +257,16 @@ class API extends \Piwik\Plugin\API
}
$pastArchive = Archive::build($idSites, $period, $strLastDate, $segment, $_restrictSitesToLogin);
+ $pastData = $pastArchive->getDataTableFromNumericAndMergeChildren($fieldsToGet);
- $pastData = $pastArchive->getDataTableFromNumeric($fieldsToGet);
-
- if ($multipleWebsitesRequested && count($idSites) === 1 && Range::isMultiplePeriod($date, $period)) {
-
- } else {
- $pastData = $this->mergeDataTableMapAndPopulateLabel($idSites, $multipleWebsitesRequested, $pastData);
- }
-
- // use past data to calculate evolution percentages
+ $this->populateLabel($pastData); // labels are needed to calculate evolution
$this->calculateEvolutionPercentages($dataTable, $pastData, $apiMetrics);
+ $this->setPastTotalVisitsMetadata($dataTable, $pastData);
}
- // move the site id to a metadata column
- $dataTable->filter('ColumnCallbackAddMetadata', array('label', 'group', array('\Piwik\Site', 'getGroupFor'), array()));
- $dataTable->filter('ColumnCallbackAddMetadata', array('label', 'main_url', array('\Piwik\Site', 'getMainUrlFor'), array()));
- $dataTable->filter('ColumnCallbackAddMetadata', array('label', 'idsite'));
+ // move the site id to a metadata column
+ $dataTable->queueFilter('MetadataCallbackAddMetadata', array('idsite', 'group', array('\Piwik\Site', 'getGroupFor'), array()));
+ $dataTable->queueFilter('MetadataCallbackAddMetadata', array('idsite', 'main_url', array('\Piwik\Site', 'getMainUrlFor'), array()));
// set the label of each row to the site name
if ($multipleWebsitesRequested) {
@@ -420,6 +407,17 @@ class API extends \Piwik\Plugin\API
return $metrics;
}
+ private function preformatApiMetricsForTotalsCalculation($apiMetrics)
+ {
+ $metrics = array();
+ foreach ($apiMetrics as $label => $metricsInfo) {
+ $totalMetadataName = self::getTotalMetadataName($label);
+ $metrics[$totalMetadataName] = $metricsInfo[self::METRIC_RECORD_NAME_KEY];
+ }
+
+ return $metrics;
+ }
+
/**
* Sets the total visits, actions & revenue for a DataTable returned by
* $this->buildDataTable.
@@ -436,92 +434,63 @@ class API extends \Piwik\Plugin\API
}
} else {
$totals = array();
- foreach ($apiMetrics as $label => $metricInfo) {
- $totalMetadataName = self::getTotalMetadataName($label);
- $totals[$totalMetadataName] = 0;
+ foreach ($apiMetrics as $label => $recordName) {
+ $totals[$label] = 0;
}
foreach ($dataTable->getRows() as $row) {
- foreach ($apiMetrics as $label => $metricInfo) {
- $totalMetadataName = self::getTotalMetadataName($label);
- $totals[$totalMetadataName] += $row->getColumn($metricInfo[self::METRIC_RECORD_NAME_KEY]);
+ foreach ($apiMetrics as $totalMetadataName => $recordName) {
+ $totals[$totalMetadataName] += $row->getColumn($recordName);
}
}
- foreach ($totals as $name => $value) {
- $dataTable->setMetadata($name, $value);
- }
+ $dataTable->setMetadataValues($totals);
}
}
- private static function getTotalMetadataName($name)
- {
- return 'total_' . $name;
- }
-
- private static function getLastPeriodMetadataName($name)
- {
- return 'last_period_' . $name;
- }
-
/**
- * @param DataTable|DataTable\Map $dataTable
- * @param $fieldsToGet
- * @param $sitesToProblablyAdd
+ * Sets the number of total visits in tha pastTable on the dataTable as metadata.
+ *
+ * @param DataTable $dataTable
+ * @param DataTable $pastTable
*/
- private function addMissingWebsites($dataTable, $fieldsToGet, $sitesToProblablyAdd)
+ private function setPastTotalVisitsMetadata($dataTable, $pastTable)
{
- $siteIdsInDataTable = array();
- foreach ($dataTable->getRows() as $row) {
- /** @var DataTable\Row $row */
- $siteIdsInDataTable[] = $row->getColumn('label');
- }
+ if ($pastTable instanceof DataTable) {
+ $total = 0;
+ $metric = 'nb_visits';
- foreach ($sitesToProblablyAdd as $site) {
- if (!in_array($site['idsite'], $siteIdsInDataTable)) {
- $siteRow = array_combine($fieldsToGet, array_pad(array(), count($fieldsToGet), 0));
- $siteRow['label'] = (int) $site['idsite'];
- $dataTable->addRowFromSimpleArray($siteRow);
+ foreach ($pastTable->getRows() as $row) {
+ $total += $row->getColumn($metric);
}
+
+ $dataTable->setMetadata(self::getTotalMetadataName($metric . '_lastdate'), $total);
}
}
- private function removeEcommerceRelatedMetricsOnNonEcommercePiwikSites($dataTable, $apiECommerceMetrics)
+ private static function getTotalMetadataName($name)
{
- // $dataTableRows instanceOf Row[]
- $dataTableRows = $dataTable->getRows();
-
- foreach ($dataTableRows as $dataTableRow) {
- $siteId = $dataTableRow->getColumn('label');
- if (!Site::isEcommerceEnabledFor($siteId)) {
- foreach ($apiECommerceMetrics as $metricSettings) {
- $dataTableRow->deleteColumn($metricSettings[self::METRIC_RECORD_NAME_KEY]);
- $dataTableRow->deleteColumn($metricSettings[self::METRIC_EVOLUTION_COL_NAME_KEY]);
- }
- }
- }
+ return 'total_' . $name;
}
- private function mergeDataTableMapAndPopulateLabel($idSitesOrIdSite, $multipleWebsitesRequested, $dataTable)
+ private static function getLastPeriodMetadataName($name)
{
- // get rid of the DataTable\Map that is created by the IndexedBySite archive type
- if ($dataTable instanceof DataTable\Map && $multipleWebsitesRequested) {
-
- return $dataTable->mergeChildren();
-
- } else {
-
- if (!$dataTable instanceof DataTable\Map && $dataTable->getRowsCount() > 0) {
-
- $firstSite = is_array($idSitesOrIdSite) ? reset($idSitesOrIdSite) : $idSitesOrIdSite;
-
- $firstDataTableRow = $dataTable->getFirstRow();
+ return 'last_period_' . $name;
+ }
- $firstDataTableRow->setColumn('label', $firstSite);
+ private function populateLabel($dataTable)
+ {
+ $dataTable->filter(function (DataTable $table) {
+ foreach ($table->getRowsWithoutSummaryRow() as $row) {
+ $row->setColumn('label', $row->getMetadata('idsite'));
}
- }
-
- return $dataTable;
+ });
+ // make sure label column is always first column
+ $dataTable->queueFilter(function (DataTable $table) {
+ foreach ($table->getRowsWithoutSummaryRow() as $row) {
+ $row->setColumns(array_merge(array('label' => $row->getColumn('label')), $row->getColumns()));
+ }
+ });
}
private function isEcommerceEvolutionMetric($metricSettings)
@@ -532,4 +501,4 @@ class API extends \Piwik\Plugin\API
self::ECOMMERCE_REVENUE_METRIC . '_evolution'
));
}
-} \ No newline at end of file
+}
diff --git a/plugins/MultiSites/Columns/Metrics/EcommerceOnlyEvolutionMetric.php b/plugins/MultiSites/Columns/Metrics/EcommerceOnlyEvolutionMetric.php
index cee88af3f5..e43504154e 100644
--- a/plugins/MultiSites/Columns/Metrics/EcommerceOnlyEvolutionMetric.php
+++ b/plugins/MultiSites/Columns/Metrics/EcommerceOnlyEvolutionMetric.php
@@ -36,13 +36,13 @@ class EcommerceOnlyEvolutionMetric extends EvolutionMetric
// if the site this is for doesn't support ecommerce & this is for the revenue_evolution column,
// we don't add the new column
- if (($currentValue === false
- || !$this->isRevenueEvolution)
- && !Site::isEcommerceEnabledFor($row->getColumn('label'))
- ) {
- $row->deleteColumn($columnName);
+ if ($currentValue === false || !$this->isRevenueEvolution) {
+ $idSite = $row->getMetadata('idsite');
+ if (!$idSite || !Site::isEcommerceEnabledFor($idSite)) {
+ $row->deleteColumn($columnName);
- return false;
+ return false;
+ }
}
return parent::compute($row);
diff --git a/plugins/MultiSites/Controller.php b/plugins/MultiSites/Controller.php
index 647ae147dc..a59e8a4cdb 100644
--- a/plugins/MultiSites/Controller.php
+++ b/plugins/MultiSites/Controller.php
@@ -8,10 +8,15 @@
*/
namespace Piwik\Plugins\MultiSites;
+use Piwik\API\Request;
+use Piwik\API\ResponseBuilder;
use Piwik\Common;
use Piwik\Config;
use Piwik\Date;
use Piwik\Period;
+use Piwik\DataTable;
+use Piwik\DataTable\Row;
+use Piwik\DataTable\Row\DataTableSummaryRow;
use Piwik\Piwik;
use Piwik\Translation\Translator;
use Piwik\View;
@@ -40,6 +45,34 @@ class Controller extends \Piwik\Plugin\Controller
return $this->getSitesInfo($isWidgetized = true);
}
+ public function getAllWithGroups()
+ {
+ Piwik::checkUserHasSomeViewAccess();
+
+ $period = Common::getRequestVar('period', null, 'string');
+ $date = Common::getRequestVar('date', null, 'string');
+ $segment = Common::getRequestVar('segment', false, 'string');
+ $pattern = Common::getRequestVar('pattern', '', 'string');
+ $limit = Common::getRequestVar('filter_limit', 0, 'int');
+ $segment = $segment ?: false;
+ $request = $_GET + $_POST;
+
+ $dashboard = new Dashboard($period, $date, $segment);
+
+ if ($pattern !== '') {
+ $dashboard->search(strtolower($pattern));
+ }
+
+ $response = array(
+ 'numSites' => $dashboard->getNumSites(),
+ 'totals' => $dashboard->getTotals(),
+ 'lastDate' => $dashboard->getLastDate(),
+ 'sites' => $dashboard->getSites($request, $limit)
+ );
+
+ return json_encode($response);
+ }
+
public function getSitesInfo($isWidgetized = false)
{
Piwik::checkUserHasSomeViewAccess();
diff --git a/plugins/MultiSites/Dashboard.php b/plugins/MultiSites/Dashboard.php
new file mode 100644
index 0000000000..6e973a8be6
--- /dev/null
+++ b/plugins/MultiSites/Dashboard.php
@@ -0,0 +1,294 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ *
+ */
+namespace Piwik\Plugins\MultiSites;
+
+use Piwik\API\ResponseBuilder;
+use Piwik\Config;
+use Piwik\Metrics\Formatter;
+use Piwik\Period;
+use Piwik\DataTable;
+use Piwik\DataTable\Row;
+use Piwik\DataTable\Row\DataTableSummaryRow;
+use Piwik\Plugins\API\ProcessedReport;
+use Piwik\Site;
+use Piwik\View;
+
+class Dashboard
+{
+ /** @var DataTable */
+ private $sitesByGroup;
+
+ /**
+ * @var int
+ */
+ private $numSites = 0;
+
+ /**
+ * @param string $period
+ * @param string $date
+ * @param string|false $segment
+ */
+ public function __construct($period, $date, $segment)
+ {
+ $sites = API::getInstance()->getAll($period, $date, $segment, $_restrictSitesToLogin = false,
+ $enhanced = true, $searchTerm = false,
+ $showColumns = array('nb_visits', 'nb_pageviews', 'revenue'));
+ $sites->deleteRow(DataTable::ID_SUMMARY_ROW);
+ $sites->filter(function (DataTable $table) {
+ foreach ($table->getRows() as $row) {
+ $idSite = $row->getColumn('label');
+ $site = Site::getSite($idSite);
+ // we cannot queue label and group as we might need them for search!
+ $row->setColumn('label', $site['name']);
+ $row->setMetadata('group', $site['group']);
+ }
+ });
+
+ $this->setSitesTable($sites);
+ }
+
+ public function setSitesTable(DataTable $sites)
+ {
+ $this->numSites = $sites->getRowsCount();
+ $this->sitesByGroup = $this->moveSitesHavingAGroupIntoSubtables($sites);
+ }
+
+ public function getSites($request, $limit)
+ {
+ $request['filter_limit'] = $limit;
+
+ $sitesExpanded = $this->convertDataTableToArrayAndApplyFilters($this->sitesByGroup, $request);
+ $sitesFlat = $this->makeSitesFlat($sitesExpanded);
+ $sitesFlat = $this->applyLimitIfNeeded($sitesFlat, $limit);
+ $sitesFlat = $this->enrichValues($sitesFlat);
+
+ return $sitesFlat;
+ }
+
+ public function getTotals()
+ {
+ return array(
+ 'nb_pageviews' => $this->sitesByGroup->getMetadata('total_nb_pageviews'),
+ 'nb_visits' => $this->sitesByGroup->getMetadata('total_nb_visits'),
+ 'revenue' => $this->sitesByGroup->getMetadata('total_revenue'),
+ 'nb_visits_lastdate' => $this->sitesByGroup->getMetadata('total_nb_visits_lastdate') ? : 0,
+ );
+ }
+
+ public function getNumSites()
+ {
+ return $this->numSites;
+ }
+
+ public function search($pattern)
+ {
+ $this->nestedSearch($this->sitesByGroup, $pattern);
+
+ $this->numSites = $this->sitesByGroup->getRowsCountRecursive();
+ }
+
+ private function nestedSearch(DataTable $sitesByGroup, $pattern)
+ {
+ foreach ($sitesByGroup->getRows() as $index => $site) {
+
+ $label = strtolower($site->getColumn('label'));
+ $labelMatches = false !== strpos($label, $pattern);
+
+ if ($site->getMetadata('isGroup')) {
+ $subtable = $site->getSubtable();
+ $this->nestedSearch($subtable, $pattern);
+
+ if (!$labelMatches && !$subtable->getRowsCount()) {
+ // we keep the group if at least one site within the group matches the pattern
+ $sitesByGroup->deleteRow($index);
+ }
+
+ } elseif (!$labelMatches) {
+ $group = $site->getMetadata('group');
+
+ if (!$group || false === strpos(strtolower($group), $pattern)) {
+ $sitesByGroup->deleteRow($index);
+ }
+ }
+ }
+ }
+
+ /**
+ * @return string
+ */
+ public function getLastDate()
+ {
+ $lastPeriod = $this->sitesByGroup->getMetadata('last_period_date');
+
+ if (!empty($lastPeriod)) {
+ $lastPeriod = $lastPeriod->toString();
+ } else {
+ $lastPeriod = '';
+ }
+
+ return $lastPeriod;
+ }
+
+ private function convertDataTableToArrayAndApplyFilters(DataTable $table, $request)
+ {
+ $request['serialize'] = 0;
+ $request['expanded'] = 1;
+ $request['totals'] = 0;
+ $request['format_metrics'] = 1;
+
+ // filter_sort_column does not work correctly is a bug in MultiSites.getAll
+ if (!empty($request['filter_sort_column']) && $request['filter_sort_column'] === 'nb_pageviews') {
+ $request['filter_sort_column'] = 'Actions_nb_pageviews';
+ } elseif (!empty($request['filter_sort_column']) && $request['filter_sort_column'] === 'revenue') {
+ $request['filter_sort_column'] = 'Goal_revenue';
+ }
+
+ $responseBuilder = new ResponseBuilder('php', $request);
+ $rows = $responseBuilder->getResponse($table, 'MultiSites', 'getAll');
+
+ return $rows;
+ }
+
+ private function moveSitesHavingAGroupIntoSubtables(DataTable $sites)
+ {
+ /** @var DataTableSummaryRow[] $groups */
+ $groups = array();
+
+ $sitesByGroup = $this->makeCloneOfDataTableSites($sites);
+ $sitesByGroup->enableRecursiveFilters(); // we need to make sure filters get applied to subtables (groups)
+
+ foreach ($sites->getRows() as $site) {
+
+ $group = $site->getMetadata('group');
+
+ if (!empty($group) && !array_key_exists($group, $groups)) {
+ $row = new DataTableSummaryRow();
+ $row->setColumn('label', $group);
+ $row->setMetadata('isGroup', 1);
+ $row->setSubtable($this->createGroupSubtable($sites));
+ $sitesByGroup->addRow($row);
+
+ $groups[$group] = $row;
+ }
+
+ if (!empty($group)) {
+ $groups[$group]->getSubtable()->addRow($site);
+ } else {
+ $sitesByGroup->addRow($site);
+ }
+ }
+
+ foreach ($groups as $group) {
+ // we need to recalculate as long as all rows are there, as soon as some rows are removed
+ // we can no longer recalculate the correct value. We might even calculate values for groups
+ // that are not returned. If this becomes a problem we need to keep a copy of this to recalculate
+ // only actual returned groups.
+ $group->recalculate();
+ }
+
+ return $sitesByGroup;
+ }
+
+ private function createGroupSubtable(DataTable $sites)
+ {
+ $table = new DataTable();
+ $processedMetrics = $sites->getMetadata(DataTable::EXTRA_PROCESSED_METRICS_METADATA_NAME);
+ $table->setMetadata(DataTable::EXTRA_PROCESSED_METRICS_METADATA_NAME, $processedMetrics);
+
+ return $table;
+ }
+
+ private function makeCloneOfDataTableSites(DataTable $sites)
+ {
+ $sitesByGroup = $sites->getEmptyClone(true);
+ // we handle them ourselves for faster performance etc. This way we also avoid to apply them twice.
+ $sitesByGroup->disableFilter('ColumnCallbackReplace');
+ $sitesByGroup->disableFilter('MetadataCallbackAddMetadata');
+
+ return $sitesByGroup;
+ }
+
+ /**
+ * Makes sure to not have any subtables anymore.
+ * So if $sites is
+ * array(
+ * site1
+ * site2
+ * subtable => site3
+ * site4
+ * site5
+ * site6
+ * site7
+ * )
+ *
+ * it will return
+ *
+ * array(
+ * site1
+ * site2
+ * site3
+ * site4
+ * site5
+ * site6
+ * site7
+ * )
+ *
+ * @param $sites
+ * @return array
+ */
+ private function makeSitesFlat($sites)
+ {
+ $flatSites = array();
+
+ foreach ($sites as $site) {
+ if (!empty($site['subtable'])) {
+ if (isset($site['idsubdatatable'])) {
+ unset($site['idsubdatatable']);
+ }
+
+ $subtable = $site['subtable'];
+ unset($site['subtable']);
+ $flatSites[] = $site;
+ foreach ($subtable as $siteWithinGroup) {
+ $flatSites[] = $siteWithinGroup;
+ }
+ } else {
+ $flatSites[] = $site;
+ }
+ }
+
+ return $flatSites;
+ }
+
+ private function applyLimitIfNeeded($sites, $limit)
+ {
+ // why do we need to apply a limit again? because we made sitesFlat and it may contain many more sites now
+ if ($limit > 0) {
+ $sites = array_slice($sites, 0, $limit);
+ }
+
+ return $sites;
+ }
+
+ private function enrichValues($sites)
+ {
+ $formatter = new Formatter();
+
+ foreach ($sites as &$site) {
+ if (!isset($site['idsite'])) {
+ continue;
+ }
+
+ $site['revenue'] = $formatter->getPrettyMoney($site['revenue'], $site['idsite']);
+ $site['main_url'] = Site::getMainUrlFor($site['idsite']);
+ }
+
+ return $sites;
+ }
+}
diff --git a/plugins/MultiSites/MultiSites.php b/plugins/MultiSites/MultiSites.php
index f3f62ecae4..c69f174d84 100644
--- a/plugins/MultiSites/MultiSites.php
+++ b/plugins/MultiSites/MultiSites.php
@@ -68,13 +68,13 @@ class MultiSites extends \Piwik\Plugin
$translations[] = 'MultiSites_LoadingWebsites';
$translations[] = 'General_ErrorRequest';
$translations[] = 'General_Pagination';
+ $translations[] = 'General_ClickToSearch';
}
public function getJsFiles(&$jsFiles)
{
$jsFiles[] = "plugins/MultiSites/angularjs/dashboard/dashboard-model.service.js";
$jsFiles[] = "plugins/MultiSites/angularjs/dashboard/dashboard.controller.js";
- $jsFiles[] = "plugins/MultiSites/angularjs/dashboard/dashboard-group.filter.js";
$jsFiles[] = "plugins/MultiSites/angularjs/dashboard/dashboard.directive.js";
$jsFiles[] = "plugins/MultiSites/angularjs/site/site.controller.js";
$jsFiles[] = "plugins/MultiSites/angularjs/site/site.directive.js";
diff --git a/plugins/MultiSites/angularjs/dashboard/dashboard-group.filter.js b/plugins/MultiSites/angularjs/dashboard/dashboard-group.filter.js
deleted file mode 100644
index b8f9040e9b..0000000000
--- a/plugins/MultiSites/angularjs/dashboard/dashboard-group.filter.js
+++ /dev/null
@@ -1,67 +0,0 @@
-/*!
- * Piwik - free/libre analytics platform
- *
- * @link http://piwik.org
- * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
- */
-
-/**
- * Filters a given list of websites and groups and makes sure only the websites within a given offset and limit are
- * displayed. It also makes sure sites are displayed under the groups. That means it flattens a structure like this:
- *
- * - website1
- * - website2
- * - website3.sites // this is a group
- * - website4
- * - website5
- * - website6
- *
- * to the following structure
- * - website1
- * - website2
- * - website3.sites // this is a group
- * - website4
- * - website5
- * - website6
- */
-(function () {
- angular.module('piwikApp').filter('multiSitesGroupFilter', multiSitesGroupFilter);
-
- function multiSitesGroupFilter() {
- return function(websites, from, to) {
- var offsetEnd = parseInt(from, 10) + parseInt(to, 10);
- var groups = {};
-
- var sites = [];
- for (var index = 0; index < websites.length; index++) {
- var website = websites[index];
-
- sites.push(website);
- if (website.sites && website.sites.length) {
- groups[website.label] = website;
- for (var innerIndex = 0; innerIndex < website.sites.length; innerIndex++) {
- sites.push(website.sites[innerIndex]);
- }
- }
-
- if (sites.length >= offsetEnd) {
- break;
- }
- }
-
- // if the first site is a website having a group, then try to find the related group and prepend it to the list
- // of sites to make sure we always display the name of the group that belongs to a website.
- var filteredSites = sites.slice(from, offsetEnd);
-
- if (filteredSites.length && filteredSites[0] && filteredSites[0].group) {
- var groupName = filteredSites[0].group;
- if (groups[groupName]) {
- filteredSites.unshift(groups[groupName]);
- }
- }
-
- return filteredSites;
- };
- }
-})();
-
diff --git a/plugins/MultiSites/angularjs/dashboard/dashboard-model.service.js b/plugins/MultiSites/angularjs/dashboard/dashboard-model.service.js
index f2261aad1c..a213d892f5 100644
--- a/plugins/MultiSites/angularjs/dashboard/dashboard-model.service.js
+++ b/plugins/MultiSites/angularjs/dashboard/dashboard-model.service.js
@@ -7,30 +7,14 @@
multisitesDashboardModel.$inject = ['piwikApi', '$filter', '$timeout'];
function multisitesDashboardModel(piwikApi, $filter, $timeout) {
- /**
- *
- * this is the list of all available sites. For performance reason this list is different to model.sites. ngRepeat
- * won't operate on the whole list this way. The allSites array contains websites and groups in the following
- * structure
- *
- * - website1
- * - website2
- * - website3.sites = [ // this is a group
- * - website4
- * - website5
- * ]
- * - website6
- *
- * This structure allows us to display the sites within a group directly under the group without big logic and also
- * allows us to calculate the summary for each group easily
- */
- var allSitesByGroup = [];
+
+ var refreshPromise = null;
// those sites are going to be displayed
var model = {
sites : [],
isLoading : false,
- pageSize : 5,
+ pageSize : 25,
currentPage : 0,
totalVisits : '?',
totalActions : '?',
@@ -38,6 +22,7 @@
searchTerm : '',
lastVisits : '?',
lastVisitsDate : '?',
+ numberOfSites : 0,
updateWebsitesList: updateWebsitesList,
getNumberOfFilteredSites: getNumberOfFilteredSites,
getNumberOfPages: getNumberOfPages,
@@ -46,153 +31,52 @@
previousPage: previousPage,
nextPage: nextPage,
searchSite: searchSite,
- fetchAllSites: fetchAllSites
+ sortBy: sortBy,
+ reverse: true,
+ sortColumn: 'nb_visits',
+ fetchAllSites: fetchAllSites,
+ refreshInterval: 0
};
- fetchPreviousSummary();
-
return model;
- // create a new group object which has similar structure than a website
- function createGroup(name){
- return {
- label: name,
- sites: [],
- nb_visits: 0,
- nb_pageviews: 0,
- revenue: 0,
- isGroup: true
- };
- }
-
- // create a new group with empty site to make sure we do not change the original group in $allSites
- function copyGroup(group)
+ function cancelRefereshInterval()
{
- return {
- label: group.label,
- sites: [],
- nb_visits: group.nb_visits,
- nb_pageviews: group.nb_pageviews,
- revenue: group.revenue,
- isGroup: true
+ if (refreshPromise) {
+ $timeout.cancel(refreshPromise);
+ refreshPromise = null;
};
}
function onError () {
model.errorLoadingSites = true;
- model.sites = [];
- allSitesByGroup = [];
- }
-
- function calculateMetricsForEachGroup(groups)
- {
- angular.forEach(groups, function (group) {
- angular.forEach(group.sites, function (site) {
- var revenue = 0;
- if (site.revenue) {
- revenue = (site.revenue+'').match(/(\d+\.?\d*)/); // convert $ 0.00 to 0.00 or 5€ to 5
- }
-
- group.nb_visits += parseInt(site.nb_visits, 10);
- group.nb_pageviews += parseInt(site.nb_pageviews, 10);
- if (revenue.length) {
- group.revenue += parseInt(revenue[0], 10);
- }
- });
- });
+ model.sites = [];
}
- function createGroupsAndMoveSitesIntoRelatedGroup(allSitesUnordered, reportMetadata)
- {
- var sitesByGroup = [];
- var groups = {};
+ function updateWebsitesList(report) {
+ if (!report) {
+ onError();
+ return;
+ }
- // we do 3 things (complete site information, create groups, move sites into group) in one step for
- // performance reason, there can be > 20k sites
- angular.forEach(allSitesUnordered, function (site, index) {
- site.idsite = reportMetadata[index].idsite;
- site.group = reportMetadata[index].group;
- site.main_url = reportMetadata[index].main_url;
- // casting evolution to int fixes sorting, see: https://github.com/piwik/piwik/issues/4885
+ var allSites = report.sites;
+ angular.forEach(allSites, function (site, index) {
site.visits_evolution = parseInt(site.visits_evolution, 10);
site.pageviews_evolution = parseInt(site.pageviews_evolution, 10);
site.revenue_evolution = parseInt(site.revenue_evolution, 10);
-
- if (site.group) {
-
- if (!groups[site.group]) {
- var group = createGroup(site.group);
-
- groups[site.group] = group;
- sitesByGroup.push(group);
- }
-
- groups[site.group].sites.push(site);
-
- } else {
- sitesByGroup.push(site);
- }
});
- calculateMetricsForEachGroup(groups);
-
- return sitesByGroup;
- }
-
- function getSumTotalActions(allSitesUnordered)
- {
- var totalActions = 0;
-
- if (allSitesUnordered && allSitesUnordered.length) {
- for (var index in allSitesUnordered) {
- var site = allSitesUnordered[index];
- if (site && site.nb_pageviews) {
- totalActions += parseInt(site.nb_pageviews, 10);
- }
- }
- }
-
- return totalActions;
- }
-
- function updateWebsitesList(processedReport) {
- if (!processedReport) {
- onError();
- return;
- }
-
- var allSitesUnordered = processedReport.reportData;
-
- model.totalActions = getSumTotalActions(allSitesUnordered);
- model.totalVisits = processedReport.reportTotal.nb_visits;
- model.totalRevenue = processedReport.reportTotal.revenue;
-
- allSitesByGroup = createGroupsAndMoveSitesIntoRelatedGroup(allSitesUnordered, processedReport.reportMetadata);
-
- if (!allSitesByGroup.length) {
- return;
- }
-
- if (model.searchTerm) {
- searchSite(model.searchTerm);
- } else {
- model.sites = allSitesByGroup;
- }
+ model.totalActions = report.totals.nb_pageviews;
+ model.totalVisits = report.totals.nb_visits;
+ model.totalRevenue = report.totals.revenue;
+ model.lastVisits = report.totals.nb_visits_lastdate;
+ model.sites = allSites;
+ model.numberOfSites = report.numSites;
+ model.lastVisitsDate = report.lastDate;
}
function getNumberOfFilteredSites () {
- var numSites = model.sites.length;
-
- var groupNames = {};
-
- for (var index = 0; index < model.sites.length; index++) {
- var site = model.sites[index];
- if (site && site.isGroup) {
- numSites += site.sites.length;
- }
- }
-
- return numSites;
+ return model.numberOfSites; // todo
}
function getNumberOfPages() {
@@ -214,100 +98,78 @@
function previousPage() {
model.currentPage = model.currentPage - 1;
+ fetchAllSites();
}
- function nextPage() {
- model.currentPage = model.currentPage + 1;
- }
-
- function nestedSearch(sitesByGroup, term)
- {
- var filteredSites = [];
+ function sortBy(metric) {
+ if (model.sortColumn == metric) {
+ model.reverse = !model.reverse;
+ }
- term = term.toLowerCase();
+ model.sortColumn = metric;
+ fetchAllSites();
+ };
- for (var index in sitesByGroup) {
- var site = sitesByGroup[index];
- if (site.isGroup) {
- var matchingSites = nestedSearch(site.sites, term);
- if (matchingSites.length || (''+site.label).toLowerCase().indexOf(term) > -1) {
- var clonedGroup = copyGroup(site);
- clonedGroup.sites = matchingSites;
- filteredSites.push(clonedGroup);
- }
- } else if ((''+site.label).toLowerCase().indexOf(term) > -1) {
- filteredSites.push(site);
- } else if (site.group && (''+site.group).toLowerCase().indexOf(term) > -1) {
- filteredSites.push(site);
- }
- }
+ function previousPage() {
+ model.currentPage = model.currentPage - 1;
+ fetchAllSites();
+ }
- return filteredSites;
+ function nextPage() {
+ model.currentPage = model.currentPage + 1;
+ fetchAllSites();
}
function searchSite (term) {
model.searchTerm = term;
model.currentPage = 0;
- model.sites = nestedSearch(allSitesByGroup, term);
- }
-
- function fetchPreviousSummary () {
- piwikApi.fetch({
- method: 'API.getLastDate'
- }).then(function (response) {
- if (response && response.value) {
- return response.value;
- }
- }).then(function (lastDate) {
- if (!lastDate) {
- return;
- }
-
- model.lastVisitsDate = lastDate;
-
- return piwikApi.fetch({
- method: 'API.getProcessedReport',
- apiModule: 'MultiSites',
- apiAction: 'getAll',
- hideMetricsDoc: '1',
- filter_limit: '0',
- showColumns: 'label,nb_visits',
- enhanced: 1,
- date: lastDate
- });
- }).then(function (response) {
- if (response && response.reportTotal) {
- model.lastVisits = response.reportTotal.nb_visits;
- }
- });
+ fetchAllSites();
}
- function fetchAllSites(refreshInterval) {
+ function fetchAllSites() {
if (model.isLoading) {
piwikApi.abort();
+ cancelRefereshInterval();
}
model.isLoading = true;
model.errorLoadingSites = false;
- return piwikApi.fetch({
- method: 'API.getProcessedReport',
- apiModule: 'MultiSites',
- apiAction: 'getAll',
+ var params = {
+ module: 'MultiSites',
+ action: 'getAllWithGroups',
hideMetricsDoc: '1',
- filter_limit: '-1',
- showColumns: 'label,nb_visits,nb_pageviews,visits_evolution,pageviews_evolution,revenue_evolution,nb_actions,revenue',
- enhanced: 1
- }).then(function (response) {
+ filter_sort_order: 'asc',
+ filter_limit: model.pageSize,
+ filter_offset: getCurrentPagingOffsetStart(),
+ showColumns: 'label,nb_visits,nb_pageviews,visits_evolution,pageviews_evolution,revenue_evolution,nb_actions,revenue'
+ };
+
+ if (model.searchTerm) {
+ params.pattern = model.searchTerm;
+ }
+
+ if (model.sortColumn) {
+ params.filter_sort_column = model.sortColumn;
+ }
+
+ if (model.reverse) {
+ params.filter_sort_order = 'desc';
+ }
+
+ return piwikApi.fetch(params).then(function (response) {
updateWebsitesList(response);
}, onError)['finally'](function () {
model.isLoading = false;
- if (refreshInterval && refreshInterval > 0) {
- $timeout(function () {
- fetchAllSites(refreshInterval);
- }, refreshInterval * 1000);
+ if (model.refreshInterval && model.refreshInterval > 0) {
+ cancelRefereshInterval();
+
+ refreshPromise = $timeout(function () {
+ refreshPromise = null;
+ fetchAllSites(model.refreshInterval);
+ }, model.refreshInterval * 1000);
}
});
}
diff --git a/plugins/MultiSites/angularjs/dashboard/dashboard.controller.js b/plugins/MultiSites/angularjs/dashboard/dashboard.controller.js
index d4dd4eba3f..3ba1b15a21 100644
--- a/plugins/MultiSites/angularjs/dashboard/dashboard.controller.js
+++ b/plugins/MultiSites/angularjs/dashboard/dashboard.controller.js
@@ -13,8 +13,6 @@
$scope.model = multisitesDashboardModel;
- $scope.reverse = true;
- $scope.predicate = 'nb_visits';
$scope.evolutionSelector = 'visits_evolution';
$scope.hasSuperUserAccess = piwik.hasSuperUserAccess;
$scope.date = piwik.broadcast.getValueFromUrl('date');
@@ -22,19 +20,9 @@
$scope.url = piwik.piwik_url;
$scope.period = piwik.period;
- $scope.sortBy = function (metric) {
-
- var reverse = $scope.reverse;
- if ($scope.predicate == metric) {
- reverse = !reverse;
- }
-
- $scope.predicate = metric;
- $scope.reverse = reverse;
- };
-
this.refresh = function (interval) {
- multisitesDashboardModel.fetchAllSites(interval);
+ multisitesDashboardModel.refreshInterval = interval;
+ multisitesDashboardModel.fetchAllSites();
};
}
})();
diff --git a/plugins/MultiSites/angularjs/dashboard/dashboard.directive.html b/plugins/MultiSites/angularjs/dashboard/dashboard.directive.html
index 287ef25c98..5c9159d076 100644
--- a/plugins/MultiSites/angularjs/dashboard/dashboard.directive.html
+++ b/plugins/MultiSites/angularjs/dashboard/dashboard.directive.html
@@ -12,30 +12,30 @@
<table id="mt" class="dataTable" cellspacing="0">
<thead>
<tr>
- <th id="names" class="label" ng-click="sortBy('label')" ng-class="{columnSorted: 'label' == predicate}">
+ <th id="names" class="label" ng-click="model.sortBy('label')" ng-class="{columnSorted: 'label' == model.sortColumn}">
<span class="heading">{{ 'General_Website'|translate }}</span>
- <span ng-class="{multisites_asc: !reverse && 'label' == predicate, multisites_desc: reverse && 'label' == predicate}" class="arrow"></span>
+ <span ng-class="{multisites_asc: !model.reverse && 'label' == model.sortColumn, multisites_desc: model.reverse && 'label' == model.sortColumn}" class="arrow"></span>
</th>
- <th id="visits" class="multisites-column" ng-click="sortBy('nb_visits')" ng-class="{columnSorted: 'nb_visits' == predicate}">
+ <th id="visits" class="multisites-column" ng-click="model.sortBy('nb_visits')" ng-class="{columnSorted: 'nb_visits' == model.sortColumn}">
<span class="heading">{{ 'General_ColumnNbVisits'|translate }}</span>
- <span ng-class="{multisites_asc: !reverse && 'nb_visits' == predicate, multisites_desc: reverse && 'nb_visits' == predicate}" class="arrow"></span>
+ <span ng-class="{multisites_asc: !model.reverse && 'nb_visits' == model.sortColumn, multisites_desc: model.reverse && 'nb_visits' == model.sortColumn}" class="arrow"></span>
</th>
- <th id="pageviews" class="multisites-column" ng-click="sortBy('nb_pageviews')" ng-class="{columnSorted: 'nb_pageviews' == predicate}">
+ <th id="pageviews" class="multisites-column" ng-click="model.sortBy('nb_pageviews')" ng-class="{columnSorted: 'nb_pageviews' == model.sortColumn}">
<span class="heading">{{ 'General_ColumnPageviews'|translate }}</span>
- <span ng-class="{multisites_asc: !reverse && 'nb_pageviews' == predicate, multisites_desc: reverse && 'nb_pageviews' == predicate}" class="arrow"></span>
+ <span ng-class="{multisites_asc: !model.reverse && 'nb_pageviews' == model.sortColumn, multisites_desc: model.reverse && 'nb_pageviews' == model.sortColumn}" class="arrow"></span>
</th>
- <th ng-if="displayRevenueColumn" id="revenue" class="multisites-column" ng-click="sortBy('revenue')" ng-class="{columnSorted: 'revenue' == predicate}">
+ <th ng-if="displayRevenueColumn" id="revenue" class="multisites-column" ng-click="model.sortBy('revenue')" ng-class="{columnSorted: 'revenue' == model.sortColumn}">
<span class="heading">{{ 'General_ColumnRevenue'|translate }}</span>
- <span ng-class="{multisites_asc: !reverse && 'revenue' == predicate, multisites_desc: reverse && 'revenue' == predicate}" class="arrow"></span>
+ <span ng-class="{multisites_asc: !model.reverse && 'revenue' == model.sortColumn, multisites_desc: model.reverse && 'revenue' == model.sortColumn}" class="arrow"></span>
</th>
- <th id="evolution" colspan="{{ showSparklines ? 2 : 1 }}" ng-class="{columnSorted: evolutionSelector == predicate}">
- <span class="arrow" ng-class="{multisites_asc: !reverse && evolutionSelector == predicate, multisites_desc: reverse && evolutionSelector == predicate}"></span>
+ <th id="evolution" colspan="{{ showSparklines ? 2 : 1 }}" ng-class="{columnSorted: evolutionSelector == model.sortColumn}">
+ <span class="arrow" ng-class="{multisites_asc: !model.reverse && evolutionSelector == model.sortColumn, multisites_desc: model.reverse && evolutionSelector == model.sortColumn}"></span>
<span class="evolution"
- ng-click="sortBy(evolutionSelector)"> {{ 'MultiSites_Evolution'|translate }}</span>
+ ng-click="model.sortBy(evolutionSelector)"> {{ 'MultiSites_Evolution'|translate }}</span>
<select class="selector" id="evolution_selector" ng-model="evolutionSelector"
- ng-change="predicate = evolutionSelector">
+ ng-change="model.sortBy(evolutionSelector)">
<option value="visits_evolution">{{ 'General_ColumnNbVisits'|translate }}</option>
<option value="pageviews_evolution">{{ 'General_ColumnPageviews'|translate }}</option>
<option ng-if="displayRevenueColumn" value="revenue_evolution">{{ 'General_ColumnRevenue'|translate }}</option>
@@ -68,10 +68,10 @@
piwik-multisites-site
date-sparkline="dateSparkline"
show-sparklines="showSparklines"
- metric="predicate"
+ metric="model.sortColumn"
ng-class-odd="'columnodd'"
display-revenue-column="displayRevenueColumn"
- ng-repeat="website in model.sites | orderBy:predicate:reverse | multiSitesGroupFilter:model.getCurrentPagingOffsetStart():model.pageSize">
+ ng-repeat="website in model.sites">
</tr>
</tbody>
@@ -108,18 +108,13 @@
<tr row_id="last">
<td colspan="8" class="site_search">
<input type="text"
- ng-change="model.searchSite(searchTerm)"
ng-model="searchTerm"
+ piwik-onenter="model.searchSite(searchTerm)"
placeholder="{{ 'Actions_SubmenuSitesearch' | translate }}">
- <img title="Search"
- ng-show="!searchTerm"
+ <img title="{{ 'General_ClickToSearch' | translate }}"
+ ng-click="model.searchSite(searchTerm)"
class="search_ico"
src="plugins/Morpheus/images/search_ico.png"/>
- <img title="Clear"
- ng-show="searchTerm"
- ng-click="searchTerm='';model.searchSite('')"
- class="reset"
- src="plugins/CoreHome/images/reset_search.png"/>
</td>
</tr>
diff --git a/plugins/MultiSites/angularjs/dashboard/dashboard.directive.less b/plugins/MultiSites/angularjs/dashboard/dashboard.directive.less
index 6502f4244c..580e3b2772 100644
--- a/plugins/MultiSites/angularjs/dashboard/dashboard.directive.less
+++ b/plugins/MultiSites/angularjs/dashboard/dashboard.directive.less
@@ -86,6 +86,7 @@
left: -25px;
margin-right: 0px;
margin-top: -1px;
+ cursor: pointer;
}
.reset {
position: relative;
diff --git a/plugins/MultiSites/tests/Fixtures/ManySitesWithVisits.php b/plugins/MultiSites/tests/Fixtures/ManySitesWithVisits.php
new file mode 100644
index 0000000000..8305ad9b33
--- /dev/null
+++ b/plugins/MultiSites/tests/Fixtures/ManySitesWithVisits.php
@@ -0,0 +1,83 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+namespace Piwik\Plugins\MultiSites\tests\Fixtures;
+
+use Piwik\Date;
+use Piwik\Tests\Framework\Fixture;
+
+/**
+ * Generates tracker testing data for our ControllerTest
+ *
+ * This Simple fixture adds one website and tracks one visit with couple pageviews and an ecommerce conversion
+ */
+class ManySitesWithVisits extends Fixture
+{
+ public $dateTime = '2013-01-23 01:23:45';
+ public $idSite = 1;
+
+ public function setUp()
+ {
+ $this->setUpWebsite();
+ $this->trackFirstVisit($this->idSite);
+ $this->trackSecondVisit($this->idSite);
+ $this->trackFirstVisit($siteId = 2);
+ $this->trackSecondVisit($siteId = 3);
+ $this->trackSecondVisit($siteId = 3);
+ $this->trackSecondVisit($siteId = 4);
+ }
+
+ public function tearDown()
+ {
+ // empty
+ }
+
+ private function setUpWebsite()
+ {
+ for ($i = 1; $i <= 15; $i++) {
+ if (!self::siteCreated($i)) {
+ $idSite = self::createWebsite($this->dateTime, $ecommerce = 1, 'Site ' . $i);
+ $this->assertSame($i, $idSite);
+ }
+ }
+ }
+
+ protected function trackFirstVisit($idSite)
+ {
+ $t = self::getTracker($idSite, $this->dateTime, $defaultInit = true);
+
+ $t->setForceVisitDateTime(Date::factory($this->dateTime)->addHour(0.1)->getDatetime());
+ $t->setUrl('http://example.com/');
+ self::checkResponse($t->doTrackPageView('Viewing homepage'));
+
+ $t->setForceVisitDateTime(Date::factory($this->dateTime)->addHour(0.2)->getDatetime());
+ $t->setUrl('http://example.com/sub/page');
+ self::checkResponse($t->doTrackPageView('Second page view'));
+
+ $t->setForceVisitDateTime(Date::factory($this->dateTime)->addHour(0.25)->getDatetime());
+ $t->addEcommerceItem($sku = 'SKU_ID', $name = 'Test item!', $category = 'Test & Category', $price = 777, $quantity = 33);
+ self::checkResponse($t->doTrackEcommerceOrder('TestingOrder', $grandTotal = 33 * 77));
+ }
+
+ protected function trackSecondVisit($idSite)
+ {
+ $t = self::getTracker($idSite, $this->dateTime, $defaultInit = true);
+ $t->setIp('56.11.55.73');
+
+ $t->setForceVisitDateTime(Date::factory($this->dateTime)->addHour(0.1)->getDatetime());
+ $t->setUrl('http://example.com/sub/page');
+ self::checkResponse($t->doTrackPageView('Viewing homepage'));
+
+ $t->setForceVisitDateTime(Date::factory($this->dateTime)->addHour(0.2)->getDatetime());
+ $t->setUrl('http://example.com/?search=this is a site search query');
+ self::checkResponse($t->doTrackPageView('Site search query'));
+
+ $t->setForceVisitDateTime(Date::factory($this->dateTime)->addHour(0.3)->getDatetime());
+ $t->addEcommerceItem($sku = 'SKU_ID2', $name = 'A durable item', $category = 'Best seller', $price = 321);
+ self::checkResponse($t->doTrackEcommerceCartUpdate($grandTotal = 33 * 77));
+ }
+} \ No newline at end of file
diff --git a/plugins/MultiSites/tests/Integration/ControllerTest.php b/plugins/MultiSites/tests/Integration/ControllerTest.php
new file mode 100644
index 0000000000..cd6ea91b11
--- /dev/null
+++ b/plugins/MultiSites/tests/Integration/ControllerTest.php
@@ -0,0 +1,126 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\MultiSites\tests\Integration;
+
+use Piwik\FrontController;
+use Piwik\Plugins\MultiSites\tests\fixtures\ManySitesWithVisits;
+use Piwik\Tests\Framework\TestCase\SystemTestCase;
+
+/**
+ * @group MultiSites
+ * @group ControllerTest
+ * @group Plugins
+ */
+class ControllerTest extends SystemTestCase
+{
+ /**
+ * @var ManySitesWithVisits
+ */
+ public static $fixture = null; // initialized below class definition
+
+ public function test_getAllWithGroups()
+ {
+ $sites = $this->requestGetAllWithGroups(array('filter_limit' => 20));
+ $this->assertTrue(is_string($sites));
+
+ $sites = json_decode($sites, true);
+
+ // as limit is 20 make sure it returns all 15 sites but we do not check for all the detailed sites info,
+ // this is tested in other tests. We only check for first site.
+ $this->assertSame(15, count($sites['sites']));
+ $this->assertEquals(array(
+ 'label' => 'Site 1',
+ 'nb_visits' => 2,
+ 'nb_pageviews' => 3,
+ 'revenue' => '$ 2541',
+ 'visits_evolution' => '100%',
+ 'pageviews_evolution' => '100%',
+ 'revenue_evolution' => '100%',
+ 'idsite' => 1,
+ 'group' => '',
+ 'main_url' => 'http://piwik.net',
+ ), $sites['sites'][0]);
+
+ unset($sites['sites']);
+ $expected = array(
+ 'numSites' => 15,
+ 'totals' => array(
+ 'nb_pageviews' => 8,
+ 'nb_visits' => 5,
+ 'revenue' => 5082,
+ 'nb_visits_lastdate' => 0,
+ ),
+ 'lastDate' => '2013-01-22'
+ );
+
+ $this->assertEquals($expected, $sites);
+ }
+
+ public function test_getAllWithGroups_ifLimitIsApplied_ShouldStill_ReturnCorrectNumberOfSitesAvailable()
+ {
+ $sites = $this->requestGetAllWithGroups(array('filter_limit' => 5));
+ $sites = json_decode($sites, true);
+
+ $this->assertSame(5, count($sites['sites']));
+ $this->assertSame(15, $sites['numSites']);
+ $this->assertReturnedSitesEquals(array(1, 2, 3, 4, 5), $sites);
+ }
+
+ public function test_getAllWithGroups_shouldBeAbleToHandleLimitAndOffset()
+ {
+ $sites = $this->requestGetAllWithGroups(array('filter_limit' => 5, 'filter_offset' => 4));
+ $sites = json_decode($sites, true);
+
+ $this->assertSame(5, count($sites['sites']));
+ $this->assertSame(15, $sites['numSites']);
+ $this->assertReturnedSitesEquals(array(5, 6, 7, 8, 9), $sites);
+ }
+
+ public function test_getAllWithGroups_shouldApplySearchAndReturnInNumSitesOnlyTheNumberOfMatchingSites()
+ {
+ $pattern = 'Site 1';
+ $sites = $this->requestGetAllWithGroups(array('filter_limit' => 5, 'pattern' => $pattern));
+ $sites = json_decode($sites, true);
+
+ $this->assertSame(5, count($sites['sites']));
+ $this->assertSame(1 + 6, $sites['numSites']); // Site 1 + Site10-15
+ $this->assertReturnedSitesEquals(array(1, 10, 11, 12, 13), $sites);
+ }
+
+ private function assertReturnedSitesEquals($expectedSiteIds, $sites)
+ {
+ foreach ($expectedSiteIds as $index => $expectedSiteId) {
+ $this->assertSame($expectedSiteId, $sites['sites'][$index]['idsite']);
+ }
+ }
+
+ private function requestGetAllWithGroups($params)
+ {
+ $oldGet = $_GET;
+ $params['period'] = 'day';
+ $params['date'] = '2013-01-23';
+ $_GET = $params;
+ $sites = FrontController::getInstance()->dispatch('MultiSites', 'getAllWithGroups');
+ $_GET = $oldGet;
+ return $sites;
+ }
+
+ public static function getOutputPrefix()
+ {
+ return '';
+ }
+
+ public static function getPathToTestDirectory()
+ {
+ return dirname(__FILE__);
+ }
+
+}
+
+ControllerTest::$fixture = new ManySitesWithVisits(); \ No newline at end of file
diff --git a/plugins/MultiSites/tests/Integration/DashboardTest.php b/plugins/MultiSites/tests/Integration/DashboardTest.php
new file mode 100644
index 0000000000..33d671cbb2
--- /dev/null
+++ b/plugins/MultiSites/tests/Integration/DashboardTest.php
@@ -0,0 +1,401 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Plugins\MultiSites\tests\Integration;
+
+use Piwik\DataTable;
+use Piwik\Period;
+use Piwik\Plugins\MultiSites\Dashboard;
+use Piwik\Tests\Framework\Fixture;
+use Piwik\Tests\Framework\TestCase\IntegrationTestCase;
+
+/**
+ * @group MultiSites
+ * @group DashboardTest
+ * @group Dashboard
+ * @group Plugins
+ */
+class DashboardTest extends IntegrationTestCase
+{
+ /**
+ * @var Dashboard
+ */
+ private $dashboard;
+
+ private $numSitesToCreate = 3;
+
+ public function setUp()
+ {
+ parent::setUp();
+
+ for ($i = 1; $i <= $this->numSitesToCreate; $i++) {
+ Fixture::createWebsite('2012-12-12 00:00:00', $ecommerce = 0, 'Site ' . $i);
+ }
+
+ $this->dashboard = $this->getMockBuilder('Piwik\Plugins\MultiSites\Dashboard')
+ ->setMethods(null)
+ ->disableOriginalConstructor()
+ ->getMock();
+ }
+
+ public function test__construct_shouldFetchSitesWithNeededColumns_AndReturnEvenSitesHavingNoVisits()
+ {
+ $dayToFetch = '2012-12-13';
+ $lastDate = '2012-12-12';
+
+ $dashboard = new Dashboard('day', $dayToFetch, false);
+
+ $this->assertSame($this->numSitesToCreate, $dashboard->getNumSites());
+ $this->assertEquals($lastDate, $dashboard->getLastDate());
+
+ $expectedTotals = array(
+ 'nb_pageviews' => 0,
+ 'nb_visits' => 0,
+ 'revenue' => 0,
+ 'nb_visits_lastdate' => 0,
+ );
+ $this->assertEquals($expectedTotals, $dashboard->getTotals());
+
+ $expectedSites = array (
+ array (
+ 'label' => 'Site 1',
+ 'nb_visits' => 0,
+ 'nb_pageviews' => 0,
+ 'revenue' => '$ 0',
+ 'visits_evolution' => '0%',
+ 'pageviews_evolution' => '0%',
+ 'revenue_evolution' => '0%',
+ 'idsite' => 1,
+ 'group' => '',
+ 'main_url' => 'http://piwik.net',
+ ),
+ array (
+ 'label' => 'Site 2',
+ 'nb_visits' => 0,
+ 'nb_pageviews' => 0,
+ 'revenue' => '$ 0',
+ 'visits_evolution' => '0%',
+ 'pageviews_evolution' => '0%',
+ 'revenue_evolution' => '0%',
+ 'idsite' => 2,
+ 'group' => '',
+ 'main_url' => 'http://piwik.net',
+ ),
+ array (
+ 'label' => 'Site 3',
+ 'nb_visits' => 0,
+ 'nb_pageviews' => 0,
+ 'revenue' => '$ 0',
+ 'visits_evolution' => '0%',
+ 'pageviews_evolution' => '0%',
+ 'revenue_evolution' => '0%',
+ 'idsite' => 3,
+ 'group' => '',
+ 'main_url' => 'http://piwik.net',
+ ),
+ );
+ $this->assertEquals($expectedSites, $dashboard->getSites(array(), $limit = 10));
+ }
+
+ public function test__construct_shouldActuallyFindSitesWhenSeaching()
+ {
+ $dashboard = new Dashboard('day', '2012-12-13', false);
+ $this->assertSame($this->numSitesToCreate, $dashboard->getNumSites());
+
+ $expectedSites = array (
+ array (
+ 'label' => 'Site 2',
+ 'nb_visits' => 0,
+ 'nb_pageviews' => 0,
+ 'revenue' => '$ 0',
+ 'visits_evolution' => '0%',
+ 'pageviews_evolution' => '0%',
+ 'revenue_evolution' => '0%',
+ 'idsite' => 2,
+ 'group' => '',
+ 'main_url' => 'http://piwik.net',
+ ),
+ );
+ $dashboard->search('site 2');
+ $this->assertEquals($expectedSites, $dashboard->getSites(array(), $limit = 10));
+ $this->assertSame(1, $dashboard->getNumSites());
+ }
+
+ public function test_getNumSites_shouldBeZeroIfNoSitesAreSet()
+ {
+ $this->assertSame(0, $this->dashboard->getNumSites());
+ }
+
+ public function test_getNumSites_shouldReturnTheNumberOfSetSites()
+ {
+ $this->setSitesTable(4);
+
+ $this->assertSame(4, $this->dashboard->getNumSites());
+ }
+
+ public function test_getSites_shouldReturnAnArrayOfSites()
+ {
+ $this->setSitesTable(8);
+
+ $expectedSites = $this->buildSitesArray(array(1, 2, 3, 4, 5, 6, 7, 8));
+
+ $this->assertEquals($expectedSites, $this->dashboard->getSites(array(), $limit = 20));
+ }
+
+ public function test_getSites_shouldApplyALimit()
+ {
+ $this->setSitesTable(8);
+
+ $expectedSites = $this->buildSitesArray(array(1, 2, 3, 4));
+
+ $this->assertEquals($expectedSites, $this->dashboard->getSites(array(), $limit = 4));
+ }
+
+ public function test_getSites_WithGroup_shouldApplyALimitAndKeepSitesWithinGroup()
+ {
+ $sites = $this->setSitesTable(20);
+
+ $this->setGroupForSiteId($sites, $siteId = 1, 'group1');
+ $this->setGroupForSiteId($sites, $siteId = 2, 'group2');
+ $this->setGroupForSiteId($sites, $siteId = 3, 'group1');
+ $this->setGroupForSiteId($sites, $siteId = 4, 'group4');
+ $this->setGroupForSiteId($sites, $siteId = 15, 'group1');
+ $this->setGroupForSiteId($sites, $siteId = 16, 'group1');
+ $this->setGroupForSiteId($sites, $siteId = 18, 'group1');
+ $this->setGroupForSiteId($sites, $siteId = 6, 'group4');
+ $this->dashboard->setSitesTable($sites);
+
+ $expectedSites = array (
+ array (
+ 'label' => 'group1',
+ 'nb_visits' => 50, // there are 5 matching sites having that group, we only return 4, still result is correct!
+ 'isGroup' => 1,
+ ), array (
+ 'label' => 'Site1',
+ 'nb_visits' => 10,
+ 'group' => 'group1',
+ ), array (
+ 'label' => 'Site3',
+ 'nb_visits' => 10,
+ 'group' => 'group1',
+ ), array (
+ 'label' => 'Site15',
+ 'nb_visits' => 10,
+ 'group' => 'group1',
+ ),
+ );
+
+ $this->assertEquals($expectedSites, $this->dashboard->getSites(array(), $limit = 4));
+ }
+
+ public function test_search_shouldUpdateTheNumberOfAvailableSites()
+ {
+ $this->setSitesTable(100);
+
+ $this->dashboard->search('site1');
+
+ // site1 + site1* matches
+ $this->assertSame(12, $this->dashboard->getNumSites());
+ }
+
+ public function test_search_shouldOnlyKeepMatchingSites()
+ {
+ $this->setSitesTable(100);
+
+ $this->dashboard->search('site1');
+
+ $expectedSites = $this->buildSitesArray(array(1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 100));
+
+ $this->assertEquals($expectedSites, $this->dashboard->getSites(array(), $limit = 20));
+ }
+
+ public function test_search_noSiteMatches()
+ {
+ $this->setSitesTable(100);
+
+ $this->dashboard->search('anYString');
+
+ $this->assertSame(0, $this->dashboard->getNumSites());
+ $this->assertEquals(array(), $this->dashboard->getSites(array(), $limit = 20));
+ }
+
+ public function test_search_WithGroup_shouldDoesSearchInGroupNameAndMatchesEvenSitesHavingThatGroupName()
+ {
+ $sites = $this->setSitesTable(20);
+
+ $this->setGroupForSiteId($sites, $siteId = 1, 'group1');
+ $this->setGroupForSiteId($sites, $siteId = 2, 'group2');
+ $this->setGroupForSiteId($sites, $siteId = 3, 'group1');
+ $this->setGroupForSiteId($sites, $siteId = 4, 'group4');
+ $this->setGroupForSiteId($sites, $siteId = 15, 'group1');
+ $this->setGroupForSiteId($sites, $siteId = 16, 'group1');
+ $this->setGroupForSiteId($sites, $siteId = 18, 'group1');
+ $this->setGroupForSiteId($sites, $siteId = 6, 'group4');
+
+ $this->dashboard->setSitesTable($sites);
+ $this->dashboard->search('group');
+
+ // groups within that site should be listed first.
+ $expectedSites = array (
+ array (
+ 'label' => 'group1',
+ 'nb_visits' => 50,
+ 'isGroup' => 1,
+ ),
+ array (
+ 'label' => 'Site1',
+ 'nb_visits' => 10,
+ 'group' => 'group1',
+ ),
+ array (
+ 'label' => 'Site3',
+ 'nb_visits' => 10,
+ 'group' => 'group1',
+ ),
+ array (
+ 'label' => 'Site15',
+ 'nb_visits' => 10,
+ 'group' => 'group1',
+ ),
+ array (
+ 'label' => 'Site16',
+ 'nb_visits' => 10,
+ 'group' => 'group1',
+ ),
+ array (
+ 'label' => 'Site18',
+ 'nb_visits' => 10,
+ 'group' => 'group1',
+ ),
+ array (
+ 'label' => 'group4',
+ 'nb_visits' => 20,
+ 'isGroup' => 1,
+ ),
+ array (
+ 'label' => 'Site4',
+ 'nb_visits' => 10,
+ 'group' => 'group4',
+ ),
+ array (
+ 'label' => 'Site6',
+ 'nb_visits' => 10,
+ 'group' => 'group4',
+ ),
+ array (
+ 'label' => 'group2',
+ 'nb_visits' => 10,
+ 'isGroup' => 1,
+ ),
+ array (
+ 'label' => 'Site2',
+ 'nb_visits' => 10,
+ 'group' => 'group2',
+ ),
+ );
+
+ // 3 groups + 8 sites having a group.
+ $this->assertSame(3 + 8, $this->dashboard->getNumSites());
+
+ $matchingSites = $this->dashboard->getSites(array(), $limit = 20);
+ $this->assertEquals($expectedSites, $matchingSites);
+
+ // test with limit should only return the first results
+ $matchingSites = $this->dashboard->getSites(array(), $limit = 8);
+ $this->assertEquals(array_slice($expectedSites, 0, 8), $matchingSites);
+ }
+
+ public function test_search_WithGroup_IfASiteMatchesButNotTheGroupName_ItShouldKeepTheGroupThough()
+ {
+ $sites = $this->setSitesTable(20);
+
+ $this->setGroupForSiteId($sites, $siteId = 1, 'group1');
+ $this->setGroupForSiteId($sites, $siteId = 2, 'group2');
+ $this->setGroupForSiteId($sites, $siteId = 3, 'group1');
+ $this->setGroupForSiteId($sites, $siteId = 20, 'group4');
+ $this->setGroupForSiteId($sites, $siteId = 15, 'group1');
+ $this->setGroupForSiteId($sites, $siteId = 16, 'group1');
+ $this->setGroupForSiteId($sites, $siteId = 18, 'group1');
+ $this->setGroupForSiteId($sites, $siteId = 6, 'group4');
+
+ $this->dashboard->setSitesTable($sites);
+ $this->dashboard->search('site2');
+
+ $expectedSites = array (
+ array (
+ 'label' => 'group4',
+ 'nb_visits' => 20, // another site belongs to that group which doesn't match that name yet still we need to sum the correct result.
+ 'isGroup' => 1,
+ ),
+ array (
+ 'label' => 'Site20',
+ 'nb_visits' => 10,
+ 'group' => 'group4',
+ ),
+ array (
+ 'label' => 'group2',
+ 'nb_visits' => 10,
+ 'isGroup' => 1,
+ ),
+ array (
+ 'label' => 'Site2',
+ 'nb_visits' => 10,
+ 'group' => 'group2',
+ ),
+ );
+
+ // 2 matching sites + their group
+ $this->assertSame(2 + 2, $this->dashboard->getNumSites());
+
+ $matchingSites = $this->dashboard->getSites(array(), $limit = 20);
+ $this->assertEquals($expectedSites, $matchingSites);
+ }
+
+ public function test_getLastDate_shouldReturnTheLastDate_IfAnyIsSet()
+ {
+ $this->setSitesTable(1);
+
+ $this->assertSame('2012-12-12', $this->dashboard->getLastDate());
+ }
+
+ public function test_getLastDate_shouldReturnAnEmptyString_IfNoLastDateIsSet()
+ {
+ $this->dashboard->setSitesTable(new DataTable());
+
+ $this->assertSame('', $this->dashboard->getLastDate());
+ }
+
+ private function setGroupForSiteId(DataTable $table, $siteId, $groupName)
+ {
+ $table->getRowFromLabel('Site' . $siteId)->setMetadata('group', $groupName);
+ }
+
+ private function setSitesTable($numSites)
+ {
+ $sites = new DataTable();
+ $sites->addRowsFromSimpleArray($this->buildSitesArray(range(1, $numSites)));
+ $sites->setMetadata('last_period_date', Period\Factory::build('day', '2012-12-12'));
+
+ $this->dashboard->setSitesTable($sites);
+
+ return $sites;
+ }
+
+ private function buildSitesArray($siteIds)
+ {
+ $sites = array();
+
+ foreach ($siteIds as $siteId) {
+ $sites[] = array('label' => 'Site' . $siteId, 'nb_visits' => 10);
+ }
+
+ return $sites;
+
+ }
+
+}
diff --git a/tests/PHPUnit/Integration/Archive/DataTableFactoryTest.php b/tests/PHPUnit/Integration/Archive/DataTableFactoryTest.php
new file mode 100644
index 0000000000..9645685c34
--- /dev/null
+++ b/tests/PHPUnit/Integration/Archive/DataTableFactoryTest.php
@@ -0,0 +1,360 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Tests\Integration\Archive;
+
+use Piwik\Access;
+use Piwik\Archive;
+use Piwik\ArchiveProcessor;
+use Piwik\DataTable;
+use Piwik\DataTable\DataTableInterface;
+use Piwik\DataTable\Row;
+use Piwik\Db;
+use Piwik\Period;
+use Piwik\Segment;
+use Piwik\Site;
+use Piwik\Tests\Framework\Fixture;
+use Piwik\Tests\Framework\Mock\FakeAccess;
+use Piwik\Tests\Framework\TestCase\IntegrationTestCase;
+use Piwik\Period\Factory as PeriodFactory;
+use Piwik\Archive\DataTableFactory;
+
+/**
+ * @group DataTableFactoryTest
+ * @group DataTableFactoryjj
+ * @group Archive
+ * @group Core
+ */
+class DataTableFactoryTest extends IntegrationTestCase
+{
+ private $site1 = 3;
+ private $site2 = 4;
+ private $date1range = '2012-12-12,2012-12-12';
+ private $date2range = '2012-12-13,2012-12-13';
+
+ private $date1 = '2012-12-12';
+ private $date2 = '2012-12-13';
+
+ private $defaultRow = array(
+ 'nb_visits' => 97
+ );
+
+ public function setUp()
+ {
+ parent::setUp();
+
+ // setup the access layer
+ $pseudoMockAccess = new FakeAccess;
+ FakeAccess::$superUser = true;
+ Access::setSingletonInstance($pseudoMockAccess);
+
+ for ($i = 0; $i < $this->site2; $i++) {
+ Fixture::createWebsite('2015-01-01 00:00:00');
+ }
+ }
+
+ public function test_makeMerged_numeric_noIndices_shouldContainDefaultRow_IfNoDataGiven()
+ {
+ $indices = $this->getResultIndices($period = false, $site = false);
+ $factory = $this->createFactory($indices);
+
+ $table = $factory->makeMerged($index = array(), $indices);
+
+ $this->assertTableIsDataTableSimpleInstance($table);
+ $this->assertRowCountEquals(1, $table);
+ $this->assertRowEquals($this->defaultRow, $this->site1, $table->getFirstRow());
+ $this->assertTableMetadataEquals($this->date1, $table);
+ }
+
+ public function test_makeMerged_numeric_noIndices_shouldContainOnlyOneRowWithTheData_IfAnyDataIsSet()
+ {
+ $indices = $this->getResultIndices($period = false, $site = false);
+ $factory = $this->createFactory($indices);
+
+ $index = array('nb_visits' => 10, 'nb_pageviews' => 21);
+
+ $dataTable = $factory->makeMerged($index, $indices);
+
+ $this->assertTableIsDataTableSimpleInstance($dataTable);
+ $this->assertRowCountEquals(1, $dataTable);
+ $this->assertRowEquals($index, $this->site1, $dataTable->getFirstRow());
+ $this->assertTableMetadataEquals($this->date1, $dataTable);
+ }
+
+ public function test_makeMerged_numeric_periodIndices_shouldGenerateAMapOfTables_AndUseDefaultRow_IfNoData()
+ {
+ $indices = $this->getResultIndices($period = true, $site = false);
+ $factory = $this->createFactory($indices);
+
+ $index = array(
+ $this->date1range => array(),
+ $this->date2range => array(),
+ );
+
+ $map = $factory->makeMerged($index, $indices);
+
+ $this->assertTrue($map instanceof DataTable\Map);
+ $this->assertRowCountEquals(2, $map);
+
+ foreach ($map->getDataTables() as $label => $table) {
+ $this->assertTrue(in_array($label, array($this->date1, $this->date2)));
+ $this->assertTableIsDataTableSimpleInstance($table);
+ $this->assertRowCountEquals(1, $table);
+ $this->assertRowEquals($this->defaultRow, $this->site1, $table->getFirstRow());
+ $this->assertTableMetadataEquals($label, $table);
+ }
+ }
+
+ public function test_makeMerged_numeric_periodIndices_shouldGenerateAMapOfTables_WithData()
+ {
+ $indices = $this->getResultIndices($period = true, $site = false);
+ $factory = $this->createFactory($indices);
+
+ $row1 = array('nb_visits' => 37, 'nb_pageviews' => 10);
+ $row2 = array('nb_visits' => 34, 'nb_hits' => 21);
+
+ $index = array(
+ $this->date1range => $row1,
+ $this->date2range => $row2,
+ );
+
+ $map = $factory->makeMerged($index, $indices);
+
+ $this->assertTrue($map instanceof DataTable\Map);
+ $this->assertRowCountEquals(2, $map);
+
+ foreach ($map->getDataTables() as $label => $table) {
+ $this->assertTableIsDataTableSimpleInstance($table);
+ $this->assertRowCountEquals(1, $table);
+ $this->assertTableMetadataEquals($label, $table);
+ }
+
+ $this->assertRowEquals($row1, $this->site1, $map->getTable($this->date1)->getFirstRow());
+ $this->assertRowEquals($row2, $this->site1, $map->getTable($this->date2)->getFirstRow());
+ }
+
+ public function test_makeMerged_numeric_periodIndices_shouldSetAKeyName()
+ {
+ $indices = $this->getResultIndices($period = true, $site = false);
+ $factory = $this->createFactory($indices);
+
+ $index = array(
+ $this->date1range => array(),
+ $this->date2range => array(),
+ );
+
+ $map = $factory->makeMerged($index, $indices);
+
+ $this->assertSame('date', $map->getKeyName());
+ }
+
+ public function test_makeMerged_numeric_siteIndices_shouldUseDefaultRow_IfNoData()
+ {
+ $indices = $this->getResultIndices($period = false, $site = true);
+ $factory = $this->createFactory($indices);
+
+ $index = array(
+ $this->site1 => array(),
+ $this->site2 => array(),
+ );
+
+ $table = $factory->makeMerged($index, $indices);
+
+ $this->assertTableIsDataTableInstance($table);
+ $this->assertRowCountEquals(2, $table);
+
+ $this->assertRowEquals($this->defaultRow, $this->site1, $table->getRowFromId(0));
+ $this->assertRowEquals($this->defaultRow, $this->site2, $table->getRowFromId(1));
+ $this->assertTableMetadataEquals($this->date1, $table);
+ }
+
+ public function test_makeMerged_numeric_siteIndices_shouldGenerateAMapOfTables_WithData()
+ {
+ $indices = $this->getResultIndices($period = false, $site = true);
+ $factory = $this->createFactory($indices);
+
+ $row1 = array('nb_visits' => 37, 'nb_pageviews' => 10);
+ $row2 = array('nb_visits' => 34, 'nb_hits' => 21);
+
+ $index = array(
+ $this->site1 => $row1,
+ $this->site2 => $row2,
+ );
+
+ $table = $factory->makeMerged($index, $indices);
+
+ $this->assertTableIsDataTableInstance($table);
+ $this->assertRowCountEquals(2, $table);
+
+ $this->assertRowEquals($row1, $this->site1, $table->getRowFromId(0));
+ $this->assertRowEquals($row2, $this->site2, $table->getRowFromId(1));
+ $this->assertTableMetadataEquals($this->date1, $table);
+ }
+
+ public function test_makeMerged_numeric_siteAndPeriodIndices_shouldUseDefaultRow_IfNoData()
+ {
+ $indices = $this->getResultIndices($period = true, $site = true);
+ $factory = $this->createFactory($indices);
+
+ $index = array(
+ $this->site1 => array(
+ $this->date1range => array(),
+ $this->date2range => array()
+ ),
+ $this->site2 => array(
+ $this->date1range => array(),
+ $this->date2range => array()
+ ),
+ );
+
+ $map = $factory->makeMerged($index, $indices);
+
+ $this->assertTrue($map instanceof DataTable\Map);
+ $this->assertRowCountEquals(2, $map);
+ $this->assertSame('date', $map->getKeyName());
+
+ foreach ($map->getDataTables() as $label => $table) {
+ $this->assertTrue(in_array($label, array($this->date1, $this->date2)));
+ $this->assertTableIsDataTableInstance($table);
+ $this->assertRowCountEquals(2, $table);
+ $this->assertTableMetadataEquals($label, $table);
+ }
+
+ $this->assertRowEquals($this->defaultRow, $this->site1, $map->getTable($this->date1)->getRowFromId(0));
+ $this->assertRowEquals($this->defaultRow, $this->site2, $map->getTable($this->date1)->getRowFromId(1));
+ $this->assertRowEquals($this->defaultRow, $this->site1, $map->getTable($this->date2)->getRowFromId(0));
+ $this->assertRowEquals($this->defaultRow, $this->site2, $map->getTable($this->date2)->getRowFromId(1));
+ }
+
+ public function test_makeMerged_numeric_siteAndPeriodIndices_shouldGenerateAMapOfTables_WithData()
+ {
+ $indices = $this->getResultIndices($period = true, $site = true);
+ $factory = $this->createFactory($indices);
+
+ $row1 = array('nb_visits' => 37, 'nb_pageviews' => 10);
+ $row2 = array('nb_visits' => 34, 'nb_hits' => 21);
+ $row3 = array('nb_visits' => 23);
+
+ $index = array(
+ $this->site1 => array(
+ $this->date1range => $row1,
+ $this->date2range => array()
+ ),
+ $this->site2 => array(
+ $this->date1range => $row2,
+ $this->date2range => $row3
+ ),
+ );
+
+ $map = $factory->makeMerged($index, $indices);
+
+ $this->assertTrue($map instanceof DataTable\Map);
+ $this->assertRowCountEquals(2, $map);
+
+ foreach ($map->getDataTables() as $label => $table) {
+ $this->assertTrue(in_array($label, array($this->date1, $this->date2)));
+ $this->assertTableIsDataTableInstance($table);
+ $this->assertRowCountEquals(2, $table);
+ $this->assertTableMetadataEquals($label, $table);
+ }
+
+ $this->assertRowEquals($row1, $this->site1, $map->getTable($this->date1)->getRowFromId(0));
+ $this->assertRowEquals($row2, $this->site2, $map->getTable($this->date1)->getRowFromId(1));
+ $this->assertRowEquals($this->defaultRow, $this->site1, $map->getTable($this->date2)->getRowFromId(0));
+ $this->assertRowEquals($row3, $this->site2, $map->getTable($this->date2)->getRowFromId(1));
+ }
+
+ /**
+ * @expectedException \Exception
+ * @expectedExceptionMessage supposed to work with non-numeric data types but it is not tested
+ */
+ public function test_makeMerged_shouldThrowAnException_IfANonNumericDataTypeIsGiven()
+ {
+ $dataType = 'blob';
+ $dataNames = array('nb_visits');
+
+ $factory = new DataTableFactory($dataNames, $dataType, array($this->site1), $periods = array(), $this->defaultRow);
+ $factory->makeMerged(array(), array());
+ }
+
+ private function assertTableMetadataEquals($expectedPeriod, DataTable $dataTable)
+ {
+ $period = $dataTable->getMetadata(DataTableFactory::TABLE_METADATA_PERIOD_INDEX);
+
+ $this->assertFalse($dataTable->getMetadata(DataTableFactory::TABLE_METADATA_SITE_INDEX));
+ $this->assertTrue($period instanceof Period);
+ $this->assertSame($expectedPeriod, $period->toString());
+ }
+
+ private function assertRowCountEquals($expectedCount, $tableOrMap)
+ {
+ if ($tableOrMap instanceof DataTable\Map) {
+ $this->assertSame($expectedCount, $tableOrMap->getRowsCount());
+ } elseif ($tableOrMap instanceof DataTable) {
+ $this->assertSame($expectedCount, $tableOrMap->getRowsCountRecursive());
+ } else {
+ throw new \Exception('wrong argument passed to assertRowCountEquals()');
+ }
+ }
+
+ private function assertRowEquals($expectedColumns, $expectedSiteIdInMetadata, Row $row)
+ {
+ $this->assertEquals($expectedColumns, $row->getColumns());
+ $this->assertEquals(array('idsite' => $expectedSiteIdInMetadata), $row->getMetadata());
+ }
+
+ private function assertTableIsDataTableInstance($table)
+ {
+ $this->assertTrue($table instanceof DataTable);
+ $this->assertFalse($table instanceof DataTable\Simple);
+ }
+
+ private function assertTableIsDataTableSimpleInstance($table)
+ {
+ $this->assertTrue($table instanceof DataTable\Simple);
+ }
+
+ private function createFactory($resultIndices)
+ {
+ $periods = array(
+ $this->date1range => PeriodFactory::build('day', $this->date1),
+ $this->date2range => PeriodFactory::build('day', $this->date2),
+ );
+ $dataType = 'numeric';
+ $siteIds = array($this->site1, $this->site2);
+ $dataNames = array('nb_visits', 'nb_pageviews');
+ $defaultRow = $this->defaultRow;
+
+ if (!array_key_exists(DataTableFactory::TABLE_METADATA_PERIOD_INDEX, $resultIndices)) {
+ $periods = array($periods[$this->date1range]);
+ }
+
+ if (!array_key_exists(DataTableFactory::TABLE_METADATA_SITE_INDEX, $resultIndices)) {
+ $siteIds = array($siteIds[0]);
+ }
+
+ return new DataTableFactory($dataNames, $dataType, $siteIds, $periods, $defaultRow);
+ }
+
+ private function getResultIndices($periodIndex = false, $siteIndex = false)
+ {
+ $indices = array();
+
+ if ($siteIndex) {
+ $indices[DataTableFactory::TABLE_METADATA_SITE_INDEX] = 'idSite';
+ }
+
+ if ($periodIndex) {
+ $indices[DataTableFactory::TABLE_METADATA_PERIOD_INDEX] = 'date';
+ }
+
+ return $indices;
+ }
+
+
+}
diff --git a/tests/PHPUnit/System/expected/test_BackwardsCompatibility1XTest__MultiSites.getAll_day.xml b/tests/PHPUnit/System/expected/test_BackwardsCompatibility1XTest__MultiSites.getAll_day.xml
index 01a5e25002..5218ad3961 100644
--- a/tests/PHPUnit/System/expected/test_BackwardsCompatibility1XTest__MultiSites.getAll_day.xml
+++ b/tests/PHPUnit/System/expected/test_BackwardsCompatibility1XTest__MultiSites.getAll_day.xml
@@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8" ?>
<result>
<row>
+ <label>new name</label>
<nb_visits>2</nb_visits>
<nb_actions>8</nb_actions>
<nb_pageviews>4</nb_pageviews>
<revenue>43</revenue>
- <label>new name</label>
<visits_evolution>100%</visits_evolution>
<actions_evolution>100%</actions_evolution>
<pageviews_evolution>100%</pageviews_evolution>
<revenue_evolution>100%</revenue_evolution>
+ <idsite>1</idsite>
<group />
<main_url>http://site.com</main_url>
- <idsite>1</idsite>
</row>
</result> \ No newline at end of file
diff --git a/tests/PHPUnit/System/expected/test_ImportLogs__MultiSites.getAll_month.xml b/tests/PHPUnit/System/expected/test_ImportLogs__MultiSites.getAll_month.xml
index 74524d2848..5f6148c689 100644
--- a/tests/PHPUnit/System/expected/test_ImportLogs__MultiSites.getAll_month.xml
+++ b/tests/PHPUnit/System/expected/test_ImportLogs__MultiSites.getAll_month.xml
@@ -10,9 +10,9 @@
<actions_evolution>100%</actions_evolution>
<pageviews_evolution>100%</pageviews_evolution>
<revenue_evolution>100%</revenue_evolution>
+ <idsite>1</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>1</idsite>
</row>
<row>
<label>Piwik test two</label>
@@ -24,8 +24,8 @@
<actions_evolution>100%</actions_evolution>
<pageviews_evolution>100%</pageviews_evolution>
<revenue_evolution>0%</revenue_evolution>
+ <idsite>2</idsite>
<group />
<main_url>http://example-site-two.com</main_url>
- <idsite>2</idsite>
</row>
</result> \ No newline at end of file
diff --git a/tests/PHPUnit/System/expected/test_ImportLogs_withEnhancedAndLast7__MultiSites.getAll_month.xml b/tests/PHPUnit/System/expected/test_ImportLogs_withEnhancedAndLast7__MultiSites.getAll_month.xml
index 84d1611e02..4ab55e378e 100644
--- a/tests/PHPUnit/System/expected/test_ImportLogs_withEnhancedAndLast7__MultiSites.getAll_month.xml
+++ b/tests/PHPUnit/System/expected/test_ImportLogs_withEnhancedAndLast7__MultiSites.getAll_month.xml
@@ -13,9 +13,9 @@
<pageviews_evolution>100%</pageviews_evolution>
<revenue_evolution>100%</revenue_evolution>
<nb_conversions_evolution>100%</nb_conversions_evolution>
+ <idsite>1</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>1</idsite>
</row>
<row>
<label>Piwik test two</label>
@@ -29,9 +29,9 @@
<pageviews_evolution>100%</pageviews_evolution>
<revenue_evolution>0%</revenue_evolution>
<nb_conversions_evolution>0%</nb_conversions_evolution>
+ <idsite>2</idsite>
<group />
<main_url>http://example-site-two.com</main_url>
- <idsite>2</idsite>
</row>
</result>
<result date="2012-09">
@@ -47,195 +47,195 @@
<pageviews_evolution>-73.7%</pageviews_evolution>
<revenue_evolution>-97%</revenue_evolution>
<nb_conversions_evolution>-97%</nb_conversions_evolution>
+ <idsite>1</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>1</idsite>
</row>
<row>
+ <label>Piwik test two</label>
<nb_visits>0</nb_visits>
<nb_actions>0</nb_actions>
<nb_pageviews>0</nb_pageviews>
<revenue>0</revenue>
<nb_conversions>0</nb_conversions>
- <label>Piwik test two</label>
<visits_evolution>-100%</visits_evolution>
<actions_evolution>-100%</actions_evolution>
<pageviews_evolution>-100%</pageviews_evolution>
<revenue_evolution>0%</revenue_evolution>
<nb_conversions_evolution>0%</nb_conversions_evolution>
+ <idsite>2</idsite>
<group />
<main_url>http://example-site-two.com</main_url>
- <idsite>2</idsite>
</row>
</result>
<result date="2012-10">
<row>
+ <label>Piwik test</label>
<nb_visits>0</nb_visits>
<nb_actions>0</nb_actions>
<nb_pageviews>0</nb_pageviews>
<revenue>0</revenue>
<nb_conversions>0</nb_conversions>
- <label>Piwik test</label>
<visits_evolution>-100%</visits_evolution>
<actions_evolution>-100%</actions_evolution>
<pageviews_evolution>-100%</pageviews_evolution>
<revenue_evolution>-100%</revenue_evolution>
<nb_conversions_evolution>-100%</nb_conversions_evolution>
+ <idsite>1</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>1</idsite>
</row>
<row>
+ <label>Piwik test two</label>
<nb_visits>0</nb_visits>
<nb_actions>0</nb_actions>
<nb_pageviews>0</nb_pageviews>
<revenue>0</revenue>
<nb_conversions>0</nb_conversions>
- <label>Piwik test two</label>
<visits_evolution>0%</visits_evolution>
<actions_evolution>0%</actions_evolution>
<pageviews_evolution>0%</pageviews_evolution>
<revenue_evolution>0%</revenue_evolution>
<nb_conversions_evolution>0%</nb_conversions_evolution>
+ <idsite>2</idsite>
<group />
<main_url>http://example-site-two.com</main_url>
- <idsite>2</idsite>
</row>
</result>
<result date="2012-11">
<row>
+ <label>Piwik test</label>
<nb_visits>0</nb_visits>
<nb_actions>0</nb_actions>
<nb_pageviews>0</nb_pageviews>
<revenue>0</revenue>
<nb_conversions>0</nb_conversions>
- <label>Piwik test</label>
<visits_evolution>0%</visits_evolution>
<actions_evolution>0%</actions_evolution>
<pageviews_evolution>0%</pageviews_evolution>
<revenue_evolution>0%</revenue_evolution>
<nb_conversions_evolution>0%</nb_conversions_evolution>
+ <idsite>1</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>1</idsite>
</row>
<row>
+ <label>Piwik test two</label>
<nb_visits>0</nb_visits>
<nb_actions>0</nb_actions>
<nb_pageviews>0</nb_pageviews>
<revenue>0</revenue>
<nb_conversions>0</nb_conversions>
- <label>Piwik test two</label>
<visits_evolution>0%</visits_evolution>
<actions_evolution>0%</actions_evolution>
<pageviews_evolution>0%</pageviews_evolution>
<revenue_evolution>0%</revenue_evolution>
<nb_conversions_evolution>0%</nb_conversions_evolution>
+ <idsite>2</idsite>
<group />
<main_url>http://example-site-two.com</main_url>
- <idsite>2</idsite>
</row>
</result>
<result date="2012-12">
<row>
+ <label>Piwik test</label>
<nb_visits>0</nb_visits>
<nb_actions>0</nb_actions>
<nb_pageviews>0</nb_pageviews>
<revenue>0</revenue>
<nb_conversions>0</nb_conversions>
- <label>Piwik test</label>
<visits_evolution>0%</visits_evolution>
<actions_evolution>0%</actions_evolution>
<pageviews_evolution>0%</pageviews_evolution>
<revenue_evolution>0%</revenue_evolution>
<nb_conversions_evolution>0%</nb_conversions_evolution>
+ <idsite>1</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>1</idsite>
</row>
<row>
+ <label>Piwik test two</label>
<nb_visits>0</nb_visits>
<nb_actions>0</nb_actions>
<nb_pageviews>0</nb_pageviews>
<revenue>0</revenue>
<nb_conversions>0</nb_conversions>
- <label>Piwik test two</label>
<visits_evolution>0%</visits_evolution>
<actions_evolution>0%</actions_evolution>
<pageviews_evolution>0%</pageviews_evolution>
<revenue_evolution>0%</revenue_evolution>
<nb_conversions_evolution>0%</nb_conversions_evolution>
+ <idsite>2</idsite>
<group />
<main_url>http://example-site-two.com</main_url>
- <idsite>2</idsite>
</row>
</result>
<result date="2013-01">
<row>
+ <label>Piwik test</label>
<nb_visits>0</nb_visits>
<nb_actions>0</nb_actions>
<nb_pageviews>0</nb_pageviews>
<revenue>0</revenue>
<nb_conversions>0</nb_conversions>
- <label>Piwik test</label>
<visits_evolution>0%</visits_evolution>
<actions_evolution>0%</actions_evolution>
<pageviews_evolution>0%</pageviews_evolution>
<revenue_evolution>0%</revenue_evolution>
<nb_conversions_evolution>0%</nb_conversions_evolution>
+ <idsite>1</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>1</idsite>
</row>
<row>
+ <label>Piwik test two</label>
<nb_visits>0</nb_visits>
<nb_actions>0</nb_actions>
<nb_pageviews>0</nb_pageviews>
<revenue>0</revenue>
<nb_conversions>0</nb_conversions>
- <label>Piwik test two</label>
<visits_evolution>0%</visits_evolution>
<actions_evolution>0%</actions_evolution>
<pageviews_evolution>0%</pageviews_evolution>
<revenue_evolution>0%</revenue_evolution>
<nb_conversions_evolution>0%</nb_conversions_evolution>
+ <idsite>2</idsite>
<group />
<main_url>http://example-site-two.com</main_url>
- <idsite>2</idsite>
</row>
</result>
<result date="2013-02">
<row>
+ <label>Piwik test</label>
<nb_visits>0</nb_visits>
<nb_actions>0</nb_actions>
<nb_pageviews>0</nb_pageviews>
<revenue>0</revenue>
<nb_conversions>0</nb_conversions>
- <label>Piwik test</label>
<visits_evolution>0%</visits_evolution>
<actions_evolution>0%</actions_evolution>
<pageviews_evolution>0%</pageviews_evolution>
<revenue_evolution>0%</revenue_evolution>
<nb_conversions_evolution>0%</nb_conversions_evolution>
+ <idsite>1</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>1</idsite>
</row>
<row>
+ <label>Piwik test two</label>
<nb_visits>0</nb_visits>
<nb_actions>0</nb_actions>
<nb_pageviews>0</nb_pageviews>
<revenue>0</revenue>
<nb_conversions>0</nb_conversions>
- <label>Piwik test two</label>
<visits_evolution>0%</visits_evolution>
<actions_evolution>0%</actions_evolution>
<pageviews_evolution>0%</pageviews_evolution>
<revenue_evolution>0%</revenue_evolution>
<nb_conversions_evolution>0%</nb_conversions_evolution>
+ <idsite>2</idsite>
<group />
<main_url>http://example-site-two.com</main_url>
- <idsite>2</idsite>
</row>
</result>
</results> \ No newline at end of file
diff --git a/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits__MultiSites.getAll_day.xml b/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits__MultiSites.getAll_day.xml
index 0ecae3aea1..5218ad3961 100644
--- a/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits__MultiSites.getAll_day.xml
+++ b/tests/PHPUnit/System/expected/test_OneVisitorTwoVisits__MultiSites.getAll_day.xml
@@ -10,8 +10,8 @@
<actions_evolution>100%</actions_evolution>
<pageviews_evolution>100%</pageviews_evolution>
<revenue_evolution>100%</revenue_evolution>
+ <idsite>1</idsite>
<group />
<main_url>http://site.com</main_url>
- <idsite>1</idsite>
</row>
</result> \ No newline at end of file
diff --git a/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions_MultiSites.getAll_firstSite_lastN__API.getProcessedReport_day.xml b/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions_MultiSites.getAll_firstSite_lastN__API.getProcessedReport_day.xml
index 885d4243f4..bf674444b7 100644
--- a/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions_MultiSites.getAll_firstSite_lastN__API.getProcessedReport_day.xml
+++ b/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions_MultiSites.getAll_firstSite_lastN__API.getProcessedReport_day.xml
@@ -207,56 +207,56 @@
<reportMetadata>
<result prettyDate="Sunday 3 January 2010">
<row>
+ <idsite>1</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>1</idsite>
</row>
</result>
<result prettyDate="Monday 4 January 2010">
<row>
+ <idsite>1</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>1</idsite>
</row>
<row>
+ <idsite>2</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>2</idsite>
</row>
</result>
<result prettyDate="Tuesday 5 January 2010">
<row>
+ <idsite>1</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>1</idsite>
</row>
</result>
<result prettyDate="Wednesday 6 January 2010">
<row>
+ <idsite>1</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>1</idsite>
</row>
</result>
<result prettyDate="Thursday 7 January 2010">
<row>
+ <idsite>1</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>1</idsite>
</row>
</result>
<result prettyDate="Friday 8 January 2010">
<row>
+ <idsite>1</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>1</idsite>
</row>
</result>
<result prettyDate="Saturday 9 January 2010">
<row>
+ <idsite>1</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>1</idsite>
</row>
</result>
</reportMetadata>
diff --git a/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions__MultiSites.getAll_day.xml b/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions__MultiSites.getAll_day.xml
index 365dd2ff8d..2efd1eaab3 100644
--- a/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions__MultiSites.getAll_day.xml
+++ b/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions__MultiSites.getAll_day.xml
@@ -11,9 +11,9 @@
<actions_evolution>100%</actions_evolution>
<pageviews_evolution>100%</pageviews_evolution>
<revenue_evolution>100%</revenue_evolution>
+ <idsite>1</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>1</idsite>
</row>
</result>
<result date="2010-01-04">
@@ -27,9 +27,9 @@
<actions_evolution>-50%</actions_evolution>
<pageviews_evolution>-50%</pageviews_evolution>
<revenue_evolution>-100%</revenue_evolution>
+ <idsite>1</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>1</idsite>
</row>
<row>
<label>Site 2</label>
@@ -41,9 +41,9 @@
<actions_evolution>100%</actions_evolution>
<pageviews_evolution>100%</pageviews_evolution>
<revenue_evolution>0%</revenue_evolution>
+ <idsite>2</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>2</idsite>
</row>
</result>
<result date="2010-01-05">
@@ -57,9 +57,9 @@
<actions_evolution>400%</actions_evolution>
<pageviews_evolution>400%</pageviews_evolution>
<revenue_evolution>100%</revenue_evolution>
+ <idsite>1</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>1</idsite>
</row>
</result>
<result date="2010-01-06">
@@ -73,9 +73,9 @@
<actions_evolution>0%</actions_evolution>
<pageviews_evolution>0%</pageviews_evolution>
<revenue_evolution>0%</revenue_evolution>
+ <idsite>1</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>1</idsite>
</row>
</result>
<result date="2010-01-07">
@@ -89,9 +89,9 @@
<actions_evolution>0%</actions_evolution>
<pageviews_evolution>0%</pageviews_evolution>
<revenue_evolution>0%</revenue_evolution>
+ <idsite>1</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>1</idsite>
</row>
</result>
<result date="2010-01-08">
@@ -105,9 +105,9 @@
<actions_evolution>0%</actions_evolution>
<pageviews_evolution>0%</pageviews_evolution>
<revenue_evolution>0%</revenue_evolution>
+ <idsite>1</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>1</idsite>
</row>
</result>
<result date="2010-01-09">
@@ -121,9 +121,9 @@
<actions_evolution>0%</actions_evolution>
<pageviews_evolution>0%</pageviews_evolution>
<revenue_evolution>0%</revenue_evolution>
+ <idsite>1</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>1</idsite>
</row>
</result>
</results> \ No newline at end of file
diff --git a/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions__MultiSites.getAll_month.xml b/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions__MultiSites.getAll_month.xml
index cf37606414..a06b60b001 100644
--- a/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions__MultiSites.getAll_month.xml
+++ b/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions__MultiSites.getAll_month.xml
@@ -11,9 +11,9 @@
<actions_evolution>100%</actions_evolution>
<pageviews_evolution>100%</pageviews_evolution>
<revenue_evolution>100%</revenue_evolution>
+ <idsite>1</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>1</idsite>
</row>
<row>
<label>Site 2</label>
@@ -25,9 +25,9 @@
<actions_evolution>100%</actions_evolution>
<pageviews_evolution>100%</pageviews_evolution>
<revenue_evolution>0%</revenue_evolution>
+ <idsite>2</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>2</idsite>
</row>
</result>
<result date="2010-02" />
diff --git a/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions__MultiSites.getAll_week.xml b/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions__MultiSites.getAll_week.xml
index 59f331588b..b3df616c47 100644
--- a/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions__MultiSites.getAll_week.xml
+++ b/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions__MultiSites.getAll_week.xml
@@ -11,9 +11,9 @@
<actions_evolution>100%</actions_evolution>
<pageviews_evolution>100%</pageviews_evolution>
<revenue_evolution>100%</revenue_evolution>
+ <idsite>1</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>1</idsite>
</row>
</result>
<result date="From 2010-01-04 to 2010-01-10">
@@ -27,9 +27,9 @@
<actions_evolution>1450%</actions_evolution>
<pageviews_evolution>1450%</pageviews_evolution>
<revenue_evolution>200%</revenue_evolution>
+ <idsite>1</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>1</idsite>
</row>
<row>
<label>Site 2</label>
@@ -41,9 +41,9 @@
<actions_evolution>100%</actions_evolution>
<pageviews_evolution>100%</pageviews_evolution>
<revenue_evolution>0%</revenue_evolution>
+ <idsite>2</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>2</idsite>
</row>
</result>
<result date="From 2010-01-11 to 2010-01-17">
@@ -57,9 +57,9 @@
<actions_evolution>-67.7%</actions_evolution>
<pageviews_evolution>-67.7%</pageviews_evolution>
<revenue_evolution>-66.7%</revenue_evolution>
+ <idsite>1</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>1</idsite>
</row>
</result>
<result date="From 2010-01-18 to 2010-01-24" />
diff --git a/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions__MultiSites.getAll_year.xml b/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions__MultiSites.getAll_year.xml
index b58279dbc5..aad34392f8 100644
--- a/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions__MultiSites.getAll_year.xml
+++ b/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions__MultiSites.getAll_year.xml
@@ -11,9 +11,9 @@
<actions_evolution>100%</actions_evolution>
<pageviews_evolution>100%</pageviews_evolution>
<revenue_evolution>100%</revenue_evolution>
+ <idsite>1</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>1</idsite>
</row>
<row>
<label>Site 2</label>
@@ -25,9 +25,9 @@
<actions_evolution>100%</actions_evolution>
<pageviews_evolution>100%</pageviews_evolution>
<revenue_evolution>0%</revenue_evolution>
+ <idsite>2</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>2</idsite>
</row>
</result>
<result date="2011" />
diff --git a/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions_idSiteOne___MultiSites.getAll_day.xml b/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions_idSiteOne___MultiSites.getAll_day.xml
index 365dd2ff8d..2efd1eaab3 100644
--- a/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions_idSiteOne___MultiSites.getAll_day.xml
+++ b/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions_idSiteOne___MultiSites.getAll_day.xml
@@ -11,9 +11,9 @@
<actions_evolution>100%</actions_evolution>
<pageviews_evolution>100%</pageviews_evolution>
<revenue_evolution>100%</revenue_evolution>
+ <idsite>1</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>1</idsite>
</row>
</result>
<result date="2010-01-04">
@@ -27,9 +27,9 @@
<actions_evolution>-50%</actions_evolution>
<pageviews_evolution>-50%</pageviews_evolution>
<revenue_evolution>-100%</revenue_evolution>
+ <idsite>1</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>1</idsite>
</row>
<row>
<label>Site 2</label>
@@ -41,9 +41,9 @@
<actions_evolution>100%</actions_evolution>
<pageviews_evolution>100%</pageviews_evolution>
<revenue_evolution>0%</revenue_evolution>
+ <idsite>2</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>2</idsite>
</row>
</result>
<result date="2010-01-05">
@@ -57,9 +57,9 @@
<actions_evolution>400%</actions_evolution>
<pageviews_evolution>400%</pageviews_evolution>
<revenue_evolution>100%</revenue_evolution>
+ <idsite>1</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>1</idsite>
</row>
</result>
<result date="2010-01-06">
@@ -73,9 +73,9 @@
<actions_evolution>0%</actions_evolution>
<pageviews_evolution>0%</pageviews_evolution>
<revenue_evolution>0%</revenue_evolution>
+ <idsite>1</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>1</idsite>
</row>
</result>
<result date="2010-01-07">
@@ -89,9 +89,9 @@
<actions_evolution>0%</actions_evolution>
<pageviews_evolution>0%</pageviews_evolution>
<revenue_evolution>0%</revenue_evolution>
+ <idsite>1</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>1</idsite>
</row>
</result>
<result date="2010-01-08">
@@ -105,9 +105,9 @@
<actions_evolution>0%</actions_evolution>
<pageviews_evolution>0%</pageviews_evolution>
<revenue_evolution>0%</revenue_evolution>
+ <idsite>1</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>1</idsite>
</row>
</result>
<result date="2010-01-09">
@@ -121,9 +121,9 @@
<actions_evolution>0%</actions_evolution>
<pageviews_evolution>0%</pageviews_evolution>
<revenue_evolution>0%</revenue_evolution>
+ <idsite>1</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>1</idsite>
</row>
</result>
</results> \ No newline at end of file
diff --git a/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions_idSiteOne___MultiSites.getAll_month.xml b/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions_idSiteOne___MultiSites.getAll_month.xml
index cf37606414..a06b60b001 100644
--- a/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions_idSiteOne___MultiSites.getAll_month.xml
+++ b/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions_idSiteOne___MultiSites.getAll_month.xml
@@ -11,9 +11,9 @@
<actions_evolution>100%</actions_evolution>
<pageviews_evolution>100%</pageviews_evolution>
<revenue_evolution>100%</revenue_evolution>
+ <idsite>1</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>1</idsite>
</row>
<row>
<label>Site 2</label>
@@ -25,9 +25,9 @@
<actions_evolution>100%</actions_evolution>
<pageviews_evolution>100%</pageviews_evolution>
<revenue_evolution>0%</revenue_evolution>
+ <idsite>2</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>2</idsite>
</row>
</result>
<result date="2010-02" />
diff --git a/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions_idSiteOne___MultiSites.getAll_week.xml b/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions_idSiteOne___MultiSites.getAll_week.xml
index 59f331588b..b3df616c47 100644
--- a/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions_idSiteOne___MultiSites.getAll_week.xml
+++ b/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions_idSiteOne___MultiSites.getAll_week.xml
@@ -11,9 +11,9 @@
<actions_evolution>100%</actions_evolution>
<pageviews_evolution>100%</pageviews_evolution>
<revenue_evolution>100%</revenue_evolution>
+ <idsite>1</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>1</idsite>
</row>
</result>
<result date="From 2010-01-04 to 2010-01-10">
@@ -27,9 +27,9 @@
<actions_evolution>1450%</actions_evolution>
<pageviews_evolution>1450%</pageviews_evolution>
<revenue_evolution>200%</revenue_evolution>
+ <idsite>1</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>1</idsite>
</row>
<row>
<label>Site 2</label>
@@ -41,9 +41,9 @@
<actions_evolution>100%</actions_evolution>
<pageviews_evolution>100%</pageviews_evolution>
<revenue_evolution>0%</revenue_evolution>
+ <idsite>2</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>2</idsite>
</row>
</result>
<result date="From 2010-01-11 to 2010-01-17">
@@ -57,9 +57,9 @@
<actions_evolution>-67.7%</actions_evolution>
<pageviews_evolution>-67.7%</pageviews_evolution>
<revenue_evolution>-66.7%</revenue_evolution>
+ <idsite>1</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>1</idsite>
</row>
</result>
<result date="From 2010-01-18 to 2010-01-24" />
diff --git a/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions_idSiteOne___MultiSites.getAll_year.xml b/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions_idSiteOne___MultiSites.getAll_year.xml
index b58279dbc5..aad34392f8 100644
--- a/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions_idSiteOne___MultiSites.getAll_year.xml
+++ b/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions_idSiteOne___MultiSites.getAll_year.xml
@@ -11,9 +11,9 @@
<actions_evolution>100%</actions_evolution>
<pageviews_evolution>100%</pageviews_evolution>
<revenue_evolution>100%</revenue_evolution>
+ <idsite>1</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>1</idsite>
</row>
<row>
<label>Site 2</label>
@@ -25,9 +25,9 @@
<actions_evolution>100%</actions_evolution>
<pageviews_evolution>100%</pageviews_evolution>
<revenue_evolution>0%</revenue_evolution>
+ <idsite>2</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>2</idsite>
</row>
</result>
<result date="2011" />
diff --git a/tests/PHPUnit/System/expected/test_oneVisitor_oneWebsite_severalDays_DateRange_IndexedByDate__MultiSites.getAll_day.xml b/tests/PHPUnit/System/expected/test_oneVisitor_oneWebsite_severalDays_DateRange_IndexedByDate__MultiSites.getAll_day.xml
index 3d6283b321..a1c7e55b5f 100644
--- a/tests/PHPUnit/System/expected/test_oneVisitor_oneWebsite_severalDays_DateRange_IndexedByDate__MultiSites.getAll_day.xml
+++ b/tests/PHPUnit/System/expected/test_oneVisitor_oneWebsite_severalDays_DateRange_IndexedByDate__MultiSites.getAll_day.xml
@@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8" ?>
<result>
<row>
+ <label>Site AAAAAA</label>
<nb_visits>2</nb_visits>
<nb_actions>3</nb_actions>
<nb_pageviews>3</nb_pageviews>
<revenue>0</revenue>
- <label>Site AAAAAA</label>
<visits_evolution>0%</visits_evolution>
<actions_evolution>0%</actions_evolution>
<pageviews_evolution>0%</pageviews_evolution>
<revenue_evolution>0%</revenue_evolution>
+ <idsite>1</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>1</idsite>
</row>
</result> \ No newline at end of file
diff --git a/tests/PHPUnit/System/expected/test_oneVisitor_oneWebsite_severalDays_DateRange_MultipleDatesNotSupported__MultiSites.getAll_day.xml b/tests/PHPUnit/System/expected/test_oneVisitor_oneWebsite_severalDays_DateRange_MultipleDatesNotSupported__MultiSites.getAll_day.xml
index 2fb69ad710..f714cc123d 100644
--- a/tests/PHPUnit/System/expected/test_oneVisitor_oneWebsite_severalDays_DateRange_MultipleDatesNotSupported__MultiSites.getAll_day.xml
+++ b/tests/PHPUnit/System/expected/test_oneVisitor_oneWebsite_severalDays_DateRange_MultipleDatesNotSupported__MultiSites.getAll_day.xml
@@ -11,9 +11,9 @@
<actions_evolution>0%</actions_evolution>
<pageviews_evolution>0%</pageviews_evolution>
<revenue_evolution>0%</revenue_evolution>
+ <idsite>1</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>1</idsite>
</row>
<row>
<label>SITE BBbbBB</label>
@@ -25,9 +25,9 @@
<actions_evolution>0%</actions_evolution>
<pageviews_evolution>0%</pageviews_evolution>
<revenue_evolution>0%</revenue_evolution>
+ <idsite>2</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>2</idsite>
</row>
</result>
<result date="2010-12-16" />
@@ -50,9 +50,9 @@
<actions_evolution>100%</actions_evolution>
<pageviews_evolution>100%</pageviews_evolution>
<revenue_evolution>0%</revenue_evolution>
+ <idsite>1</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>1</idsite>
</row>
<row>
<label>SITE BBbbBB</label>
@@ -64,9 +64,9 @@
<actions_evolution>100%</actions_evolution>
<pageviews_evolution>100%</pageviews_evolution>
<revenue_evolution>0%</revenue_evolution>
+ <idsite>2</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>2</idsite>
</row>
</result>
<result date="2010-12-26" />
@@ -100,9 +100,9 @@
<actions_evolution>100%</actions_evolution>
<pageviews_evolution>100%</pageviews_evolution>
<revenue_evolution>0%</revenue_evolution>
+ <idsite>1</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>1</idsite>
</row>
</result>
</results> \ No newline at end of file
diff --git a/tests/PHPUnit/System/expected/test_oneVisitor_oneWebsite_severalDays_DateRange__MultiSites.getAll_range.xml b/tests/PHPUnit/System/expected/test_oneVisitor_oneWebsite_severalDays_DateRange__MultiSites.getAll_range.xml
index 5e03453232..d758a762ff 100644
--- a/tests/PHPUnit/System/expected/test_oneVisitor_oneWebsite_severalDays_DateRange__MultiSites.getAll_range.xml
+++ b/tests/PHPUnit/System/expected/test_oneVisitor_oneWebsite_severalDays_DateRange__MultiSites.getAll_range.xml
@@ -6,9 +6,9 @@
<nb_actions>9</nb_actions>
<nb_pageviews>9</nb_pageviews>
<revenue>0</revenue>
+ <idsite>1</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>1</idsite>
</row>
<row>
<label>SITE BBbbBB</label>
@@ -16,8 +16,8 @@
<nb_actions>2</nb_actions>
<nb_pageviews>2</nb_pageviews>
<revenue>0</revenue>
+ <idsite>2</idsite>
<group />
<main_url>http://piwik.net</main_url>
- <idsite>2</idsite>
</row>
</result> \ No newline at end of file
diff --git a/tests/PHPUnit/Unit/Archive/DataCollectionTest.php b/tests/PHPUnit/Unit/Archive/DataCollectionTest.php
new file mode 100644
index 0000000000..1aebd06f2b
--- /dev/null
+++ b/tests/PHPUnit/Unit/Archive/DataCollectionTest.php
@@ -0,0 +1,284 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Tests\Unit;
+
+use Piwik\Archive\DataCollection;
+use Piwik\Archive\DataTableFactory;
+use Piwik\Period;
+use Piwik\Tests\Framework\TestCase\UnitTestCase;
+
+/**
+ * @group DataCollectionTest
+ * @group DataCollection
+ * @group Archive
+ * @group Core
+ */
+class DataCollectionTest extends UnitTestCase
+{
+ private $site1 = 1;
+ private $site2 = 2;
+ private $date1 = '2012-12-12,2012-12-12';
+ private $date2 = '2012-12-13,2012-12-13';
+
+ public function setUp()
+ {
+ parent::setUp();
+
+ }
+
+ private function createCollection($onlyOnePeriod = false, $onlyOneSite = false)
+ {
+ $periods = array(
+ Period\Factory::build('day', '2012-12-12'),
+ Period\Factory::build('day', '2012-12-13'),
+ );
+ $dataType = 'numeric';
+ $siteIds = array($this->site1, $this->site2);
+ $dataNames = array('Name1', 'Name2');
+ $defaultRow = array(
+ 'default' => 1
+ );
+
+ if ($onlyOnePeriod) {
+ $periods = array($periods[0]);
+ }
+
+ if ($onlyOneSite) {
+ $siteIds = array($siteIds[0]);
+ }
+
+ return new DataCollection($dataNames, $dataType, $siteIds, $periods, $defaultRow);
+ }
+
+ public function test_getIndexedArray_numeric_noResultIndices_noData()
+ {
+ $collection = $this->createCollection($onlyOnePeriod = true, $onlyOneSite = true);
+ $this->assertEquals(array(), $collection->getIndexedArray($resultIndices = array()));
+ }
+
+ public function test_getIndexedArray_numeric_noResultIndices_withData()
+ {
+ $collection = $this->createCollection($onlyOnePeriod = true, $onlyOneSite = true);
+ $collection->set($this->site1, '2012-12-12,2012-12-12', 'nb_visits', '5');
+ $collection->set($this->site1, '2012-12-12,2012-12-12', 'nb_unique_visits', '10');
+
+ $expected = array(
+ 'default' => 1,
+ 'nb_visits' => '5',
+ 'nb_unique_visits' => '10',
+ );
+
+ $this->assertEquals($expected, $collection->getIndexedArray($resultIndices = array()));
+ }
+
+ public function test_getIndexedArray_numeric_noResultIndices_withDefaultOverwritten()
+ {
+ $collection = $this->createCollection($onlyOnePeriod = true, $onlyOneSite = true);
+ $collection->set($this->site1, '2012-12-12,2012-12-12', 'nb_visits', '5');
+ $collection->set($this->site1, '2012-12-12,2012-12-12', 'default', '10');
+
+ $expected = array(
+ 'default' => '10',
+ 'nb_visits' => '5'
+ );
+
+ $this->assertEquals($expected, $collection->getIndexedArray($resultIndices = array()));
+ }
+
+ private function getSiteResultIndices()
+ {
+ return array(DataTableFactory::TABLE_METADATA_SITE_INDEX => 'idSite');
+ }
+
+ public function test_getIndexedArray_numeric_withSiteResultIndices_noData()
+ {
+ $collection = $this->createCollection();
+
+ $this->assertEquals(array(
+ 1 => array(),
+ 2 => array()
+ ), $collection->getIndexedArray($this->getSiteResultIndices()));
+ }
+
+ public function test_getIndexedArray_numeric_withSiteResultIndices_withData()
+ {
+ $collection = $this->createCollection();
+ $collection->set($this->site1, '2012-12-12,2012-12-12', 'nb_visits', '5');
+ $collection->set($this->site1, '2012-12-12,2012-12-12', 'nb_unique_visits', '10');
+
+ $expected = array(
+ 1 => array(
+ 'default' => 1,
+ 'nb_visits' => '5',
+ 'nb_unique_visits' => '10',
+ ),
+ 2 => array(
+ )
+ );
+
+ $this->assertEquals($expected, $collection->getIndexedArray($this->getSiteResultIndices()));
+ }
+
+ public function test_getIndexedArray_numeric_withSiteResultIndices_withDefaultOverwritten()
+ {
+ $collection = $this->createCollection();
+ $collection->set($this->site1, '2012-12-12,2012-12-12', 'nb_visits', '5');
+ $collection->set($this->site1, '2012-12-12,2012-12-12', 'default', '10');
+ $collection->set($this->site2, '2012-12-12,2012-12-12', 'nb_visits', '15');
+
+ $expected = array(
+ 1 => array(
+ 'default' => '10',
+ 'nb_visits' => '5'
+ ),
+ 2 => array(
+ 'default' => 1,
+ 'nb_visits' => '15'
+ )
+ );
+
+ $this->assertEquals($expected, $collection->getIndexedArray($this->getSiteResultIndices()));
+ }
+
+ private function getPeriodResultIndices()
+ {
+ return array(DataTableFactory::TABLE_METADATA_PERIOD_INDEX => 'date');
+ }
+
+ public function test_getIndexedArray_numeric_withPeriodResultIndices_noData()
+ {
+ $collection = $this->createCollection($onlyOnePeriod = false, $onlyOneSite = true);
+
+ $this->assertEquals(array(
+ $this->date1 => array(),
+ $this->date2 => array()
+ ), $collection->getIndexedArray($this->getPeriodResultIndices()));
+ }
+
+ public function test_getIndexedArray_numeric_withPeriodResultIndices_withData()
+ {
+ $collection = $this->createCollection($onlyOnePeriod = false, $onlyOneSite = true);
+ $collection->set($this->site1, $this->date1, 'nb_visits', '5');
+ $collection->set($this->site1, $this->date1, 'nb_unique_visits', '10');
+
+ $expected = array(
+ $this->date1 => array(
+ 'default' => 1,
+ 'nb_visits' => '5',
+ 'nb_unique_visits' => '10',
+ ),
+ $this->date2 => array(
+ )
+ );
+
+ $this->assertEquals($expected, $collection->getIndexedArray($this->getPeriodResultIndices()));
+ }
+
+ public function test_getIndexedArray_numeric_withPeriodResultIndices_withDefaultOverwritten()
+ {
+ $collection = $this->createCollection($onlyOnePeriod = false, $onlyOneSite = true);
+ $collection->set($this->site1, $this->date1, 'nb_visits', '5');
+ $collection->set($this->site1, $this->date1, 'default', '10');
+ $collection->set($this->site1, $this->date2, 'nb_visits', '15');
+
+ $expected = array(
+ $this->date1 => array(
+ 'default' => '10',
+ 'nb_visits' => '5'
+ ),
+ $this->date2 => array(
+ 'default' => 1,
+ 'nb_visits' => '15'
+ )
+ );
+
+ $this->assertEquals($expected, $collection->getIndexedArray($this->getPeriodResultIndices()));
+ }
+
+
+ private function getPeriodAndSiteResultIndices()
+ {
+ return array_merge($this->getSiteResultIndices(), $this->getPeriodResultIndices());
+ }
+
+ public function test_getIndexedArray_numeric_withPeriodAndSiteResultIndices_noData()
+ {
+ $collection = $this->createCollection();
+
+ $expected = array(
+ $this->site1 => array(
+ $this->date1 => array(),
+ $this->date2 => array()
+ ),
+ $this->site2 => array(
+ $this->date1 => array(),
+ $this->date2 => array()
+ )
+ );
+
+ $this->assertEquals($expected, $collection->getIndexedArray($this->getPeriodAndSiteResultIndices()));
+ }
+
+ public function test_getIndexedArray_numeric_withPeriodAndSiteResultIndices_withData()
+ {
+ $collection = $this->createCollection();
+ $collection->set($this->site1, $this->date1, 'nb_visits', '5');
+ $collection->set($this->site1, $this->date1, 'nb_unique_visits', '10');
+ $collection->set($this->site2, $this->date1, 'nb_unique_visits', '21');
+ $collection->set($this->site2, $this->date2, 'nb_unique_visits', '22');
+
+ $expected = array(
+ $this->site1 => array(
+ $this->date1 => array(
+ 'default' => 1,
+ 'nb_visits' => '5',
+ 'nb_unique_visits' => '10',
+ ),
+ $this->date2 => array()
+ ),
+ $this->site2 => array(
+ $this->date1 => array(
+ 'default' => 1,
+ 'nb_unique_visits' => '21',
+ ),
+ $this->date2 => array(
+ 'default' => 1,
+ 'nb_unique_visits' => '22',
+ )
+ )
+ );
+
+ $this->assertEquals($expected, $collection->getIndexedArray($this->getPeriodAndSiteResultIndices()));
+ }
+
+ public function test_getIndexedArray_numeric_withPeriodAndSiteResultIndices_withDefaultOverwritten()
+ {
+ $collection = $this->createCollection();
+ $collection->set($this->site1, $this->date1, 'nb_visits', '5');
+ $collection->set($this->site1, $this->date1, 'default', '10');
+ $collection->set($this->site2, $this->date1, 'default', '21');
+
+ $expected = array(
+ $this->site1 => array(
+ $this->date1 => array(
+ 'default' => 10,
+ 'nb_visits' => '5',
+ ),
+ $this->date2 => array()
+ ),
+ $this->site2 => array(
+ $this->date1 => array('default' => 21),
+ $this->date2 => array()
+ )
+ );
+
+ $this->assertEquals($expected, $collection->getIndexedArray($this->getPeriodAndSiteResultIndices()));
+ }
+
+} \ No newline at end of file
diff --git a/tests/UI/specs/MultiSites_spec.js b/tests/UI/specs/MultiSites_spec.js
new file mode 100644
index 0000000000..869c0f9075
--- /dev/null
+++ b/tests/UI/specs/MultiSites_spec.js
@@ -0,0 +1,47 @@
+/*!
+ * Piwik - free/libre analytics platform
+ *
+ * Screenshot integration tests.
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+describe("MultiSitesTest", function () {
+ this.timeout(0);
+
+ var generalParams = 'idSite=1&period=year&date=2012-08-09';
+ var selector = '.pageWrap,.expandDataTableFooterDrawer';
+
+ beforeEach(function () {
+ delete testEnvironment.configOverride;
+ testEnvironment.testUseRegularAuth = 0;
+ testEnvironment.save();
+ });
+
+ after(function () {
+ delete testEnvironment.queryParamOverride;
+ testEnvironment.testUseRegularAuth = 0;
+ testEnvironment.save();
+ });
+
+ it('should load the all websites dashboard correctly', function (done) {
+ expect.screenshot('all_websites').to.be.captureSelector(selector, function (page) {
+ page.load("?" + generalParams + "&module=MultiSites&action=index");
+ }, done);
+ });
+
+ it('should search correctly', function (done) {
+ expect.screenshot('all_websites_search').to.be.captureSelector(selector, function (page) {
+ page.sendKeys('.site_search input', 'Site');
+ page.click('.site_search .search_ico');
+ }, done);
+ });
+
+ it('should toggle sort order when click on current metric', function (done) {
+ expect.screenshot('all_websites_changed_sort_order').to.be.captureSelector(selector, function (page) {
+ page.click('#visits .heading');
+ }, done);
+ });
+
+}); \ No newline at end of file
diff --git a/tests/UI/specs/UIIntegration_spec.js b/tests/UI/specs/UIIntegration_spec.js
index af0846dbf2..ad497ad849 100644
--- a/tests/UI/specs/UIIntegration_spec.js
+++ b/tests/UI/specs/UIIntegration_spec.js
@@ -514,12 +514,6 @@ describe("UIIntegrationTest", function () { // TODO: Rename to Piwik?
});
// top bar pages
- it('should load the all websites dashboard correctly', function (done) {
- expect.screenshot('all_websites').to.be.captureSelector('.pageWrap,.expandDataTableFooterDrawer', function (page) {
- page.load("?" + generalParams + "&module=MultiSites&action=index");
- }, done);
- });
-
it('should load the widgets listing page correctly', function (done) {
expect.screenshot('widgets_listing').to.be.captureSelector('#content', function (page) {
page.load("?" + generalParams + "&module=Widgetize&action=index");