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

@@ -2,6 +2,34 @@ import { createContext, useContext as useBaseContext } from 'react';
import { Tooltip as RechartsTooltip, type TooltipProps } from 'recharts'; import { Tooltip as RechartsTooltip, type TooltipProps } from 'recharts';
export const ChartTooltipContainer = ({
children,
}: { children: React.ReactNode }) => {
return (
<div className="min-w-[180px] col gap-2 rounded-xl border bg-background/80 p-3 shadow-xl backdrop-blur-sm">
{children}
</div>
);
};
export const ChartTooltipHeader = ({
children,
}: { children: React.ReactNode }) => {
return <div className="flex justify-between gap-8">{children}</div>;
};
export const ChartTooltipItem = ({
children,
color,
}: { children: React.ReactNode; color: string }) => {
return (
<div className="flex gap-2">
<div className="w-[3px] rounded-full" style={{ background: color }} />
<div className="col flex-1 gap-1">{children}</div>
</div>
);
};
export function createChartTooltip< export function createChartTooltip<
PropsFromTooltip, PropsFromTooltip,
PropsFromContext extends Record<string, unknown>, PropsFromContext extends Record<string, unknown>,
@@ -31,9 +59,9 @@ export function createChartTooltip<
} }
return ( return (
<div className="flex min-w-[180px] flex-col gap-2 rounded-xl border bg-background/80 p-3 shadow-xl backdrop-blur-sm"> <ChartTooltipContainer>
<Tooltip data={data} context={context} {...tooltip} /> <Tooltip data={data} context={context} {...tooltip} />
</div> </ChartTooltipContainer>
); );
}; };

View File

