+
{props.payload?.map((serie, index) => {
const item = serie.payload;
return (
{index === 0 && item.date && (
-
+
{formatDate(new Date(item.date))}
-
+
)}
-
-
-
-
-
-
-
-
-
- {number.formatWithUnit(item.count)}
- {!!item.previous && (
-
- ({number.formatWithUnit(item.previous.sum.value)})
-
- )}
-
-
-
+
+
+
+
-
+
+
+ {number.formatWithUnit(item.count)}
+ {!!item.previous && (
+
+ ({number.formatWithUnit(item.previous.sum.value)})
+
+ )}
+
+
+
+
);
})}
-
+
);
};
diff --git a/packages/common/src/group-by-labels.ts b/packages/common/src/group-by-labels.ts
index 32da93be..d8dadbb1 100644
--- a/packages/common/src/group-by-labels.ts
+++ b/packages/common/src/group-by-labels.ts
@@ -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 };
}),
};
});
diff --git a/packages/constants/index.ts b/packages/constants/index.ts
index 998cb3a3..6d331626 100644
--- a/packages/constants/index.ts
+++ b/packages/constants/index.ts
@@ -180,6 +180,7 @@ export const deprecated_timeRanges = {
};
export const metrics = {
+ count: 'count',
sum: 'sum',
average: 'average',
min: 'min',
diff --git a/packages/db/src/services/chart.service.ts b/packages/db/src/services/chart.service.ts
index 9deca5d3..39b98dbb 100644
--- a/packages/db/src/services/chart.service.ts
+++ b/packages/db/src/services/chart.service.ts
@@ -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, ' '));
diff --git a/packages/db/src/services/overview.service.ts b/packages/db/src/services/overview.service.ts
index 38ef8cc7..ef43378c 100644
--- a/packages/db/src/services/overview.service.ts
+++ b/packages/db/src/services/overview.service.ts
@@ -83,62 +83,12 @@ export type IGetTopGenericInput = z.infer
& {
};
export class OverviewService {
- private pendingQueries: Map> = 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;
}
diff --git a/packages/trpc/src/routers/chart.helpers.ts b/packages/trpc/src/routers/chart.helpers.ts
index 09a40f83..788f6e13 100644
--- a/packages/trpc/src/routers/chart.helpers.ts
+++ b/packages/trpc/src/routers/chart.helpers.ts
@@ -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(
- getChartSql({ ...payload, timezone }),
+ let result = await chQuery(
+ getChartSql({ ...payload, timezone }),
+ {
+ session_timezone: timezone,
+ },
+ );
+
+ if (result.length === 0 && payload.breakdowns.length > 0) {
+ result = await chQuery(
+ getChartSql({
+ ...payload,
+ breakdowns: [],
+ timezone,
+ }),
{
session_timezone: timezone,
},
);
-
- if (result.length === 0 && payload.breakdowns.length > 0) {
- return await chQuery(
- 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>[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,
};
}
diff --git a/packages/trpc/src/routers/report.ts b/packages/trpc/src/routers/report.ts
index 723803ea..d4ae4cef 100644
--- a/packages/trpc/src/routers/report.ts
+++ b/packages/trpc/src/routers/report.ts
@@ -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,
},
diff --git a/packages/validation/src/types.validation.ts b/packages/validation/src/types.validation.ts
index 612a0742..2c348dca 100644
--- a/packages/validation/src/types.validation.ts
+++ b/packages/validation/src/types.validation.ts
@@ -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;
};
};