Skip to content

Commit 7c15116

Browse files
committed
refs matomo-org#1816 return ratio in API
1 parent 10dee6a commit 7c15116

5 files changed

Lines changed: 280 additions & 20 deletions

File tree

core/API/DataTableManipulator.php

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -128,26 +128,8 @@ protected function loadSubtable($dataTable, $row)
128128
}
129129
}
130130

131-
$class = Request::getClassNameAPI($this->apiModule);
132131
$method = $this->getApiMethodForSubtable();
133-
134-
$this->manipulateSubtableRequest($request);
135-
$request['serialize'] = 0;
136-
$request['expanded'] = 0;
137-
138-
// don't want to run recursive filters on the subtables as they are loaded,
139-
// otherwise the result will be empty in places (or everywhere). instead we
140-
// run it on the flattened table.
141-
unset($request['filter_pattern_recursive']);
142-
143-
$dataTable = Proxy::getInstance()->call($class, $method, $request);
144-
$response = new ResponseBuilder($format = 'original', $request);
145-
$dataTable = $response->getResponse($dataTable);
146-
if (method_exists($dataTable, 'applyQueuedFilters')) {
147-
$dataTable->applyQueuedFilters();
148-
}
149-
150-
return $dataTable;
132+
return $this->callApiAndReturnDataTable($this->apiModule, $method, $request);
151133
}
152134

153135
/**
@@ -177,4 +159,27 @@ private function getApiMethodForSubtable()
177159
}
178160
return $this->apiMethodForSubtable;
179161
}
162+
163+
protected function callApiAndReturnDataTable($apiModule, $method, $request)
164+
{
165+
$class = Request::getClassNameAPI($apiModule);
166+
167+
$this->manipulateSubtableRequest($request);
168+
$request['serialize'] = 0;
169+
$request['expanded'] = 0;
170+
171+
// don't want to run recursive filters on the subtables as they are loaded,
172+
// otherwise the result will be empty in places (or everywhere). instead we
173+
// run it on the flattened table.
174+
unset($request['filter_pattern_recursive']);
175+
176+
$dataTable = Proxy::getInstance()->call($class, $method, $request);
177+
$response = new ResponseBuilder($format = 'original', $request);
178+
$dataTable = $response->getResponse($dataTable);
179+
if (method_exists($dataTable, 'applyQueuedFilters')) {
180+
$dataTable->applyQueuedFilters();
181+
}
182+
183+
return $dataTable;
184+
}
180185
}
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
<?php
2+
/**
3+
* Piwik - Open source web analytics
4+
*
5+
* @link http://piwik.org
6+
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
7+
*
8+
* @category Piwik
9+
* @package Piwik
10+
*/
11+
namespace Piwik\API\DataTableManipulator;
12+
13+
use Piwik\API\DataTableManipulator;
14+
use Piwik\DataTable;
15+
use Piwik\DataTable\Row;
16+
use Piwik\DataTable\Filter;
17+
use Piwik\Piwik;
18+
use Piwik\Metrics;
19+
use Piwik\Plugins\API\API;
20+
21+
/**
22+
* This class is responsible for adding ratio columns.
23+
*
24+
* @package Piwik
25+
* @subpackage Piwik_API
26+
*/
27+
class AddRatioColumn extends DataTableManipulator
28+
{
29+
protected $roundPrecision = 2;
30+
private $totalValues = array();
31+
32+
/**
33+
* @param DataTable $table
34+
* @return \Piwik\DataTable|\Piwik\DataTable\Map
35+
*/
36+
public function addColumns($table)
37+
{
38+
return $this->manipulate($table);
39+
}
40+
41+
/**
42+
* Adds ratio metrics if possible.
43+
*
44+
* @param DataTable $dataTable
45+
* @return DataTable
46+
*/
47+
protected function manipulateDataTable($dataTable)
48+
{
49+
$metricsToCalculate = Metrics::getMetricIdsToProcessRatio();
50+
51+
$parentTable = $this->getFirstLevelDataTable();
52+
53+
foreach ($parentTable->getRows() as $row) {
54+
foreach ($metricsToCalculate as $metricId) {
55+
$this->addColumnValueToTotal($row, $metricId);
56+
}
57+
}
58+
59+
foreach ($dataTable->getRows() as $row) {
60+
foreach ($metricsToCalculate as $metricId) {
61+
$this->addRatioColumnIfNeeded($row, $metricId);
62+
}
63+
}
64+
65+
return $dataTable;
66+
}
67+
68+
protected function getFirstLevelDataTable()
69+
{
70+
$reportMetadata = API::getInstance()->getReportMetadata();
71+
72+
$firstLevelReport = array();
73+
foreach ($reportMetadata as $report) {
74+
if (!empty($report['actionToLoadSubTables'])
75+
&& $this->apiMethod == $report['actionToLoadSubTables']
76+
&& $this->apiModule == $report['module']) {
77+
78+
$firstLevelReport = $report;
79+
break;
80+
}
81+
}
82+
83+
if (empty($firstLevelReport)) {
84+
// it is not a subtable report
85+
$module = $this->apiModule;
86+
$action = $this->apiMethod;
87+
} else {
88+
$module = $firstLevelReport['module'];
89+
$action = $firstLevelReport['action'];
90+
}
91+
92+
$request = $this->request;
93+
94+
return $this->callApiAndReturnDataTable($module, $action, $request);
95+
}
96+
97+
private function addColumnValueToTotal(Row $row, $columnIdRaw)
98+
{
99+
$value = $this->getColumn($row, $columnIdRaw);
100+
101+
if (false === $value) {
102+
103+
return;
104+
}
105+
106+
if (array_key_exists($columnIdRaw, $this->totalValues)) {
107+
$this->totalValues[$columnIdRaw] += $value;
108+
} else {
109+
$this->totalValues[$columnIdRaw] = $value;
110+
}
111+
}
112+
113+
private function addRatioColumnIfNeeded(Row $row, $columnIdRaw)
114+
{
115+
if (!array_key_exists($columnIdRaw, $this->totalValues)) {
116+
return;
117+
}
118+
119+
$value = $this->getColumn($row, $columnIdRaw);
120+
121+
if (false === $value) {
122+
return;
123+
}
124+
125+
$mappingIdToName = Metrics::$mappingFromIdToName;
126+
$columnIdReadable = $mappingIdToName[$columnIdRaw];
127+
$relativeValue = $this->getPercentage($value, $this->totalValues[$columnIdRaw]);
128+
129+
$row->addColumn($columnIdReadable . '_ratio_report', $relativeValue);
130+
}
131+
132+
private function getPercentage($value, $totalValue)
133+
{
134+
$percentage = Piwik::getPercentageSafe($value, $totalValue, $this->roundPrecision);
135+
136+
return $percentage . '%';
137+
}
138+
139+
/**
140+
* Returns column from a given row.
141+
* Will work with 2 types of datatable
142+
* - raw datatables coming from the archive DB, which columns are int indexed
143+
* - datatables processed resulting of API calls, which columns have human readable english names
144+
*
145+
* @param Row|array $row
146+
* @param int $columnIdRaw see consts in Archive::
147+
* @param bool|array $mappingIdToName
148+
* @return mixed Value of column, false if not found
149+
*/
150+
protected function getColumn($row, $columnIdRaw, $mappingIdToName = false)
151+
{
152+
if (empty($mappingIdToName)) {
153+
$mappingIdToName = Metrics::$mappingFromIdToName;
154+
}
155+
156+
$columnIdReadable = $mappingIdToName[$columnIdRaw];
157+
158+
if ($row instanceof Row) {
159+
$raw = $row->getColumn($columnIdRaw);
160+
if ($raw !== false) {
161+
return $raw;
162+
}
163+
return $row->getColumn($columnIdReadable);
164+
}
165+
166+
return false;
167+
}
168+
169+
/**
170+
* Make sure to get all rows of the first level table.
171+
*
172+
* @param array $request
173+
*/
174+
protected function manipulateSubtableRequest(&$request)
175+
{
176+
$request['ratio'] = 0;
177+
$request['filter_limit'] = -1;
178+
$request['filter_offset'] = 0;
179+
180+
$parametersToRemove = array('flat', '');
181+
182+
foreach ($parametersToRemove as $param) {
183+
if (array_key_exists($param, $request)) {
184+
unset($request[$param]);
185+
}
186+
}
187+
}
188+
}

