dimensionInfo = $dimensionInfo; $this->maxRowsInTable = Config::getInstance()->General['datatable_archiving_maximum_rows_custom_dimensions']; $this->maxRowsInSubtable = Config::getInstance()->General['datatable_archiving_maximum_rows_subtable_custom_dimensions']; $this->columnToSortByBeforeTruncation = Metrics::INDEX_NB_VISITS; $this->rankingQueryLimit = $this->getRankingQueryLimit(); } public function getRecordMetadata(ArchiveProcessor $archiveProcessor): array { $recordName = Archiver::buildRecordNameForCustomDimensionId($this->dimensionInfo['idcustomdimension']); return [ Record::make(Record::TYPE_BLOB, $recordName), ]; } protected function aggregate(ArchiveProcessor $archiveProcessor): array { $dimension = $this->dimensionInfo; if (!$dimension['active']) { // sanity check return []; } $logAggregator = $archiveProcessor->getLogAggregator(); $report = new DataTable(); $recordName = Archiver::buildRecordNameForCustomDimensionId($dimension['idcustomdimension']); $valueField = LogTable::buildCustomDimensionColumnName($dimension); $dimensions = [$valueField]; if ($dimension['scope'] === CustomDimensions::SCOPE_VISIT) { $this->aggregateFromVisits($report, $logAggregator, $valueField, $dimensions, " log_visit.$valueField is not null"); $this->aggregateFromConversions($report, $logAggregator, $valueField, $dimensions, " log_conversion.$valueField is not null"); } elseif ($dimension['scope'] === CustomDimensions::SCOPE_ACTION) { $this->aggregateFromActions($report, $logAggregator, $valueField); } $report->filter(DataTable\Filter\EnrichRecordWithGoalMetricSums::class); return [ $recordName => $report, ]; } private function aggregateFromVisits( DataTable $report, LogAggregator $logAggregator, string $valueField, array $dimensions, string $where ): void { if ($this->rankingQueryLimit > 0) { $rankingQuery = new RankingQuery($this->rankingQueryLimit); $rankingQuery->addLabelColumn($dimensions[0]); $query = $logAggregator->queryVisitsByDimension( $dimensions, $where, [], false, $rankingQuery, false, -1, $rankingQueryGenerate = true ); } else { $query = $logAggregator->queryVisitsByDimension($dimensions, $where); } $defaultColumns = [ Metrics::INDEX_NB_UNIQ_VISITORS => 0, Metrics::INDEX_NB_VISITS => 0, Metrics::INDEX_NB_ACTIONS => 0, Metrics::INDEX_NB_USERS => 0, Metrics::INDEX_MAX_ACTIONS => 0, Metrics::INDEX_SUM_VISIT_LENGTH => 0, Metrics::INDEX_BOUNCE_COUNT => 0, Metrics::INDEX_NB_VISITS_CONVERTED => 0, ]; while ($row = $query->fetch()) { $customDimensionValue = $this->cleanCustomDimensionValue($row[$valueField]); unset($row[$valueField]); $columns = $defaultColumns; foreach ($row as $name => $columnValue) { $columns[$name] = $columnValue; } $report->sumRowWithLabel($customDimensionValue, $columns); } } private function aggregateFromConversions( DataTable $report, LogAggregator $logAggregator, string $valueField, array $dimensions, string $where ): void { if ($this->rankingQueryLimit > 0) { $rankingQuery = new RankingQuery($this->rankingQueryLimit); $rankingQuery->addLabelColumn([$dimensions[0], 'idgoal']); $query = $logAggregator->queryConversionsByDimension($dimensions, $where, false, [], $rankingQuery, $rankingQueryGenerate = true); } else { $query = $logAggregator->queryConversionsByDimension($dimensions, $where); } while ($row = $query->fetch()) { $value = $this->cleanCustomDimensionValue($row[$valueField]); unset($row[$valueField]); if ($value === RankingQuery::LABEL_SUMMARY_ROW) { // skip summary row continue; } $idGoal = (int) $row['idgoal']; $columns = [ Metrics::INDEX_GOALS => [ $idGoal => Metrics::makeGoalColumnsRow($idGoal, $row), ], ]; $report->sumRowWithLabel($value, $columns); } } protected function aggregateFromActions(DataTable $report, LogAggregator $logAggregator, $valueField): void { $metricsConfig = ActionsMetrics::getActionMetrics(); $featureFlagManager = StaticContainer::get(FeatureFlagManager::class); $withRollup = $featureFlagManager->isFeatureActive(CustomDimensionReportWithRollUp::class); $resultSet = $this->queryCustomDimensionActions($metricsConfig, $logAggregator, $valueField, $additionalWhere = '', $withRollup); $metricIds = array_keys($metricsConfig); $metricIds[] = Metrics::INDEX_PAGE_SUM_TIME_SPENT; $metricIds[] = Metrics::INDEX_BOUNCE_COUNT; $metricIds[] = Metrics::INDEX_PAGE_EXIT_NB_VISITS; while ($row = $resultSet->fetch()) { if (!isset($row[Metrics::INDEX_NB_VISITS])) { return; } $label = $row[$valueField]; if ($withRollup && $label === null) { // top-level rollup result continue; } $columns = []; foreach ($metricIds as $id) { $columns[$id] = (float) ($row[$id] ?? 0); } $label = $this->cleanCustomDimensionValue($label); $url = $row['url']; if (!$withRollup) { $tableRow = $report->sumRowWithLabel($label, $columns); } else { if ($url === null) { // second-level rollup result $report->sumRowWithLabel($label, $columns); continue; } $tableRow = $report->getRowFromLabel($label); if (false === $tableRow) { // non-rollup row but rollup row is missing // should not happen, but don't break continue; } } // make sure we always work with normalized URL no matter how the individual action stores it $normalized = Tracker\PageUrl::normalizeUrl($url); $url = $normalized['url']; if (empty($url)) { continue; } if ( $withRollup && $url === RankingQuery::LABEL_SUMMARY_ROW && !$tableRow->isSubtableLoaded() ) { // skip creating the subtable if: // - we are using rollups // - the only row would be "Others" continue; } $tableRow->sumRowWithLabelToSubtable($url, $columns); } } public function queryCustomDimensionActions(array $metricsConfig, LogAggregator $logAggregator, $valueField, $additionalWhere = '', bool $withRollup = false) { $logActionNameAlias = 'log_action.name as url,'; if ($withRollup) { $logActionNameAlias = "COALESCE(log_action.name, '') as url,"; } $select = "log_link_visit_action.$valueField, $logActionNameAlias sum(log_link_visit_action.time_spent) as `" . Metrics::INDEX_PAGE_SUM_TIME_SPENT . "`, sum(case log_visit.visit_total_actions when 1 then 1 when 0 then 1 else 0 end) as `" . Metrics::INDEX_BOUNCE_COUNT . "`, sum(IF(log_visit.last_idlink_va = log_link_visit_action.idlink_va, 1, 0)) as `" . Metrics::INDEX_PAGE_EXIT_NB_VISITS . "`"; $select = $this->addMetricsToSelect($select, $metricsConfig); $from = [ "log_link_visit_action", [ "table" => "log_visit", "joinOn" => "log_visit.idvisit = log_link_visit_action.idvisit", ], [ "table" => "log_action", "joinOn" => "log_link_visit_action.idaction_url = log_action.idaction", ], ]; $where = $logAggregator->getWhereStatement('log_link_visit_action', 'server_time'); $where .= " AND log_link_visit_action.$valueField is not null"; if (!empty($additionalWhere)) { $where .= ' AND ' . $additionalWhere; } $groupBy = "log_link_visit_action.$valueField, url"; $orderBy = "`" . Metrics::INDEX_PAGE_NB_HITS . "` DESC"; // get query with segmentation $query = $logAggregator->generateQuery( $select, $from, $where, $groupBy, $orderBy, $limit = 0, $offset = 0, $withRollup ); if ($this->rankingQueryLimit > 0) { $rankingQuery = new RankingQuery($this->rankingQueryLimit); $rankingQuery->addLabelColumn([$valueField, 'url']); $sumMetrics = [ Metrics::INDEX_PAGE_SUM_TIME_SPENT, Metrics::INDEX_BOUNCE_COUNT, Metrics::INDEX_PAGE_EXIT_NB_VISITS, // NOTE: INDEX_NB_UNIQ_VISITORS is summed in LogAggregator's queryActionsByDimension, so we do it here as well Metrics::INDEX_NB_UNIQ_VISITORS, ]; $rankingQuery->addColumn($sumMetrics, 'sum'); foreach ($metricsConfig as $column => $config) { if (empty($config['aggregation'])) { continue; } $rankingQuery->addColumn($column, $config['aggregation']); } $query['sql'] = $rankingQuery->generateRankingQuery($query['sql'], $withRollup); } $db = $logAggregator->getDb(); $resultSet = $db->query($query['sql'], $query['bind']); return $resultSet; } private function getRankingQueryLimit(): int { $configGeneral = Config::getInstance()->General; $configLimit = max($configGeneral['archiving_ranking_query_row_limit'], 10 * $this->maxRowsInTable); $limit = $configLimit == 0 ? 0 : max( $configLimit, $this->maxRowsInTable ); return $limit; } protected function cleanCustomDimensionValue(string $value): string { if (isset($value) && strlen($value)) { return $value; } return Archiver::LABEL_CUSTOM_VALUE_NOT_DEFINED; } private function addMetricsToSelect(string $select, array $metricsConfig): string { if (!empty($metricsConfig)) { foreach ($metricsConfig as $metric => $config) { $select .= ', ' . $config['query'] . " as `" . $metric . "`"; } } return $select; } }