Skip to content

Commit

Permalink
Improve rendering of incomplete evolution graph data points (matomo-o…
Browse files Browse the repository at this point in the history
…rg#21694)

* Clean up JqplotGraph/Evolution

* Reuse already existing jqplotParams[incompleteDataPoints]

* Pass incomplete data points around as array

* Support rendering any data point as incomplete

* Render circles instead of dots for incomplete data points

* Test content of "incomplete if today" graph tooltip

* Use specific state for rendering incomplete data points

* Allow rendering data points as invalidated

* Move "today is incomplete" check to backend

* Rename "archiveState" to "dataState"

* Log if chart contains inconsistent data/state amount

* Update expected screenshots

* Update plugins/CoreVisualizations/javascripts/jqplot.js

Co-authored-by: Michal Kleiner <[email protected]>

* Tone down logging to not warn about expected "inconsistencies"

---------

Co-authored-by: Michal Kleiner <[email protected]>
  • Loading branch information
mneudert and michalkleiner authored Jan 23, 2024
1 parent 764a38e commit 6b5cef0
Show file tree
Hide file tree
Showing 15 changed files with 578 additions and 180 deletions.
27 changes: 27 additions & 0 deletions config/environment/ui-test.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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(),
]),

Expand Down
7 changes: 7 additions & 0 deletions core/DataTable.php
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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.
*/
Expand Down
1 change: 1 addition & 0 deletions lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions plugins/CoreVisualizations/CoreVisualizations.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,5 +66,6 @@ public function getClientSideTranslationKeys(&$translationKeys)
$translationKeys[] = 'General_NoDataForGraph';
$translationKeys[] = 'General_EvolutionSummaryGeneric';
$translationKeys[] = 'General_IncompletePeriod';
$translationKeys[] = 'General_InvalidatedPeriod';
}
}
87 changes: 72 additions & 15 deletions plugins/CoreVisualizations/JqplotDataGenerator/Chart.php
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>
*/
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)
{
Expand Down Expand Up @@ -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;
}
Expand All @@ -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<string> $dataStates
*/
public function setDataStates(array $dataStates): void
{
$this->dataStates = $dataStates;
}

private function getXAxis($index)
{
$axisName = 'xaxis';
Expand All @@ -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]);
}
}
44 changes: 42 additions & 2 deletions plugins/CoreVisualizations/JqplotDataGenerator/Evolution.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -139,6 +140,8 @@ protected function initChartObjectData($dataTable, $visualization)
}
$visualization->setAxisXOnClick($axisXOnClick);
}

$this->setDataStates($visualization, $dataTables);
}

private function getSeriesData($rowLabel, $columnName, DataTable\Map $dataTable)
Expand Down Expand Up @@ -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 {
Expand All @@ -323,4 +326,41 @@ private function getSeriesMetadata(array $rowsToDisplay, array $columnsToDisplay

return [$seriesMetadata, $seriesUnits, $seriesLabels, $seriesToXAxis];
}

/**
* @param array<DataTable> $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));
}
}
Loading

0 comments on commit 6b5cef0

Please sign in to comment.