Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions apps/api/src/agents/tools/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,15 @@ export const generateReport = chatTool(
)
.default([])
.optional(),
metric: z.enum(['sum', 'count', 'average']).default('sum').optional(),
// No `.default()` here on purpose: it would make an omitted metric
// indistinguishable from an explicit `sum`, and the fallback depends on
// the chart type (see below).
metric: z
.enum(['sum', 'count', 'average', 'min', 'max'])
.optional()
.describe(
'How a series is aggregated for display. Only the metric and map chart types read this; `count` is unique profiles. Omit it unless the user asked for a specific aggregation — metric cards then default to unique profiles.',
),
Comment thread
coderabbitai[bot] marked this conversation as resolved.
previous: z
.boolean()
.optional()
Expand Down Expand Up @@ -338,7 +346,11 @@ export const generateReport = chatTool(
}),
),
range: 'custom' as const,
metric: input.metric ?? 'sum',
// A metric card has always shown the total unique count, so an
// unspecified metric must stay `count` there — `sum` would silently turn
// "1.2k users" into "45k events".
metric:
input.metric ?? (input.chartType === 'metric' ? 'count' : 'sum'),
previous: input.previous ?? false,
...(input.lineType ? { lineType: input.lineType } : {}),
...(input.limit ? { limit: input.limit } : {}),
Expand Down
4 changes: 2 additions & 2 deletions apps/start/src/components/report-chart/metric/chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ interface Props {
export function Chart({ data }: Props) {
const {
isEditMode,
report: { unit },
report: { metric, unit },
} = useReportChartContext();
const { series } = useVisibleSeries(data, { limit: isEditMode ? 20 : 4 });
return (
Expand All @@ -27,7 +27,7 @@ export function Chart({ data }: Props) {
<MetricCard
key={serie.id}
serie={serie}
metric={'count'}
metric={metric}
unit={unit}
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,11 @@ export function MetricCard({
const number = useNumber();

const renderValue = (value: number | undefined, unitClassName?: string) => {
if (!value) {
// A genuine 0 is a real value, not a missing one — `min` is 0 whenever the
// range contains an empty bucket. Only absent metrics render as N/A, which
// still matters: getAggregateChartSql never selects total_count, so `count`
// really is undefined for bar/pie series.
if (value === undefined || value === null) {
return <div className="text-muted-foreground">N/A</div>;
}

Expand Down
15 changes: 15 additions & 0 deletions apps/start/src/components/report/reportSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type {
IChartEventFilter,
IChartEventItem,
IChartLineType,
IChartMetric,
IChartRange,
IChartType,
IInterval,
Expand Down Expand Up @@ -209,6 +210,14 @@ export const reportSlice = createSlice({
state.dirty = true;
state.chartType = action.payload;

// The Metric card has always shown the total unique count. Existing
// reports are backfilled to 'count' by migration, so default a newly
// switched one the same way rather than leaving old and new metric
// reports showing different aggregations. The picker overrides it.
if (action.payload === 'metric') {
state.metric = 'count';
}

// Initialize sankey options if switching to sankey
if (action.payload === 'sankey' && !state.options) {
state.options = {
Expand Down Expand Up @@ -301,6 +310,11 @@ export const reportSlice = createSlice({
state.unit = action.payload || undefined;
},

changeMetric(state, action: PayloadAction<IChartMetric>) {
state.dirty = true;
state.metric = action.payload;
},

changeFunnelGroup(state, action: PayloadAction<string | undefined>) {
state.dirty = true;
if (!state.options || state.options.type !== 'funnel') {
Expand Down Expand Up @@ -445,6 +459,7 @@ export const {
changePrevious,
changeCriteria,
changeUnit,
changeMetric,
changeFunnelGroup,
changeFunnelWindow,
changeOptions,
Expand Down
29 changes: 29 additions & 0 deletions apps/start/src/components/report/sidebar/ReportSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import { Label } from '@/components/ui/label';
import { Switch } from '@/components/ui/switch';
import { useAppParams } from '@/hooks/use-app-params';
import { useEventNames } from '@/hooks/use-event-names';
import type { IChartMetric } from '@openpanel/validation';
import { useMemo } from 'react';
import {
changeCriteria,
changeFunnelGroup,
changeFunnelWindow,
changeMetric,
changePrevious,
changeSankeyExclude,
changeSankeyInclude,
Expand All @@ -25,6 +27,7 @@ export function ReportSettings() {
const chartType = useSelector((state) => state.report.chartType);
const previous = useSelector((state) => state.report.previous);
const unit = useSelector((state) => state.report.unit);
const metric = useSelector((state) => state.report.metric);
const options = useSelector((state) => state.report.options);

const retentionOptions = options?.type === 'retention' ? options : undefined;
Expand Down Expand Up @@ -69,6 +72,11 @@ export function ReportSettings() {
fields.push('stacked');
}

// `map` already reads report.metric; it just never had a way to set it.
if (chartType === 'metric' || chartType === 'map') {
fields.push('metric');
}

return fields;
}, [chartType]);

Expand Down Expand Up @@ -137,6 +145,27 @@ export function ReportSettings() {
/>
</div>
)}
{fields.includes('metric') && (
<div className="flex items-center justify-between gap-4">
<Label className="whitespace-nowrap font-medium mb-0">
Aggregation
</Label>
<Combobox
align="end"
placeholder="Aggregation"
value={metric}
onChange={(val) => dispatch(changeMetric(val as IChartMetric))}
// Same labels the report table uses for these columns.
items={[
{ label: 'Unique', value: 'count' },
{ label: 'Sum', value: 'sum' },
{ label: 'Average', value: 'average' },
{ label: 'Min', value: 'min' },
{ label: 'Max', value: 'max' },
]}
/>
</div>
)}
{fields.includes('funnelGroup') && (
<div className="flex items-center justify-between gap-4">
<Label className="whitespace-nowrap font-medium mb-0">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
-- Metric charts have always rendered the total unique count, but `count` had no
-- home in the enum, so `report.create`/`report.update` silently rewrote it to
-- `sum`. Add the value so a picked aggregation can actually persist.
--
-- This must be its own migration: Postgres refuses to use a new enum value in
-- the same transaction that added it ("unsafe use of new value ... of enum
-- type Metric"). The backfill lives in the next migration.
ALTER TYPE "Metric" ADD VALUE 'count';
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
-- Every existing metric-type report stores 'sum' because the column is
-- NOT NULL DEFAULT 'sum' and nothing ever wrote anything else — the value was
-- read by nothing, since the SQL builders ignore `metric` and the Metric card
-- hardcoded 'count'. Now that the card honours the stored value, leaving them
-- at 'sum' would flip every existing tile from unique-users to summed events.
--
-- Lossless: for chartType = 'metric' the stored value carried no user intent.
UPDATE "reports" SET "metric" = 'count' WHERE "chartType" = 'metric';
Comment thread
coderabbitai[bot] marked this conversation as resolved.
3 changes: 3 additions & 0 deletions packages/db/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,9 @@ enum Metric {
average
min
max
// Appended last to match what `ALTER TYPE ... ADD VALUE` does in the DB,
// so `prisma migrate` reports no drift.
count
}

model Report {
Expand Down
4 changes: 2 additions & 2 deletions packages/trpc/src/routers/report.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export const reportRouter = createTRPCRouter({
formula: report.formula,
previous: report.previous ?? false,
unit: report.unit,
metric: report.metric === 'count' ? 'sum' : report.metric,
metric: report.metric,
options: report.options,
visibleSeries: report.visibleSeries ?? [],
startDate: report.range === 'custom' ? report.startDate : null,
Expand Down Expand Up @@ -112,7 +112,7 @@ export const reportRouter = createTRPCRouter({
formula: report.formula,
previous: report.previous ?? false,
unit: report.unit,
metric: report.metric === 'count' ? 'sum' : report.metric,
metric: report.metric,
options: report.options,
visibleSeries: report.visibleSeries ?? [],
startDate: report.range === 'custom' ? report.startDate : null,
Expand Down
Loading