diff options
Diffstat (limited to 'plugins/CoreVisualizations')
9 files changed, 830 insertions, 72 deletions
diff --git a/plugins/CoreVisualizations/JqplotDataGenerator/Evolution.php b/plugins/CoreVisualizations/JqplotDataGenerator/Evolution.php index 5675153c34..5114167364 100644 --- a/plugins/CoreVisualizations/JqplotDataGenerator/Evolution.php +++ b/plugins/CoreVisualizations/JqplotDataGenerator/Evolution.php @@ -12,7 +12,6 @@ use Piwik\Archive\DataTableFactory; use Piwik\Common; use Piwik\DataTable; use Piwik\DataTable\Row; -use Piwik\Menu\MenuMain; use Piwik\Plugins\CoreVisualizations\JqplotDataGenerator; use Piwik\Url; @@ -76,7 +75,6 @@ class Evolution extends JqplotDataGenerator $periodLabel = reset($dataTables)->getMetadata(DataTableFactory::TABLE_METADATA_PERIOD_INDEX)->getLabel(); $axisXOnClick = array(); - $queryStringAsHash = $this->getQueryStringAsHash(); foreach ($dataTable->getDataTables() as $metadataDataTable) { $dateInUrl = $metadataDataTable->getMetadata(DataTableFactory::TABLE_METADATA_PERIOD_INDEX)->getDateStart(); $parameters = array( @@ -85,16 +83,7 @@ class Evolution extends JqplotDataGenerator 'date' => $dateInUrl->toString(), 'segment' => \Piwik\API\Request::getRawSegmentFromRequest() ); - $hash = ''; - if (!empty($queryStringAsHash)) { - $hash = '#' . Url::getQueryStringFromParameters($queryStringAsHash + $parameters); - } - $link = 'index.php?' . - Url::getQueryStringFromParameters(array( - 'module' => 'CoreHome', - 'action' => 'index', - ) + $parameters) - . $hash; + $link = Url::getQueryStringFromParameters($parameters); $axisXOnClick[] = $link; } $visualization->setAxisXOnClick($axisXOnClick); @@ -144,34 +133,6 @@ class Evolution extends JqplotDataGenerator return $label; } - /** - * We link the graph dots to the same report as currently being displayed (only the date would change). - * - * In some cases the widget is loaded within a report that doesn't exist as such. - * For example, the dashboards loads the 'Last visits graph' widget which can't be directly linked to. - * Instead, the graph must link back to the dashboard. - * - * In other cases, like Visitors>Overview or the Goals graphs, we can link the graph clicks to the same report. - * - * To detect whether or not we can link to a report, we simply check if the current URL from which it was loaded - * belongs to the menu or not. If it doesn't belong to the menu, we do not append the hash to the URL, - * which results in loading the dashboard. - * - * @return array Query string array to append to the URL hash or false if the dashboard should be displayed - */ - private function getQueryStringAsHash() - { - $queryString = Url::getArrayFromCurrentQueryString(); - $piwikParameters = array('idSite', 'date', 'period', 'XDEBUG_SESSION_START', 'KEY'); - foreach ($piwikParameters as $parameter) { - unset($queryString[$parameter]); - } - if (MenuMain::getInstance()->isUrlFound($queryString)) { - return $queryString; - } - return false; - } - private function isLinkEnabled() { static $linkEnabled; diff --git a/plugins/CoreVisualizations/Visualizations/Sparkline.php b/plugins/CoreVisualizations/Visualizations/Sparkline.php index 2ca75bbcfe..3c1dbf5a56 100644 --- a/plugins/CoreVisualizations/Visualizations/Sparkline.php +++ b/plugins/CoreVisualizations/Visualizations/Sparkline.php @@ -25,7 +25,7 @@ class Sparkline extends ViewDataTable * @see ViewDataTable::main() * @return mixed */ - protected function buildView() + public function render() { // If period=range, we force the sparkline to draw daily data points $period = Common::getRequestVar('period'); @@ -58,7 +58,7 @@ class Sparkline extends ViewDataTable $graph->main(); - return $graph; + return $graph->render(); } /** diff --git a/plugins/CoreVisualizations/Visualizations/Sparklines.php b/plugins/CoreVisualizations/Visualizations/Sparklines.php new file mode 100644 index 0000000000..3b576c885e --- /dev/null +++ b/plugins/CoreVisualizations/Visualizations/Sparklines.php @@ -0,0 +1,147 @@ +<?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\CoreVisualizations\Visualizations; + +use Piwik\DataTable; +use Piwik\Metrics; +use Piwik\Plugin\ViewDataTable; +use Piwik\Url; +use Piwik\View; + +/** + * Reads the requested DataTable from the API and prepares data for the Sparklines view. It can display any amount + * of sparklines. Within a reporting page sparklines are shown in 2 columns, in a dashboard or when exported as a widget + * the sparklines are shown in one column. + * + * The sparklines view currently only supports requesting columns from the same API (the API method of the defining + * report) via {Sparklines\Config::addSparklineMetric($columns = array('nb_visits', 'nb_unique_visitors'))}. + * + * Example: + * $view->config->addSparklineMetric('nb_visits'); // if an array of metrics given, they will be displayed comma separated + * $view->config->addTranslation('nb_visits', 'Visits'); + * Results in: [sparkline image] X visits + * Data is fetched from the configured $view->requestConfig->apiMethodToRequestDataTable. + * + * In case you want to add any custom sparklines from any other API method you can call + * {@link Sparklines\Config::addSparkline()}. + * + * Example: + * $sparklineUrlParams = array('columns' => array('nb_visits)); + * $evolution = array('currentValue' => 5, 'pastValue' => 10, 'tooltip' => 'Foo bar'); + * $view->config->addSparkline($sparklineUrlParams, $value = 5, $description = 'Visits', $evolution); + * + * @property Sparklines\Config $config + */ +class Sparklines extends ViewDataTable +{ + const ID = 'sparklines'; + + public static function getDefaultConfig() + { + return new Sparklines\Config(); + } + + /** + * @see ViewDataTable::main() + * @return mixed + */ + public function render() + { + $view = new View('@CoreVisualizations/_dataTableViz_sparklines.twig'); + + $columnsList = array(); + if ($this->config->hasSparklineMetrics()) { + foreach ($this->config->getSparklineMetrics() as $cols) { + $columnsList = array_merge($cols['columns'], $columnsList); + } + } + + $this->requestConfig->request_parameters_to_modify['columns'] = $columnsList; + $this->requestConfig->request_parameters_to_modify['format_metrics'] = '1'; + + if (!empty($this->requestConfig->apiMethodToRequestDataTable)) { + $this->fetchConfiguredSparklines(); + } + + $view->sparklines = $this->config->getSortedSparklines(); + + return $view->render(); + } + + private function fetchConfiguredSparklines() + { + $data = $this->loadDataTableFromAPI(); + + $this->applyFilters($data); + + if (!$this->config->hasSparklineMetrics()) { + foreach ($data->getColumns() as $column) { + $this->config->addSparklineMetric($column); + } + } + + $translations = $this->config->translations; + + $firstRow = $data->getFirstRow(); + + foreach ($this->config->getSparklineMetrics() as $sparklineMetric) { + $column = $sparklineMetric['columns']; + $order = $sparklineMetric['order']; + + if ($column === 'label') { + continue; + } + + if (empty($column)) { + $this->config->addPlaceholder($order); + continue; + } + + if (!is_array($column)) { + $column = array($column); + } + + $values = array(); + $descriptions = array(); + + foreach ($column as $col) { + $value = $firstRow->getColumn($col); + + if ($value === false) { + $value = 0; + } + + $values[] = $value; + $descriptions[] = isset($translations[$col]) ? $translations[$col] : $col; + } + + $sparklineUrlParams = array( + 'columns' => $column, + 'module' => $this->requestConfig->getApiModuleToRequest(), + 'action' => $this->requestConfig->getApiMethodToRequest() + ); + + $this->config->addSparkline($sparklineUrlParams, $values, $descriptions, null, $order); + } + } + + private function applyFilters(DataTable\DataTableInterface $table) + { + foreach ($this->config->getPriorityFilters() as $filter) { + $table->filter($filter[0], $filter[1]); + } + + // queue other filters so they can be applied later if queued filters are disabled + foreach ($this->config->getPresentationFilters() as $filter) { + $table->queueFilter($filter[0], $filter[1]); + } + + $table->applyQueuedFilters(); + } +} diff --git a/plugins/CoreVisualizations/Visualizations/Sparklines/Config.php b/plugins/CoreVisualizations/Visualizations/Sparklines/Config.php new file mode 100644 index 0000000000..ca54a6d564 --- /dev/null +++ b/plugins/CoreVisualizations/Visualizations/Sparklines/Config.php @@ -0,0 +1,354 @@ +<?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\CoreVisualizations\Visualizations\Sparklines; +use Piwik\Common; +use Piwik\DataTable\Filter\CalculateEvolutionFilter; +use Piwik\Metrics; +use Piwik\NoAccessException; +use Piwik\Period\Range; +use Piwik\Site; +use Piwik\Url; + +/** + * DataTable Visualization that derives from Sparklines. + */ +class Config extends \Piwik\ViewDataTable\Config +{ + /** + * Holds metrics / column names that will be used to fetch data from the configured $requestConfig API. + * Default value: array + */ + private $sparkline_metrics = array(); + + /** + * Holds the actual sparkline entries based on fetched data that will be used in the template. + * @var array + */ + private $sparklines = array(); + + public function __construct() + { + parent::__construct(); + + $this->translations = Metrics::getDefaultMetricTranslations(); + } + + /** + * @ignore + * @return array + */ + public function getSparklineMetrics() + { + return $this->sparkline_metrics; + } + + /** + * @ignore + * @return bool + */ + public function hasSparklineMetrics() + { + return !empty($this->sparkline_metrics); + } + + /** + * Removes an existing sparkline entry. Especially useful in dataTable filters in case sparklines should be not + * displayed depending on the fetched data. + * + * Example: + * $config->addSparklineMetric('nb_users'); + * $config->filters[] = function ($dataTable) use ($config) { + * if ($dataTable->getFirstRow()->getColumn('nb_users') == 0) { + * // do not show a sparkline if there are no recorded users + * $config->removeSparklineMetric('nb_users'); + * } + * } + * + * @param array|string $metricNames The name of the metrics in the same format they were used when added via + * {@link addSparklineMetric} + */ + public function removeSparklineMetric($metricNames) + { + foreach ($this->sparkline_metrics as $index => $metric) { + if ($metric['columns'] === $metricNames) { + array_splice($this->sparkline_metrics, $index, 1); + + break; + } + } + } + + /** + * Replaces an existing sparkline entry with different columns. Especially useful in dataTable filters in case + * sparklines should be not displayed depending on the fetched data. + * + * Example: + * $config->addSparklineMetric('nb_users'); + * $config->filters[] = function ($dataTable) use ($config) { + * if ($dataTable->getFirstRow()->getColumn('nb_users') == 0) { + * // instead of showing the sparklines for users, show a placeholder if there are no recorded users + * $config->replaceSparklineMetric(array('nb_users'), ''); + * } + * } + * + * @param array|string $metricNames The name of the metrics in the same format they were used when added via + * {@link addSparklineMetric} + * @param array|string $replacementColumns The removed columns will be replaced with these columns + */ + public function replaceSparklineMetric($metricNames, $replacementColumns) + { + foreach ($this->sparkline_metrics as $index => $metric) { + if ($metric['columns'] === $metricNames) { + $this->sparkline_metrics[$index]['columns'] = $replacementColumns; + } + } + } + + /** + * Adds a new sparkline. + * + * It will show a sparkline image, the value of the resolved metric name and a descrption. Optionally, multiple + * values can be shown after a sparkline image by passing multiple metric names + * (eg array('nb_visits', 'nb_actions')). The data will be requested from the configured api method see + * {@link Piwik\ViewDataTable\RequestConfig::$apiMethodToRequestDataTable}. + * + * Example: + * $config->addSparklineMetric('nb_visits'); + * $config->addTranslation('nb_visits', 'Visits'); + * Results in: [sparkline image] X visits + * + * Example: + * $config->addSparklineMetric(array('nb_visits', 'nb_actions')); + * $config->addTranslations(array('nb_visits' => 'Visits', 'nb_actions' => 'Actions')); + * Results in: [sparkline image] X visits, Y actions + * + * @param string|array $metricName Either one metric name (eg 'nb_visits') or an array of metric names + * @param int|null $order Defines the order. The lower the order the earlier the sparkline will be displayed. + * By default the sparkline will be appended to the end. + */ + public function addSparklineMetric($metricName, $order = null) + { + $this->sparkline_metrics[] = array( + 'columns' => $metricName, + 'order' => $order + ); + } + + /** + * Adds a placeholder. In this case nothing will be shown, neither a sparkline nor any description. This can be + * useful if you want to have some kind of separator. Eg if you want to have a sparkline on the left side but + * not sparkline on the right side. + * + * @param int|null $order Defines the order. The lower the order the earlier the sparkline will be displayed. + * By default the sparkline will be appended to the end. + */ + public function addPlaceholder($order = null) + { + $this->sparklines[] = array( + 'url' => '', + 'metrics' => array(), + 'order' => $this->getSparklineOrder($order) + ); + } + + /** + * Add a new sparkline to be displayed to the view. + * + * Each sparkline can consist of one or multiple metrics. One metric consists of a value and a description. By + * default the value is shown first, then the description. The description can optionally contain a '%s' in case + * the value shall be displayed within the description. If multiple metrics are given, they will be separated by + * a comma. + * + * @param array $requestParamsForSparkline You need to at least set a module / action eg + * array('columns' => array('nb_visit'), 'module' => '', 'action' => '') + * @param int|float|string|array $value Either the metric value or an array of values. + * @param string|array $description Either one description or an array of descriptions. If an array, both + * $value and $description need the same amount of array entries. + * $description[0] should be the description for $value[0]. + * $description should be already translated. If $value should appear + * somewhere within the text a `%s` can be used in the translation. + * @param array|null $evolution Optional array containing at least the array keys 'currentValue' and + * 'pastValue' which are needed to calculate the correct percentage. + * An optional 'tooltip' can be set as well. Eg + * array('currentValue' => 10, 'pastValue' => 20, + * 'tooltip' => '10 visits in 2015-07-26 compared to 20 visits in 2015-07-25') + * @param int $order Defines the order. The lower the order the earlier the sparkline will be + * displayed. By default the sparkline will be appended to the end. + * @throws \Exception In case an evolution parameter is set but has wrong data structure + */ + public function addSparkline($requestParamsForSparkline, $value, $description, $evolution = null, $order = null) + { + $metrics = array(); + + if (is_array($value)) { + $values = $value; + } else { + $values = array($value); + } + + if (!is_array($description)) { + $description = array($description); + } + + if (count($values) === count($description)) { + foreach ($values as $index => $value) { + $metrics[] = array( + 'value' => $value, + 'description' => $description[$index] + ); + } + } else { + $msg = 'The number of values and descriptions need to be the same to add a sparkline. '; + $msg .= 'Values: ' . implode(', ', $values). ' Descriptions: ' . implode(', ', $description); + throw new \Exception($msg); + } + + if (empty($metrics)) { + return; + } + + $sparkline = array( + 'url' => $this->getUrlSparkline($requestParamsForSparkline), + 'metrics' => $metrics, + 'order' => $this->getSparklineOrder($order) + ); + + if (!empty($evolution)) { + if (!is_array($evolution) || + !array_key_exists('currentValue', $evolution) || + !array_key_exists('pastValue', $evolution)) { + throw new \Exception('In order to show an evolution in the sparklines view a currentValue and pastValue array key needs to be present'); + } + + $evolutionPercent = CalculateEvolutionFilter::calculate($evolution['currentValue'], $evolution['pastValue'], $precision = 1); + + // do not display evolution if evolution percent is 0 and current value is 0 + if ($evolutionPercent != 0 || $evolution['currentValue'] != 0) { + $sparkline['evolution'] = array( + 'percent' => $evolutionPercent, + 'tooltip' => !empty($evolution['tooltip']) ? $evolution['tooltip'] : null + ); + } + + } + + $this->sparklines[] = $sparkline; + } + + /** + * @return array + * @ignore + */ + public function getSortedSparklines() + { + usort($this->sparklines, function ($a, $b) { + if ($a['order'] == $b['order']) { + return 0; + } + return ($a['order'] < $b['order']) ? -1 : 1; + }); + + return $this->sparklines; + } + + private function getSparklineOrder($order) + { + if (!isset($order)) { + // make sure to append to the end if nothing set (in the order they are added) + $order = 999 + count($this->sparklines); + } + + return (int) $order; + } + + /** + * Returns a URL to a sparkline image for a report served by the current plugin. + * + * The result of this URL should be used with the [sparkline()](/api-reference/Piwik/View#twig) twig function. + * + * The current site ID and period will be used. + * + * @param array $customParameters The array of query parameter name/value pairs that + * should be set in result URL. + * @return string The generated URL. + */ + private function getUrlSparkline($customParameters = array()) + { + $customParameters['viewDataTable'] = 'sparkline'; + + $params = $this->getGraphParamsModified($customParameters); + + // convert array values to comma separated + foreach ($params as &$value) { + if (is_array($value)) { + $value = rawurlencode(implode(',', $value)); + } + } + $url = Url::getCurrentQueryStringWithParametersModified($params); + return $url; + } + + /** + * Returns the array of new processed parameters once the parameters are applied. + * For example: if you set range=last30 and date=2008-03-10, + * the date element of the returned array will be "2008-02-10,2008-03-10" + * + * Parameters you can set: + * - range: last30, previous10, etc. + * - date: YYYY-MM-DD, today, yesterday + * - period: day, week, month, year + * + * @param array $paramsToSet array( 'date' => 'last50', 'viewDataTable' =>'sparkline' ) + * @throws \Piwik\NoAccessException + * @return array + */ + private function getGraphParamsModified($paramsToSet = array()) + { + if (!isset($paramsToSet['period'])) { + $period = Common::getRequestVar('period'); + } else { + $period = $paramsToSet['period']; + } + + if ($period == 'range') { + return $paramsToSet; + } + + if (!isset($paramsToSet['range'])) { + $range = 'last30'; + } else { + $range = $paramsToSet['range']; + } + + if (!isset($paramsToSet['idSite'])) { + $idSite = Common::getRequestVar('idSite'); + } else { + $idSite = $paramsToSet['idSite']; + } + + if (!isset($paramsToSet['date'])) { + $endDate = Common::getRequestVar('date', 'yesterday', 'string'); + } else { + $endDate = $paramsToSet['date']; + } + + $site = new Site($idSite); + + if (is_null($site)) { + throw new NoAccessException("Website not initialized, check that you are logged in and/or using the correct token_auth."); + } + + $paramDate = Range::getRelativeToEndDate($period, $range, $endDate, $site); + + $params = array_merge($paramsToSet, array('date' => $paramDate)); + return $params; + } + +} diff --git a/plugins/CoreVisualizations/javascripts/jqplotEvolutionGraph.js b/plugins/CoreVisualizations/javascripts/jqplotEvolutionGraph.js index 3b62e9197e..b77add4962 100644 --- a/plugins/CoreVisualizations/javascripts/jqplotEvolutionGraph.js +++ b/plugins/CoreVisualizations/javascripts/jqplotEvolutionGraph.js @@ -90,36 +90,7 @@ && typeof self.jqplotParams.axes.xaxis.onclick[lastTick] == 'string') { var url = self.jqplotParams.axes.xaxis.onclick[lastTick]; - if (url && -1 === url.indexOf('#')) { - var module = broadcast.getValueFromHash('module'); - var action = broadcast.getValueFromHash('action'); - var idGoal = broadcast.getValueFromHash('idGoal'); - var idSite = broadcast.getValueFromUrl('idSite', url); - var period = broadcast.getValueFromUrl('period', url); - var date = broadcast.getValueFromUrl('date', url); - - if (module && action) { - url += '#module=' + module + '&action=' + action; - - if (idSite) { - url += '&idSite=' + idSite; - } - - if (idGoal) { - url += '&idGoal=' + idGoal; - } - - if (period) { - url += '&period=' + period; - } - - if (period) { - url += '&date=' + date; - } - } - } - - piwikHelper.redirectToUrl(url); + broadcast.propagateNewPage(url); } }) .on('jqplotPiwikTickOver', function (e, tick) { @@ -161,6 +132,10 @@ render: function () { JqplotGraphDataTablePrototype.render.call(this); + + if (initializeSparklines) { + initializeSparklines(); + } } }); diff --git a/plugins/CoreVisualizations/templates/_dataTableViz_sparklines.twig b/plugins/CoreVisualizations/templates/_dataTableViz_sparklines.twig new file mode 100644 index 0000000000..359c0f9768 --- /dev/null +++ b/plugins/CoreVisualizations/templates/_dataTableViz_sparklines.twig @@ -0,0 +1,31 @@ +{% import '@CoreVisualizations/macros.twig' as macros %} + +{% if not isWidget %} +<div class="row"> + <div class="col-md-6"> +{% endif %} + + {% for key, sparkline in sparklines %} + {% if key is even %} + {{ macros.singleSparkline(sparkline) }} + {% endif %} + {% endfor %} + +{% if not isWidget %} + </div> + <div class="col-md-6"> +{% endif %} + + {% for key, sparkline in sparklines %} + {% if key is odd %} + {{ macros.singleSparkline(sparkline) }} + {% endif %} + {% endfor %} + +{% if not isWidget %} + </div> +</div> +{% endif %} + +{% include "_sparklineFooter.twig" %} + diff --git a/plugins/CoreVisualizations/templates/macros.twig b/plugins/CoreVisualizations/templates/macros.twig new file mode 100644 index 0000000000..ffd1885be1 --- /dev/null +++ b/plugins/CoreVisualizations/templates/macros.twig @@ -0,0 +1,32 @@ +{% macro singleSparkline(sparkline) %} + <div class="sparkline"> + {% if sparkline.url %}{{ sparkline(sparkline.url)|raw }}{% endif %} + {% for metric in sparkline.metrics %} + {% if '%s' in metric.description -%} + {{ metric.description|translate("<strong>"~metric.value~"</strong>")|raw }} + {%- else %} + <strong>{{ metric.value }}</strong> {{ metric.description }} + {%- endif %}{% if not loop.last %}, {% endif %} + {% endfor %} + {% if sparkline.evolution is defined %} + + {% set evolutionPretty = sparkline.evolution.percent %} + + {% if sparkline.evolution.percent < 0 %} + {% set evolutionClass = 'negative-evolution' %} + {% set evolutionIcon = 'arrow_down.png' %} + {% elseif sparkline.evolution.percent == 0 %} + {% set evolutionClass = 'neutral-evolution' %} + {% set evolutionIcon = 'stop.png' %} + {% else %} + {% set evolutionClass = 'positive-evolution' %} + {% set evolutionIcon = 'arrow_up.png' %} + {% set evolutionPretty = '+' ~ sparkline.evolution.percent %} + {% endif %} + + <span class="metricEvolution" title="{{ sparkline.evolution.tooltip }}"><img + style="padding-right:4px" src="plugins/MultiSites/images/{{ evolutionIcon }}"/> + <strong class="{{ evolutionClass }}">{{ evolutionPretty }}</strong></span> + {% endif %} + </div> +{% endmacro %} diff --git a/plugins/CoreVisualizations/tests/Integration/SparklinesConfigTest.php b/plugins/CoreVisualizations/tests/Integration/SparklinesConfigTest.php new file mode 100644 index 0000000000..3541344f04 --- /dev/null +++ b/plugins/CoreVisualizations/tests/Integration/SparklinesConfigTest.php @@ -0,0 +1,128 @@ +<?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\CoreVisualizations\tests\Integration; + +use Piwik\Plugins\CoreVisualizations\Visualizations\Sparklines\Config; +use Piwik\Tests\Framework\Fixture; +use Piwik\Tests\Framework\Mock\FakeAccess; +use Piwik\Tests\Framework\TestCase\IntegrationTestCase; + +/** + * @group CoreVisualizations + * @group SparklinesConfigTest + * @group Plugins + */ +class SparklinesConfigTest extends IntegrationTestCase +{ + /** + * @var Config + */ + private $config; + + public function setUp() + { + parent::setUp(); + FakeAccess::$superUser = true; + + if (!Fixture::siteCreated(1)) { + Fixture::createWebsite('2014-01-01 00:00:00'); + } + + $this->config = new Config(); + } + + public function test_addSparkline_shouldAddAMinimalSparklineWithOneValueAndUseDefaultOrder() + { + $this->config->addSparkline($this->sparklineParams(), $value = 10, $description = 'Visits'); + + $expectedSparkline = array( + 'url' => '?period=day&date=2012-03-06,2012-04-04&idSite=1&module=CoreHome&action=renderMe&viewDataTable=sparkline', + 'metrics' => array ( + array ('value' => 10, 'description' => 'Visits'), + ), + 'order' => 999 + ); + + $this->assertSame(array($expectedSparkline), $this->config->getSortedSparklines()); + } + + public function test_addSparkline_shouldAddSparklineWithMultipleValues() + { + $this->config->addSparkline($this->sparklineParams(), $values = array(10, 20), $description = array('Visits', 'Actions')); + + $sparklines = $this->config->getSortedSparklines(); + + $this->assertSame(array ( + array ('value' => 10, 'description' => 'Visits'), + array ('value' => 20, 'description' => 'Actions'), + ), $sparklines[0]['metrics']); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage Values: 10, 20, 30 Descriptions: Visits, Actions + */ + public function test_addSparkline_shouldThrowAnException_IfValuesDoesNotMatchAmountOfDescriptions() + { + $this->config->addSparkline($this->sparklineParams(), $values = array(10, 20, 30), $description = array('Visits', 'Actions')); + } + + public function test_addSparkline_shouldAddEvolution() + { + $evolution = array('currentValue' => 10, 'pastValue' => 21, + 'tooltip' => '1 visit compared to 2 visits'); + $this->config->addSparkline($this->sparklineParams(), $value = 10, $description = 'Visits', $evolution); + + $sparklines = $this->config->getSortedSparklines(); + + $this->assertSame(array ( + 'percent' => '-52.4%', + 'tooltip' => '1 visit compared to 2 visits' + ), $sparklines[0]['evolution']); + } + + public function test_addSparkline_shouldAddOrder() + { + $this->config->addSparkline($this->sparklineParams(), $value = 10, $description = 'Visits', $evolution = null, $order = '42'); + + $sparklines = $this->config->getSortedSparklines(); + + $this->assertSame(42, $sparklines[0]['order']); + } + + public function test_addSparkline_shouldBeAbleToBuildSparklineUrlBasedOnGETparams() + { + $oldGet = $_GET; + $_GET = $this->sparklineParams(); + $this->config->addSparkline(array('columns' => 'nb_visits'), $value = 10, $description = 'Visits'); + $_GET = $oldGet; + + $sparklines = $this->config->getSortedSparklines(); + + $this->assertSame('?columns=nb_visits&viewDataTable=sparkline&date=2012-03-06,2012-04-04', $sparklines[0]['url']); + } + + private function sparklineParams($params = array()) + { + $params['period'] = 'day'; + $params['date'] = '2012-04-04'; + $params['idSite'] = '1'; + $params['module'] = 'CoreHome'; + $params['action'] = 'renderMe'; + + return $params; + } + + public function provideContainerConfig() + { + return array( + 'Piwik\Access' => new FakeAccess() + ); + } +} diff --git a/plugins/CoreVisualizations/tests/Unit/SparklinesConfigTest.php b/plugins/CoreVisualizations/tests/Unit/SparklinesConfigTest.php new file mode 100644 index 0000000000..516f19f8e1 --- /dev/null +++ b/plugins/CoreVisualizations/tests/Unit/SparklinesConfigTest.php @@ -0,0 +1,130 @@ +<?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\CoreVisualizations\tests\Unit; +use Piwik\Plugins\CoreVisualizations\Visualizations\Sparklines\Config; + +/** + * @group CoreVisualizations + * @group SparklinesConfigTest + * @group Sparklines + * @group Plugins + */ +class SparklinesConfigTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var Config + */ + private $config; + + public function setUp() + { + $this->config = new Config(); + } + + public function test_hasSparklineMetrics_shouldNotHaveSparklineMetrics_ByDefault() + { + $this->assertFalse($this->config->hasSparklineMetrics()); + } + + public function test_hasSparklineMetrics_shouldHaveSparklineMetrics_IfAtLeastOneWasAdded() + { + $this->config->addSparklineMetric('nb_visits'); + + $this->assertTrue($this->config->hasSparklineMetrics()); + } + + public function test_getSparklineMetrics_shouldNotHaveSparklineMetrics_ByDefault() + { + $this->assertSame(array(), $this->config->getSparklineMetrics()); + } + + public function test_addSparklineMetric_getSparklineMetrics_shouldReturnAllAddedSparklineMetrics() + { + $this->addFewSparklines(); + + $this->assertSame(array( + array('columns' => 'nb_visits', 'order' => null), + array('columns' => 'nb_unique_visitors', 'order' => 99), + array('columns' => array('nb_downloads', 'nb_outlinks'), 'order' => null), + ), $this->config->getSparklineMetrics()); + } + + public function test_removeSparklineMetric_shouldRemoveMetric_IfOnlySingleMetricIsGiven() + { + $this->addFewSparklines(); + + $this->config->removeSparklineMetric('nb_unique_visitors'); + + $this->assertSame(array( + array('columns' => 'nb_visits', 'order' => null), + array('columns' => array('nb_downloads', 'nb_outlinks'), 'order' => null), + ), $this->config->getSparklineMetrics()); + } + + public function test_removeSparklineMetric_shouldRemoveMetric_IfMultipleMetricsAreGiven() + { + $this->addFewSparklines(); + + $this->config->removeSparklineMetric(array('nb_downloads', 'nb_outlinks')); + + $this->assertSame(array( + array('columns' => 'nb_visits', 'order' => null), + array('columns' => 'nb_unique_visitors', 'order' => 99), + ), $this->config->getSparklineMetrics()); + } + + public function test_replaceSparklineMetric_shouldBeAbleToReplaceColumns_IfSingleMetricIsGiven() + { + $this->addFewSparklines(); + + $this->config->replaceSparklineMetric('nb_unique_visitors', ''); + + $this->assertSame(array( + array('columns' => 'nb_visits', 'order' => null), + array('columns' => '', 'order' => 99), + array('columns' => array('nb_downloads', 'nb_outlinks'), 'order' => null), + ), $this->config->getSparklineMetrics()); + } + + public function test_replaceSparklineMetric_shouldBeAbleToReplaceColumns_IfMultipleMetricsAreGiven() + { + $this->addFewSparklines(); + + $this->config->replaceSparklineMetric(array('nb_downloads', 'nb_outlinks'), ''); + + $this->assertSame(array( + array('columns' => 'nb_visits', 'order' => null), + array('columns' => 'nb_unique_visitors', 'order' => 99), + array('columns' => '', 'order' => null), + ), $this->config->getSparklineMetrics()); + } + + public function test_addPlaceholder_getSortedSparklines() + { + $this->config->addPlaceholder(); + $this->config->addPlaceholder($order = 10); + $this->config->addPlaceholder(); + $this->config->addPlaceholder($order = 3); + + $this->assertSame(array( + array('url' => '', 'metrics' => array(), 'order' => 3), + array('url' => '', 'metrics' => array(), 'order' => 10), + array('url' => '', 'metrics' => array(), 'order' => 999), + array('url' => '', 'metrics' => array(), 'order' => 1001), + ), $this->config->getSortedSparklines()); + } + + private function addFewSparklines() + { + $this->config->addSparklineMetric('nb_visits'); + $this->config->addSparklineMetric('nb_unique_visitors', 99); + $this->config->addSparklineMetric(array('nb_downloads', 'nb_outlinks')); + } + +} |