| <?php |
|
|
| |
| |
| |
| |
| |
| |
|
|
| declare(strict_types=1); |
|
|
| namespace Piwik\Plugins\BotTracking\RecordBuilders; |
|
|
| use Piwik\ArchiveProcessor; |
| use Piwik\ArchiveProcessor\Record; |
| use Piwik\ArchiveProcessor\RecordBuilder; |
| use Piwik\Common; |
| use Piwik\Config\GeneralConfig; |
| use Piwik\DataTable; |
| use Piwik\DataTable\Row; |
| use Piwik\Db; |
| use Piwik\Plugins\BotTracking\Archiver; |
| use Piwik\Plugins\BotTracking\Columns\Metrics\DiscrepancyScore; |
| use Piwik\Plugins\BotTracking\DataTable\FavouredPagesScorer; |
| use Piwik\Plugins\BotTracking\Metrics; |
| use Piwik\RankingQuery; |
| use Piwik\Tracker\Action; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| class AIChatbotFavouredPages extends RecordBuilder |
| { |
| use AIChatbotPageMetricsTrait; |
|
|
| |
| |
| |
| protected $maxRowsInTable; |
|
|
| |
| |
| |
| private $rankingQueryLimit; |
|
|
| public function __construct() |
| { |
| parent::__construct(); |
|
|
| $this->maxRowsInTable = GeneralConfig::getIntegerConfigValue('datatable_archiving_maximum_rows_ai_chatbot_favoured_pages', 50000); |
| $this->rankingQueryLimit = $this->getRankingQueryLimit($this->maxRowsInTable); |
| } |
|
|
| public function getRecordMetadata(ArchiveProcessor $archiveProcessor): array |
| { |
| $records = []; |
| foreach ($this->variantByRecord() as $recordName => $variant) { |
| |
| |
| |
| $records[] = Record::make(Record::TYPE_BLOB, $recordName) |
| ->setColumnToSortByBeforeTruncation(Metrics::COLUMN_DISCREPANCY_SCORE) |
| ->setBlobColumnAggregationOps([ |
| Metrics::COLUMN_UNIQUE_HUMAN_PAGEVIEWS => 'sum', |
| Metrics::COLUMN_AI_CHATBOT_REQUESTS => 'sum', |
| Metrics::COLUMN_DISCREPANCY_SCORE => 'skip', |
| ]) |
| ->setAggregatedRecordTransform(function (DataTable $table) use ($variant): void { |
| (new FavouredPagesScorer($variant))->addScores($table); |
| }); |
| } |
|
|
| return $records; |
| } |
|
|
| public function isEnabled(ArchiveProcessor $archiveProcessor): bool |
| { |
| |
| return $archiveProcessor->getParams()->getSegment()->isEmpty(); |
| } |
|
|
| |
| |
| |
| |
| protected function aggregate(ArchiveProcessor $archiveProcessor): array |
| { |
| |
| $aiTable = $this->queryPageOrDocumentUrls($archiveProcessor, Action::TYPE_PAGE_URL, $this->rankingQueryLimit, [Metrics::COLUMN_REQUESTS]); |
|
|
| |
| |
| if ($aiTable->getRowsCount() === 0) { |
| Common::destroy($aiTable); |
| return $this->emptyRecords(); |
| } |
|
|
| $humanTable = $this->queryHumanPageviews($archiveProcessor); |
|
|
| $records = []; |
| foreach ($this->variantByRecord() as $recordName => $variant) { |
| |
| |
| $table = self::mergeHumanAndAiTables($humanTable, $aiTable); |
| (new FavouredPagesScorer($variant))->addScores($table); |
| $records[$recordName] = $table; |
| } |
|
|
| Common::destroy($humanTable); |
| Common::destroy($aiTable); |
|
|
| return $records; |
| } |
|
|
| |
| |
| |
| private function emptyRecords(): array |
| { |
| $records = []; |
| foreach (array_keys($this->variantByRecord()) as $recordName) { |
| $records[$recordName] = new DataTable(); |
| } |
|
|
| return $records; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| public static function mergeHumanAndAiTables(DataTable $humanTable, DataTable $aiTable): DataTable |
| { |
| $byLabel = []; |
|
|
| |
| |
| foreach ($aiTable->getRows() as $row) { |
| if ($row->isSummaryRow()) { |
| continue; |
| } |
| $label = (string) $row->getColumn('label'); |
| if ($label === '') { |
| continue; |
| } |
| $byLabel[$label] = [ |
| Metrics::COLUMN_UNIQUE_HUMAN_PAGEVIEWS => 0, |
| Metrics::COLUMN_AI_CHATBOT_REQUESTS => (int) $row->getColumn(Metrics::COLUMN_REQUESTS), |
| ]; |
| } |
|
|
| foreach ($humanTable->getRows() as $row) { |
| if ($row->isSummaryRow()) { |
| continue; |
| } |
| $label = (string) $row->getColumn('label'); |
| if ($label === '') { |
| continue; |
| } |
| $human = (int) $row->getColumn(Metrics::COLUMN_UNIQUE_HUMAN_PAGEVIEWS); |
| if (isset($byLabel[$label])) { |
| $byLabel[$label][Metrics::COLUMN_UNIQUE_HUMAN_PAGEVIEWS] = $human; |
| } else { |
| $byLabel[$label] = [ |
| Metrics::COLUMN_UNIQUE_HUMAN_PAGEVIEWS => $human, |
| Metrics::COLUMN_AI_CHATBOT_REQUESTS => 0, |
| ]; |
| } |
| } |
|
|
| $table = new DataTable(); |
| foreach ($byLabel as $label => $cols) { |
| $table->addRow(new Row([ |
| Row::COLUMNS => [ |
| 'label' => (string) $label, |
| Metrics::COLUMN_UNIQUE_HUMAN_PAGEVIEWS => $cols[Metrics::COLUMN_UNIQUE_HUMAN_PAGEVIEWS], |
| Metrics::COLUMN_AI_CHATBOT_REQUESTS => $cols[Metrics::COLUMN_AI_CHATBOT_REQUESTS], |
| ], |
| ])); |
| } |
|
|
| self::addCombinedOthersRow($table, $humanTable, $aiTable); |
|
|
| return $table; |
| } |
|
|
| |
| |
| |
| |
| |
| private static function addCombinedOthersRow(DataTable $table, DataTable $humanTable, DataTable $aiTable): void |
| { |
| $humanOthers = $humanTable->getSummaryRow(); |
| $aiOthers = $aiTable->getSummaryRow(); |
|
|
| if (!$humanOthers instanceof Row && !$aiOthers instanceof Row) { |
| return; |
| } |
|
|
| $label = ($humanOthers instanceof Row ? $humanOthers : $aiOthers)->getColumn('label') ?: DataTable::LABEL_SUMMARY_ROW; |
|
|
| $summary = new Row([ |
| Row::COLUMNS => [ |
| 'label' => $label, |
| Metrics::COLUMN_UNIQUE_HUMAN_PAGEVIEWS => $humanOthers instanceof Row ? (int) $humanOthers->getColumn(Metrics::COLUMN_UNIQUE_HUMAN_PAGEVIEWS) : 0, |
| Metrics::COLUMN_AI_CHATBOT_REQUESTS => $aiOthers instanceof Row ? (int) $aiOthers->getColumn(Metrics::COLUMN_REQUESTS) : 0, |
| ], |
| ]); |
| $summary->setIsSummaryRow(); |
| $table->addSummaryRow($summary); |
| } |
|
|
| |
| |
| |
| private function variantByRecord(): array |
| { |
| return [ |
| Archiver::AI_CHATBOTS_HUMAN_FAVOURED_PAGES_RECORD => DiscrepancyScore::VARIANT_HUMAN_FAVOURED, |
| Archiver::AI_CHATBOTS_AI_FAVOURED_PAGES_RECORD => DiscrepancyScore::VARIANT_AI_FAVOURED, |
| ]; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| private function queryHumanPageviews(ArchiveProcessor $archiveProcessor): DataTable |
| { |
| $logAggregator = $archiveProcessor->getLogAggregator(); |
| $where = $logAggregator->getWhereStatement('log_link_visit_action', 'server_time'); |
| $actionTable = Common::prefixTable('log_action'); |
| $visitActions = Common::prefixTable('log_link_visit_action'); |
|
|
| $innerSql = sprintf( |
| "SELECT log_action.name AS url, |
| COUNT(DISTINCT log_link_visit_action.idvisit) AS %s |
| FROM `%s` AS log_link_visit_action |
| INNER JOIN `%s` AS log_action ON log_action.idaction = log_link_visit_action.idaction_url |
| WHERE log_action.name IS NOT NULL |
| AND log_action.name <> '' |
| AND log_action.type = %d |
| AND log_link_visit_action.idaction_event_category IS NULL |
| AND %s |
| GROUP BY log_action.name |
| ORDER BY %s DESC", |
| Metrics::COLUMN_UNIQUE_HUMAN_PAGEVIEWS, |
| $visitActions, |
| $actionTable, |
| Action::TYPE_PAGE_URL, |
| $where, |
| Metrics::COLUMN_UNIQUE_HUMAN_PAGEVIEWS |
| ); |
|
|
| $rankingQuery = new RankingQuery($this->rankingQueryLimit); |
| $rankingQuery->addLabelColumn('url'); |
| $rankingQuery->addColumn(Metrics::COLUMN_UNIQUE_HUMAN_PAGEVIEWS, 'sum'); |
|
|
| $wrappedSql = $rankingQuery->generateRankingQuery($innerSql); |
|
|
| $stmt = Db::query($wrappedSql, $logAggregator->getGeneralQueryBindParams()); |
|
|
| $table = new DataTable(); |
| while ($row = $stmt->fetch()) { |
| |
| $label = (string) $row['url']; |
| $raw = $row[Metrics::COLUMN_UNIQUE_HUMAN_PAGEVIEWS] ?? 0; |
|
|
| $table->sumRowWithLabel($label, [ |
| Metrics::COLUMN_UNIQUE_HUMAN_PAGEVIEWS => is_numeric($raw) ? (int) $raw : 0, |
| ]); |
| } |
|
|
| return $table; |
| } |
| } |
|
|