Skip to content

Commit 5c83ec1

Browse files
committed
Move AI referrer reports into own record builder with different subtables
1 parent 3be7ddf commit 5c83ec1

File tree

11 files changed

+470
-218
lines changed

11 files changed

+470
-218
lines changed

core/DataAccess/LogAggregator.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -454,7 +454,7 @@ public function getSegmentTableSql(): array
454454
return $segmentSql;
455455
}
456456

457-
protected function getVisitsMetricFields()
457+
public function getVisitsMetricFields()
458458
{
459459
return array(
460460
Metrics::INDEX_NB_UNIQ_VISITORS => "count(distinct " . self::LOG_VISIT_TABLE . ".idvisitor)",

core/DataTable.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -957,7 +957,7 @@ public function getRowsCountWithoutSummaryRow()
957957
/**
958958
* Returns an array containing all column values for the requested column.
959959
*
960-
* @param string $name The column name.
960+
* @param string|int $name The column name.
961961
* @return array The array of column values.
962962
*/
963963
public function getColumn($name)

plugins/Referrers/API.php

Lines changed: 112 additions & 197 deletions
Large diffs are not rendered by default.

plugins/Referrers/Archiver.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ class Archiver extends \Piwik\Plugin\Archiver
1313
{
1414
public const SEARCH_ENGINES_RECORD_NAME = 'Referrers_keywordBySearchEngine';
1515
public const SOCIAL_NETWORKS_RECORD_NAME = 'Referrers_urlBySocialNetwork';
16-
public const AI_ASSISTANTS_RECORD_NAME = 'Referrers_urlByAIAssistant';
16+
public const AI_ASSISTANTS_ENTRY_URL_RECORD_NAME = 'Referrers_entryUrlByAIAssistant';
17+
public const AI_ASSISTANTS_ENTRY_TITLE_RECORD_NAME = 'Referrers_entryTitleByAIAssistant';
1718
public const KEYWORDS_RECORD_NAME = 'Referrers_searchEngineByKeyword';
1819
public const CAMPAIGNS_RECORD_NAME = 'Referrers_keywordByCampaign';
1920
public const WEBSITES_RECORD_NAME = 'Referrers_urlByWebsite';

plugins/Referrers/DataTable/Filter/UrlsForAIAssistant.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
use Piwik\DataTable\BaseFilter;
1313
use Piwik\DataTable;
14+
use Piwik\Plugins\Actions\ArchivingHelper;
15+
use Piwik\Tracker\Action;
1416

1517
class UrlsForAIAssistant extends BaseFilter
1618
{
@@ -24,6 +26,13 @@ public function filter($table)
2426

2527
// prettify the DataTable
2628
$table->filter('ColumnCallbackReplace', array('label', 'Piwik\Plugins\Referrers\removeUrlProtocol'));
29+
$table->filter(function (DataTable $table) {
30+
$emptyUrlRRow = $table->getRowFromLabel('');
31+
32+
if ($emptyUrlRRow) {
33+
$emptyUrlRRow->setColumn('label', ArchivingHelper::getUnknownActionName(Action::TYPE_PAGE_URL));
34+
}
35+
});
2736
$table->queueFilter('ReplaceColumnNames');
2837
}
2938
}
Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
<?php
2+
3+
/**
4+
* Matomo - free/libre analytics platform
5+
*
6+
* @link https://matomo.org
7+
* @license https://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
8+
*/
9+
10+
namespace Piwik\Plugins\Referrers\RecordBuilders;
11+
12+
use Piwik\ArchiveProcessor;
13+
use Piwik\ArchiveProcessor\Record;
14+
use Piwik\ArchiveProcessor\RecordBuilder;
15+
use Piwik\Common;
16+
use Piwik\Config;
17+
use Piwik\DataAccess\LogAggregator;
18+
use Piwik\DataTable;
19+
use Piwik\Metrics;
20+
use Piwik\Plugins\Referrers\Archiver;
21+
use Piwik\RankingQuery;
22+
use Piwik\Tracker\PageUrl;
23+
24+
class AIReferrers extends RecordBuilder
25+
{
26+
private $rankingQueryLimit;
27+
28+
public function __construct()
29+
{
30+
parent::__construct();
31+
32+
$this->columnToSortByBeforeTruncation = Metrics::INDEX_NB_VISITS;
33+
34+
// Reading pre 2.0 config file settings
35+
$this->maxRowsInTable = @Config::getInstance()->General['datatable_archiving_maximum_rows_referers'];
36+
$this->maxRowsInSubtable = @Config::getInstance()->General['datatable_archiving_maximum_rows_subtable_referers'];
37+
if (empty($this->maxRowsInTable)) {
38+
$this->maxRowsInTable = Config::getInstance()->General['datatable_archiving_maximum_rows_referrers'];
39+
$this->maxRowsInSubtable = Config::getInstance()->General['datatable_archiving_maximum_rows_subtable_referrers'];
40+
}
41+
$this->rankingQueryLimit = $this->getRankingQueryLimit();
42+
}
43+
44+
public function getRecordMetadata(ArchiveProcessor $archiveProcessor): array
45+
{
46+
return [
47+
Record::make(Record::TYPE_BLOB, Archiver::AI_ASSISTANTS_ENTRY_URL_RECORD_NAME),
48+
Record::make(Record::TYPE_BLOB, Archiver::AI_ASSISTANTS_ENTRY_TITLE_RECORD_NAME),
49+
50+
Record::make(Record::TYPE_NUMERIC, Archiver::METRIC_DISTINCT_AI_ASSISTANT_RECORD_NAME)
51+
->setIsCountOfBlobRecordRows(Archiver::AI_ASSISTANTS_ENTRY_URL_RECORD_NAME),
52+
];
53+
}
54+
55+
protected function aggregate(ArchiveProcessor $archiveProcessor): array
56+
{
57+
$records = [];
58+
foreach ($this->getRecordNames() as $record) {
59+
$records[$record] = new DataTable();
60+
}
61+
62+
$logAggregator = $archiveProcessor->getLogAggregator();
63+
64+
$this->aggregateFromVisits($logAggregator, $records[Archiver::AI_ASSISTANTS_ENTRY_URL_RECORD_NAME], 'visit_entry_idaction_url');
65+
$this->aggregateFromVisits($logAggregator, $records[Archiver::AI_ASSISTANTS_ENTRY_TITLE_RECORD_NAME], 'visit_entry_idaction_name');
66+
67+
$this->aggregateFromConversions($logAggregator, $records, ["referer_name"]);
68+
69+
$numericRecords = [
70+
Archiver::METRIC_DISTINCT_AI_ASSISTANT_RECORD_NAME => count($records[Archiver::AI_ASSISTANTS_ENTRY_URL_RECORD_NAME]->getRows()),
71+
];
72+
73+
return array_merge($records, $numericRecords);
74+
}
75+
76+
protected function getRecordNames()
77+
{
78+
return [
79+
Archiver::AI_ASSISTANTS_ENTRY_URL_RECORD_NAME,
80+
Archiver::AI_ASSISTANTS_ENTRY_TITLE_RECORD_NAME,
81+
];
82+
}
83+
84+
private function aggregateFromVisits(LogAggregator $logAggregator, DataTable $report, string $field): void
85+
{
86+
$resultSet = $this->queryAIReferrerEntryPages($logAggregator, $field);
87+
88+
$actionRows = [];
89+
90+
while ($row = $resultSet->fetch()) {
91+
if (!isset($row[Metrics::INDEX_NB_VISITS])) {
92+
return;
93+
}
94+
95+
$label = $row['referer_name'];
96+
97+
$pageUrlOrTitle = $row['action_name'];
98+
99+
if (is_null($label)) {
100+
continue;
101+
}
102+
103+
if (!is_null($pageUrlOrTitle)) {
104+
$actionRows[] = $row;
105+
continue;
106+
}
107+
108+
$report->sumRowWithLabel($label, $this->makeVisitRow($row));
109+
}
110+
111+
foreach ($actionRows as $row) {
112+
if (!isset($row[Metrics::INDEX_NB_VISITS])) {
113+
return;
114+
}
115+
116+
$label = $row['referer_name'];
117+
$pageUrlOrTitle = $row['action_name'];
118+
119+
$tableRow = $report->getRowFromLabel($label);
120+
121+
if (empty($tableRow)) {
122+
continue;
123+
}
124+
125+
if ($field === 'visit_entry_idaction_url') {
126+
// make sure we always work with normalized URL no matter how the individual action stores it
127+
$normalized = PageUrl::normalizeUrl($pageUrlOrTitle);
128+
$pageUrlOrTitle = $normalized['url'];
129+
}
130+
131+
$tableRow->sumRowWithLabelToSubtable($pageUrlOrTitle, $this->makeVisitRow($row));
132+
}
133+
}
134+
135+
public function makeVisitRow(array $row)
136+
{
137+
$metricIds = [
138+
Metrics::INDEX_NB_UNIQ_VISITORS,
139+
Metrics::INDEX_NB_VISITS,
140+
Metrics::INDEX_NB_ACTIONS,
141+
Metrics::INDEX_NB_USERS,
142+
Metrics::INDEX_MAX_ACTIONS,
143+
Metrics::INDEX_SUM_VISIT_LENGTH,
144+
Metrics::INDEX_BOUNCE_COUNT,
145+
Metrics::INDEX_NB_VISITS_CONVERTED,
146+
];
147+
148+
$columns = [];
149+
foreach ($metricIds as $id) {
150+
$columns[$id] = (float)($row[$id] ?? 0);
151+
}
152+
153+
return $columns;
154+
}
155+
156+
/**
157+
* @param DataTable[] $reports
158+
*/
159+
protected function aggregateFromConversions(LogAggregator $logAggregator, array $reports, array $dimensions): void
160+
{
161+
$where = 'referer_type = ' . Common::REFERRER_TYPE_AI_ASSISTANT;
162+
$query = $logAggregator->queryConversionsByDimension($dimensions, $where);
163+
while ($row = $query->fetch()) {
164+
$idGoal = (int) $row['idgoal'];
165+
$columns = [
166+
Metrics::INDEX_GOALS => [
167+
$idGoal => Metrics::makeGoalColumnsRow($idGoal, $row),
168+
],
169+
];
170+
171+
$this->aggregateConversionRow($row, $reports, $columns);
172+
}
173+
174+
foreach ($reports as $dataTable) {
175+
$dataTable->filter(DataTable\Filter\EnrichRecordWithGoalMetricSums::class);
176+
}
177+
}
178+
179+
/**
180+
* @param DataTable[] $reports
181+
*/
182+
protected function aggregateConversionRow(array $row, array $reports, array $columns): void
183+
{
184+
$reports[Archiver::AI_ASSISTANTS_ENTRY_URL_RECORD_NAME]->sumRowWithLabel($row['referer_name'], $columns);
185+
$reports[Archiver::AI_ASSISTANTS_ENTRY_TITLE_RECORD_NAME]->sumRowWithLabel($row['referer_name'], $columns);
186+
}
187+
188+
protected function queryAIReferrerEntryPages(LogAggregator $logAggregator, string $actionIdField)
189+
{
190+
$metricsConfig = $logAggregator->getVisitsMetricFields();
191+
$select = "log_visit.referer_name, COALESCE(log_action.name, '') as action_name";
192+
193+
$select = $this->addMetricsToSelect($select, $metricsConfig);
194+
195+
$from = [
196+
"log_visit",
197+
[
198+
"table" => "log_action",
199+
"joinOn" => "log_visit.$actionIdField = log_action.idaction",
200+
],
201+
];
202+
203+
$where = $logAggregator->getWhereStatement('log_visit', 'visit_last_action_time');
204+
$where .= ' AND log_visit.referer_type = ' . Common::REFERRER_TYPE_AI_ASSISTANT;
205+
206+
$groupBy = "log_visit.referer_name, action_name";
207+
$orderBy = "`" . Metrics::INDEX_NB_VISITS . "` DESC";
208+
209+
// get query with segmentation
210+
$query = $logAggregator->generateQuery(
211+
$select,
212+
$from,
213+
$where,
214+
$groupBy,
215+
$orderBy,
216+
$limit = 0,
217+
$offset = 0,
218+
true
219+
);
220+
221+
if ($this->rankingQueryLimit > 0) {
222+
$rankingQuery = new RankingQuery($this->rankingQueryLimit);
223+
$rankingQuery->addLabelColumn(['referer_name', 'action_name']);
224+
225+
$rankingQuery->addColumn(array_keys($metricsConfig), 'sum');
226+
227+
foreach ($metricsConfig as $column => $config) {
228+
if (empty($config['aggregation'])) {
229+
continue;
230+
}
231+
$rankingQuery->addColumn($column, $config['aggregation']);
232+
}
233+
234+
235+
$query['sql'] = $rankingQuery->generateRankingQuery($query['sql'], true);
236+
}
237+
238+
$db = $logAggregator->getDb();
239+
return $db->query($query['sql'], $query['bind']);
240+
}
241+
242+
private function getRankingQueryLimit(): int
243+
{
244+
$configGeneral = Config::getInstance()->General;
245+
$configLimit = max($configGeneral['archiving_ranking_query_row_limit'], 10 * $this->maxRowsInTable);
246+
return $configLimit == 0 ? 0 : max($configLimit, $this->maxRowsInTable);
247+
}
248+
249+
private function addMetricsToSelect(string $select, array $metricsConfig): string
250+
{
251+
if (!empty($metricsConfig)) {
252+
foreach ($metricsConfig as $metric => $query) {
253+
$select .= ', ' . $query . " as `" . $metric . "`";
254+
}
255+
}
256+
257+
return $select;
258+
}
259+
}

plugins/Referrers/RecordBuilders/Referrers.php

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ public function getRecordMetadata(ArchiveProcessor $archiveProcessor): array
4242
return [
4343
Record::make(Record::TYPE_BLOB, Archiver::SEARCH_ENGINES_RECORD_NAME),
4444
Record::make(Record::TYPE_BLOB, Archiver::SOCIAL_NETWORKS_RECORD_NAME),
45-
Record::make(Record::TYPE_BLOB, Archiver::AI_ASSISTANTS_RECORD_NAME),
4645
Record::make(Record::TYPE_BLOB, Archiver::KEYWORDS_RECORD_NAME),
4746
Record::make(Record::TYPE_BLOB, Archiver::CAMPAIGNS_RECORD_NAME),
4847
Record::make(Record::TYPE_BLOB, Archiver::WEBSITES_RECORD_NAME),
@@ -52,8 +51,6 @@ public function getRecordMetadata(ArchiveProcessor $archiveProcessor): array
5251
->setIsCountOfBlobRecordRows(Archiver::SEARCH_ENGINES_RECORD_NAME),
5352
Record::make(Record::TYPE_NUMERIC, Archiver::METRIC_DISTINCT_SOCIAL_NETWORK_RECORD_NAME)
5453
->setIsCountOfBlobRecordRows(Archiver::SOCIAL_NETWORKS_RECORD_NAME),
55-
Record::make(Record::TYPE_NUMERIC, Archiver::METRIC_DISTINCT_AI_ASSISTANT_RECORD_NAME)
56-
->setIsCountOfBlobRecordRows(Archiver::AI_ASSISTANTS_RECORD_NAME),
5754
Record::make(Record::TYPE_NUMERIC, Archiver::METRIC_DISTINCT_KEYWORD_RECORD_NAME)
5855
->setIsCountOfBlobRecordRows(Archiver::KEYWORDS_RECORD_NAME),
5956
Record::make(Record::TYPE_NUMERIC, Archiver::METRIC_DISTINCT_CAMPAIGN_RECORD_NAME)
@@ -87,7 +84,6 @@ protected function aggregate(ArchiveProcessor $archiveProcessor): array
8784
$numericRecords = [
8885
Archiver::METRIC_DISTINCT_SEARCH_ENGINE_RECORD_NAME => count($records[Archiver::SEARCH_ENGINES_RECORD_NAME]->getRows()),
8986
Archiver::METRIC_DISTINCT_SOCIAL_NETWORK_RECORD_NAME => count($records[Archiver::SOCIAL_NETWORKS_RECORD_NAME]->getRows()),
90-
Archiver::METRIC_DISTINCT_AI_ASSISTANT_RECORD_NAME => count($records[Archiver::AI_ASSISTANTS_RECORD_NAME]->getRows()),
9187
Archiver::METRIC_DISTINCT_KEYWORD_RECORD_NAME => count($records[Archiver::KEYWORDS_RECORD_NAME]->getRows()),
9288
Archiver::METRIC_DISTINCT_CAMPAIGN_RECORD_NAME => count($records[Archiver::CAMPAIGNS_RECORD_NAME]->getRows()),
9389
Archiver::METRIC_DISTINCT_WEBSITE_RECORD_NAME => count($records[Archiver::WEBSITES_RECORD_NAME]->getRows()),
@@ -97,14 +93,13 @@ protected function aggregate(ArchiveProcessor $archiveProcessor): array
9793
return array_merge($records, $numericRecords);
9894
}
9995

100-
protected function getRecordNames()
96+
protected function getRecordNames(): array
10197
{
10298
return [
10399
Archiver::REFERRER_TYPE_RECORD_NAME,
104100
Archiver::KEYWORDS_RECORD_NAME,
105101
Archiver::SEARCH_ENGINES_RECORD_NAME,
106102
Archiver::SOCIAL_NETWORKS_RECORD_NAME,
107-
Archiver::AI_ASSISTANTS_RECORD_NAME,
108103
Archiver::WEBSITES_RECORD_NAME,
109104
Archiver::CAMPAIGNS_RECORD_NAME,
110105
];
@@ -162,8 +157,7 @@ protected function aggregateVisitRow($row, array $reports, array &$distinctUrls)
162157
break;
163158

164159
case Common::REFERRER_TYPE_AI_ASSISTANT:
165-
$topLevelRow = $reports[Archiver::AI_ASSISTANTS_RECORD_NAME]->sumRowWithLabel($row['referer_name'], $columns);
166-
$topLevelRow->sumRowWithLabelToSubtable($row['referer_url'], $columns);
160+
// Handled in AIReferrers record builder, kept here only for the referrer type report
167161
break;
168162

169163
case Common::REFERRER_TYPE_WEBSITE:
@@ -241,7 +235,7 @@ protected function aggregateConversionRow(array $row, array $reports, array $col
241235
break;
242236

243237
case Common::REFERRER_TYPE_AI_ASSISTANT:
244-
$reports[Archiver::AI_ASSISTANTS_RECORD_NAME]->sumRowWithLabel($row['referer_name'], $columns);
238+
// Handled in AIReferrers record builder
245239
break;
246240

247241
case Common::REFERRER_TYPE_WEBSITE:

0 commit comments

Comments
 (0)