diff --git a/config/environment/ui-test.php b/config/environment/ui-test.php index cc9e47d6630..a1b9bd50145 100644 --- a/config/environment/ui-test.php +++ b/config/environment/ui-test.php @@ -2,6 +2,8 @@ use Piwik\Container\Container; use Piwik\Container\StaticContainer; +use Piwik\DataTable; +use Piwik\Plugin\Visualization; use Piwik\Plugins\Diagnostics\Diagnostic\FileIntegrityCheck; use Piwik\Plugins\Diagnostics\Diagnostic\PhpVersionCheck; use Piwik\Plugins\Diagnostics\Diagnostic\RequiredPrivateDirectories; @@ -111,6 +113,31 @@ public function write(string $key, string $content): void $result = ''; })], + ['Visualization.beforeRender', Piwik\DI::value(function (Visualization $visualization) { + $dataStates = StaticContainer::get('test.vars.forceDataStates'); + + if (!is_array($dataStates) || [] === $dataStates) { + return; + } + + $dataTable = $visualization->getDataTable(); + + if (!($dataTable instanceof DataTable\Map)) { + return; + } + + foreach ($dataTable->getDataTables() as $date => $subTable) { + if (!isset($dataStates[$date])) { + continue; + } + + $subTable->setMetadata( + DataTable::ARCHIVE_STATE_METADATA_NAME, + $dataStates[$date] + ); + } + })], + \Piwik\Tests\Framework\XssTesting::getJavaScriptAddEvent(), ]), diff --git a/core/DataTable.php b/core/DataTable.php index 3276f7fa6bd..cdd8d6961ca 100644 --- a/core/DataTable.php +++ b/core/DataTable.php @@ -168,6 +168,9 @@ class DataTable implements DataTableInterface, \IteratorAggregate, \ArrayAccess { const MAX_DEPTH_DEFAULT = 15; + /** Name for metadata that describes the archiving state of a report */ + const ARCHIVE_STATE_METADATA_NAME = 'archive_state'; + /** Name for metadata that describes when a report was archived. */ const ARCHIVED_DATE_METADATA_NAME = 'ts_archived'; @@ -221,6 +224,10 @@ class DataTable implements DataTableInterface, \IteratorAggregate, \ArrayAccess const ROW_IDENTIFIER_METADATA_NAME = 'rowIdentifier'; + const ID_ARCHIVE_STATE_COMPLETE = 'complete'; + const ID_ARCHIVE_STATE_INCOMPLETE = 'incomplete'; + const ID_ARCHIVE_STATE_INVALIDATED = 'invalidated'; + /** * Maximum nesting level. */ diff --git a/lang/en.json b/lang/en.json index 1545660015f..75108f05783 100644 --- a/lang/en.json +++ b/lang/en.json @@ -266,6 +266,7 @@ "InvalidDateRange": "Invalid Date Range, Please Try Again", "InvalidResponse": "The received data is invalid.", "IncompletePeriod": "Incomplete Period", + "InvalidatedPeriod": "Invalidated Period", "JsTrackingTag": "JavaScript Tracking Code", "KpiMetric": "KPI Metric", "Language": "Language", diff --git a/plugins/CoreVisualizations/CoreVisualizations.php b/plugins/CoreVisualizations/CoreVisualizations.php index a2f0c18205e..f03cc78fb5f 100644 --- a/plugins/CoreVisualizations/CoreVisualizations.php +++ b/plugins/CoreVisualizations/CoreVisualizations.php @@ -66,5 +66,6 @@ public function getClientSideTranslationKeys(&$translationKeys) $translationKeys[] = 'General_NoDataForGraph'; $translationKeys[] = 'General_EvolutionSummaryGeneric'; $translationKeys[] = 'General_IncompletePeriod'; + $translationKeys[] = 'General_InvalidatedPeriod'; } } diff --git a/plugins/CoreVisualizations/JqplotDataGenerator/Chart.php b/plugins/CoreVisualizations/JqplotDataGenerator/Chart.php index aa7d946de78..67b56559367 100644 --- a/plugins/CoreVisualizations/JqplotDataGenerator/Chart.php +++ b/plugins/CoreVisualizations/JqplotDataGenerator/Chart.php @@ -8,24 +8,38 @@ */ namespace Piwik\Plugins\CoreVisualizations\JqplotDataGenerator; +use Exception; use Piwik\Common; use Piwik\Container\StaticContainer; +use Piwik\Log\LoggerInterface; use Piwik\NumberFormatter; use Piwik\ProxyHttp; -/** - * - */ class Chart { + // temporary + public $properties; + // the data kept here conforms to the jqplot data layout // @see http://www.jqplot.com/docs/files/jqPlotOptions-txt.html - protected $series = array(); - protected $data = array(); - protected $axes = array(); + protected $series = []; + protected $data = []; + protected $axes = []; - // temporary - public $properties; + /** + * @var array + */ + protected $dataStates = []; + + /** + * @var LoggerInterface + */ + protected $logger; + + public function __construct(LoggerInterface $logger = null) + { + $this->logger = $logger ?? StaticContainer::get(LoggerInterface::class); + } public function setAxisXLabels($xLabels, $xTicks = null, $index = 0) { @@ -144,14 +158,17 @@ public function render() { ProxyHttp::overrideCacheControlHeaders(); + $this->checkDataStateAvailableForAllTicks(); + // See http://www.jqplot.com/docs/files/jqPlotOptions-txt.html - $data = array( - 'params' => array( - 'axes' => &$this->axes, - 'series' => &$this->series - ), - 'data' => &$this->data - ); + $data = [ + 'params' => [ + 'axes' => &$this->axes, + 'series' => &$this->series, + ], + 'data' => &$this->data, + 'dataStates' => &$this->dataStates, + ]; return $data; } @@ -171,6 +188,18 @@ public function setAxisXLabelsMultiple($xLabels, $seriesToXAxis, $ticks = null) } } + /** + * Set state information ("complete", "incomplete", ...) for the data points. + * + * Each entry is associated with all values of all series having the same index (stored in $data). + * + * @param array $dataStates + */ + public function setDataStates(array $dataStates): void + { + $this->dataStates = $dataStates; + } + private function getXAxis($index) { $axisName = 'xaxis'; @@ -179,4 +208,32 @@ private function getXAxis($index) } return $axisName; } + + private function checkDataStateAvailableForAllTicks(): void + { + if ([] === $this->dataStates) { + return; + } + + $maxTickCount = 0; + $stateCount = count($this->dataStates); + + if ([] !== $this->data) { + $dataCounts = array_map('count', $this->data); + $uniqueCounts = array_unique($dataCounts); + $maxTickCount = max($uniqueCounts); + } + + if ($stateCount === $maxTickCount) { + return; + } + + $ex = new Exception(sprintf( + 'Data state information does not span graph length (%u ticks, %u states)', + $maxTickCount, + $stateCount + )); + + $this->logger->info('{exception}', ['exception' => $ex]); + } } diff --git a/plugins/CoreVisualizations/JqplotDataGenerator/Evolution.php b/plugins/CoreVisualizations/JqplotDataGenerator/Evolution.php index c19f9202830..b8997af3d1c 100644 --- a/plugins/CoreVisualizations/JqplotDataGenerator/Evolution.php +++ b/plugins/CoreVisualizations/JqplotDataGenerator/Evolution.php @@ -19,6 +19,7 @@ use Piwik\Period\Factory; use Piwik\Plugins\API\Filter\DataComparisonFilter; use Piwik\Plugins\CoreVisualizations\JqplotDataGenerator; +use Piwik\Site; use Piwik\Url; /** @@ -73,7 +74,7 @@ protected function initChartObjectData($dataTable, $visualization) $columnsToDisplay = array_values($this->properties['columns_to_display']); - list($seriesMetadata, $seriesUnits, $seriesLabels, $seriesToXAxis) = + [$seriesMetadata, $seriesUnits, $seriesLabels, $seriesToXAxis] = $this->getSeriesMetadata($rowsToDisplay, $columnsToDisplay, $units, $dataTables); // collect series data to show. each row-to-display/column-to-display permutation creates a series. @@ -139,6 +140,8 @@ protected function initChartObjectData($dataTable, $visualization) } $visualization->setAxisXOnClick($axisXOnClick); } + + $this->setDataStates($visualization, $dataTables); } private function getSeriesData($rowLabel, $columnName, DataTable\Map $dataTable) @@ -311,7 +314,7 @@ private function getSeriesMetadata(array $rowsToDisplay, array $columnsToDisplay $seriesUnits[$wholeSeriesLabel] = $units[$columnName]; - list($periodIndex, $segmentIndex) = DataComparisonFilter::getIndividualComparisonRowIndices($table, $seriesIndex); + [$periodIndex, $segmentIndex] = DataComparisonFilter::getIndividualComparisonRowIndices($table, $seriesIndex); $seriesToXAxis[] = $periodIndex; } } else { @@ -323,4 +326,41 @@ private function getSeriesMetadata(array $rowsToDisplay, array $columnsToDisplay return [$seriesMetadata, $seriesUnits, $seriesLabels, $seriesToXAxis]; } + + /** + * @param array $dataTables + */ + private function setDataStates(Chart $visualization, array $dataTables): void + { + if (0 === count($dataTables)) { + return; + } + + $dataTableDates = array_keys($dataTables); + $mostRecentDate = end($dataTableDates); + + /** @var Site $site */ + $site = $dataTables[$mostRecentDate]->getMetadata(DataTableFactory::TABLE_METADATA_SITE_INDEX); + + $dataStates = []; + $siteToday = Date::factoryInTimezone('today', $site->getTimezone())->getTimestamp(); + + foreach ($dataTableDates as $dataTableDate) { + /** @var Period $period */ + $period = $dataTables[$dataTableDate]->getMetadata(DataTableFactory::TABLE_METADATA_PERIOD_INDEX); + $state = $dataTables[$dataTableDate]->getMetadata(DataTable::ARCHIVE_STATE_METADATA_NAME); + + if (false === $state) { + $state = DataTable::ID_ARCHIVE_STATE_COMPLETE; + } + + if ($siteToday <= $period->getDateEnd()->getTimestamp()) { + $state = DataTable::ID_ARCHIVE_STATE_INCOMPLETE; + } + + $dataStates[$dataTableDate] = $state; + } + + $visualization->setDataStates(array_values($dataStates)); + } } diff --git a/plugins/CoreVisualizations/javascripts/jqplot.js b/plugins/CoreVisualizations/javascripts/jqplot.js index 9aabf3a08a2..93bdf81e32a 100644 --- a/plugins/CoreVisualizations/javascripts/jqplot.js +++ b/plugins/CoreVisualizations/javascripts/jqplot.js @@ -14,7 +14,6 @@ function rowEvolutionGetMetricNameFromRow(tr) } (function ($, require) { - var exports = require('piwik/UI'), DataTable = exports.DataTable, dataTablePrototype = DataTable.prototype, @@ -58,7 +57,8 @@ function rowEvolutionGetMetricNameFromRow(tr) metricsToPlot: _pk_translate('General_MetricsToPlot'), metricToPlot: _pk_translate('General_MetricToPlot'), recordsToPlot: _pk_translate('General_RecordsToPlot'), - incompletePeriod: _pk_translate('General_IncompletePeriod') + incompletePeriod: _pk_translate('General_IncompletePeriod'), + invalidatedPeriod: _pk_translate('General_InvalidatedPeriod') }; // set a unique ID for the graph element (required by jqPlot) @@ -74,6 +74,7 @@ function rowEvolutionGetMetricNameFromRow(tr) this.data = graphData.data; this._setJqplotParameters(graphData.params); + this._setDataStates(graphData.dataStates); if (this.props.display_percentage_in_tooltip) { this._setTooltipPercentages(); @@ -95,6 +96,14 @@ function rowEvolutionGetMetricNameFromRow(tr) setTimeout(function () { self.render(); }, 1); }, + _setDataStates: function (dataStates) { + this.jqplotParams.dataStates = []; + + if (Array.isArray(dataStates)) { + this.jqplotParams.dataStates = dataStates; + } + }, + _setJqplotParameters: function (params) { defaultParams = { grid: { @@ -385,21 +394,6 @@ function rowEvolutionGetMetricNameFromRow(tr) // create jqplot chart try { - - // Work out incomplete data points - this.jqplotParams['incompleteDataPoints'] = 0; - - var piwikPeriods = window.CoreHome.Periods; - - var period = this.param.period; - // If date is actually a range then adjust the period type for the containsToday check - if (period === 'day' && this.param.date.indexOf(',') !== -1) { - period = 'range'; - } - if (piwikPeriods.parse(period, this.param.date).containsToday()) { - this.jqplotParams['incompleteDataPoints'] = 1; - } - var plot = self._plot = $.jqplot(targetDivId, this.data, this.jqplotParams); } catch (e) { // this is thrown when refreshing piwik in the browser @@ -1385,8 +1379,12 @@ RowEvolutionSeriesToggle.prototype.beforeReplot = function () { var xmin, ymin, xmax, ymax; // Only change in this overridden method, to pass the option to the renderers - if (plot.options.hasOwnProperty('incompleteDataPoints')) { - opts.incompleteDataPoints = plot.options.incompleteDataPoints; + if (plot.options.hasOwnProperty('dataStates')) { + opts.dataStates = plot.options.dataStates; + } + + if (!Array.isArray(opts.dataStates)) { + opts.dataStates = []; } ctx.save(); @@ -1594,10 +1592,17 @@ RowEvolutionSeriesToggle.prototype.beforeReplot = function () { if (this.renderer.smooth) { gd = this.gridData; } - for (i=0; i 0) { + if (closePath) { + ctxPattern.closePath(); + } - var lp = points.length - 1; + if (fill) { + ctx.fill(); + } else { + ctx.stroke(); + } - ctx.save(); - ctx.setLineDash([3, 3]); - ctx.lineWidth = opts.lineWidth || this.lineWidth; - ctx.lineJoin = opts.lineJoin || this.lineJoin; - ctx.lineCap = opts.lineCap || this.lineCap; - ctx.strokeStyle = (opts.strokeStyle || opts.color) || this.strokeStyle; + // draw dashed lines for incomplete data points + ctx.beginPath(); + ctx.setLineDash([3, 3]); - ctx.beginPath(); - for (var ii = (points.length - incompleteDataPoints); ii < points.length; ii++) { + move = true; - ctx.moveTo(points[ii - 1][0], points[ii - 1][1]); - ctx.lineTo(points[ii][0], points[ii][1]); - ctx.stroke(); - } - ctx.restore(); + for (let i = 0; i < points.length; i++) { + // skip to the first non-null point and move to it. + if (points[i][0] === null && points[i][1] === null) { + continue; + } + + if (move) { + move = false; + + ctxPattern.moveTo(points[i][0], points[i][1]); + continue; + } + + // draw dashed line to current point or skip if not incomplete data point + if (!dataStates[i] || 'complete' === dataStates[i]) { + ctxPattern.moveTo(points[i][0], points[i][1]); + } else { + ctxPattern.lineTo(points[i][0], points[i][1]); + } } + ctx.stroke(); + ctx.closePath(); + ctx.restore(); }; // Only overriding this method to prevent drawing the shadow for the last line segment @@ -1728,48 +1765,54 @@ RowEvolutionSeriesToggle.prototype.beforeReplot = function () { ctx.strokeStyle = opts.strokeStyle || this.strokeStyle || 'rgba(0,0,0,'+alpha+')'; ctx.fillStyle = opts.fillStyle || this.fillStyle || 'rgba(0,0,0,'+alpha+')'; - // Only do the incomplete visualization for line charts - var incompleteDataPoints = 0; - if (!closePath && !fill && opts.hasOwnProperty('incompleteDataPoints')) { - incompleteDataPoints = opts.incompleteDataPoints; + let dataStates = []; + + if (!closePath && !fill && Array.isArray(opts.dataStates)) { + // only do the incomplete visualization for line charts + dataStates = opts.dataStates; } - for (var j=0; j' + '' + value + ' ' + piwikHelper.htmlEntities(series)); + dataByAxis[axis].push( + `` + + `${value} ${piwikHelper.htmlEntities(series)}` + ); } - var xAxisCount = 0; + let xAxisCount = 0; + Object.keys(self.jqplotParams.axes).forEach(function (axis) { - if (axis.substring(0, 1) === 'x') { - ++xAxisCount; + if (!axis.startsWith('x')) { + return; } + + ++xAxisCount; }); - var content = ''; - for (var i = 0; i < xAxisCount; ++i) { - var axisName = i === 0 ? 'xaxis' : 'x' + (i + 1) + 'axis'; + let content = ''; + + for (let i = 0; i < xAxisCount; ++i) { + const axisName = i === 0 ? 'xaxis' : `x${i + 1}axis`; + if (!dataByAxis[axisName] || !dataByAxis[axisName].length) { continue; } - if (typeof self.jqplotParams.axes[axisName].labels != 'undefined') { + let label; + + if (typeof self.jqplotParams.axes[axisName].labels !== 'undefined') { label = self.jqplotParams.axes[axisName].labels[tick]; } else { label = self.jqplotParams.axes[axisName].ticks[tick]; } - if (typeof label === 'undefined') { // sanity check + if (typeof label === 'undefined') { + // sanity check continue; } - content += '

'+piwikHelper.htmlEntities(label)+'

'+dataByAxis[axisName].join('
'); - - var last_n = null; - switch (self.param.period) { - case 'day': - last_n = self.param.evolution_day_last_n; - break; - case 'week': - last_n = self.param.evolution_week_last_n; - break; - case 'month': - last_n = self.param.evolution_month_last_n; - break; - case 'year': - last_n = self.param.evolution_year_last_n; - break; - } - if (last_n) { - var RangePeriod = window.CoreHome.Range; - if (self.param.hasOwnProperty('dateUsedInGraph') && self.param.dateUsedInGraph.indexOf(',') > 0) { - var graphDateRange = self.param.dateUsedInGraph.split(','); - if (graphDateRange.length == 2) { - var hoverDateRange = RangePeriod.getLastNRangeChild(self.param.period, graphDateRange[1], (last_n - lastTick)-1); - if (hoverDateRange.containsToday()) { - content += '
(' + self._lang.incompletePeriod + ')'; - } - } - } - } + content += ` +

${piwikHelper.htmlEntities(label)}

+ ${dataByAxis[axisName].join('
')} + `; + } + + switch (self.jqplotParams.dataStates[tick]) { + case 'incomplete': + content += `
(${self._lang.incompletePeriod})`; + break; + + case 'invalidated': + content += `
(${self._lang.invalidatedPeriod})`; + break; } $(this).tooltip({ track: true, items: 'div', content: content, - show: false, - hide: false + show: false, + hide: false }).trigger('mouseover'); - if (typeof self.jqplotParams.axes.xaxis.onclick != 'undefined' - && typeof self.jqplotParams.axes.xaxis.onclick[lastTick] == 'string') { + + if (typeof self.jqplotParams.axes.xaxis.onclick !== 'undefined' && + typeof self.jqplotParams.axes.xaxis.onclick[tick] === 'string' + ) { $(this).css('cursor', 'pointer'); } }); @@ -195,10 +200,11 @@ render: function () { JqplotGraphDataTablePrototype.render.call(this); - if (initializeSparklines) { - initializeSparklines(); + if (!initializeSparklines) { + return; } + + initializeSparklines(); } }); - })(jQuery, require); diff --git a/plugins/CoreVisualizations/tests/Unit/JqplotDataGenerator/ChartTest.php b/plugins/CoreVisualizations/tests/Unit/JqplotDataGenerator/ChartTest.php new file mode 100644 index 00000000000..7d0a9411ffe --- /dev/null +++ b/plugins/CoreVisualizations/tests/Unit/JqplotDataGenerator/ChartTest.php @@ -0,0 +1,137 @@ + $dataCounts + */ + public function testItDoesNotLogIfDataStatesSpanFullGraphLength(array $dataCounts, int $stateCount): void + { + $logger = new FakeLogger(); + $chart = $this->createChart($logger, $dataCounts, $stateCount); + + $chart->render(); + + self::assertSame('', $logger->output); + } + + /** + * @return iterable, int}> + */ + public function dataSpanFullGraphLength(): iterable + { + yield 'empty chart' => [ + [], + 0, + ]; + + yield 'single series, no states' => [ + [3], + 0, + ]; + + yield 'multiple series, no states' => [ + [5, 5, 5], + 0, + ]; + + yield 'single series, matching state' => [ + [3], + 3, + ]; + + yield 'multiple series, matching state' => [ + [5, 5, 5], + 5, + ]; + + yield 'multiple series, matching longest series' => [ + [5, 9, 5], + 9, + ]; + } + + /** + * @dataProvider dataSpanNotMatchingGraphLength + * + * @param array $dataCounts + */ + public function testIfLogsIfDataStateCountDoesMatchFullGraphLength(array $dataCounts, int $stateCount): void + { + $logger = new FakeLogger(); + $chart = $this->createChart($logger, $dataCounts, $stateCount); + + $chart->render(); + + self::assertStringContainsString( + sprintf( + 'Data state information does not span graph length (%u ticks, %u states)', + [] === $dataCounts ? 0 : max(...$dataCounts), + $stateCount + ), + $logger->output + ); + } + + /** + * @return iterable, int}> + */ + public function dataSpanNotMatchingGraphLength(): iterable + { + yield 'only state' => [ + [], + 1, + ]; + + yield 'too many state points' => [ + [3, 5], + 7, + ]; + + yield 'not enough state points' => [ + [5, 9], + 7, + ]; + } + + /** + * @param array $dataCounts + */ + private function createChart(LoggerInterface $logger, array $dataCounts, int $stateCount): Chart + { + $chart = new Chart($logger); + + foreach ($dataCounts as $index => $dataCount) { + $label = 'yAxis' . $index; + $data = array_fill(0, $dataCount, $index); + $values = [$label => $data]; + + $chart->setAxisYValues($values); + } + + if (0 !== $stateCount) { + $chart->setDataStates(array_fill(0, $stateCount, 'test')); + } + + return $chart; + } +} diff --git a/tests/UI/expected-screenshots/Comparison_multi_row_evolution.png b/tests/UI/expected-screenshots/Comparison_multi_row_evolution.png index e1d3d132592..2127bc5bfb8 100644 --- a/tests/UI/expected-screenshots/Comparison_multi_row_evolution.png +++ b/tests/UI/expected-screenshots/Comparison_multi_row_evolution.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7b6624e5065bf2451a2a517d8447df95e7877936a8a8517fb97826f61f0dd80c -size 44750 +oid sha256:1f86b3afb389d1a3ffcfea27d40156dabc390d0423973aaec3b5c27817658973 +size 45169 diff --git a/tests/UI/expected-screenshots/IncompletePeriodVisualisation_visitors_overview.png b/tests/UI/expected-screenshots/IncompletePeriodVisualisation_visitors_overview.png index 6aee8d830f6..66454fdb027 100644 --- a/tests/UI/expected-screenshots/IncompletePeriodVisualisation_visitors_overview.png +++ b/tests/UI/expected-screenshots/IncompletePeriodVisualisation_visitors_overview.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:88c63f13fb7fa4b4bba6456ff7f67fb03ea67da1ae5929d9c7e99a69df997479 -size 62405 +oid sha256:cd43a8c278aa88c64bb6400028e195f1b8e83c9921b979ed62e3fef528b4f94f +size 62443 diff --git a/tests/UI/expected-screenshots/InvalidatedPeriodVisualisation_comparison.png b/tests/UI/expected-screenshots/InvalidatedPeriodVisualisation_comparison.png new file mode 100644 index 00000000000..c5a3a60a3f0 --- /dev/null +++ b/tests/UI/expected-screenshots/InvalidatedPeriodVisualisation_comparison.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b8698c6d2bb45b331457269b287ddf9dffd3f21d1bfe1aee6f1df56ebd92f377 +size 48803 diff --git a/tests/UI/expected-screenshots/InvalidatedPeriodVisualisation_graph.png b/tests/UI/expected-screenshots/InvalidatedPeriodVisualisation_graph.png new file mode 100644 index 00000000000..aff365ff3d7 --- /dev/null +++ b/tests/UI/expected-screenshots/InvalidatedPeriodVisualisation_graph.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c90a1cd28d0765bbbb79806ebe06a3b7ed3c79d002145ce663906622e9ce5d65 +size 57343 diff --git a/tests/UI/specs/IncompletePeriodVisualisation_spec.js b/tests/UI/specs/IncompletePeriodVisualisation_spec.js index fba99e91963..71f0fa0bc86 100644 --- a/tests/UI/specs/IncompletePeriodVisualisation_spec.js +++ b/tests/UI/specs/IncompletePeriodVisualisation_spec.js @@ -7,17 +7,31 @@ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later */ -describe("IncompletePeriodVisualisation", function () { - this.fixture = "Piwik\\Tests\\Fixtures\\SomeVisitsLastYearAndThisYear"; +describe('IncompletePeriodVisualisation', function () { + this.fixture = 'Piwik\\Tests\\Fixtures\\SomeVisitsLastYearAndThisYear'; const generalParams = 'idSite=1&period=year&date=today'; const pageUrl = '?module=CoreHome&action=index&' + generalParams; it('should load visitors > overview page and show incomplete period', async function () { - await page.goto(pageUrl + generalParams + "&segment=&category=General_Visitors&subcategory=General_Overview#?idSite=1&period=year&date=today&segment=&category=General_Visitors&subcategory=General_Overview"); + await page.goto(pageUrl + generalParams + '&segment=&category=General_Visitors&subcategory=General_Overview#?idSite=1&period=year&date=today&segment=&category=General_Visitors&subcategory=General_Overview'); await page.waitForNetworkIdle(); const pageWrap = await page.$('.pageWrap'); + expect(await pageWrap.screenshot()).to.matchImage('visitors_overview'); }); + it('tooltip for incomplete period should say "incomplete period"', async function () { + const graph = await page.$('.piwik-graph'); + const boundingBox = await graph.boundingBox(); + + await page.mouse.move( + boundingBox.x + boundingBox.width - 50, + boundingBox.y + boundingBox.height - 50 + ); + + const tooltipContent = await page.evaluate(() => $('.ui-tooltip').text().trim()); + + expect(tooltipContent).to.contain('Incomplete Period'); + }); }); diff --git a/tests/UI/specs/InvalidatedPeriodVisualisation_spec.js b/tests/UI/specs/InvalidatedPeriodVisualisation_spec.js new file mode 100644 index 00000000000..04342794c83 --- /dev/null +++ b/tests/UI/specs/InvalidatedPeriodVisualisation_spec.js @@ -0,0 +1,59 @@ +/*! + * Matomo - free/libre analytics platform + * + * Invalidated Period Visualisation Test + * + * @link https://matomo.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ + +describe('InvalidatedPeriodVisualisation', function () { + const url = '?module=Widgetize&action=iframe&idSite=1&evolution_day_last_n=30' + + '&moduleToWidgetize=UserCountry&actionToWidgetize=getCountry&viewDataTable=graphEvolution' + + '&isFooterExpandedInDashboard=1'; + + before(() => { + testEnvironment.forceDataStates = { + '2012-01-08': 'invalidated', + '2012-01-09': 'invalidated', + '2012-01-10': 'invalidated', + // center point used for tooltip check + '2012-01-16': 'invalidated', + }; + + testEnvironment.save(); + }); + + after(() => { + delete testEnvironment.forceDataStates; + testEnvironment.save(); + }); + + it('should show invalidated data points', async function () { + await page.goto(url + '&period=day&date=2012-01-31'); + await page.waitForNetworkIdle(); + + expect(await page.screenshot({ fullPage: true })).to.matchImage('graph'); + }); + + it('tooltip for invalidated period should say "invalidated period"', async function () { + const graph = await page.$('.piwik-graph'); + const boundingBox = await graph.boundingBox(); + + await page.mouse.move( + boundingBox.x + boundingBox.width / 2, + boundingBox.y + boundingBox.height / 2 + ); + + const tooltipContent = await page.evaluate(() => $('.ui-tooltip').text().trim()); + + expect(tooltipContent).to.contain('Invalidated Period'); + }); + + it('should show invalidated data points', async function () { + await page.goto(url + '&period=range&date=2012-01-01,2012-01-31&comparePeriods[]=range&compareDates[]=2011-12-01,2011-12-31'); + await page.waitForNetworkIdle(); + + expect(await page.screenshot({ fullPage: true })).to.matchImage('comparison'); + }); +});