diff --git a/config/global.ini.php b/config/global.ini.php index 9e471c0cd78..eef5511a0d3 100644 --- a/config/global.ini.php +++ b/config/global.ini.php @@ -632,6 +632,11 @@ ; maximum number of rows for the Products reports datatable_archiving_maximum_rows_products = 10000 +; maximum number of AI Assistants listed in Bot Tracking reports +datatable_archiving_maximum_rows_bots = 250 +; maximum number of page/document rows listed per AI Assistant in Bot Tracking reports +datatable_archiving_maximum_rows_subtable_bots = 250 + ; maximum number of rows for other tables (Providers, User settings configurations) datatable_archiving_maximum_rows_standard = 500 diff --git a/plugins/AIAgents/Reports/Get.php b/plugins/AIAgents/Reports/Get.php index 73a912d751b..33f60ff54c5 100644 --- a/plugins/AIAgents/Reports/Get.php +++ b/plugins/AIAgents/Reports/Get.php @@ -33,7 +33,7 @@ protected function init() $this->name = Piwik::translate('AIAgents_AIAgentVisits'); $this->categoryId = 'AIAgents_AIAssistants'; $this->subcategoryId = 'General_Overview'; - $this->order = 10; + $this->order = 90; $this->processedMetrics = [ new AIAgentMetric(new AverageTimeOnSite(), API::AI_AGENT_COLUMN_SUFFIX), @@ -66,14 +66,14 @@ public function configureWidgets(WidgetsList $widgetsList, ReportWidgetFactory $ ->setName('AIAgents_WidgetGraphAIAgents') ->forceViewDataTable(Evolution::ID) ->setAction('getEvolutionGraph') - ->setOrder(1) + ->setOrder(90) ); $widgetsList->addWidgetConfig( $factory->createWidget() ->forceViewDataTable(Sparklines::ID) ->setName('AIAgents_WidgetOverviewAIAgents') - ->setOrder(2) + ->setOrder(91) ); } diff --git a/plugins/AIAgents/tests/UI/AIAgents_spec.js b/plugins/AIAgents/tests/UI/AIAgents_spec.js index 86b466b4184..b3945817fab 100644 --- a/plugins/AIAgents/tests/UI/AIAgents_spec.js +++ b/plugins/AIAgents/tests/UI/AIAgents_spec.js @@ -17,7 +17,7 @@ describe('AIAgents', function () { await page.waitForNetworkIdle(); const widgets = await page.$$('.matomo-widget'); - expect(widgets.length).to.equal(2); + expect(widgets.length).to.equal(3); }); it('should show the AI assistants report menu items', async function () { diff --git a/plugins/AIAgents/tests/UI/expected-ui-screenshots/AIAgents_overview.png b/plugins/AIAgents/tests/UI/expected-ui-screenshots/AIAgents_overview.png index 4e03bf0c852..20dc8236c93 100644 Binary files a/plugins/AIAgents/tests/UI/expected-ui-screenshots/AIAgents_overview.png and b/plugins/AIAgents/tests/UI/expected-ui-screenshots/AIAgents_overview.png differ diff --git a/plugins/BotTracking/API.php b/plugins/BotTracking/API.php new file mode 100644 index 00000000000..b9717847782 --- /dev/null +++ b/plugins/BotTracking/API.php @@ -0,0 +1,76 @@ +filter(function (DataTable $table) { + foreach ($table->getRows() as $key => $row) { + if (!$row->getIdSubDataTable()) { + $table->deleteRow($key); + } + } + }); + } + + return $dataTable; + } + + /** + * @param string|int|int[] $idSite + * @return DataTable|DataTable\Map + */ + public function getPageUrlsForAIAssistant($idSite, string $period, string $date, int $idSubtable): DataTableInterface + { + Piwik::checkUserHasViewAccess($idSite); + + return Archive::createDataTableFromArchive(Archiver::AI_ASSISTANTS_PAGES_RECORD, $idSite, $period, $date, '', false, false, $idSubtable); + } + + /** + * @param string|int|int[] $idSite + * @return DataTable|DataTable\Map + */ + public function getDocumentUrlsForAIAssistant($idSite, string $period, string $date, int $idSubtable): DataTableInterface + { + Piwik::checkUserHasViewAccess($idSite); + + return Archive::createDataTableFromArchive(Archiver::AI_ASSISTANTS_DOCUMENTS_RECORD, $idSite, $period, $date, '', false, false, $idSubtable); + } +} diff --git a/plugins/BotTracking/Archiver.php b/plugins/BotTracking/Archiver.php new file mode 100644 index 00000000000..d52fd62b7f8 --- /dev/null +++ b/plugins/BotTracking/Archiver.php @@ -0,0 +1,23 @@ +getPrettyNumber($value); + } + + public function getSemanticType(): ?string + { + return Dimension::TYPE_NUMBER; + } +} diff --git a/plugins/BotTracking/Columns/Metrics/DocumentRequests.php b/plugins/BotTracking/Columns/Metrics/DocumentRequests.php new file mode 100644 index 00000000000..16ffb9ceedd --- /dev/null +++ b/plugins/BotTracking/Columns/Metrics/DocumentRequests.php @@ -0,0 +1,46 @@ +getPrettyNumber($value); + } + + public function getSemanticType(): ?string + { + return Dimension::TYPE_NUMBER; + } +} diff --git a/plugins/BotTracking/Columns/Metrics/PageRequests.php b/plugins/BotTracking/Columns/Metrics/PageRequests.php new file mode 100644 index 00000000000..87dc458df95 --- /dev/null +++ b/plugins/BotTracking/Columns/Metrics/PageRequests.php @@ -0,0 +1,46 @@ +getPrettyNumber($value); + } + + public function getSemanticType(): ?string + { + return Dimension::TYPE_NUMBER; + } +} diff --git a/plugins/BotTracking/Columns/Metrics/Requests.php b/plugins/BotTracking/Columns/Metrics/Requests.php new file mode 100644 index 00000000000..657c7286a14 --- /dev/null +++ b/plugins/BotTracking/Columns/Metrics/Requests.php @@ -0,0 +1,46 @@ +getPrettyNumber($value); + } + + public function getSemanticType(): ?string + { + return Dimension::TYPE_NUMBER; + } +} diff --git a/plugins/BotTracking/Columns/PageUrl.php b/plugins/BotTracking/Columns/PageUrl.php new file mode 100644 index 00000000000..cf6515736e8 --- /dev/null +++ b/plugins/BotTracking/Columns/PageUrl.php @@ -0,0 +1,20 @@ + + */ + private const ASSISTANT_MAPPING = [ + 'ChatGPT-User' => 'ChatGPT', + 'MistralAI-User' => 'Le Chat', + 'Gemini-Deep-Research' => 'Gemini', + 'Claude-User' => 'Claude', + 'Perplexity-User' => 'Perplexity', + 'Google-NotebookLM' => 'NotebookLM', + 'Devin' => '', + ]; + + public function __construct() + { + parent::__construct(); + + $this->columnToSortByBeforeTruncation = Metrics::COLUMN_REQUESTS; + $this->maxRowsInTable = (int)GeneralConfig::getConfigValue('datatable_archiving_maximum_rows_bots'); + $this->maxRowsInSubtable = (int)GeneralConfig::getConfigValue('datatable_archiving_maximum_rows_subtable_bots'); + } + + public function getRecordMetadata(ArchiveProcessor $archiveProcessor): array + { + return [ + Record::make(Record::TYPE_BLOB, Archiver::AI_ASSISTANTS_PAGES_RECORD), + Record::make(Record::TYPE_BLOB, Archiver::AI_ASSISTANTS_DOCUMENTS_RECORD), + ]; + } + + public function isEnabled(ArchiveProcessor $archiveProcessor): bool + { + // don't process reports for any segment + return $archiveProcessor->getParams()->getSegment()->isEmpty(); + } + + protected function aggregate(ArchiveProcessor $archiveProcessor): array + { + $tables = [ + Archiver::AI_ASSISTANTS_PAGES_RECORD => new DataTable(), + Archiver::AI_ASSISTANTS_DOCUMENTS_RECORD => new DataTable(), + ]; + + $this->populateTables($archiveProcessor, $tables); + + return $tables; + } + + /** + * @param array $tables + */ + private function populateTables(ArchiveProcessor $archiveProcessor, array &$tables): void + { + $logAggregator = $archiveProcessor->getLogAggregator(); + $params = $archiveProcessor->getParams(); + $sites = $params->getIdSites(); + + if (empty($sites)) { + return; + } + + $visits = $this->queryAcquiredVisitsByAIAssistant($logAggregator); + + $this->populateTableForActionType($tables, Action::TYPE_PAGE_URL, $logAggregator, $visits); + $this->populateTableForActionType($tables, Action::TYPE_DOWNLOAD, $logAggregator, $visits); + } + + /** + * @return array + */ + private function queryAcquiredVisitsByAIAssistant(LogAggregator $logAggregator): array + { + $where = $logAggregator->getWhereStatement('log_visit', 'visit_last_action_time'); + $bindBase = $logAggregator->getGeneralQueryBindParams(); + + $sql = sprintf( + "SELECT `referer_name`, COUNT(*) AS `visits` + FROM %s AS `log_visit` + WHERE `referer_type` = %d + AND `referer_name` <> '' + AND %s + GROUP BY `referer_name`", + Common::prefixTable('log_visit'), + Common::REFERRER_TYPE_AI_ASSISTANT, + $where + ); + + $stmt = Db::query($sql, $bindBase); + $result = []; + + while ($row = $stmt->fetch()) { + /** + * @var array{visits: string|int, referer_name: string} $row + */ + if (in_array($row['referer_name'], self::ASSISTANT_MAPPING)) { + $key = (string)array_search($row['referer_name'], self::ASSISTANT_MAPPING); + $result[$key] = (int)$row['visits']; + } + } + + return $result; + } + + /** + * @param array $tables + * @param array $visits + * @return void + */ + private function populateTableForActionType(array $tables, int $actionType, LogAggregator $logAggregator, array $visits): void + { + $where = $logAggregator->getWhereStatement('bot', 'server_time'); + $bindBase = $logAggregator->getGeneralQueryBindParams(); + + $sql = sprintf( + "SELECT * FROM (SELECT bot.bot_name, log_action.name AS url, COUNT(*) AS requests + FROM %s AS bot + INNER JOIN %s AS log_action ON log_action.idaction = bot.idaction_url + WHERE log_action.name IS NOT NULL + AND log_action.name <> '' + AND log_action.type = %d + AND %s + GROUP BY bot.bot_name, url WITH ROLLUP) AS rollupQuery + ORDER BY bot_name, requests DESC, url", + BotRequestsDao::getPrefixedTableName(), + Common::prefixTable('log_action'), + $actionType, + $where + ); + + $resultSet = Db::query($sql, $bindBase); + $actionRows = []; + + while ($row = $resultSet->fetch()) { + /** + * @var array{requests: int, bot_name: ?string, url: ?string} $row + */ + $label = $row['bot_name']; + $url = $row['url']; + + if (is_null($label)) { + continue; + } + + if (!is_null($url)) { + $actionRows[] = $row; + continue; + } + + $metrics = [ + Metrics::COLUMN_REQUESTS => $row['requests'], + Metrics::COLUMN_DOCUMENT_REQUESTS => $actionType === Action::TYPE_DOWNLOAD ? $row['requests'] : 0, + Metrics::COLUMN_PAGE_REQUESTS => $actionType === Action::TYPE_PAGE_URL ? $row['requests'] : 0, + Metrics::COLUMN_ACQUIRED_VISITS => $visits[$label] ?? 0, + ]; + + // we add all records to both tables, so we in the end have the total count of pages & documents in the main table + $tables[Archiver::AI_ASSISTANTS_PAGES_RECORD]->sumRowWithLabel($label, $metrics, [Metrics::COLUMN_ACQUIRED_VISITS => 'max']); + $tables[Archiver::AI_ASSISTANTS_DOCUMENTS_RECORD]->sumRowWithLabel($label, $metrics, [Metrics::COLUMN_ACQUIRED_VISITS => 'max']); + } + + $table = $tables[Archiver::AI_ASSISTANTS_PAGES_RECORD]; + + if ($actionType === Action::TYPE_DOWNLOAD) { + $table = $tables[Archiver::AI_ASSISTANTS_DOCUMENTS_RECORD]; + } + + // use while / array_shift combination instead of foreach to save memory + while (is_array($actionRows) && count($actionRows)) { + /** + * @var array{requests: int, bot_name: string, url: string} $row + */ + $row = array_shift($actionRows); + $label = $row['bot_name']; + $url = $row['url']; + + $tableRow = $table->getRowFromLabel($label); + + if (empty($tableRow)) { + continue; + } + + $normalized = PageUrl::normalizeUrl($url); + $url = $normalized['url']; + + $tableRow->sumRowWithLabelToSubtable($url, [ + Metrics::COLUMN_REQUESTS => $row['requests'], + ]); + } + } +} diff --git a/plugins/BotTracking/Reports/GetAIAssistantRequests.php b/plugins/BotTracking/Reports/GetAIAssistantRequests.php new file mode 100644 index 00000000000..7a7f1662016 --- /dev/null +++ b/plugins/BotTracking/Reports/GetAIAssistantRequests.php @@ -0,0 +1,96 @@ +name = Piwik::translate('BotTracking_AIAssistantsReportTitle'); + $this->documentation = Piwik::translate('BotTracking_AIAssistantsReportDocumentation'); + $this->categoryId = 'AIAgents_AIAssistants'; + $this->subcategoryId = 'General_Overview'; + $this->dimension = new AIAssistantName(); + $this->metrics = [ + new Requests(), + new PageRequests(), + new DocumentRequests(), + new AcquiredVisits(), + ]; + $this->processedMetrics = []; + $this->order = 30; + $this->defaultSortColumn = Metrics::COLUMN_ACQUIRED_VISITS; + if (\Piwik\Request::fromRequest()->getStringParameter('secondaryDimension', '') === 'documents') { + $this->actionToLoadSubTables = 'getDocumentUrlsForAIAssistant'; + } else { + $this->actionToLoadSubTables = 'getPageUrlsForAIAssistant'; + } + } + + public function configureView(ViewDataTable $view): void + { + parent::configureView($view); + + $view->config->show_table_all_columns = false; + $view->config->show_insights = false; + + // Show segment not supported message when a segment is selected + if (!empty(Request::getRawSegmentFromRequest())) { + $message = '

' . Piwik::translate('BotTracking_SegmentNotSupported') . '

'; + $view->config->show_header_message = $message; + } + + $view->config->setDefaultColumnsToDisplay( + ['label', Metrics::COLUMN_REQUESTS, Metrics::COLUMN_PAGE_REQUESTS, Metrics::COLUMN_DOCUMENT_REQUESTS, Metrics::COLUMN_ACQUIRED_VISITS], + false, + false + ); + + // only show request count for flat table, as subtable doesn't have other metrics + if ((int)$view->requestConfig->getRequestParam('flat') === 1) { + $view->config->setDefaultColumnsToDisplay( + ['label', Metrics::COLUMN_REQUESTS], + false, + false + ); + } + + $secondaryDimensions = [ + 'pages' => Piwik::translate('BotTracking_ColumnPageRequests'), + 'documents' => Piwik::translate('BotTracking_ColumnDocumentRequests'), + ]; + $view->config->setSecondaryDimensions($secondaryDimensions, 'pages'); + } + + /** + * @return void + */ + public function configureWidgets(WidgetsList $widgetsList, ReportWidgetFactory $factory) + { + $widgetsList->addWidgetConfig($factory->createWidget()->setOrder($this->order)); + } +} diff --git a/plugins/BotTracking/Reports/GetDocumentUrlsForAIAssistant.php b/plugins/BotTracking/Reports/GetDocumentUrlsForAIAssistant.php new file mode 100644 index 00000000000..8a0e994a90c --- /dev/null +++ b/plugins/BotTracking/Reports/GetDocumentUrlsForAIAssistant.php @@ -0,0 +1,32 @@ +name = Piwik::translate('BotTracking_AIAssistantsReportTitle'); + $this->categoryId = 'AIAgents_AIAssistants'; + $this->metrics = [new Requests()]; + $this->processedMetrics = []; + $this->dimension = new DocumentUrl(); + $this->isSubtableReport = true; + } +} diff --git a/plugins/BotTracking/Reports/GetPageUrlsForAIAssistant.php b/plugins/BotTracking/Reports/GetPageUrlsForAIAssistant.php new file mode 100644 index 00000000000..74f04bcff94 --- /dev/null +++ b/plugins/BotTracking/Reports/GetPageUrlsForAIAssistant.php @@ -0,0 +1,32 @@ +name = Piwik::translate('BotTracking_AIAssistantsReportTitle'); + $this->categoryId = 'AIAgents_AIAssistants'; + $this->metrics = [new Requests()]; + $this->processedMetrics = []; + $this->dimension = new PageUrl(); + $this->isSubtableReport = true; + } +} diff --git a/plugins/BotTracking/lang/en.json b/plugins/BotTracking/lang/en.json index 5ab7d81b4bf..95ba3d16a8a 100644 --- a/plugins/BotTracking/lang/en.json +++ b/plugins/BotTracking/lang/en.json @@ -1,5 +1,19 @@ { "BotTracking": { - "PluginDescription": "Track and analyze visits from AI bots such as ChatGPT-User and similar. This plugin helps you understand how AI-driven tools interact with your website by logging and reporting bot-triggered requests." + "AIAssistantsReportDocumentation": "Discover which AI assistants are crawling your site and how many hits each one generates. Expand a bot to review the most frequently requested pages or documents.", + "AIAssistantsReportTitle": "AI Assistants - Bots", + "ColumnAIAssistantName": "AI Assistant Name", + "ColumnAcquiredVisits": "Acquired visits", + "ColumnAcquiredVisitsDocumentation": "Visits that started after someone clicked from an AI assistant (Referrers \u2192 AI Assistants).", + "ColumnDocumentRequests": "Document Requests", + "ColumnDocumentRequestsDocumentation": "Total number of bot requests to document URLs recorded during the selected period.", + "ColumnPageRequests": "Page Requests", + "ColumnPageRequestsDocumentation": "Total number of bot requests to page URLs recorded during the selected period.", + "ColumnRequests": "Requests", + "ColumnRequestsDocumentation": "Total number of bot requests recorded during the selected period. Includes both page and document URLs.", + "DocumentUrl": "Document URL", + "PageUrl": "Page URL", + "PluginDescription": "Track and analyze visits from AI bots such as ChatGPT-User and similar. This plugin helps you understand how AI-driven tools interact with your website by logging and reporting bot-triggered requests.", + "SegmentNotSupported": "Report does not support segmentation. The data displayed is your standard, unsegmented report data." } } diff --git a/plugins/BotTracking/tests/Fixtures/BotTraffic.php b/plugins/BotTracking/tests/Fixtures/BotTraffic.php new file mode 100644 index 00000000000..ae1e99b44ef --- /dev/null +++ b/plugins/BotTracking/tests/Fixtures/BotTraffic.php @@ -0,0 +1,174 @@ +setUpWebsite(); + $this->trackBotRequests(); + $this->trackAcquiredVisits(); + } + + public function tearDown(): void + { + // nothing to clean up + } + + private function setUpWebsite(): void + { + if (!self::siteCreated($this->idSite)) { + self::createWebsite($this->dateTime, true, 'https://example.com'); + } + } + + private function trackBotRequests(): void + { + $pages = [ + 'https://example.com/article-1', + 'https://example.com/article-2', + 'https://example.com/article-3', + 'https://example.com/article-4', + 'https://example.com/article-5', + 'https://example.com/article-6', + ]; + + $downloads = [ + 'https://example.com/resources/doc.pdf', + 'https://example.com/resources/guide.pdf', + 'https://example.com/resources/whitepaper.pdf', + 'https://example.com/resources/datasheet.pdf', + 'https://example.com/resources/case-study.pdf', + ]; + + $dailyPlans = [ + 0 => [ + ['ChatGPT-User/1.0', $pages[0], 200, 12005, false], + ['Gemini-Deep-Research/1.0', $pages[1], 200, 29658, false], + ['Perplexity-User/1.0', $downloads[0], 503, 1365955, true], + ['Google-NotebookLM/1.0', $downloads[1], 404, 36522, true], + ['ChatGPT-User/1.0', $pages[0], 200, 12584, false], + ['Gemini-Deep-Research/1.0', $pages[1], 200, 36598, false], + ['Perplexity-User/1.0', $downloads[0], 200, 99562, true], + ['Google-NotebookLM/1.0', $pages[2], 200, 25489, false], + ], + 1 => [ + ['MistralAI-User/2.0', $pages[2], 200, 32485, false], + ['Claude-User/3.0', $downloads[2], 200, 123456, true], + ['ChatGPT-User/1.0', $pages[1], 500, 25896, false], + ['ChatGPT-User/1.0', $downloads[1], 200, 33658, true], + ['Perplexity-User/1.0', $pages[2], 200, 36985, false], + ['MistralAI-User/2.0', $pages[3], 200, 85236, false], + ['Claude-User/3.0', $downloads[3], 200, 12456, true], + ], + 2 => [ + ['Perplexity-User/1.0', $downloads[3], 200, 84269, true], + ['Gemini-Deep-Research/1.0', $pages[3], 200, 3265, false], + ['Devin/1.0', 'https://example.com/api', 200, 33366, false], + ['ChatGPT-User/1.0', $pages[3], 200, 5454, false], + ['Perplexity-User/1.0', $downloads[2], 200, 69856, true], + ['Gemini-Deep-Research/1.0', $pages[4], 200, 63256, false], + ['Devin/1.0', 'https://example.com/api', 200, 25486, false], + ], + 3 => [ + ['MistralAI-User/2.0', $pages[4], 200, 12568, false], + ['Google-NotebookLM/1.0', $downloads[4], 404, 25648, true], + ['ChatGPT-User/1.0', $pages[4], 200, 12548, false], + ['Claude-User/3.0', $pages[5], 503, 36598, false], + ['Perplexity-User/1.0', $downloads[0], 200, 225445, true], + ['MistralAI-User/2.0', $pages[2], 200, 12456, false], + ['Google-NotebookLM/1.0', $downloads[1], 200, 258741, true], + ], + 4 => [ + ['Perplexity-User/1.0', $downloads[1], 200, 36985, true], + ['Gemini-Deep-Research/1.0', $pages[5], 200, 95147, false], + ['ChatGPT-User/1.0', $pages[0], 200, 25412, false], + ['Claude-User/3.0', $pages[3], 200, 36985, false], + ['Perplexity-User/1.0', $downloads[4], 200, 145811, true], + ], + ]; + + foreach ($dailyPlans as $dayOffset => $requests) { + foreach ($requests as $index => $request) { + [$userAgent, $url, $status, $bytes, $isDownload] = $request; + $date = Date::factory($this->dateTime) + ->addDay($dayOffset) + ->addHour(($index + 1) * 2) + ->getDatetime(); + + if ($isDownload) { + $this->logBotDownload($userAgent, $url, $status, $bytes, $date); + } else { + $this->logBot($userAgent, $url, $status, $bytes, $date); + } + } + } + } + + private function trackAcquiredVisits(): void + { + $sources = [ + 'https://chatgpt.com/thread/12345', + 'https://perplexity.ai/share/6789', + 'https://copilot.microsoft.com/answer/abc', + 'https://claude.ai/share/987', + 'https://gemini.google.com/share/notes', + 'https://chat.qwen.ai/share/insight', + 'https://chatgpt.com/thread/8888', + 'https://perplexity.ai/share/2222', + 'https://copilot.microsoft.com/answer/xyz', + 'https://claude.ai/share/1111', + ]; + + foreach ($sources as $index => $referrer) { + $date = Date::factory($this->dateTime) + ->addDay($index % 5) + ->addHour(($index % 4) * 3) + ->getDatetime(); + $tracker = self::getTracker($this->idSite, $date, true); + $tracker->setUrl('https://example.com/article-' . (($index % 4) + 1)); + $tracker->setUrlReferrer($referrer); + self::checkResponse($tracker->doTrackPageView('Article From AI Assistant ' . ($index + 1))); + } + } + + private function logBot(string $userAgent, string $url, int $statusCode, int $bytes, string $dateTime): void + { + $tracker = self::getTracker($this->idSite, $dateTime, true); + $tracker->setUserAgent($userAgent); + $tracker->setUrl($url); + $tracker->setCustomTrackingParameter('recMode', '1'); + $tracker->setCustomTrackingParameter('http_status', (string) $statusCode); + $tracker->setCustomTrackingParameter('bw_bytes', (string) $bytes); + self::checkResponse($tracker->doTrackPageView('')); + } + + private function logBotDownload(string $userAgent, string $url, int $statusCode, int $bytes, string $dateTime): void + { + $tracker = self::getTracker($this->idSite, $dateTime, true); + $tracker->setUserAgent($userAgent); + $tracker->setCustomTrackingParameter('recMode', '1'); + $tracker->setCustomTrackingParameter('http_status', (string) $statusCode); + $tracker->setCustomTrackingParameter('bw_bytes', (string) $bytes); + self::checkResponse($tracker->doTrackAction($url, 'download')); + } +} diff --git a/plugins/BotTracking/tests/System/ApiTest.php b/plugins/BotTracking/tests/System/ApiTest.php new file mode 100644 index 00000000000..cdc01495d2a --- /dev/null +++ b/plugins/BotTracking/tests/System/ApiTest.php @@ -0,0 +1,95 @@ +runApiTests($api, $params); + } + + public function getApiForTesting() + { + return [ + [ + [ + 'BotTracking.getAIAssistantRequests', + ], + [ + 'idSite' => 1, + 'date' => '2025-02-03', + 'periods' => ['day', 'week'], + 'otherRequestParameters' => [ + 'expanded' => 1, + 'secondaryDimension' => 'pages', + ], + 'testSuffix' => '_pages', + ], + ], + [ + [ + 'BotTracking.getAIAssistantRequests', + ], + [ + 'idSite' => 1, + 'date' => '2025-02-03', + 'periods' => ['day', 'week'], + 'otherRequestParameters' => [ + 'flat' => 1, + ], + 'testSuffix' => '_flat', + ], + ], + [ + [ + 'BotTracking.getAIAssistantRequests', + ], [ + 'idSite' => 1, + 'date' => '2025-02-03', + 'periods' => ['day', 'week'], + 'otherRequestParameters' => [ + 'expanded' => 1, + 'secondaryDimension' => 'documents', + ], + 'testSuffix' => '_documents', + ], + ], + ]; + } + + public static function getOutputPrefix() + { + return ''; + } + + public static function getPathToTestDirectory() + { + return __DIR__; + } +} + +ApiTest::$fixture = new BotTraffic(); diff --git a/plugins/BotTracking/tests/System/expected/test__documents__BotTracking.getAIAssistantRequests_day.xml b/plugins/BotTracking/tests/System/expected/test__documents__BotTracking.getAIAssistantRequests_day.xml new file mode 100644 index 00000000000..34da712b105 --- /dev/null +++ b/plugins/BotTracking/tests/System/expected/test__documents__BotTracking.getAIAssistantRequests_day.xml @@ -0,0 +1,47 @@ + + + + + 2 + 1 + 1 + 1 + + + + 1 + + + + + + 1 + 0 + 1 + 1 + + + + 2 + 2 + 0 + 0 + + + + 1 + + + + 1 + + + + + + 2 + 0 + 2 + 0 + + \ No newline at end of file diff --git a/plugins/BotTracking/tests/System/expected/test__documents__BotTracking.getAIAssistantRequests_week.xml b/plugins/BotTracking/tests/System/expected/test__documents__BotTracking.getAIAssistantRequests_week.xml new file mode 100644 index 00000000000..05e390d10c2 --- /dev/null +++ b/plugins/BotTracking/tests/System/expected/test__documents__BotTracking.getAIAssistantRequests_week.xml @@ -0,0 +1,100 @@ + + + + + 4 + 2 + 2 + 2 + + + + 1 + + + + 1 + + + + + + 6 + 5 + 1 + 2 + + + + 1 + + + + 1 + + + + 1 + + + + 1 + + + + 1 + + + + + + 5 + 1 + 4 + 1 + + + + 1 + + + + + + 3 + 0 + 3 + 1 + + + + 2 + 0 + 2 + 0 + + + + 2 + 2 + 0 + 0 + + + + 1 + + + + 1 + + + + + + 4 + 0 + 4 + 0 + + \ No newline at end of file diff --git a/plugins/BotTracking/tests/System/expected/test__flat__BotTracking.getAIAssistantRequests_day.xml b/plugins/BotTracking/tests/System/expected/test__flat__BotTracking.getAIAssistantRequests_day.xml new file mode 100644 index 00000000000..34d65cf9a1f --- /dev/null +++ b/plugins/BotTracking/tests/System/expected/test__flat__BotTracking.getAIAssistantRequests_day.xml @@ -0,0 +1,27 @@ + + + + + 1 + ChatGPT-User + example.com/article-2 + + + + 1 + MistralAI-User + example.com/article-3 + + + + 1 + MistralAI-User + example.com/article-4 + + + + 1 + Perplexity-User + example.com/article-3 + + \ No newline at end of file diff --git a/plugins/BotTracking/tests/System/expected/test__flat__BotTracking.getAIAssistantRequests_week.xml b/plugins/BotTracking/tests/System/expected/test__flat__BotTracking.getAIAssistantRequests_week.xml new file mode 100644 index 00000000000..4e73ccb6c5c --- /dev/null +++ b/plugins/BotTracking/tests/System/expected/test__flat__BotTracking.getAIAssistantRequests_week.xml @@ -0,0 +1,87 @@ + + + + + 1 + ChatGPT-User + example.com/article-1 + + + + 1 + ChatGPT-User + example.com/article-2 + + + + 1 + ChatGPT-User + example.com/article-4 + + + + 1 + ChatGPT-User + example.com/article-5 + + + + 1 + Claude-User + example.com/article-4 + + + + 1 + Claude-User + example.com/article-6 + + + + 2 + Devin + example.com/api + + + + 1 + Gemini-Deep-Research + example.com/article-4 + + + + 1 + Gemini-Deep-Research + example.com/article-5 + + + + 1 + Gemini-Deep-Research + example.com/article-6 + + + + 2 + MistralAI-User + example.com/article-3 + + + + 1 + MistralAI-User + example.com/article-4 + + + + 1 + MistralAI-User + example.com/article-5 + + + + 1 + Perplexity-User + example.com/article-3 + + \ No newline at end of file diff --git a/plugins/BotTracking/tests/System/expected/test__pages__BotTracking.getAIAssistantRequests_day.xml b/plugins/BotTracking/tests/System/expected/test__pages__BotTracking.getAIAssistantRequests_day.xml new file mode 100644 index 00000000000..2ecdd671dfb --- /dev/null +++ b/plugins/BotTracking/tests/System/expected/test__pages__BotTracking.getAIAssistantRequests_day.xml @@ -0,0 +1,53 @@ + + + + + 2 + 1 + 1 + 1 + + + + 1 + + + + + + 1 + 0 + 1 + 1 + + + + 1 + + + + + + 2 + 2 + 0 + 0 + + + + 2 + 0 + 2 + 0 + + + + 1 + + + + 1 + + + + \ No newline at end of file diff --git a/plugins/BotTracking/tests/System/expected/test__pages__BotTracking.getAIAssistantRequests_week.xml b/plugins/BotTracking/tests/System/expected/test__pages__BotTracking.getAIAssistantRequests_week.xml new file mode 100644 index 00000000000..0d4cb7f7d51 --- /dev/null +++ b/plugins/BotTracking/tests/System/expected/test__pages__BotTracking.getAIAssistantRequests_week.xml @@ -0,0 +1,120 @@ + + + + + 4 + 2 + 2 + 2 + + + + 1 + + + + 1 + + + + + + 6 + 5 + 1 + 2 + + + + 1 + + + + + + 5 + 1 + 4 + 1 + + + + 1 + + + + 1 + + + + 1 + + + + 1 + + + + + + 3 + 0 + 3 + 1 + + + + 1 + + + + 1 + + + + 1 + + + + + + 2 + 0 + 2 + 0 + + + + 2 + + + + + + 2 + 2 + 0 + 0 + + + + 4 + 0 + 4 + 0 + + + + 2 + + + + 1 + + + + 1 + + + + \ No newline at end of file diff --git a/plugins/BotTracking/tests/UI/BotTracking_spec.js b/plugins/BotTracking/tests/UI/BotTracking_spec.js new file mode 100644 index 00000000000..5abc1efbb72 --- /dev/null +++ b/plugins/BotTracking/tests/UI/BotTracking_spec.js @@ -0,0 +1,47 @@ +/*! + * Matomo - free/libre analytics platform + * + * Screenshot integration tests. + * + * @link https://matomo.org + * @license https://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ + +describe("BotTracking", function () { + this.timeout(0); + + this.fixture = "Piwik\\Plugins\\BotTracking\\tests\\Fixtures\\BotTraffic"; + + var generalParams = 'idSite=1&period=day&date=2025-02-02', + urlBase = 'module=CoreHome&action=index&' + generalParams; + + it('should render AI Assistants > Overview bot detail report', async function () { + await page.goto("?" + urlBase + "#?" + generalParams + "&category=AIAgents_AIAssistants&subcategory=General_Overview"); + await page.waitForNetworkIdle(); + + const row = await page.jQuery('tr.subDataTable:first'); + await row.click(); + await page.mouse.move(-10, -10); + + await page.waitForNetworkIdle(); + await page.waitForTimeout(250); // rendering + + var elem = await page.$('#widgetBotTrackinggetAIAssistantRequests'); + expect(await elem.screenshot()).to.matchImage('bot_requests'); + }); + + it('should switch to secondary dimension when clicked', async function () { + await page.evaluate(() => $('.datatableRelatedReports li span:contains("Document Requests")').click()); + await page.waitForNetworkIdle(); + + const row = await page.jQuery('tr.subDataTable:first'); + await row.click(); + await page.mouse.move(-10, -10); + + await page.waitForNetworkIdle(); + await page.waitForTimeout(250); // rendering + + var elem = await page.$('#widgetBotTrackinggetAIAssistantRequests'); + expect(await elem.screenshot()).to.matchImage('bot_requests_documents'); + }); +}); diff --git a/plugins/BotTracking/tests/UI/expected-screenshots/BotTracking_bot_requests.png b/plugins/BotTracking/tests/UI/expected-screenshots/BotTracking_bot_requests.png new file mode 100644 index 00000000000..7156df2ffc0 --- /dev/null +++ b/plugins/BotTracking/tests/UI/expected-screenshots/BotTracking_bot_requests.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cbefc95323cea0d97ab204c0dfd222401ea255ebe30a9016b6d20f87d9e9df27 +size 43351 diff --git a/plugins/BotTracking/tests/UI/expected-screenshots/BotTracking_bot_requests_documents.png b/plugins/BotTracking/tests/UI/expected-screenshots/BotTracking_bot_requests_documents.png new file mode 100644 index 00000000000..a5cad6d03a0 --- /dev/null +++ b/plugins/BotTracking/tests/UI/expected-screenshots/BotTracking_bot_requests_documents.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ce216ceaf1baccc5a702721a50f73f81a3554c3da2feaac690189cd18ea6af7f +size 44484 diff --git a/plugins/Dashboard/tests/UI/expected-screenshots/DashboardManager_expanded.png b/plugins/Dashboard/tests/UI/expected-screenshots/DashboardManager_expanded.png index 26c842f91f4..b023b1373b5 100644 --- a/plugins/Dashboard/tests/UI/expected-screenshots/DashboardManager_expanded.png +++ b/plugins/Dashboard/tests/UI/expected-screenshots/DashboardManager_expanded.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b38c50c17c87fe04f757f487ecc7db313d4ea3c730a88118971802d1b4038341 -size 49988 +oid sha256:e4d497d894bc6bf63d580a6fcf130833cec6caf3f4a2c193eee6809cfba70abb +size 50708 diff --git a/plugins/Dashboard/tests/UI/expected-screenshots/DashboardManager_widget_list_shown.png b/plugins/Dashboard/tests/UI/expected-screenshots/DashboardManager_widget_list_shown.png index 6fa9783af72..dc4908d47e5 100644 --- a/plugins/Dashboard/tests/UI/expected-screenshots/DashboardManager_widget_list_shown.png +++ b/plugins/Dashboard/tests/UI/expected-screenshots/DashboardManager_widget_list_shown.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f73785263642d9519a7a723597a8aca3f17dc3e9a1847607a597ffb14ee94bcd -size 64019 +oid sha256:61f687b4e75fbaad9e0279eebeec877f3a98b67b3fb7fd0b1e37bb0f9be0bd56 +size 64735 diff --git a/plugins/Dashboard/tests/UI/expected-screenshots/DashboardManager_widget_preview.png b/plugins/Dashboard/tests/UI/expected-screenshots/DashboardManager_widget_preview.png index 4437a925316..affb7ab27d7 100644 --- a/plugins/Dashboard/tests/UI/expected-screenshots/DashboardManager_widget_preview.png +++ b/plugins/Dashboard/tests/UI/expected-screenshots/DashboardManager_widget_preview.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cda2b59ed6b52fcd0da287c4f98a79b1fd937f5bbb3eac005a8e0e04d62aebfa -size 77931 +oid sha256:bf8a67a8030277ab18fe7a2348cdf2de6dc7714df2cf58e1cf0b0ee87766962d +size 78678 diff --git a/plugins/Dashboard/tests/UI/expected-screenshots/Dashboard_dashboard2.png b/plugins/Dashboard/tests/UI/expected-screenshots/Dashboard_dashboard2.png index 0486f01a0b1..604db9bc5b1 100644 --- a/plugins/Dashboard/tests/UI/expected-screenshots/Dashboard_dashboard2.png +++ b/plugins/Dashboard/tests/UI/expected-screenshots/Dashboard_dashboard2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d48bc98b7adede3e240341d7516953c96f55bcdbee04f23c54616811366a1fdf -size 739020 +oid sha256:be720d7d3cf6e7283d2a940be49b9d03f81952a0d7ee0aa8ed92c0d227f20427 +size 733673 diff --git a/plugins/Dashboard/tests/UI/expected-screenshots/Dashboard_dashboard3.png b/plugins/Dashboard/tests/UI/expected-screenshots/Dashboard_dashboard3.png index 1794368711f..9ec747f6755 100644 --- a/plugins/Dashboard/tests/UI/expected-screenshots/Dashboard_dashboard3.png +++ b/plugins/Dashboard/tests/UI/expected-screenshots/Dashboard_dashboard3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:09877a69d9b3d471bd42a8a2be43a95fe7b1fb92d2d8f51c96bab7c49c7c0f3b -size 1624733 +oid sha256:2ea94f364942646306f0408eda47be777268ffd97eea518a7ab49425872f1b24 +size 1626230 diff --git a/plugins/Dashboard/tests/UI/expected-screenshots/Dashboard_dashboard4.png b/plugins/Dashboard/tests/UI/expected-screenshots/Dashboard_dashboard4.png index a76323f07f9..ff5805e383f 100644 --- a/plugins/Dashboard/tests/UI/expected-screenshots/Dashboard_dashboard4.png +++ b/plugins/Dashboard/tests/UI/expected-screenshots/Dashboard_dashboard4.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6d41e8dc81549362f345bde2be1c9352459b8b2bf77292806ba8727830860de1 -size 748178 +oid sha256:e1a280d2ce8904c23577f6b58f7ec8eefbbdb6f7ec714f0f871abe1ab31bc2a8 +size 731958 diff --git a/plugins/Dashboard/tests/UI/expected-screenshots/Dashboard_dashboard5.png b/plugins/Dashboard/tests/UI/expected-screenshots/Dashboard_dashboard5.png index 44fc71ccc3f..d900a48618b 100644 --- a/plugins/Dashboard/tests/UI/expected-screenshots/Dashboard_dashboard5.png +++ b/plugins/Dashboard/tests/UI/expected-screenshots/Dashboard_dashboard5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d3d35345aafb307620dc50c92e94e708c955d66a528f1b09dfd9fb7b2d6b833a -size 352409 +oid sha256:8c6b9a5053ffdc8b5544c98d169674f8b2e656eaf606a5eba4cb8865105307f0 +size 376417 diff --git a/plugins/Diagnostics/tests/Integration/Commands/AnalyzeArchiveTableTest.php b/plugins/Diagnostics/tests/Integration/Commands/AnalyzeArchiveTableTest.php index fa3813ba92f..5c0f9da5706 100644 --- a/plugins/Diagnostics/tests/Integration/Commands/AnalyzeArchiveTableTest.php +++ b/plugins/Diagnostics/tests/Integration/Commands/AnalyzeArchiveTableTest.php @@ -42,9 +42,9 @@ public function testCommandOutputIsAsExpected() +-------------------------------------------+------------+---------------+-------------+---------+-----------+----------------+-------------+-------------+ | Group | # Archives | # Invalidated | # Temporary | # Error | # Segment | # Numeric Rows | # Blob Rows | # Blob Data | +-------------------------------------------+------------+---------------+-------------+---------+-----------+----------------+-------------+-------------+ -| day[2010-03-06 - 2010-03-06] idSite = 1 | 9 | 0 | 0 | 0 | 8 | 115 | 77 | %d | -| week[2010-03-01 - 2010-03-07] idSite = 1 | 9 | 0 | 0 | 0 | 8 | 157 | 99 | %d | -| month[2010-03-01 - 2010-03-31] idSite = 1 | 9 | 0 | 0 | 0 | 8 | 157 | 99 | %d | +| day[2010-03-06 - 2010-03-06] idSite = 1 | 9 | 0 | 0 | 0 | 8 | 115 | 79 | %d | +| week[2010-03-01 - 2010-03-07] idSite = 1 | 9 | 0 | 0 | 0 | 8 | 157 | 101 | %d | +| month[2010-03-01 - 2010-03-31] idSite = 1 | 9 | 0 | 0 | 0 | 8 | 157 | 101 | %d | +-------------------------------------------+------------+---------------+-------------+---------+-----------+----------------+-------------+-------------+ Total # Archives: 27 diff --git a/plugins/Ecommerce/tests/System/expected/test_ecommerceOrderWithItems_schedrep_html_tables_and_graph__ScheduledReports.generateReport_week.original.html b/plugins/Ecommerce/tests/System/expected/test_ecommerceOrderWithItems_schedrep_html_tables_and_graph__ScheduledReports.generateReport_week.original.html index 7105c448dde..91fbb808c60 100644 --- a/plugins/Ecommerce/tests/System/expected/test_ecommerceOrderWithItems_schedrep_html_tables_and_graph__ScheduledReports.generateReport_week.original.html +++ b/plugins/Ecommerce/tests/System/expected/test_ecommerceOrderWithItems_schedrep_html_tables_and_graph__ScheduledReports.generateReport_week.original.html @@ -427,6 +427,11 @@

title match, triggered ONCE - Days to Conversion + +
  • + + AI Assistants - Bots +
  • @@ -7806,6 +7811,12 @@

    + AI Assistants - Bots +

    + + There is no data for this report. +

    AI Agent Visits

    diff --git a/plugins/Ecommerce/tests/System/expected/test_ecommerceOrderWithItems_schedrep_html_tables_only__ScheduledReports.generateReport_week.original.html b/plugins/Ecommerce/tests/System/expected/test_ecommerceOrderWithItems_schedrep_html_tables_only__ScheduledReports.generateReport_week.original.html index 14a6b4cfdf9..50567524e10 100644 --- a/plugins/Ecommerce/tests/System/expected/test_ecommerceOrderWithItems_schedrep_html_tables_only__ScheduledReports.generateReport_week.original.html +++ b/plugins/Ecommerce/tests/System/expected/test_ecommerceOrderWithItems_schedrep_html_tables_only__ScheduledReports.generateReport_week.original.html @@ -427,6 +427,11 @@

    title match, triggered ONCE - Days to Conversion +

  • +
  • + + AI Assistants - Bots +
  • @@ -7407,6 +7412,12 @@

    + AI Assistants - Bots +

    + + There is no data for this report. +

    AI Agent Visits

    diff --git a/plugins/Ecommerce/tests/System/expected/test_ecommerceOrderWithItems_schedrep_in_csv__ScheduledReports.generateReport_week.original.csv b/plugins/Ecommerce/tests/System/expected/test_ecommerceOrderWithItems_schedrep_in_csv__ScheduledReports.generateReport_week.original.csv index f1ad995b4e9..f6fc7c06c48 100644 --- a/plugins/Ecommerce/tests/System/expected/test_ecommerceOrderWithItems_schedrep_in_csv__ScheduledReports.generateReport_week.original.csv +++ b/plugins/Ecommerce/tests/System/expected/test_ecommerceOrderWithItems_schedrep_in_csv__ScheduledReports.generateReport_week.original.csv @@ -548,6 +548,9 @@ label,nb_conversions 121-364 days,0 365+ days,0 +AI Assistants - Bots +No data available + AI Agent Visits nb_uniq_visitors_ai_agent,nb_users_ai_agent,nb_visits_ai_agent,nb_actions_ai_agent,max_actions_ai_agent,bounce_rate_ai_agent,nb_actions_per_visit_ai_agent,avg_time_on_site_ai_agent,nb_uniq_visitors_human,nb_users_human,nb_visits_human,nb_actions_human,max_actions_human,bounce_rate_human,nb_actions_per_visit_human,avg_time_on_site_human 0,0,0,0,0,0%,0,00:00:00,1,0,5,16,6,20%,3.2,00:22:50 diff --git a/plugins/Ecommerce/tests/System/expected/test_ecommerceOrderWithItems_schedrep_in_pdf_tables_only__ScheduledReports.generateReport_week.original.pdf b/plugins/Ecommerce/tests/System/expected/test_ecommerceOrderWithItems_schedrep_in_pdf_tables_only__ScheduledReports.generateReport_week.original.pdf index be81bb22ba7..3b51c48b6af 100644 Binary files a/plugins/Ecommerce/tests/System/expected/test_ecommerceOrderWithItems_schedrep_in_pdf_tables_only__ScheduledReports.generateReport_week.original.pdf and b/plugins/Ecommerce/tests/System/expected/test_ecommerceOrderWithItems_schedrep_in_pdf_tables_only__ScheduledReports.generateReport_week.original.pdf differ diff --git a/plugins/Ecommerce/tests/System/expected/test_ecommerceOrderWithItems_schedrep_in_tsv__ScheduledReports.generateReport_week.original.tsv b/plugins/Ecommerce/tests/System/expected/test_ecommerceOrderWithItems_schedrep_in_tsv__ScheduledReports.generateReport_week.original.tsv index a2fc5d72326..877410be141 100644 --- a/plugins/Ecommerce/tests/System/expected/test_ecommerceOrderWithItems_schedrep_in_tsv__ScheduledReports.generateReport_week.original.tsv +++ b/plugins/Ecommerce/tests/System/expected/test_ecommerceOrderWithItems_schedrep_in_tsv__ScheduledReports.generateReport_week.original.tsv @@ -548,6 +548,9 @@ label nb_conversions 121-364 days 0 365+ days 0 +AI Assistants - Bots +No data available + AI Agent Visits nb_uniq_visitors_ai_agent nb_users_ai_agent nb_visits_ai_agent nb_actions_ai_agent max_actions_ai_agent bounce_rate_ai_agent nb_actions_per_visit_ai_agent avg_time_on_site_ai_agent nb_uniq_visitors_human nb_users_human nb_visits_human nb_actions_human max_actions_human bounce_rate_human nb_actions_per_visit_human avg_time_on_site_human 0 0 0 0 0 0% 0 00:00:00 1 0 5 16 6 20% 3.2 00:22:50 diff --git a/tests/PHPUnit/Framework/TestRequest/Collection.php b/tests/PHPUnit/Framework/TestRequest/Collection.php index c7a5c44816d..aa6a8a55bae 100644 --- a/tests/PHPUnit/Framework/TestRequest/Collection.php +++ b/tests/PHPUnit/Framework/TestRequest/Collection.php @@ -53,6 +53,7 @@ class Collection 'Referrers.getKeywordNotDefinedString', 'CorePluginsAdmin.getSystemSettings', 'API.getPagesComparisonsDisabledFor', + 'BotTracking', ); /** diff --git a/tests/PHPUnit/Integration/Archive/PartialArchiveTest.php b/tests/PHPUnit/Integration/Archive/PartialArchiveTest.php index 4b6a9a01779..7d7d0bc3e8b 100644 --- a/tests/PHPUnit/Integration/Archive/PartialArchiveTest.php +++ b/tests/PHPUnit/Integration/Archive/PartialArchiveTest.php @@ -63,7 +63,7 @@ public function testRangeArchivingOnlyArchivesSingleRecordWhenQueryingNumerics() // check archive is all plugins archive as expected [$idArchives, $archiveInfo] = $this->getArchiveInfo('2020_04', Range::PERIOD_ID, false); $this->assertEquals([ - ['idsite' => 1, 'date1' => '2020-04-06', 'date2' => '2020-04-09', 'period' => Range::PERIOD_ID, 'name' => 'done', 'value' => ArchiveWriter::DONE_OK, 'blob_count' => 60], + ['idsite' => 1, 'date1' => '2020-04-06', 'date2' => '2020-04-09', 'period' => Range::PERIOD_ID, 'name' => 'done', 'value' => ArchiveWriter::DONE_OK, 'blob_count' => 62], ], $archiveInfo); $maxIdArchive = $this->getMaxIdArchive('2020_04'); @@ -124,7 +124,7 @@ public function testRangeArchivingOnlyArchivesSingleRecordWhenQueryingBlobs() // check archive is all plugins archive as expected [$idArchives, $archiveInfo] = $this->getArchiveInfo('2020_04', Range::PERIOD_ID, false); $this->assertEquals([ - ['idsite' => 1, 'date1' => '2020-04-06', 'date2' => '2020-04-09', 'period' => Range::PERIOD_ID, 'name' => 'done', 'value' => ArchiveWriter::DONE_OK, 'blob_count' => 60], + ['idsite' => 1, 'date1' => '2020-04-06', 'date2' => '2020-04-09', 'period' => Range::PERIOD_ID, 'name' => 'done', 'value' => ArchiveWriter::DONE_OK, 'blob_count' => 62], ], $archiveInfo); $maxIdArchive = $this->getMaxIdArchive('2020_04'); diff --git a/tests/PHPUnit/Integration/WidgetsListTest.php b/tests/PHPUnit/Integration/WidgetsListTest.php index e46ffa40b69..82fd947595a 100644 --- a/tests/PHPUnit/Integration/WidgetsListTest.php +++ b/tests/PHPUnit/Integration/WidgetsListTest.php @@ -52,7 +52,7 @@ public function testGet() 'Referrers_Referrers' => 11, 'About Matomo' => 11, 'Marketplace_Marketplace' => 3, - 'AIAgents_AIAssistants' => 2, + 'AIAgents_AIAssistants' => 3, // widgets provided by Professional Services plugin for plugin promos 'ProfessionalServices_PromoAbTesting' => 1, diff --git a/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_schedrep_html_tables_and_graph__ScheduledReports.generateReport_month.original.html b/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_schedrep_html_tables_and_graph__ScheduledReports.generateReport_month.original.html index ab572f8cfcf..f11ea481cb5 100644 --- a/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_schedrep_html_tables_and_graph__ScheduledReports.generateReport_month.original.html +++ b/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_schedrep_html_tables_and_graph__ScheduledReports.generateReport_month.original.html @@ -367,6 +367,11 @@

    Goals Overview - Days to Conversion +

  • +
  • + + AI Assistants - Bots +
  • @@ -5947,6 +5952,12 @@

    + AI Assistants - Bots +

    + + There is no data for this report. +

    AI Agent Visits

    diff --git a/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_schedrep_html_tables_only__ScheduledReports.generateReport_month.original.html b/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_schedrep_html_tables_only__ScheduledReports.generateReport_month.original.html index cb9f4312d76..16e07412ed0 100644 --- a/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_schedrep_html_tables_only__ScheduledReports.generateReport_month.original.html +++ b/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_schedrep_html_tables_only__ScheduledReports.generateReport_month.original.html @@ -367,6 +367,11 @@

    Goals Overview - Days to Conversion +

  • +
  • + + AI Assistants - Bots +
  • @@ -5646,6 +5651,12 @@

    + AI Assistants - Bots +

    + + There is no data for this report. +

    AI Agent Visits

    diff --git a/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_schedrep_in_csv__ScheduledReports.generateReport_month.original.csv b/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_schedrep_in_csv__ScheduledReports.generateReport_month.original.csv index d41700aa45d..19e4cf6c04e 100644 --- a/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_schedrep_in_csv__ScheduledReports.generateReport_month.original.csv +++ b/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_schedrep_in_csv__ScheduledReports.generateReport_month.original.csv @@ -374,6 +374,9 @@ No data available Goals Overview - Days to Conversion No data available +AI Assistants - Bots +No data available + AI Agent Visits nb_uniq_visitors_ai_agent,nb_users_ai_agent,nb_visits_ai_agent,nb_actions_ai_agent,max_actions_ai_agent,bounce_rate_ai_agent,nb_actions_per_visit_ai_agent,avg_time_on_site_ai_agent,nb_uniq_visitors_human,nb_users_human,nb_visits_human,nb_actions_human,max_actions_human,bounce_rate_human,nb_actions_per_visit_human,avg_time_on_site_human 0,0,0,0,0,0%,0,00:00:00,2,0,11,43,5,27%,3.9,00:10:55 diff --git a/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_schedrep_in_pdf_tables_only__ScheduledReports.generateReport_month.original.pdf b/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_schedrep_in_pdf_tables_only__ScheduledReports.generateReport_month.original.pdf index 31e6fdf5412..f92319cf87c 100644 Binary files a/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_schedrep_in_pdf_tables_only__ScheduledReports.generateReport_month.original.pdf and b/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_schedrep_in_pdf_tables_only__ScheduledReports.generateReport_month.original.pdf differ diff --git a/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_schedrep_in_tsv__ScheduledReports.generateReport_month.original.tsv b/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_schedrep_in_tsv__ScheduledReports.generateReport_month.original.tsv index be63321aed7..c78ee976125 100644 --- a/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_schedrep_in_tsv__ScheduledReports.generateReport_month.original.tsv +++ b/tests/PHPUnit/System/expected/test_TwoVisitors_twoWebsites_differentDays_schedrep_in_tsv__ScheduledReports.generateReport_month.original.tsv @@ -374,6 +374,9 @@ No data available Goals Overview - Days to Conversion No data available +AI Assistants - Bots +No data available + AI Agent Visits nb_uniq_visitors_ai_agent nb_users_ai_agent nb_visits_ai_agent nb_actions_ai_agent max_actions_ai_agent bounce_rate_ai_agent nb_actions_per_visit_ai_agent avg_time_on_site_ai_agent nb_uniq_visitors_human nb_users_human nb_visits_human nb_actions_human max_actions_human bounce_rate_human nb_actions_per_visit_human avg_time_on_site_human 0 0 0 0 0 0% 0 00:00:00 2 0 11 43 5 27% 3.9 00:10:55 diff --git a/tests/PHPUnit/System/expected/test_apiGetReportMetadata__API.getGlossaryMetrics.xml b/tests/PHPUnit/System/expected/test_apiGetReportMetadata__API.getGlossaryMetrics.xml index fcc1fee9470..53a9889d0a6 100644 --- a/tests/PHPUnit/System/expected/test_apiGetReportMetadata__API.getGlossaryMetrics.xml +++ b/tests/PHPUnit/System/expected/test_apiGetReportMetadata__API.getGlossaryMetrics.xml @@ -1,5 +1,10 @@ + + Acquired visits + visits_acquired + Visits that started after someone clicked from an AI assistant (Referrers → AI Assistants). + Actions nb_actions @@ -90,6 +95,11 @@ conversion_rate The percentage of visits that triggered a conversion. The conversion rate is calculated using the number of visits that converted at least one goal. Visits converting multiple goals are only counted once in the conversion rate. + + Document Requests + document_requests + Total number of bot requests to document URLs recorded during the selected period. + Downloads nb_downloads @@ -155,6 +165,11 @@ nb_outlinks The number of times this link was clicked. + + Page Requests + page_requests + Total number of bot requests to page URLs recorded during the selected period. + Pageviews nb_hits @@ -170,6 +185,11 @@ revenue The total revenue generated by Product sales. Excludes tax, shipping and discount. + + Requests + requests + Total number of bot requests recorded during the selected period. Includes both page and document URLs. + Search Results pages nb_pages_per_search diff --git a/tests/PHPUnit/System/expected/test_apiGetReportMetadata__API.getGlossaryReports.xml b/tests/PHPUnit/System/expected/test_apiGetReportMetadata__API.getGlossaryReports.xml index 1bda0adfddd..a4db69521ee 100644 --- a/tests/PHPUnit/System/expected/test_apiGetReportMetadata__API.getGlossaryReports.xml +++ b/tests/PHPUnit/System/expected/test_apiGetReportMetadata__API.getGlossaryReports.xml @@ -4,6 +4,10 @@ AI Assistants (Referrers) This report shows which AI assistants led visitors to your website.<br />By clicking on a row in the table, you can see from which AI assistant pages visitors came to your website. + + AI Assistants - Bots (AI Assistants) + Discover which AI assistants are crawling your site and how many hits each one generates. Expand a bot to review the most frequently requested pages or documents. + Actions - Main metrics (Actions) This report provides a very basic overview of what actions your visitors take on your website. diff --git a/tests/PHPUnit/System/expected/test_apiGetReportMetadata__API.getReportMetadata_day.xml b/tests/PHPUnit/System/expected/test_apiGetReportMetadata__API.getReportMetadata_day.xml index c596128c45b..ff8c81d4527 100644 --- a/tests/PHPUnit/System/expected/test_apiGetReportMetadata__API.getReportMetadata_day.xml +++ b/tests/PHPUnit/System/expected/test_apiGetReportMetadata__API.getReportMetadata_day.xml @@ -3544,6 +3544,41 @@ index.php?module=API&method=ImageGraph.get&idSite=1&apiModule=Goals&apiAction=getDaysToConversion&idGoal=3&period=day&date=2009-01-04 Goals_getDaysToConversion_idGoal--3 + + AI Assistants + Overview + AI Assistants - Bots + BotTracking + getAIAssistantRequests + AI Assistant Name + Discover which AI assistants are crawling your site and how many hits each one generates. Expand a bot to review the most frequently requested pages or documents. + + AI Assistant Name + Page URL + + + Requests + Page Requests + Document Requests + Acquired visits + + + Total number of bot requests recorded during the selected period. Includes both page and document URLs. + Total number of bot requests to page URLs recorded during the selected period. + Total number of bot requests to document URLs recorded during the selected period. + Visits that started after someone clicked from an AI assistant (Referrers → AI Assistants). + + + number + number + number + number + + getPageUrlsForAIAssistant + index.php?module=API&method=ImageGraph.get&idSite=1&apiModule=BotTracking&apiAction=getAIAssistantRequests&period=day&date=2009-01-04 + index.php?module=API&method=ImageGraph.get&idSite=1&apiModule=BotTracking&apiAction=getAIAssistantRequests&period=day&date=2008-12-06,2009-01-04 + BotTracking_getAIAssistantRequests + AI Assistants Overview diff --git a/tests/PHPUnit/System/expected/test_apiGetReportMetadata__API.getReportPagesMetadata.xml b/tests/PHPUnit/System/expected/test_apiGetReportMetadata__API.getReportPagesMetadata.xml index 368430b7169..5eb417e82eb 100644 --- a/tests/PHPUnit/System/expected/test_apiGetReportMetadata__API.getReportPagesMetadata.xml +++ b/tests/PHPUnit/System/expected/test_apiGetReportMetadata__API.getReportPagesMetadata.xml @@ -6730,11 +6730,25 @@ <p>The AI Assistant Overview page provide insights into website traffic originating from AI Assistants such as ChatGPT and other large language model–based assistants. These reports track key metrics including the number of requests made by these bots, the pages and documents they access, and any errors encountered. They also offer detailed breakdowns showing which bots visit specific page URLs, helping you understand how AI assistants interact with your content and identify opportunities to improve visibility and accessibility for AI-driven users.</p><p>It’s important to note that none of these pages were actually viewed by humans in the traditional way — all requests originate from AI assistants fetching content automatically.</p><p>Currently, these reports exclusively include requests from AI bots that do not execute JavaScript. They do not include traffic from AI crawlers used for training AI models or from AI agents capable of executing JavaScript.</p> + + AI Assistants - Bots + BotTracking + getAIAssistantRequests + 30 + + BotTracking + getAIAssistantRequests + + widgetBotTrackinggetAIAssistantRequests + 0 + table + 1 + AI Agents Over Time AIAgents getEvolutionGraph - 1 + 90 1 graphEvolution @@ -6750,7 +6764,7 @@ AI Agent Overview AIAgents get - 2 + 91 1 sparklines diff --git a/tests/PHPUnit/System/expected/test_apiGetReportMetadata__API.getWidgetMetadata.xml b/tests/PHPUnit/System/expected/test_apiGetReportMetadata__API.getWidgetMetadata.xml index 3b4a0bbf891..b0334271f7f 100644 --- a/tests/PHPUnit/System/expected/test_apiGetReportMetadata__API.getWidgetMetadata.xml +++ b/tests/PHPUnit/System/expected/test_apiGetReportMetadata__API.getWidgetMetadata.xml @@ -3666,7 +3666,7 @@ AIAgents getEvolutionGraph - 1 + 90 1 graphEvolution @@ -3696,7 +3696,7 @@ AIAgents get - 2 + 91 1 sparklines @@ -3708,6 +3708,34 @@ sparklines 1 + + AI Assistants - Bots + + AIAgents_AIAssistants + AI Assistants + 80 + icon-admin-platform + + + + + General_Overview + Overview + 10 + <p>The AI Assistant Overview page provide insights into website traffic originating from AI Assistants such as ChatGPT and other large language model–based assistants. These reports track key metrics including the number of requests made by these bots, the pages and documents they access, and any errors encountered. They also offer detailed breakdowns showing which bots visit specific page URLs, helping you understand how AI assistants interact with your content and identify opportunities to improve visibility and accessibility for AI-driven users.</p><p>It’s important to note that none of these pages were actually viewed by humans in the traditional way — all requests originate from AI assistants fetching content automatically.</p><p>Currently, these reports exclusively include requests from AI bots that do not execute JavaScript. They do not include traffic from AI crawlers used for training AI models or from AI agents capable of executing JavaScript.</p> + + BotTracking + getAIAssistantRequests + 30 + + BotTracking + getAIAssistantRequests + + widgetBotTrackinggetAIAssistantRequests + 0 + table + 1 + Pie graph diff --git a/tests/UI/expected-screenshots/UIIntegrationTest_admin_diagnostics_configfile.png b/tests/UI/expected-screenshots/UIIntegrationTest_admin_diagnostics_configfile.png index 3a9ee43cc0b..3fb732fc789 100644 --- a/tests/UI/expected-screenshots/UIIntegrationTest_admin_diagnostics_configfile.png +++ b/tests/UI/expected-screenshots/UIIntegrationTest_admin_diagnostics_configfile.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8e28aec021bfa0f4f9538f4efa24d349d604cad7c4afd3507bde25dbe7b189d7 -size 5197696 +oid sha256:09dfd4af9d12e5b8149fcdf41e88974a270465dcfb33f5cc388c6d65b1f1f828 +size 5213905 diff --git a/tests/UI/expected-screenshots/UIIntegrationTest_api_listing.png b/tests/UI/expected-screenshots/UIIntegrationTest_api_listing.png index d69f2910103..6e07940c5cb 100644 --- a/tests/UI/expected-screenshots/UIIntegrationTest_api_listing.png +++ b/tests/UI/expected-screenshots/UIIntegrationTest_api_listing.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:031b27a45ed8600ff49178a02cb70bc806bd3b5900f6286da8c80ef4fcbaf9c9 -size 5007529 +oid sha256:c36f9568e14bb238abd75e2008d8fc00a349ae374a8f41a84f9e0a251597004c +size 5052463 diff --git a/tests/UI/expected-screenshots/UIIntegrationTest_email_reports_editor.png b/tests/UI/expected-screenshots/UIIntegrationTest_email_reports_editor.png index d1669354ed8..2fb89c02f7a 100644 --- a/tests/UI/expected-screenshots/UIIntegrationTest_email_reports_editor.png +++ b/tests/UI/expected-screenshots/UIIntegrationTest_email_reports_editor.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1b7ee1bbb3bc59a08120b8abca95aba848b741ea5b4185c089d02dd8d692b5be -size 493103 +oid sha256:217ff601c75909060e8d10c7d8f5c9d841b2568cd49f087891a8f42932bf79b0 +size 495767 diff --git a/tests/UI/expected-screenshots/UIIntegrationTest_glossary.png b/tests/UI/expected-screenshots/UIIntegrationTest_glossary.png index 1f724b42a64..d1650259b5d 100644 --- a/tests/UI/expected-screenshots/UIIntegrationTest_glossary.png +++ b/tests/UI/expected-screenshots/UIIntegrationTest_glossary.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:783af000422a2b0d762b554f6a6bd3520a6955e8c9cbf63051cb3392357a62cd -size 566005 +oid sha256:a29a3a4a23e52c0d8d525c29c7eb552fcc7474392af37d1f84eb15ee9d7fec18 +size 609579 diff --git a/tests/UI/expected-screenshots/UIIntegrationTest_glossary_widgetized.png b/tests/UI/expected-screenshots/UIIntegrationTest_glossary_widgetized.png index 785ba8bc884..84040c47181 100644 --- a/tests/UI/expected-screenshots/UIIntegrationTest_glossary_widgetized.png +++ b/tests/UI/expected-screenshots/UIIntegrationTest_glossary_widgetized.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:983c568dce39c5d7e7e37e7dd089e02fe1286ad7632c00e31538e9586d4ad575 -size 717823 +oid sha256:08a6a66a28003390b154e5ff2f4d75eff4cf4c15ac12875a3f6990bb3a7a629c +size 775468 diff --git a/tests/UI/expected-screenshots/UIIntegrationTest_widgets_listing.png b/tests/UI/expected-screenshots/UIIntegrationTest_widgets_listing.png index 0231081caea..cf80c9ba6b3 100644 --- a/tests/UI/expected-screenshots/UIIntegrationTest_widgets_listing.png +++ b/tests/UI/expected-screenshots/UIIntegrationTest_widgets_listing.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:76da3574ee97ab67c8b20b13ccfd560f1798478d9205ca6d1bc3bafc468e62c5 -size 183843 +oid sha256:96c08bbac97716b5fdfe405b675abd5cfd68e6db8c54cd01e330a340161a750e +size 184709