fix: calculate country visitors share relative to total

as opposed to relative to the top-10 countries shown in the breakdown
This commit is contained in:
Dan Kotov 2026-01-11 15:53:57 -05:00
parent 860e6390f1
commit cd1ee8461a
3 changed files with 78 additions and 18 deletions

View file

@ -40,21 +40,25 @@ export function MetricsTable({
const filteredData = useMemo(() => { const filteredData = useMemo(() => {
if (data) { 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 (dataFilter) {
if (Array.isArray(dataFilter)) { if (Array.isArray(dataFilter)) {
items = dataFilter.reduce((arr, filter) => { filtered = dataFilter.reduce((arr, filter) => {
return filter(arr); return filter(arr);
}, items); }, filtered);
} else { } 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 []; return [];
}, [data, dataFilter, limit, type]); }, [data, dataFilter, limit, type]);

View file

@ -1,7 +1,7 @@
export const percentFilter = (data: any[]) => { export const percentFilter = (data: any[], total?: number) => {
if (!Array.isArray(data)) return []; if (!Array.isArray(data)) return [];
const total = data.reduce((n, { y }) => n + y, 0); const sum = total ?? data.reduce((n, { y }) => n + y, 0);
return data.map(({ x, y, ...props }) => ({ x, y, z: total ? (y / total) * 100 : 0, ...props })); return data.map(({ x, y, ...props }) => ({ x, y, z: sum ? (y / sum) * 100 : 0, ...props }));
}; };
export const paramFilter = (data: any[]) => { export const paramFilter = (data: any[]) => {

View file

@ -12,9 +12,14 @@ export interface SessionMetricsParameters {
offset?: number | string; offset?: number | string;
} }
export interface SessionMetricsResult {
data: { x: string; y: number; country?: string }[];
total: number;
}
export async function getSessionMetrics( export async function getSessionMetrics(
...args: [websiteId: string, parameters: SessionMetricsParameters, filters: QueryFilters] ...args: [websiteId: string, parameters: SessionMetricsParameters, filters: QueryFilters]
) { ): Promise<SessionMetricsResult> {
return runQuery({ return runQuery({
[PRISMA]: () => relationalQuery(...args), [PRISMA]: () => relationalQuery(...args),
[CLICKHOUSE]: () => clickhouseQuery(...args), [CLICKHOUSE]: () => clickhouseQuery(...args),
@ -25,7 +30,7 @@ async function relationalQuery(
websiteId: string, websiteId: string,
parameters: SessionMetricsParameters, parameters: SessionMetricsParameters,
filters: QueryFilters, filters: QueryFilters,
) { ): Promise<SessionMetricsResult> {
const { type, limit = 500, offset = 0 } = parameters; const { type, limit = 500, offset = 0 } = parameters;
let column = FILTER_COLUMNS[type] || type; let column = FILTER_COLUMNS[type] || type;
const { parseFilters, rawQuery } = prisma; const { parseFilters, rawQuery } = prisma;
@ -44,7 +49,8 @@ async function relationalQuery(
column = `lower(left(${type}, 2))`; column = `lower(left(${type}, 2))`;
} }
return rawQuery( // Get the data with limit
const data = await rawQuery(
` `
select select
${column} x, ${column} x,
@ -65,14 +71,35 @@ async function relationalQuery(
`, `,
{ ...queryParams, ...parameters }, { ...queryParams, ...parameters },
FUNCTION_NAME, 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( async function clickhouseQuery(
websiteId: string, websiteId: string,
parameters: SessionMetricsParameters, parameters: SessionMetricsParameters,
filters: QueryFilters, filters: QueryFilters,
): Promise<{ x: string; y: number }[]> { ): Promise<SessionMetricsResult> {
const { type, limit = 500, offset = 0 } = parameters; const { type, limit = 500, offset = 0 } = parameters;
let column = FILTER_COLUMNS[type] || type; let column = FILTER_COLUMNS[type] || type;
const { parseFilters, rawQuery } = clickhouse; const { parseFilters, rawQuery } = clickhouse;
@ -86,10 +113,11 @@ async function clickhouseQuery(
column = `lower(left(${type}, 2))`; column = `lower(left(${type}, 2))`;
} }
let sql = ''; let dataSql = '';
let totalSql = '';
if (EVENT_COLUMNS.some(item => Object.keys(filters).includes(item))) { if (EVENT_COLUMNS.some(item => Object.keys(filters).includes(item))) {
sql = ` dataSql = `
select select
${column} x, ${column} x,
count(distinct session_id) y count(distinct session_id) y
@ -106,8 +134,18 @@ async function clickhouseQuery(
limit ${limit} limit ${limit}
offset ${offset} 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 { } else {
sql = ` dataSql = `
select select
${column} x, ${column} x,
uniq(session_id) y uniq(session_id) y
@ -124,7 +162,25 @@ async function clickhouseQuery(
limit ${limit} limit ${limit}
offset ${offset} 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,
};
} }