fix(webapp): use stable chart bucket timestamps (#4717)
## Summary Uses explicit bucket timestamps when rendering usage charts instead of anchoring missing timestamps to the current render time. Tooltips now remain stable across rerenders, and examples use a deterministic timestamp.
This commit is contained in:
@@ -44,7 +44,7 @@ export type MiniLineChartProps = {
|
||||
throttled?: number[];
|
||||
/** Tooltip wording for the overlay buckets. Null omits the overlay line. */
|
||||
overlayLabel?: string | null;
|
||||
/** Epoch ms of the first bucket's start. When omitted, the last bucket is anchored to now. */
|
||||
/** Epoch ms of the first bucket's start. */
|
||||
bucketStartMs?: number;
|
||||
/** Width of each bucket in ms. Defaults to one hour. */
|
||||
bucketIntervalMs?: number;
|
||||
@@ -92,7 +92,12 @@ export function MiniLineChart({
|
||||
showPeak = true,
|
||||
}: MiniLineChartProps) {
|
||||
const hasPeakOverride = peakOverride !== undefined;
|
||||
if (!data || data.length === 0 || (data.every((v) => v === 0) && !hasPeakOverride)) {
|
||||
if (
|
||||
!data ||
|
||||
data.length === 0 ||
|
||||
bucketStartMs === undefined ||
|
||||
(data.every((v) => v === 0) && !hasPeakOverride)
|
||||
) {
|
||||
return <span className="text-text-dimmed">–</span>;
|
||||
}
|
||||
|
||||
@@ -103,11 +108,9 @@ export function MiniLineChart({
|
||||
const max = Math.max(...data);
|
||||
const peak = peakOverride ?? max;
|
||||
|
||||
// Map each bucket to a dated point so the tooltip can show the window it represents. Buckets are
|
||||
// `intervalMs` wide; if the caller didn't pass the first bucket's start, anchor the last bucket to
|
||||
// now (hourly default).
|
||||
// Map each bucket to a dated point so the tooltip can show the window it represents.
|
||||
const intervalMs = bucketIntervalMs ?? 3600_000;
|
||||
const startMs = bucketStartMs ?? Date.now() - (data.length - 1) * intervalMs;
|
||||
const startMs = bucketStartMs;
|
||||
const chartData: MiniLineChartDatum[] = data.map((count, i) => {
|
||||
const t = throttled?.[i] ?? 0;
|
||||
// Extend the mask one bucket forward (a segment needs both endpoints non-null), so even a
|
||||
|
||||
@@ -19,8 +19,8 @@ type UnitLabel = { singular: string; plural: string };
|
||||
export type UsageSparklineProps = {
|
||||
/** Equal-width time buckets, oldest first. */
|
||||
data?: number[];
|
||||
/** Epoch ms of the first bucket's start. When omitted, the last bucket is anchored to now. */
|
||||
bucketStartMs?: number;
|
||||
/** Epoch ms of the first bucket's start. */
|
||||
bucketStartMs: number;
|
||||
/** Width of each bucket in ms. Defaults to one hour. */
|
||||
bucketIntervalMs?: number;
|
||||
/** Bar colour. Defaults to blue. */
|
||||
@@ -64,11 +64,9 @@ export function UsageSparkline({
|
||||
const total = totalOverride ?? data.reduce((a, b) => a + b, 0);
|
||||
const max = Math.max(...data);
|
||||
|
||||
// Map each bucket to a dated point so the tooltip can show the window it
|
||||
// represents. Buckets are `intervalMs` wide; if the caller didn't pass the
|
||||
// first bucket's start, anchor the last bucket to now (hourly default).
|
||||
// Map each bucket to a dated point so the tooltip can show the window it represents.
|
||||
const intervalMs = bucketIntervalMs ?? 3600_000;
|
||||
const startMs = bucketStartMs ?? Date.now() - (data.length - 1) * intervalMs;
|
||||
const startMs = bucketStartMs;
|
||||
const chartData: UsageDatum[] = data.map((count, i) => ({
|
||||
date: new Date(startMs + i * intervalMs),
|
||||
count,
|
||||
|
||||
@@ -21,6 +21,7 @@ import SegmentedControl from "~/components/primitives/SegmentedControl";
|
||||
// Date formatters for chart display
|
||||
const xAxisTickFormatter = (value: string) => formatISODate(value);
|
||||
const tooltipLabelFormatter = (label: string) => formatISODateLong(label);
|
||||
const MINI_LINE_BUCKET_START_MS = Date.UTC(2025, 0, 1);
|
||||
|
||||
/**
|
||||
* Helper function to filter chart data by date range.
|
||||
@@ -361,6 +362,7 @@ function ChartsDashboard() {
|
||||
<td className="py-1.5">
|
||||
<MiniLineChart
|
||||
data={API_DATA.miniLineData}
|
||||
bucketStartMs={MINI_LINE_BUCKET_START_MS}
|
||||
peak={Math.max(...API_DATA.miniLineData)}
|
||||
unitLabel={{ singular: "queued", plural: "queued" }}
|
||||
color="var(--color-tasks)"
|
||||
@@ -372,6 +374,7 @@ function ChartsDashboard() {
|
||||
<td className="py-1.5">
|
||||
<MiniLineChart
|
||||
data={API_DATA.miniLineThrottledData}
|
||||
bucketStartMs={MINI_LINE_BUCKET_START_MS}
|
||||
throttled={API_DATA.miniLineThrottledBuckets}
|
||||
peak={Math.max(...API_DATA.miniLineThrottledData)}
|
||||
unitLabel={{ singular: "queued", plural: "queued" }}
|
||||
|
||||
Reference in New Issue
Block a user