fix: metric chart total count
This commit is contained in:
@@ -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 };
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -180,6 +180,7 @@ export const deprecated_timeRanges = {
|
||||
};
|
||||
|
||||
export const metrics = {
|
||||
count: 'count',
|
||||
sum: 'sum',
|
||||
average: 'average',
|
||||
min: 'min',
|
||||
|
||||
@@ -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, ' '));
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user