core/API/ResponseBuilder.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
namespace Piwik\API;
1212

1313
use Exception;
14+
use Piwik\API\DataTableManipulator\AddRatioColumn;
1415
use Piwik\API\DataTableManipulator\Flattener;
1516
use Piwik\API\DataTableManipulator\LabelFilter;
1617
use Piwik\Common;
@@ -299,6 +300,12 @@ protected function handleDataTable($datatable)
299300
$datatable = $flattener->flatten($datatable);
300301
}
301302

303+
// if the flag disable_generic_filters is defined we skip the generic filters
304+
if (1 == Common::getRequestVar('ratio', '1', 'integer', $this->request)) {
305+
$genericFilter = new AddRatioColumn($this->apiModule, $this->apiMethod, $this->request);
306+
$datatable = $genericFilter->addColumns($datatable);
307+
}
308+
302309
// if the flag disable_generic_filters is defined we skip the generic filters
303310
if (0 == Common::getRequestVar('disable_generic_filters', '0', 'string', $this->request)) {
304311
$genericFilter = new DataTableGenericFilter($this->request);

core/Metrics.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,44 @@ static public function getDefaultProcessedMetrics()
286286
return array_map(array('\\Piwik\\Piwik','translate'), $translations);
287287
}
288288

289+
static public function getMetricIdsToProcessRatio()
290+
{
291+
return array(
292+
Metrics::INDEX_NB_VISITS,
293+
Metrics::INDEX_NB_UNIQ_VISITORS,
294+
Metrics::INDEX_NB_ACTIONS,
295+
Metrics::INDEX_PAGE_NB_HITS,
296+
Metrics::INDEX_NB_VISITS_CONVERTED,
297+
Metrics::INDEX_NB_CONVERSIONS
298+
);
299+
}
300+
301+
static public function getDefaultRatioMetrics()
302+
{
303+
$translations = array(
304+
'nb_visits_ratio_report' => 'General_ColumnNbVisitsRatio',
305+
'nb_uniq_visitors_ratio_report' => 'General_ColumnNbUniqVisitorsRatio',
306+
'nb_actions_ratio_report' => 'General_ColumnNbActionsRatio',
307+
'nb_hits_ratio_report' => 'General_ColumnNbHitsRatio',
308+
'nb_visits_converted_ratio_report' => 'General_ColumnNbVisitsConvertedRatio'
309+
);
310+
311+
return array_map(array('\\Piwik\\Piwik','translate'), $translations);
312+
}
313+
314+
static public function getDefaultRatioMetricsDocumentation()
315+
{
316+
$translations = array(
317+
'nb_visits_ratio_report' => 'General_ColumnNbVisitsRatioDocumentation',
318+
'nb_uniq_visitors_ratio_report' => 'General_ColumnNbUniqVisitorsRatioDocumentation',
319+
'nb_actions_ratio_report' => 'General_ColumnNbActionsRatioDocumentation',
320+
'nb_hits_ratio_report' => 'General_ColumnNbHitsRatioDocumentation',
321+
'nb_visits_converted_ratio_report' => 'General_ColumnNbConversionRatioDocumentation'
322+
);
323+
324+
return array_map(array('\\Piwik\\Piwik','translate'), $translations);
325+
}
326+
289327
static public function getDefaultMetricsDocumentation()
290328
{
291329
$documentation = array(

plugins/API/ProcessedReport.php

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,10 @@ public function getReportMetadata($idSites, $period = false, $date = false, $hid
118118
$availableReport['processedMetrics'] = Metrics::getDefaultProcessedMetrics();
119119
}
120120

121+
if (!isset($availableReport['ratioMetrics'])) {
122+
$availableReport['ratioMetrics'] = Metrics::getDefaultRatioMetrics();
123+
}
124+
121125
if ($hideMetricsDoc) // remove metric documentation if it's not wanted
122126
{
123127
unset($availableReport['metricsDocumentation']);
@@ -154,7 +158,7 @@ public function getReportMetadata($idSites, $period = false, $date = false, $hid
154158
// Add the magic API.get report metadata aggregating all plugins API.get API calls automatically
155159
$this->addApiGetMetdata($availableReports);
156160

157-
$knownMetrics = array_merge(Metrics::getDefaultMetrics(), Metrics::getDefaultProcessedMetrics());
161+
$knownMetrics = array_merge(Metrics::getDefaultMetrics(), Metrics::getDefaultProcessedMetrics(), Metrics::getDefaultRatioMetrics());
158162
foreach ($availableReports as &$availableReport) {
159163
// Ensure all metrics have a translation
160164
$metrics = $availableReport['metrics'];
@@ -177,6 +181,9 @@ public function getReportMetadata($idSites, $period = false, $date = false, $hid
177181
if (isset($availableReport['processedMetrics'])) {
178182
$availableReport['processedMetrics'] = $this->hideShowMetrics($availableReport['processedMetrics']);
179183
}
184+
if (isset($availableReport['ratioMetrics'])) {
185+
$availableReport['ratioMetrics'] = $this->hideShowMetrics($availableReport['ratioMetrics']);
186+
}
180187
if (isset($availableReport['metricsDocumentation'])) {
181188
$availableReport['metricsDocumentation'] =
182189
$this->hideShowMetrics($availableReport['metricsDocumentation']);
@@ -376,6 +383,21 @@ private function handleTableReport($idSite, $dataTable, &$reportMetadata, $showR
376383
$columns
377384
);
378385

386+
if (isset($reportMetadata['ratioMetrics'])) {
387+
$ratioMetricDocs = Metrics::getDefaultRatioMetricsDocumentation();
388+
389+
// we automatically detect which ratio metrics should be added
390+
foreach ($reportMetadata['ratioMetrics'] as $ratioMetricId => $ratioMetricTranslation) {
391+
if (array_filter($dataTable->getColumn($ratioMetricId))) {
392+
$columns[$ratioMetricId] = $ratioMetricTranslation;
393+
394+
if (array_key_exists('metricsDocumentation', $reportMetadata)) {
395+
$reportMetadata['metricsDocumentation'][$ratioMetricId] = $ratioMetricDocs[$ratioMetricId];
396+
}
397+
}
398+
}
399+
}
400+
379401
if (isset($reportMetadata['processedMetrics'])) {
380402
$processedMetricsAdded = Metrics::getDefaultProcessedMetrics();
381403
foreach ($processedMetricsAdded as $processedMetricId => $processedMetricTranslation) {

0 commit comments

Comments
 (0)