From 509a91b9909ab2eb5c875330ce5426c723e080cb Mon Sep 17 00:00:00 2001 From: seojcarlos Date: Tue, 7 Apr 2026 19:56:33 +0200 Subject: [PATCH 1/2] fix: include url_query in pages report display Pages report now concatenates url_path and url_query when rendering URLs, so pages like /viewtopic.php?t=1 and /viewtopic.php?t=2 are shown as separate entries instead of collapsing into /viewtopic.php. Applies to PostgreSQL and ClickHouse queries for path, entry, and exit page types in both standard and expanded metrics views. Fixes #4133 --- .../pageviews/getPageviewExpandedMetrics.ts | 34 ++++++++++++++----- .../sql/pageviews/getPageviewMetrics.ts | 33 +++++++++++++----- 2 files changed, 50 insertions(+), 17 deletions(-) diff --git a/src/queries/sql/pageviews/getPageviewExpandedMetrics.ts b/src/queries/sql/pageviews/getPageviewExpandedMetrics.ts index 986d7d5a5..c16c91269 100644 --- a/src/queries/sql/pageviews/getPageviewExpandedMetrics.ts +++ b/src/queries/sql/pageviews/getPageviewExpandedMetrics.ts @@ -48,6 +48,7 @@ async function relationalQuery( let entryExitQuery = ''; let excludeDomain = ''; + const isPathType = type === 'path' || type === 'entry' || type === 'exit'; if (column === 'referrer_domain') { excludeDomain = `and website_event.referrer_domain != website_event.hostname @@ -75,6 +76,14 @@ async function relationalQuery( `; } + const selectColumn = isPathType + ? `case when website_event.url_query != '' then website_event.url_path || '?' || website_event.url_query else website_event.url_path end` + : column; + + const groupByColumn = isPathType + ? `case when website_event.url_query != '' then website_event.url_path || '?' || website_event.url_query else website_event.url_path end` + : column; + return rawQuery( ` select @@ -86,7 +95,7 @@ async function relationalQuery( sum(${getTimestampDiffSQL('t.min_time', 't.max_time')}) as "totaltime" from ( select - ${column} as name, + ${selectColumn} as name, website_event.session_id, website_event.visit_id, count(*) as "c", @@ -94,17 +103,17 @@ async function relationalQuery( max(website_event.created_at) as "max_time" from website_event ${cohortQuery} - ${joinSessionQuery} - ${entryExitQuery} + ${joinSessionQuery} + ${entryExitQuery} where website_event.website_id = {{websiteId::uuid}} and website_event.created_at between {{startDate}} and {{endDate}} and website_event.event_type != 2 ${excludeDomain} ${filterQuery} - group by ${column}, website_event.session_id, website_event.visit_id + group by ${groupByColumn}, website_event.session_id, website_event.visit_id ) as t where name != '' - group by name + group by name order by visitors desc, visits desc limit ${limit} offset ${offset} @@ -129,27 +138,34 @@ async function clickhouseQuery( let excludeDomain = ''; let entryExitQuery = ''; + const isPathType = type === 'path' || type === 'entry' || type === 'exit'; + let selectColumn = column; if (column === 'referrer_domain') { excludeDomain = `and referrer_domain != hostname and referrer_domain != ''`; if (type === 'domain') { column = toClickHouseGroupedReferrer(GROUPED_DOMAINS); + selectColumn = column; } } if (type === 'entry' || type === 'exit') { const aggregrate = type === 'entry' ? 'argMin' : 'argMax'; - column = `x.${column}`; entryExitQuery = ` JOIN (select visit_id, - ${aggregrate}(url_path, created_at) url_path + ${aggregrate}(url_path, created_at) url_path, + ${aggregrate}(url_query, created_at) url_query from website_event where website_id = {websiteId:UUID} and created_at between {startDate:DateTime64} and {endDate:DateTime64} and event_type != 2 group by visit_id) x ON x.visit_id = website_event.visit_id`; + + selectColumn = `if(x.url_query != '', concat(x.url_path, '?', x.url_query), x.url_path)`; + } else if (isPathType) { + selectColumn = `if(url_query != '', concat(url_path, '?', url_query), url_path)`; } return rawQuery( @@ -163,7 +179,7 @@ async function clickhouseQuery( sum(max_time-min_time) as "totaltime" from ( select - ${column} name, + ${selectColumn} name, session_id, visit_id, count(*) c, @@ -180,7 +196,7 @@ async function clickhouseQuery( ${filterQuery} group by name, session_id, visit_id ) as t - group by name + group by name order by visitors desc, visits desc limit ${limit} offset ${offset} diff --git a/src/queries/sql/pageviews/getPageviewMetrics.ts b/src/queries/sql/pageviews/getPageviewMetrics.ts index 9d4f62782..549dd5ed8 100644 --- a/src/queries/sql/pageviews/getPageviewMetrics.ts +++ b/src/queries/sql/pageviews/getPageviewMetrics.ts @@ -44,6 +44,7 @@ async function relationalQuery( let entryExitQuery = ''; let excludeDomain = ''; + const isPathType = type === 'path' || type === 'entry' || type === 'exit'; if (column === 'referrer_domain') { excludeDomain = `and website_event.referrer_domain != website_event.hostname @@ -52,13 +53,13 @@ async function relationalQuery( if (type === 'entry' || type === 'exit') { const order = type === 'entry' ? 'asc' : 'desc'; - column = `x.${column}`; entryExitQuery = ` join ( select distinct on (visit_id) visit_id, - url_path + url_path, + url_query from website_event where website_event.website_id = {{websiteId::uuid}} and website_event.created_at between {{startDate}} and {{endDate}} @@ -67,11 +68,19 @@ async function relationalQuery( ) x on x.visit_id = website_event.visit_id `; + + column = isPathType + ? `case when x.url_query != '' then x.url_path || '?' || x.url_query else x.url_path end` + : `x.${FILTER_COLUMNS[type] || type}`; } + const selectColumn = isPathType && type !== 'entry' && type !== 'exit' + ? `case when website_event.url_query != '' then website_event.url_path || '?' || website_event.url_query else website_event.url_path end` + : column; + return rawQuery( ` - select ${column} x, + select ${selectColumn} x, count(distinct website_event.session_id) as y from website_event ${cohortQuery} @@ -107,9 +116,11 @@ async function clickhouseQuery( let sql = ''; let excludeDomain = ''; + const isPathType = type === 'path' || type === 'entry' || type === 'exit'; if (EVENT_COLUMNS.some(item => Object.keys(filters).includes(item))) { let entryExitQuery = ''; + let selectColumn = column; if (column === 'referrer_domain') { excludeDomain = `and referrer_domain != hostname and referrer_domain != ''`; @@ -117,21 +128,25 @@ async function clickhouseQuery( if (type === 'entry' || type === 'exit') { const aggregrate = type === 'entry' ? 'argMin' : 'argMax'; - column = `x.${column}`; entryExitQuery = ` JOIN (select visit_id, - ${aggregrate}(url_path, created_at) url_path + ${aggregrate}(url_path, created_at) url_path, + ${aggregrate}(url_query, created_at) url_query from website_event where website_id = {websiteId:UUID} and created_at between {startDate:DateTime64} and {endDate:DateTime64} and event_type != 2 group by visit_id) x ON x.visit_id = website_event.visit_id`; + + selectColumn = `if(x.url_query != '', concat(x.url_path, '?', x.url_query), x.url_path)`; + } else if (isPathType) { + selectColumn = `if(url_query != '', concat(url_path, '?', url_query), url_path)`; } sql = ` - select ${column} x, + select ${selectColumn} x, uniq(website_event.session_id) as y from website_event ${cohortQuery} @@ -148,7 +163,9 @@ async function clickhouseQuery( `; } else { let groupByQuery = ''; - let columnQuery = `arrayJoin(${column})`; + let columnQuery = isPathType + ? `arrayJoin(arrayMap((p, q) -> if(q != '', concat(p, '?', q), p), url_path, url_query))` + : `arrayJoin(${column})`; if (column === 'referrer_domain') { excludeDomain = `and t != ''`; @@ -170,7 +187,7 @@ async function clickhouseQuery( select g.t as x, uniq(s) as y from ( - select session_id s, + select session_id s, ${columnQuery} as t from website_event_stats_hourly as website_event ${cohortQuery} From c229f7466fe6d625cf869f755a853acc8918bc18 Mon Sep 17 00:00:00 2001 From: seojcarlos Date: Wed, 8 Apr 2026 08:22:49 +0200 Subject: [PATCH 2/2] fix: revert arrayMap on stats_hourly to avoid misaligned arrays The website_event_stats_hourly materialized view stores url_path and url_query as independently filtered arrays, so their indices do not correspond event-by-event. Using arrayMap to zip them would produce wrong URL pairings and silently drop entries. Reverted the stats_hourly path to use arrayJoin(url_path) as before. The url_query concatenation still works correctly on direct website_event queries (both PostgreSQL and ClickHouse). --- src/queries/sql/pageviews/getPageviewMetrics.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/queries/sql/pageviews/getPageviewMetrics.ts b/src/queries/sql/pageviews/getPageviewMetrics.ts index 549dd5ed8..a5d26f66e 100644 --- a/src/queries/sql/pageviews/getPageviewMetrics.ts +++ b/src/queries/sql/pageviews/getPageviewMetrics.ts @@ -163,9 +163,7 @@ async function clickhouseQuery( `; } else { let groupByQuery = ''; - let columnQuery = isPathType - ? `arrayJoin(arrayMap((p, q) -> if(q != '', concat(p, '?', q), p), url_path, url_query))` - : `arrayJoin(${column})`; + let columnQuery = `arrayJoin(${column})`; if (column === 'referrer_domain') { excludeDomain = `and t != ''`;