@@ -1,10 +1,17 @@
import { fancyMinutes, useNumber } from '@/hooks/use-numer-formatter'; import { fancyMinutes, useNumber } from '@/hooks/use-numer-formatter';
import { cn } from '@/utils/cn'; import { cn } from '@/utils/cn';
import AutoSizer from 'react-virtualized-auto-sizer'; import AutoSizer from 'react-virtualized-auto-sizer';
import { Area, AreaChart } from 'recharts'; import { Area, AreaChart, Tooltip } from 'recharts';
import { formatDate, timeAgo } from '@/utils/date'; import { formatDate, timeAgo } from '@/utils/date';
import { getChartColor } from '@/utils/theme';
import { getPreviousMetric } from '@openpanel/common'; import { getPreviousMetric } from '@openpanel/common';
import { useState } from 'react';
import {
ChartTooltipContainer,
ChartTooltipHeader,
ChartTooltipItem,
} from '../charts/chart-tooltip';
import { import {
PreviousDiffIndicatorPure, PreviousDiffIndicatorPure,
getDiffIndicator, getDiffIndicator,
@@ -41,6 +48,7 @@ export function OverviewMetricCard({
inverted = false, inverted = false,
isLoading = false, isLoading = false,
}: MetricCardProps) { }: MetricCardProps) {
const [value, setValue] = useState(metric.current);
const number = useNumber(); const number = useNumber();
const { current, previous } = metric; const { current, previous } = metric;
@@ -79,7 +87,7 @@ export function OverviewMetricCard({
<span> <span>
{label}:{' '} {label}:{' '}
<span className="font-semibold"> <span className="font-semibold">
{renderValue(current, 'ml-1 font-light text-xl', false)} {renderValue(value, 'ml-1 font-light text-xl', false)}
</span> </span>
</span> </span>
} }
@@ -97,7 +105,7 @@ export function OverviewMetricCard({
<div className={cn('group relative p-4')}> <div className={cn('group relative p-4')}>
<div <div
className={cn( className={cn(
'pointer-events-none absolute -left-1 -right-1 bottom-0 top-0 z-0 opacity-50 transition-opacity duration-300 group-hover:opacity-100', 'absolute -left-1 -right-1 bottom-0 top-0 z-0 opacity-50 transition-opacity duration-300 group-hover:opacity-100',
)} )}
> >
<AutoSizer> <AutoSizer>
@@ -107,6 +115,11 @@ export function OverviewMetricCard({
height={height / 4} height={height / 4}
data={data} data={data}
style={{ marginTop: (height / 4) * 3 }} style={{ marginTop: (height / 4) * 3 }}
onMouseMove={(event) => {
setValue(
event.activePayload?.[0]?.payload?.current ?? current,
);
}}
> >
<defs> <defs>
<linearGradient <linearGradient
@@ -128,6 +141,7 @@ export function OverviewMetricCard({
/> />
</linearGradient> </linearGradient>
</defs> </defs>
<Tooltip content={() => null} />
<Area <Area
dataKey={'current'} dataKey={'current'}
type="step" type="step"

View File

@@ -8,7 +8,7 @@ import { getChartColor } from '@/utils/theme';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { isSameDay, isSameHour, isSameMonth, isSameWeek } from 'date-fns'; import { isSameDay, isSameHour, isSameMonth, isSameWeek } from 'date-fns';
import { last } from 'ramda'; import { last } from 'ramda';
import React, { useCallback } from 'react'; import { useCallback } from 'react';
import { import {
Area, Area,
CartesianGrid, CartesianGrid,
@@ -25,7 +25,6 @@ import {
import { useDashedStroke } from '@/hooks/use-dashed-stroke'; import { useDashedStroke } from '@/hooks/use-dashed-stroke';
import { useXAxisProps, useYAxisProps } from '../common/axis'; import { useXAxisProps, useYAxisProps } from '../common/axis';
import { SolidToDashedGradient } from '../common/linear-gradient';
import { ReportChartTooltip } from '../common/report-chart-tooltip'; import { ReportChartTooltip } from '../common/report-chart-tooltip';
import { ReportTable } from '../common/report-table'; import { ReportTable } from '../common/report-table';
import { SerieIcon } from '../common/serie-icon'; import { SerieIcon } from '../common/serie-icon';

View File

@@ -35,7 +35,9 @@ export function Chart({ data }: Props) {
() => (isEditMode ? data.series : data.series.slice(0, limit || 10)), () => (isEditMode ? data.series : data.series.slice(0, limit || 10)),
[data, isEditMode, limit], [data, isEditMode, limit],
); );
const maxCount = Math.max(...series.map((serie) => serie.metrics[metric])); const maxCount = Math.max(
...series.map((serie) => serie.metrics[metric] ?? 0),
);
const tableColumns = [ const tableColumns = [
{ {

View File

@@ -3,7 +3,11 @@ import { useNumber } from '@/hooks/use-numer-formatter';
import type { IRechartPayloadItem } from '@/hooks/use-rechart-data-model'; import type { IRechartPayloadItem } from '@/hooks/use-rechart-data-model';
import React from 'react'; import React from 'react';
import { createChartTooltip } from '@/components/charts/chart-tooltip'; import {
ChartTooltipHeader,
ChartTooltipItem,
createChartTooltip,
} from '@/components/charts/chart-tooltip';
import type { RouterOutputs } from '@/trpc/client'; import type { RouterOutputs } from '@/trpc/client';
import type { IInterval } from '@openpanel/validation'; import type { IInterval } from '@openpanel/validation';
import { import {
@@ -88,37 +92,31 @@ export const ReportChartTooltip = createChartTooltip<Data, Context>(
const hidden = sorted.slice(limit); const hidden = sorted.slice(limit);
return ( return (
<div className="flex min-w-[180px] flex-col gap-2"> <>
{visible.map((item, index) => ( {visible.map((item, index) => (
<React.Fragment key={item.id}> <React.Fragment key={item.id}>
{index === 0 && item.date && ( {index === 0 && item.date && (
<div className="flex justify-between gap-8"> <ChartTooltipHeader>
<div>{formatDate(new Date(item.date))}</div> <div>{formatDate(new Date(item.date))}</div>
</div> </ChartTooltipHeader>
)} )}
<div className="flex gap-2"> <ChartTooltipItem color={item.color}>
<div <div className="flex items-center gap-1">
className="w-[3px] rounded-full" <SerieIcon name={item.names} />
style={{ background: item.color }} <SerieName name={item.names} />
/>
<div className="col flex-1 gap-1">
<div className="flex items-center gap-1">
<SerieIcon name={item.names} />
<SerieName name={item.names} />
</div>
<div className="flex justify-between gap-8 font-mono font-medium">
<div className="row gap-1">
{number.formatWithUnit(item.count, unit)}
{!!item.previous && (
<span className="text-muted-foreground">
({number.formatWithUnit(item.previous.value, unit)})
</span>
)}
</div>
<PreviousDiffIndicator {...item.previous} />
</div>
</div> </div>
</div> <div className="flex justify-between gap-8 font-mono font-medium">
<div className="row gap-1">
{number.formatWithUnit(item.count, unit)}
{!!item.previous && (
<span className="text-muted-foreground">
({number.formatWithUnit(item.previous.value, unit)})
</span>
)}
</div>
<PreviousDiffIndicator {...item.previous} />
</div>
</ChartTooltipItem>
</React.Fragment> </React.Fragment>
))} ))}
{hidden.length > 0 && ( {hidden.length > 0 && (
@@ -142,7 +140,7 @@ export const ReportChartTooltip = createChartTooltip<Data, Context>(
))} ))}
</> </>
)} )}
</div> </>
); );
}, },
); );

View File

@@ -22,7 +22,7 @@ export function Chart({ data }: Props) {
() => () =>
series.map((s) => ({ series.map((s) => ({
country: s.names[0]?.toLowerCase() ?? '', country: s.names[0]?.toLowerCase() ?? '',
value: s.metrics[metric], value: s.metrics[metric] ?? 0,
})), })),
[series, metric], [series, metric],
); );

View File

@@ -12,7 +12,7 @@ interface Props {
export function Chart({ data }: Props) { export function Chart({ data }: Props) {
const { const {
isEditMode, isEditMode,
report: { metric, unit }, report: { unit },
} = useReportChartContext(); } = useReportChartContext();
const { series } = useVisibleSeries(data, isEditMode ? 20 : 4); const { series } = useVisibleSeries(data, isEditMode ? 20 : 4);
return ( return (
@@ -27,7 +27,7 @@ export function Chart({ data }: Props) {
<MetricCard <MetricCard
key={serie.id} key={serie.id}
serie={serie} serie={serie}
metric={metric} metric={'count'}
unit={unit} unit={unit}
/> />
); );

View File

@@ -2,10 +2,17 @@ import { fancyMinutes, useNumber } from '@/hooks/use-numer-formatter';
import type { IChartData } from '@/trpc/client'; import type { IChartData } from '@/trpc/client';
import { cn } from '@/utils/cn'; import { cn } from '@/utils/cn';
import AutoSizer from 'react-virtualized-auto-sizer'; import AutoSizer from 'react-virtualized-auto-sizer';
import { Area, AreaChart } from 'recharts'; import { Area, AreaChart, Tooltip } from 'recharts';
import type { IChartMetric } from '@openpanel/validation'; import type { IChartMetric } from '@openpanel/validation';
import {
ChartTooltipContainer,
ChartTooltipHeader,
ChartTooltipItem,
} from '@/components/charts/chart-tooltip';
import { formatDate } from '@/utils/date';
import { getChartColor } from '@/utils/theme';
import { import {
PreviousDiffIndicator, PreviousDiffIndicator,
getDiffIndicator, getDiffIndicator,
@@ -20,6 +27,27 @@ interface MetricCardProps {
unit?: string; unit?: string;
} }
const TooltipContent = (props: { payload?: any[] }) => {
const number = useNumber();
return (
<ChartTooltipContainer>
{props.payload?.map((item) => {
const { date, count } = item.payload;
return (
<div key={item.id} className="col gap-2">
<ChartTooltipHeader>
<div>{formatDate(new Date(date))}</div>
</ChartTooltipHeader>
<ChartTooltipItem color={getChartColor(0)}>
<div>{number.format(count)}</div>
</ChartTooltipItem>
</div>
);
})}
</ChartTooltipContainer>
);
};
export function MetricCard({ export function MetricCard({
serie, serie,
color: _color, color: _color,
@@ -32,7 +60,11 @@ export function MetricCard({
} = useReportChartContext(); } = useReportChartContext();
const number = useNumber(); const number = useNumber();
const renderValue = (value: number, unitClassName?: string) => { const renderValue = (value: number | undefined, unitClassName?: string) => {
if (!value) {
return <div className="text-muted-foreground">N/A</div>;
}
if (unit === 'min') { if (unit === 'min') {
return <>{fancyMinutes(value)}</>; return <>{fancyMinutes(value)}</>;
} }
@@ -62,7 +94,7 @@ export function MetricCard({
> >
<div <div
className={cn( className={cn(
'pointer-events-none absolute -left-1 -right-1 bottom-0 top-0 z-0 opacity-50 transition-opacity duration-300 group-hover:opacity-100', 'absolute -left-1 -right-1 bottom-0 top-0 z-0 opacity-100 transition-opacity duration-300 group-hover:opacity-100',
)} )}
> >
<AutoSizer> <AutoSizer>
@@ -89,6 +121,7 @@ export function MetricCard({
/> />
</linearGradient> </linearGradient>
</defs> </defs>
<Tooltip content={TooltipContent} />
<Area <Area
dataKey="count" dataKey="count"
type="step" type="step"

View File

@@ -7,6 +7,11 @@ import { truncate } from '@/utils/truncate';
import { Fragment } from 'react'; import { Fragment } from 'react';
import { Cell, Pie, PieChart, ResponsiveContainer, Tooltip } from 'recharts'; import { Cell, Pie, PieChart, ResponsiveContainer, Tooltip } from 'recharts';
import {
ChartTooltipContainer,
ChartTooltipHeader,
ChartTooltipItem,
} from '@/components/charts/chart-tooltip';
import { useNumber } from '@/hooks/use-numer-formatter'; import { useNumber } from '@/hooks/use-numer-formatter';
import { formatDate } from '@/utils/date'; import { formatDate } from '@/utils/date';
import { AXIS_FONT_PROPS } from '../common/axis'; import { AXIS_FONT_PROPS } from '../common/axis';
@@ -24,43 +29,37 @@ interface Props {
const PieTooltip = (props: { payload?: any[] }) => { const PieTooltip = (props: { payload?: any[] }) => {
const number = useNumber(); const number = useNumber();
return ( return (
<div className="bg-background/80 p-2 rounded-md backdrop-blur-md border min-w-[180px]"> <ChartTooltipContainer>
{props.payload?.map((serie, index) => { {props.payload?.map((serie, index) => {
const item = serie.payload; const item = serie.payload;
return ( return (
<Fragment key={item.id}> <Fragment key={item.id}>
{index === 0 && item.date && ( {index === 0 && item.date && (
<div className="flex justify-between gap-8"> <ChartTooltipHeader>
<div>{formatDate(new Date(item.date))}</div> <div>{formatDate(new Date(item.date))}</div>
</div> </ChartTooltipHeader>
)} )}
<div className="flex gap-2"> <ChartTooltipItem color={item.color}>
<div <div className="flex items-center gap-1">
className="w-[3px] rounded-full" <SerieIcon name={item.name} />
style={{ background: item.color }} <SerieName name={item.names} className="font-medium" />
/>
<div className="col flex-1 gap-1">
<div className="flex items-center gap-1">
<SerieIcon name={item.name} />
<SerieName name={item.names} className="font-medium" />
</div>
<div className="flex justify-between gap-8 font-mono font-medium">
<div className="row gap-1">
{number.formatWithUnit(item.count)}
{!!item.previous && (
<span className="text-muted-foreground">
({number.formatWithUnit(item.previous.sum.value)})
</span>
)}
</div>
<PreviousDiffIndicator {...item.previous?.sum} />
</div>
</div> </div>
</div> <div className="flex justify-between gap-8 font-mono font-medium">
<div className="row gap-1">
{number.formatWithUnit(item.count)}
{!!item.previous && (
<span className="text-muted-foreground">
({number.formatWithUnit(item.previous.sum.value)})
</span>
)}
</div>
<PreviousDiffIndicator {...item.previous?.sum} />
</div>
</ChartTooltipItem>
</Fragment> </Fragment>
); );
})} })}
</div> </ChartTooltipContainer>
); );
}; };

View File

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

View File

@@ -62,6 +62,7 @@ export function getChartSql({
projectId, projectId,
limit, limit,
timezone, timezone,
chartType,
}: IGetChartDataInput & { timezone: string }) { }: IGetChartDataInput & { timezone: string }) {
const { const {
sb, sb,
@@ -209,6 +210,17 @@ export function getChartSql({
return sql; 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()}`; const sql = `${getSelect()} ${getFrom()} ${getJoins()} ${getWhere()} ${getGroupBy()} ${getOrderBy()} ${getFill()}`;
console.log('-- Report --'); console.log('-- Report --');
console.log(sql.replaceAll(/[\n\r]/g, ' ')); console.log(sql.replaceAll(/[\n\r]/g, ' '));

View File

@@ -83,62 +83,12 @@ export type IGetTopGenericInput = z.infer<typeof zGetTopGenericInput> & {
}; };
export class OverviewService { export class OverviewService {
private pendingQueries: Map<string, Promise<number | null>> = new Map();
constructor(private client: typeof ch) {} constructor(private client: typeof ch) {}
isPageFilter(filters: IChartEventFilter[]) { isPageFilter(filters: IChartEventFilter[]) {
return filters.some((filter) => filter.name === 'path' && filter.value); 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({ getMetrics({
projectId, projectId,
filters, filters,
@@ -483,14 +433,6 @@ export class OverviewService {
.orderBy('sessions', 'DESC') .orderBy('sessions', 'DESC')
.limit(limit); .limit(limit);
const totalSessions = await this.getTotalSessions({
projectId,
startDate,
endDate,
filters,
timezone,
});
return mainQuery.execute(); return mainQuery.execute();
} }
@@ -556,14 +498,6 @@ export class OverviewService {
); );
} }
const totalSessions = await this.getTotalSessions({
projectId,
startDate,
endDate,
filters,
timezone,
});
return mainQuery.execute(); return mainQuery.execute();
} }
@@ -666,16 +600,7 @@ export class OverviewService {
mainQuery.rawWhere(this.getRawWhereClause('sessions', filters)); mainQuery.rawWhere(this.getRawWhereClause('sessions', filters));
} }
const [res, totalSessions] = await Promise.all([ const res = await mainQuery.execute();
mainQuery.execute(),
this.getTotalSessions({
projectId,
startDate,
endDate,
filters,
timezone,
}),
]);
return res; return res;
} }

View File

@@ -4,7 +4,6 @@ import sqlstring from 'sqlstring';
import type { ISerieDataItem } from '@openpanel/common'; import type { ISerieDataItem } from '@openpanel/common';
import { import {
DateTime,
average, average,
getPreviousMetric, getPreviousMetric,
groupByLabels, groupByLabels,
@@ -226,39 +225,32 @@ export async function getChartSerie(
payload: IGetChartDataInput, payload: IGetChartDataInput,
timezone: string, timezone: string,
) { ) {
async function getSeries() { let result = await chQuery<ISerieDataItem>(
const result = await chQuery<ISerieDataItem>( getChartSql({ ...payload, timezone }),
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, 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() return groupByLabels(result).map((serie) => {
.then(groupByLabels) return {
.then((series) => { ...serie,
return series.map((serie) => { event: payload.event,
return { };
...serie, });
event: payload.event,
};
});
});
} }
export type IGetChartSerie = Awaited<ReturnType<typeof getChartSeries>>[number]; 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), average: round(average(serie.data.map((item) => item.count)), 2),
min: min(serie.data.map((item) => item.count)), min: min(serie.data.map((item) => item.count)),
max: max(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 = { const event = {
id: serie.event.id, id: serie.event.id,
@@ -388,6 +381,10 @@ export async function getChart(input: IChartInput) {
? max(previousSerie?.data.map((item) => item.count)) ? max(previousSerie?.data.map((item) => item.count))
: null, : null,
), ),
count: getPreviousMetric(
metrics.count ?? 0,
previousSerie?.data[0]?.total_count ?? null,
),
}, },
} }
: {}), : {}),
@@ -409,6 +406,7 @@ export async function getChart(input: IChartInput) {
average: 0, average: 0,
min: 0, min: 0,
max: 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); const sumB = b.data.reduce((acc, item) => acc + (item.count ?? 0), 0);
return sumB - sumA; 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); .slice(offset, limit ? offset + limit : series.length);
@@ -456,6 +454,7 @@ export async function getChart(input: IChartInput) {
final.metrics.max, final.metrics.max,
max(final.series.map((item) => item.metrics.previous?.max?.value ?? 0)), 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, previous: report.previous ?? false,
unit: report.unit, unit: report.unit,
criteria: report.criteria, criteria: report.criteria,
metric: report.metric, metric: report.metric === 'count' ? 'sum' : report.metric,
funnelGroup: report.funnelGroup, funnelGroup: report.funnelGroup,
funnelWindow: report.funnelWindow, funnelWindow: report.funnelWindow,
}, },
@@ -101,7 +101,7 @@ export const reportRouter = createTRPCRouter({
previous: report.previous ?? false, previous: report.previous ?? false,
unit: report.unit, unit: report.unit,
criteria: report.criteria, criteria: report.criteria,
metric: report.metric, metric: report.metric === 'count' ? 'sum' : report.metric,
funnelGroup: report.funnelGroup, funnelGroup: report.funnelGroup,
funnelWindow: report.funnelWindow, funnelWindow: report.funnelWindow,
}, },

View File

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