fix: metric chart total count

This commit is contained in:
Carl-Gerhard Lindesvärd
2025-11-12 22:40:52 +01:00
parent 447b7668fd
commit 84fd5ce22f
16 changed files with 190 additions and 175 deletions

View File

@@ -4,12 +4,14 @@ export interface ISerieDataItem {
label_2?: string | null | undefined;
label_3?: string | null | undefined;
count: number;
total_count?: number;
date: string;
}
interface GroupedDataPoint {
date: string;
count: number;
total_count?: number;
}
interface GroupedResult {
@@ -45,6 +47,7 @@ export function groupByLabels(data: ISerieDataItem[]): GroupedResult[] {
group.data.push({
date: row.date,
count: row.count,
total_count: row.total_count,
});
});
@@ -63,7 +66,7 @@ export function groupByLabels(data: ISerieDataItem[]): GroupedResult[] {
// This will ensure that all dates are present in the data array
data: Array.from(timestamps).map((date) => {
const dataPoint = group.data.find((dp) => dp.date === date);
return dataPoint || { date, count: 0 };
return dataPoint || { date, count: 0, total_count: 0 };
}),
};
});

View File

@@ -180,6 +180,7 @@ export const deprecated_timeRanges = {
};
export const metrics = {
count: 'count',
sum: 'sum',
average: 'average',
min: 'min',

View File

@@ -62,6 +62,7 @@ export function getChartSql({
projectId,
limit,
timezone,
chartType,
}: IGetChartDataInput & { timezone: string }) {
const {
sb,
@@ -209,6 +210,17 @@ export function getChartSql({
return sql;
}
// Add total unique count for user segment using a scalar subquery
if (event.segment === 'user') {
const totalUniqueSubquery = `(
SELECT ${sb.select.count}
FROM ${sb.from}
${getJoins()}
${getWhere()}
)`;
sb.select.total_unique_count = `${totalUniqueSubquery} as total_count`;
}
const sql = `${getSelect()} ${getFrom()} ${getJoins()} ${getWhere()} ${getGroupBy()} ${getOrderBy()} ${getFill()}`;
console.log('-- Report --');
console.log(sql.replaceAll(/[\n\r]/g, ' '));

View File

@@ -83,62 +83,12 @@ export type IGetTopGenericInput = z.infer<typeof zGetTopGenericInput> & {
};
export class OverviewService {
private pendingQueries: Map<string, Promise<number | null>> = new Map();
constructor(private client: typeof ch) {}
isPageFilter(filters: IChartEventFilter[]) {
return filters.some((filter) => filter.name === 'path' && filter.value);
}
getTotalSessions({
projectId,
startDate,
endDate,
filters,
timezone,
}: {
projectId: string;
startDate: string;
endDate: string;
filters: IChartEventFilter[];
timezone: string;
}) {
const where = this.getRawWhereClause('sessions', filters);
const key = `total_sessions_${projectId}_${startDate}_${endDate}_${JSON.stringify(filters)}`;
// Check if there's already a pending query for this key
const pendingQuery = this.pendingQueries.get(key);
if (pendingQuery) {
return pendingQuery.then((res) => res ?? 0);
}
// Create new query promise and store it
const queryPromise = getCache(key, 15, async () => {
try {
const result = await clix(this.client, timezone)
.select<{
total_sessions: number;
}>(['sum(sign) as total_sessions'])
.from(TABLE_NAMES.sessions, true)
.where('project_id', '=', projectId)
.where('created_at', 'BETWEEN', [
clix.datetime(startDate, 'toDateTime'),
clix.datetime(endDate, 'toDateTime'),
])
.rawWhere(where)
.having('sum(sign)', '>', 0)
.execute();
return result?.[0]?.total_sessions ?? 0;
} catch (error) {
return 0;
}
});
this.pendingQueries.set(key, queryPromise);
return queryPromise;
}
getMetrics({
projectId,
filters,
@@ -483,14 +433,6 @@ export class OverviewService {
.orderBy('sessions', 'DESC')
.limit(limit);
const totalSessions = await this.getTotalSessions({
projectId,
startDate,
endDate,
filters,
timezone,
});
return mainQuery.execute();
}
@@ -556,14 +498,6 @@ export class OverviewService {
);
}
const totalSessions = await this.getTotalSessions({
projectId,
startDate,
endDate,
filters,
timezone,
});
return mainQuery.execute();
}
@@ -666,16 +600,7 @@ export class OverviewService {
mainQuery.rawWhere(this.getRawWhereClause('sessions', filters));
}
const [res, totalSessions] = await Promise.all([
mainQuery.execute(),
this.getTotalSessions({
projectId,
startDate,
endDate,
filters,
timezone,
}),
]);
const res = await mainQuery.execute();
return res;
}

View File

@@ -4,7 +4,6 @@ import sqlstring from 'sqlstring';
import type { ISerieDataItem } from '@openpanel/common';
import {
DateTime,
average,
getPreviousMetric,
groupByLabels,
@@ -226,39 +225,32 @@ export async function getChartSerie(
payload: IGetChartDataInput,
timezone: string,
) {
async function getSeries() {
const result = await chQuery<ISerieDataItem>(
getChartSql({ ...payload, timezone }),
let result = await chQuery<ISerieDataItem>(
getChartSql({ ...payload, timezone }),
{
session_timezone: timezone,
},
);
if (result.length === 0 && payload.breakdowns.length > 0) {
result = await chQuery<ISerieDataItem>(
getChartSql({
...payload,
breakdowns: [],
timezone,
}),
{
session_timezone: timezone,
},
);
if (result.length === 0 && payload.breakdowns.length > 0) {
return await chQuery<ISerieDataItem>(
getChartSql({
...payload,
breakdowns: [],
timezone,
}),
{
session_timezone: timezone,
},
);
}
return result;
}
return getSeries()
.then(groupByLabels)
.then((series) => {
return series.map((serie) => {
return {
...serie,
event: payload.event,
};
});
});
return groupByLabels(result).map((serie) => {
return {
...serie,
event: payload.event,
};
});
}
export type IGetChartSerie = Awaited<ReturnType<typeof getChartSeries>>[number];
@@ -339,6 +331,7 @@ export async function getChart(input: IChartInput) {
average: round(average(serie.data.map((item) => item.count)), 2),
min: min(serie.data.map((item) => item.count)),
max: max(serie.data.map((item) => item.count)),
count: serie.data[0]?.total_count, // We can grab any since all are the same
};
const event = {
id: serie.event.id,
@@ -388,6 +381,10 @@ export async function getChart(input: IChartInput) {
? max(previousSerie?.data.map((item) => item.count))
: null,
),
count: getPreviousMetric(
metrics.count ?? 0,
previousSerie?.data[0]?.total_count ?? null,
),
},
}
: {}),
@@ -409,6 +406,7 @@ export async function getChart(input: IChartInput) {
average: 0,
min: 0,
max: 0,
count: undefined,
},
};
@@ -420,7 +418,7 @@ export async function getChart(input: IChartInput) {
const sumB = b.data.reduce((acc, item) => acc + (item.count ?? 0), 0);
return sumB - sumA;
}
return b.metrics[input.metric] - a.metrics[input.metric];
return (b.metrics[input.metric] ?? 0) - (a.metrics[input.metric] ?? 0);
})
.slice(offset, limit ? offset + limit : series.length);
@@ -456,6 +454,7 @@ export async function getChart(input: IChartInput) {
final.metrics.max,
max(final.series.map((item) => item.metrics.previous?.max?.value ?? 0)),
),
count: undefined,
};
}

View File

@@ -56,7 +56,7 @@ export const reportRouter = createTRPCRouter({
previous: report.previous ?? false,
unit: report.unit,
criteria: report.criteria,
metric: report.metric,
metric: report.metric === 'count' ? 'sum' : report.metric,
funnelGroup: report.funnelGroup,
funnelWindow: report.funnelWindow,
},
@@ -101,7 +101,7 @@ export const reportRouter = createTRPCRouter({
previous: report.previous ?? false,
unit: report.unit,
criteria: report.criteria,
metric: report.metric,
metric: report.metric === 'count' ? 'sum' : report.metric,
funnelGroup: report.funnelGroup,
funnelWindow: report.funnelWindow,
},

View File

@@ -61,11 +61,13 @@ export type Metrics = {
average: number;
min: number;
max: number;
count: number | undefined;
previous?: {
sum: PreviousValue;
average: PreviousValue;
min: PreviousValue;
max: PreviousValue;
count: PreviousValue;
};
};