implement URL pageview metric/expanded metric

This commit is contained in:
Francis Cao
2026-05-07 16:59:30 -07:00
parent 1b61ee4692
commit fa182d0947
10 changed files with 51 additions and 31 deletions
@@ -49,6 +49,7 @@ export const CATEGORIES = [
const METRIC_TYPES = [
{ label: 'Path', value: 'path' },
{ label: 'URL', value: 'fullPath' },
{ label: 'Entry page', value: 'entry' },
{ label: 'Exit page', value: 'exit' },
{ label: 'Title', value: 'title' },
+1 -1
View File
@@ -10,7 +10,7 @@ import { WebsiteChart } from '@/app/(main)/websites/[websiteId]/WebsiteChart';
import { PageBody } from '@/components/common/PageBody';
import { Panel } from '@/components/common/Panel';
const excludedIds = ['path', 'entry', 'exit', 'title', 'language', 'screen', 'event'];
const excludedIds = ['path', 'fullPath', 'entry', 'exit', 'title', 'language', 'screen', 'event'];
export function LinkPage({
linkId,
@@ -10,7 +10,7 @@ import { WebsiteChart } from '@/app/(main)/websites/[websiteId]/WebsiteChart';
import { PageBody } from '@/components/common/PageBody';
import { Panel } from '@/components/common/Panel';
const excludedIds = ['path', 'entry', 'exit', 'title', 'language', 'screen', 'event'];
const excludedIds = ['path', 'fullPath', 'entry', 'exit', 'title', 'language', 'screen', 'event'];
export function PixelPage({
pixelId,
@@ -25,6 +25,7 @@ import {
Tag,
Target,
Type,
Waypoints,
} from '@/components/icons';
import { Lightning } from '@/components/svg';
@@ -45,7 +46,7 @@ export function WebsiteExpandedMenu({
const items = [
{
label: 'URL',
label: t(labels.pages),
items: [
{
id: 'path',
@@ -53,6 +54,12 @@ export function WebsiteExpandedMenu({
path: updateParams({ view: 'path' }),
icon: <SquareSlash />,
},
{
id: 'fullPath',
label: t(labels.url),
path: updateParams({ view: 'fullPath' }),
icon: <Waypoints />,
},
{
id: 'entry',
label: t(labels.entry),
@@ -17,6 +17,7 @@ export function WebsiteExpandedView({
const {
query: { view },
} = useNavigation();
const title = view === 'fullPath' ? t(labels.url) : t(labels[view]);
return (
<Column height="100%" overflow="hidden" gap>
@@ -44,12 +45,7 @@ export function WebsiteExpandedView({
<WebsiteExpandedMenu excludedIds={excludedIds} />
</Column>
<Column id="metrics-expanded-table" overflow="hidden">
<MetricsExpandedTable
title={t(labels[view])}
type={view}
websiteId={websiteId}
onClose={onClose}
/>
<MetricsExpandedTable title={title} type={view} websiteId={websiteId} onClose={onClose} />
</Column>
</Grid>
</Column>
@@ -26,12 +26,16 @@ export function WebsitePanels({ websiteId }: { websiteId: string }) {
<Tabs>
<TabList>
<Tab id="path">{t(labels.path)}</Tab>
<Tab id="fullPath">{t(labels.url)}</Tab>
<Tab id="entry">{t(labels.entry)}</Tab>
<Tab id="exit">{t(labels.exit)}</Tab>
</TabList>
<TabPanel id="path">
<MetricsTable type="path" title={t(labels.path)} {...tableProps} />
</TabPanel>
<TabPanel id="fullPath">
<MetricsTable type="fullPath" title={t(labels.url)} {...tableProps} />
</TabPanel>
<TabPanel id="entry">
<MetricsTable type="entry" title={t(labels.path)} {...tableProps} />
</TabPanel>
+3
View File
@@ -94,6 +94,9 @@ export function MetricLabel({ type, data }: MetricLabelProps) {
/>
);
case 'fullPath':
return label || `(${t(labels.none)})`;
case 'device':
return (
<FilterLink
+1
View File
@@ -37,6 +37,7 @@ export const UNIT_TYPES = ['year', 'month', 'hour', 'day', 'minute'];
export const EVENT_COLUMNS = [
'path',
'fullPath',
'entry',
'exit',
'referrer',
@@ -36,7 +36,7 @@ async function relationalQuery(
filters: QueryFilters,
): Promise<PageviewExpandedMetricsData[]> {
const { type, limit = 500, offset = 0 } = parameters;
let column = FILTER_COLUMNS[type] || type;
let column = getPageviewColumn(type);
const { rawQuery, parseFilters, getTimestampDiffSQL } = prisma;
const { filterQuery, joinSessionQuery, cohortQuery, excludeBounceQuery, queryParams } =
parseFilters(
@@ -49,8 +49,6 @@ 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 != regexp_replace(website_event.hostname, '^www.', '')
and website_event.referrer_domain != ''`;
@@ -77,11 +75,11 @@ async function relationalQuery(
`;
}
const selectColumn = isPathType
const selectColumn = type === 'fullPath'
? `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
const groupByColumn = type === 'fullPath'
? `case when website_event.url_query != '' then website_event.url_path || '?' || website_event.url_query else website_event.url_path end`
: column;
@@ -131,7 +129,7 @@ async function clickhouseQuery(
filters: QueryFilters,
): Promise<{ x: string; y: number }[]> {
const { type, limit = 500, offset = 0 } = parameters;
let column = FILTER_COLUMNS[type] || type;
let column = getPageviewColumn(type);
const { rawQuery, parseFilters } = clickhouse;
const { filterQuery, cohortQuery, excludeBounceQuery, queryParams } = parseFilters({
...filters,
@@ -140,7 +138,6 @@ async function clickhouseQuery(
let excludeDomain = '';
let entryExitQuery = '';
const isPathType = type === 'path' || type === 'entry' || type === 'exit';
let selectColumn = column;
if (column === 'referrer_domain') {
@@ -165,8 +162,8 @@ async function clickhouseQuery(
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 = `x.url_path`;
} else if (type === 'fullPath') {
selectColumn = `if(url_query != '', concat(url_path, '?', url_query), url_path)`;
}
@@ -244,3 +241,11 @@ export function toPostgresGroupedReferrer(
function toPostgresLikeClause(column: string, arr: string[]) {
return arr.map(val => `${column} ilike '%${val.replace(/'/g, "''")}%'`).join(' OR\n ');
}
function getPageviewColumn(type: string) {
if (type === 'fullPath') {
return 'url_path';
}
return FILTER_COLUMNS[type] || type;
}
+16 -13
View File
@@ -32,7 +32,7 @@ async function relationalQuery(
filters: QueryFilters,
): Promise<PageviewMetricsData[]> {
const { type, limit = 500, offset = 0 } = parameters;
let column = FILTER_COLUMNS[type] || type;
let column = getPageviewColumn(type);
const { rawQuery, parseFilters } = prisma;
const { filterQuery, joinSessionQuery, cohortQuery, excludeBounceQuery, queryParams } =
parseFilters(
@@ -45,8 +45,6 @@ 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 != regexp_replace(website_event.hostname, '^www.', '')
and website_event.referrer_domain != ''`;
@@ -70,12 +68,10 @@ async function relationalQuery(
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}`;
column = `x.${FILTER_COLUMNS[type] || type}`;
}
const selectColumn = isPathType && type !== 'entry' && type !== 'exit'
const selectColumn = type === 'fullPath'
? `case when website_event.url_query != '' then website_event.url_path || '?' || website_event.url_query else website_event.url_path end`
: column;
@@ -110,7 +106,7 @@ async function clickhouseQuery(
filters: QueryFilters,
): Promise<{ x: string; y: number }[]> {
const { type, limit = 500, offset = 0 } = parameters;
let column = FILTER_COLUMNS[type] || type;
let column = getPageviewColumn(type);
const { rawQuery, parseFilters } = clickhouse;
const { filterQuery, cohortQuery, excludeBounceQuery, queryParams } = parseFilters({
...filters,
@@ -119,9 +115,7 @@ 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))) {
if (type === 'fullPath' || EVENT_COLUMNS.some(item => Object.keys(filters).includes(item))) {
let entryExitQuery = '';
let selectColumn = column;
@@ -143,8 +137,9 @@ async function clickhouseQuery(
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) {
column = `x.url_path`;
selectColumn = `x.url_path`;
} else if (type === 'fullPath') {
selectColumn = `if(url_query != '', concat(url_path, '?', url_query), url_path)`;
}
@@ -210,3 +205,11 @@ async function clickhouseQuery(
return rawQuery(sql, { ...queryParams, ...parameters }, FUNCTION_NAME);
}
function getPageviewColumn(type: string) {
if (type === 'fullPath') {
return 'url_path';
}
return FILTER_COLUMNS[type] || type;
}