Home: one time range across the health block, and failures that follow it
The health screen was fixed at 24 hours everywhere except its lists, which were fixed at nothing: `failuresQueryOptions` read the newest 100 rows and `HealthActivity` filtered them client-side, so on a busy engine the failures list covered whatever few minutes 100 rows happened to span while the chart beside it spanned a day — and pinning an older minute showed an empty card. One `RangePicker` now sits on the Health heading and governs the whole block: the tiles, the flow table, both charts and both lists. Presets are 1h / 6h / 24h / 7d — the collector prunes at `OBS_RETENTION_DAYS` (30), so a week is behind the last one. The longer windows ask for coarser buckets, since a week of minute rollups is ten thousand points nobody can see. Failures get the escape hatch the runs already had: a pinned minute is asked for with `since`/`until` rather than filtered out of what is held, and the list itself is bound to the selected range. `RUN_DEPTH`/`EVENT_DEPTH` become one `LIST_DEPTH`, which now buys coverage of the window on screen instead of a fixed newest-N — narrowing the range is what makes the same rows reach the whole of it. The "Failures (24h)" tile counts errors over the selected window from the rollups the table is drawn from, so tile, column and chart agree. The node-panel and edge trend curves get no picker. They are a Redis ring of the last 120 values per message with no window to ask for, so hovering one reveals what it actually shows — how many readings, and the span they cover. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XC2jX6Hdj7pxGGKzBTrbqB
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
/**
|
||||
* A window of history, and everything a query needs to ask for it.
|
||||
*
|
||||
* `bucketS` keeps the number of points a chart draws roughly constant: a week
|
||||
* of minute buckets is ten thousand readings nobody can see and a megabyte of
|
||||
* JSON every refresh, so the longer windows are folded coarser server-side.
|
||||
*/
|
||||
export type Range = { label: string; hours: number; bucketS: number }
|
||||
|
||||
/**
|
||||
* The windows on offer.
|
||||
*
|
||||
* Bounded by what the collector keeps: it prunes buckets, failures and runs at
|
||||
* `OBS_RETENTION_DAYS` (30 by default), so a week is behind the last preset
|
||||
* rather than an empty chart.
|
||||
*/
|
||||
export const RANGES: Range[] = [
|
||||
{ label: "1h", hours: 1, bucketS: 60 },
|
||||
{ label: "6h", hours: 6, bucketS: 60 },
|
||||
{ label: "24h", hours: 24, bucketS: 60 },
|
||||
{ label: "7d", hours: 168, bucketS: 900 },
|
||||
]
|
||||
|
||||
/** A day: long enough to hold a night's worth of trouble, short enough to read. */
|
||||
export const DEFAULT_RANGE = RANGES[2]
|
||||
|
||||
/**
|
||||
* Where the window starts, as the stamp the history endpoints take.
|
||||
*
|
||||
* Read at fetch time rather than when the options are built, so the window
|
||||
* slides with the clock instead of freezing where the screen opened.
|
||||
*/
|
||||
export const rangeStart = (range: Range) =>
|
||||
new Date(Date.now() - range.hours * 3600_000).toISOString()
|
||||
|
||||
/**
|
||||
* The window a screen is showing, as presets.
|
||||
*
|
||||
* The one segmented shape: a single border pill, transparent segments,
|
||||
* bg-accent on the selected one (root DESIGN-GUIDELINES.md).
|
||||
*/
|
||||
export function RangePicker({
|
||||
value,
|
||||
onChange,
|
||||
}: {
|
||||
value: Range
|
||||
onChange: (range: Range) => void
|
||||
}) {
|
||||
return (
|
||||
<fieldset
|
||||
data-testid="range-picker"
|
||||
className="flex w-fit items-center gap-1 rounded-full border border-border p-1"
|
||||
>
|
||||
<legend className="sr-only">Time range</legend>
|
||||
{RANGES.map((range) => (
|
||||
<button
|
||||
key={range.label}
|
||||
type="button"
|
||||
aria-pressed={range.hours === value.hours}
|
||||
onClick={() => onChange(range)}
|
||||
className={cn(
|
||||
"rounded-full px-2.5 py-1 text-xs transition-colors",
|
||||
range.hours === value.hours
|
||||
? "bg-accent text-accent-foreground"
|
||||
: "text-muted-foreground hover:bg-accent/50",
|
||||
)}
|
||||
>
|
||||
{range.label}
|
||||
</button>
|
||||
))}
|
||||
</fieldset>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user