diff --git a/src/components/metrics/MetricsTable.tsx b/src/components/metrics/MetricsTable.tsx index e99bd216..6cb64436 100644 --- a/src/components/metrics/MetricsTable.tsx +++ b/src/components/metrics/MetricsTable.tsx @@ -40,21 +40,25 @@ export function MetricsTable({ const filteredData = useMemo(() => { if (data) { - let items = data as any[]; + // Handle both old format (array) and new format ({ data, total }) + const items = Array.isArray(data) ? data : data.data; + const total = Array.isArray(data) ? undefined : data.total; + + let filtered = items as any[]; if (dataFilter) { if (Array.isArray(dataFilter)) { - items = dataFilter.reduce((arr, filter) => { + filtered = dataFilter.reduce((arr, filter) => { return filter(arr); - }, items); + }, filtered); } else { - items = dataFilter(items); + filtered = dataFilter(filtered); } } - items = percentFilter(items); + filtered = percentFilter(filtered, total); - return items.map(({ x, y, z, ...props }) => ({ label: x, count: y, percent: z, ...props })); + return filtered.map(({ x, y, z, ...props }) => ({ label: x, count: y, percent: z, ...props })); } return []; }, [data, dataFilter, limit, type]); diff --git a/src/lib/filters.ts b/src/lib/filters.ts index 3da268d8..588a3189 100644 --- a/src/lib/filters.ts +++ b/src/lib/filters.ts @@ -1,7 +1,7 @@ -export const percentFilter = (data: any[]) => { +export const percentFilter = (data: any[], total?: number) => { if (!Array.isArray(data)) return []; - const total = data.reduce((n, { y }) => n + y, 0); - return data.map(({ x, y, ...props }) => ({ x, y, z: total ? (y / total) * 100 : 0, ...props })); + const sum = total ?? data.reduce((n, { y }) => n + y, 0); + return data.map(({ x, y, ...props }) => ({ x, y, z: sum ? (y / sum) * 100 : 0, ...props })); }; export const paramFilter = (data: any[]) => { diff --git a/src/queries/sql/sessions/getSessionMetrics.ts b/src/queries/sql/sessions/getSessionMetrics.ts index c519bdd0..2fad86da 100644 --- a/src/queries/sql/sessions/getSessionMetrics.ts +++ b/src/queries/sql/sessions/getSessionMetrics.ts @@ -12,9 +12,14 @@ export interface SessionMetricsParameters { offset?: number | string; } +export interface SessionMetricsResult { + data: { x: string; y: number; country?: string }[]; + total: number; +} + export async function getSessionMetrics( ...args: [websiteId: string, parameters: SessionMetricsParameters, filters: QueryFilters] -) { +): Promise { return runQuery({ [PRISMA]: () => relationalQuery(...args), [CLICKHOUSE]: () => clickhouseQuery(...args), @@ -25,7 +30,7 @@ async function relationalQuery( websiteId: string, parameters: SessionMetricsParameters, filters: QueryFilters, -) { +): Promise { const { type, limit = 500, offset = 0 } = parameters; let column = FILTER_COLUMNS[type] || type; const { parseFilters, rawQuery } = prisma; @@ -44,7 +49,8 @@ async function relationalQuery( column = `lower(left(${type}, 2))`; } - return rawQuery( + // Get the data with limit + const data = await rawQuery( ` select ${column} x, @@ -65,14 +71,35 @@ async function relationalQuery( `, { ...queryParams, ...parameters }, FUNCTION_NAME, - ); + ) as { x: string; y: number; country?: string }[]; + + // Get total unique sessions + const totalResult = await rawQuery( + ` + select count(distinct website_event.session_id) as total + from website_event + ${cohortQuery} + ${joinSessionQuery} + where website_event.website_id = {{websiteId::uuid}} + and website_event.created_at between {{startDate}} and {{endDate}} + and website_event.event_type != 2 + ${filterQuery} + `, + queryParams, + FUNCTION_NAME, + ) as { total: number }[]; + + return { + data, + total: Number(totalResult[0]?.total) || 0, + }; } async function clickhouseQuery( websiteId: string, parameters: SessionMetricsParameters, filters: QueryFilters, -): Promise<{ x: string; y: number }[]> { +): Promise { const { type, limit = 500, offset = 0 } = parameters; let column = FILTER_COLUMNS[type] || type; const { parseFilters, rawQuery } = clickhouse; @@ -86,10 +113,11 @@ async function clickhouseQuery( column = `lower(left(${type}, 2))`; } - let sql = ''; + let dataSql = ''; + let totalSql = ''; if (EVENT_COLUMNS.some(item => Object.keys(filters).includes(item))) { - sql = ` + dataSql = ` select ${column} x, count(distinct session_id) y @@ -106,8 +134,18 @@ async function clickhouseQuery( limit ${limit} offset ${offset} `; + + totalSql = ` + select count(distinct session_id) as total + from website_event + ${cohortQuery} + where website_id = {websiteId:UUID} + and created_at between {startDate:DateTime64} and {endDate:DateTime64} + and event_type != 2 + ${filterQuery} + `; } else { - sql = ` + dataSql = ` select ${column} x, uniq(session_id) y @@ -124,7 +162,25 @@ async function clickhouseQuery( limit ${limit} offset ${offset} `; + + totalSql = ` + select uniq(session_id) as total + from website_event_stats_hourly as website_event + ${cohortQuery} + where website_id = {websiteId:UUID} + and created_at between {startDate:DateTime64} and {endDate:DateTime64} + and event_type != 2 + ${filterQuery} + `; } - return rawQuery(sql, { ...queryParams, ...parameters }, FUNCTION_NAME); + const [data, totalResult] = await Promise.all([ + rawQuery(dataSql, { ...queryParams, ...parameters }, FUNCTION_NAME) as Promise<{ x: string; y: number; country?: string }[]>, + rawQuery(totalSql, queryParams, FUNCTION_NAME) as Promise<{ total: number }[]>, + ]); + + return { + data, + total: Number(totalResult[0]?.total) || 0, + }; }