From cdc9c5f27f5ab1238e1cf91ab6b1ffeee96ee2b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sat, 16 May 2026 10:38:27 +0200 Subject: [PATCH] perf(charts): wire aggregate=daily for windows beyond a week MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The chart fetcher always pulled raw rows from `/api/measurements` — up to ~5 000 pulse samples per range-tab change on a continuous- monitoring account. The server's `aggregate=daily` SQL path lands in v1.4.29 C2; this commit teaches `` to ask for it on every window over 7 days. Short windows (7-day overview) keep raw fetching so hour-by-hour detail stays visible. Effect on a 30-day pulse view: payload drops from up to ~5 000 rows to one row per day. The client-side `dailyAggregates` Map becomes a no-op on already-bucketed rows. Range test gains two assertions: the default 30-day window asks for `aggregate=daily`; the `windowOverride="last7days"` mini-chart omits the parameter. --- .../__tests__/health-chart-range.test.tsx | 30 +++++++++++++++++++ src/components/charts/health-chart.tsx | 9 ++++++ 2 files changed, 39 insertions(+) diff --git a/src/components/charts/__tests__/health-chart-range.test.tsx b/src/components/charts/__tests__/health-chart-range.test.tsx index bf01f2394..0610579e1 100644 --- a/src/components/charts/__tests__/health-chart-range.test.tsx +++ b/src/components/charts/__tests__/health-chart-range.test.tsx @@ -84,6 +84,36 @@ describe(" — bounded range fetch", () => { expect(measurementsUrl!).toMatch(/type=PULSE/); // Guard against the legacy unbounded paginator returning. expect(measurementsUrl!).not.toMatch(/offset=/); + // v1.4.29 C3 — the default 30-day window must request server- + // side daily aggregation so the client doesn't pay for ~5000 + // raw pulse rows on every range-tab change. + expect(measurementsUrl!).toMatch(/aggregate=daily/); + }); + + it("omits aggregate=daily on windows of 7 days or fewer", async () => { + // v1.4.29 C3 — short windows keep raw fetching so the user can + // see hour-by-hour detail on the 7-day view. + const { I18nProvider } = await import("@/lib/i18n/context"); + const { HealthChart } = await import("../health-chart"); + + renderToStaticMarkup( + + + , + ); + + await new Promise((resolve) => setTimeout(resolve, 0)); + + const measurementsUrl = fetchCalls.find((u) => + u.includes("/api/measurements"), + ); + expect(measurementsUrl).toBeDefined(); + expect(measurementsUrl!).not.toMatch(/aggregate=daily/); }); it("re-keys the query when the range window changes", async () => { diff --git a/src/components/charts/health-chart.tsx b/src/components/charts/health-chart.tsx index 0dcfbc535..fd33024b5 100644 --- a/src/components/charts/health-chart.tsx +++ b/src/components/charts/health-chart.tsx @@ -577,6 +577,15 @@ export function HealthChart({ typeParams.set("from", fetchWindow.from); typeParams.set("to", fetchWindow.to); typeParams.set("limit", "5000"); + // v1.4.29 C3 — windows over 7 days ask the server to bucket + // daily. Caps the chart's per-type payload at ~365 rows + // instead of ~5 000 for high-density types (pulse), and drops + // Recharts paint cost ~50× on continuous-monitoring accounts. + // Short windows keep raw fetching so hour-by-hour detail + // stays visible. + if (fetchWindow.windowDays > 7) { + typeParams.set("aggregate", "daily"); + } const res = await fetch(`/api/measurements?${typeParams}`); if (!res.ok) return;