diff --git a/CHANGELOG.md b/CHANGELOG.md index ce013f131c20c23174775c3f766a165d8e55b644..382fd9d4d5e9f0e357c3ed05ba9980fd8ffd22c7 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 5178718e732ed1c3d75000b554a2afb8a7ad79bb..8026fc0f5b7611acabe06cf30b662f9d4a3b8b61 100644 --- a/core/Archive.php +++ b/core/Archive.php @@ -356,6 +356,20 @@ class Archive return $data->getDataTable($this->getResultIndices()); } + /** + * 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. * @@ -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 efd63cd925b3fa4cc6e3a0c110da2a3ba2551bbf..7973d0148181443d73120b64cb77775a2e027c71 100644 --- a/core/Archive/DataCollection.php +++ b/core/Archive/DataCollection.php @@ -137,6 +137,21 @@ class DataCollection return $this->data[$idSite][$period]; } + /** + * 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. @@ -213,6 +228,23 @@ class DataCollection return $dataTableFactory->make($index, $resultIndices); } + /** + * 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 @@ -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 cfc19378130cfbdf674e8b93bf58eed062e2464c..36e985e6dc1ce8fc147d8e87d4c7297ab34724fc 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), + ); } /** @@ -387,39 +497,16 @@ class DataTableFactory return $label; } - /** - * @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 faeef35b88fe40cab5659093749c7eba5ef12dd3..a500ef66e6092f20ab6595ec5c1a86ebc2ab0d37 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 a9be60d98ce070e5da2b5bab2b6e10d04645381b..d67e0984cb012a94b3e0d509cffd7c9abc24e08e 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 805a5db5ab8646efd12b1dadc3c990cb91b027b8..cb2af14262da0636c78524c3273f602dac451dca 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 76444a074f43855d0c972cefff36516ce6106573..c823293c769089347d3e1cf6c859f2c549614449 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 1a828fce4784cd5d79aecc48a9d6c0152701b973..7b9ed154fa1f1e2fbe3e04287d4b1e6abfff42c0 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 ed98e18273ce1b85f302c3ce91d1779ea599247e..81b093925be790b8e0275b4ac094e8f3b1d93d50 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 0000000000000000000000000000000000000000..c8c9480010db494b8cf54ca8f9561e7a14ff51ea --- /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 c85dc94024e91d44d3880919076579fff150b76f..df92877416a5f3713778251299851c54b1babb93 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 cee88af3f5fd16a775aafe876d34642ba69c2c26..e43504154e4630acc2fd7f57f1941eb683d59244 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 647ae147dcb9bbb6712a6e15028dddd458880018..a59e8a4cdb1dae149bb2aaf5f28487ebe3e4ac22 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 0000000000000000000000000000000000000000..6e973a8be61460ee779eb14c151ffd669da70138 --- /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 f3f62ecae45a3790ea5a1b052e72c821116087aa..c69f174d8424aeef8d2045663359885745b4f8a7 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 b8f9040e9b6da0179076c8437fc772752b03c8de..0000000000000000000000000000000000000000 --- 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 f2261aad1c7e291189a31100a05ffd6428261ba6..a213d892f57390311fd13091c992eb084089c3d9 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 d4dd4eba3f817139b787d90497c4e359c024ff71..3ba1b15a21be498e0cbfa7a3209e23fc8f1f32d2 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 287ef25c98c926aa51620e398c03f99b3359f1cb..5c9159d0767ee7202ddee1b17d42efbb9c319f5c 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 6502f4244c327f06dfb78d7e52b4c1b352e86fbb..580e3b2772aea6ffd8ddb5fc81622bd417141b5e 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 0000000000000000000000000000000000000000..8305ad9b33cec06be1d27f5d223dfbe6dc288707 --- /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 0000000000000000000000000000000000000000..cd6ea91b110573d8c343583e30ea9bb668a74a9f --- /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 0000000000000000000000000000000000000000..33d671cbb2f352e75d33a7a2fec9f13c9dfe6b09 --- /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 0000000000000000000000000000000000000000..9645685c34a8f5ab40b4daf7349985131bbd0609 --- /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 01a5e250024ffd2b60bd1f7646893922f254d155..5218ad3961518fbae7fa38c3bfdeb35bdc86e1f9 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 74524d284803228d6183411eebfd4457224d0938..5f6148c689d9c89a89e30f85a39b00c09fb0df6d 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 84d1611e0244d00988287e82b6d0a08f11578d5c..4ab55e378e30d181d4714da2489ce01b9fa32101 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 0ecae3aea116902fa07b955d1911c5a9e67640d7..5218ad3961518fbae7fa38c3bfdeb35bdc86e1f9 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 885d4243f4933d44d89b11d71236cce1b695ff00..bf674444b7457e1193f1a67964caf0e440e7fe5e 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 365dd2ff8df7bf23830eafc90b0db78e2e1a7745..2efd1eaab3fcd6f50e8a89aa7efc669dcd0b2104 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 cf376064145fb5245f617d15ca160178d967ac7f..a06b60b0013f7c00957fb71e0f0e6655b2c8bd65 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 59f331588bc556185a6a11b0d310c39b9ccf95f7..b3df616c4741603c41b33bd65387e3e925f3ba57 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 b58279dbc5b5755bddbb2482e7278411759bc949..aad34392f8aff2869356a5a495562e6fee6f1923 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 365dd2ff8df7bf23830eafc90b0db78e2e1a7745..2efd1eaab3fcd6f50e8a89aa7efc669dcd0b2104 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 cf376064145fb5245f617d15ca160178d967ac7f..a06b60b0013f7c00957fb71e0f0e6655b2c8bd65 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 59f331588bc556185a6a11b0d310c39b9ccf95f7..b3df616c4741603c41b33bd65387e3e925f3ba57 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 b58279dbc5b5755bddbb2482e7278411759bc949..aad34392f8aff2869356a5a495562e6fee6f1923 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 3d6283b32197620b40fce5f3c60f47032c9581cf..a1c7e55b5f374a5e69c2ffb9bc1143b236e2ba43 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 2fb69ad710273a27d8055cbe8040258cb3a33e57..f714cc123d8754af2df17595fa0f6ebb8a4aa93b 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 5e03453232e6b7c254d19c2626ad0f98d69094af..d758a762ff99ca1539e3864da9afef5e05dfa3ed 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 0000000000000000000000000000000000000000..1aebd06f2b0a9904b0b7274e0ed53fd7ac8f7010 --- /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 0000000000000000000000000000000000000000..869c0f9075f8ad500ed4b3fc37c1303bc57ba04d --- /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 af0846dbf2f0524edf089ff6c998574611ad7ad7..ad497ad8499015b6b2787493aa95985dafc7be42 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");