import { ObservabilityService } from "@/client" /** The live view; anything older is the collector's rollups. */ export const HOURS = 24 /** Another tab can change things, and the engine fails flows on its own. */ const REFRESH = 30_000 /** * How deep the run and failure lists are read. * * Deeper than they are shown: picking a minute off a chart filters these rows * client-side, and a list holding only the newest handful would have nothing * to find for any minute but the current one. * * ponytail: the runs endpoint caps at 200 and takes no time range, so a busy * engine still only covers its last minute or so. A `since` parameter is what * would let a moment on the chart reach the whole day. */ const RUN_DEPTH = 200 const EVENT_DEPTH = 100 export const healthKeys = { all: ["observability"] as const, summary: ["observability", "summary"] as const, events: ["observability", "events"] as const, } /** The resting surface these screens are built from. */ export const CARD = "rounded-lg border border-border bg-card p-4 shadow-e1" export const summaryQueryOptions = () => ({ queryKey: healthKeys.summary, queryFn: () => ObservabilityService.readSummary(), refetchInterval: 10_000, }) export const timeseriesQueryOptions = () => ({ queryKey: ["observability", "timeseries", HOURS] as const, queryFn: () => ObservabilityService.readTimeseries({ hours: HOURS }), refetchInterval: REFRESH, }) export const flowRollupsQueryOptions = () => ({ queryKey: ["observability", "flows", HOURS] as const, queryFn: () => ObservabilityService.readFlowRollups({ hours: HOURS }), refetchInterval: REFRESH, }) export const runsQueryOptions = () => ({ queryKey: ["observability", "runs"] as const, queryFn: () => ObservabilityService.readRuns({ limit: RUN_DEPTH }), refetchInterval: REFRESH, }) export const failuresQueryOptions = () => ({ queryKey: [...healthKeys.events, "failure"] as const, queryFn: () => ObservabilityService.readEvents({ kind: "failure", limit: EVENT_DEPTH }), refetchInterval: REFRESH, }) export const auditQueryOptions = () => ({ queryKey: [...healthKeys.events, "audit"] as const, queryFn: () => ObservabilityService.readEvents({ kind: "audit", limit: 15 }), refetchInterval: REFRESH, }) export const deadLetterQueryOptions = () => ({ queryKey: ["observability", "dead-letter"] as const, queryFn: () => ObservabilityService.readDeadLetters({ limit: 20 }), refetchInterval: REFRESH, }) /** How long ago something happened, in the coarsest unit that still says it. */ export function ago(ts: string | number | null | undefined): string { if (!ts) return "—" const stamp = typeof ts === "number" ? ts * 1000 : Date.parse(ts) const seconds = Math.max(0, (Date.now() - stamp) / 1000) if (seconds < 90) return `${Math.round(seconds)}s ago` if (seconds < 5400) return `${Math.round(seconds / 60)}m ago` if (seconds < 172800) return `${Math.round(seconds / 3600)}h ago` return `${Math.round(seconds / 86400)}d ago` } /** * The minute a timestamp belongs to, as unix seconds. * * Minutes are the granularity the engine stores its metrics at, so this is * also the finest moment a chart can be asked about. */ export const minuteOf = (ts: string) => Math.floor(Date.parse(ts) / 60000) * 60 /** A moment as a clock reading, which is how the charts label their axis. */ export const clock = (ts: number) => new Date(ts * 1000).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", })