mirror of
https://github.com/umami-software/umami.git
synced 2026-02-04 04:37:11 +01:00
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:
parent
860e6390f1
commit
cd1ee8461a
3 changed files with 78 additions and 18 deletions
|
|
@ -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]);
|
||||
|
|
|
|||
|
|
@ -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[]) => {
|
||||
|
|
|
|||
|
|
@ -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<SessionMetricsResult> {
|
||||
return runQuery({
|
||||
[PRISMA]: () => relationalQuery(...args),
|
||||
[CLICKHOUSE]: () => clickhouseQuery(...args),
|
||||
|
|
@ -25,7 +30,7 @@ async function relationalQuery(
|
|||
websiteId: string,
|
||||
parameters: SessionMetricsParameters,
|
||||
filters: QueryFilters,
|
||||
) {
|
||||
): Promise<SessionMetricsResult> {
|
||||
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<SessionMetricsResult> {
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue