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
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ export interface UseAnalyticsRecordsResult {
fieldKeys: Set<string>;
/** Subset of `requiredFields` that did not appear on any row. */
missingFields: string[];
/** True while `data` is the previous window's rows held by
* `keepPreviousData` during an in-flight window change — the rows do
* NOT belong to the requested [startTime, endTime]. Consumers that
* pair `data` with the requested window (CSV export filenames,
* previous-vs-current deltas) must gate on this. */
isPlaceholderData: boolean;
refetch: () => void;
}

Expand Down Expand Up @@ -132,6 +138,7 @@ export function useAnalyticsRecords({
isEmpty: data.length === 0,
fieldKeys,
missingFields,
isPlaceholderData: query.isPlaceholderData,
Comment thread
dawsontoth marked this conversation as resolved.
refetch: query.refetch,
};
}
8 changes: 6 additions & 2 deletions src/features/instance/status/analytics/tabs/HealthTab.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { KpiStrip } from './kpi/KpiStrip';
import { MetricPanel } from './MetricPanel';

const METRICS = [
Expand All @@ -10,8 +11,11 @@ const METRICS = [

export function HealthTab() {
return (
<div className="grid grid-cols-1 xl:grid-cols-2 gap-4">
{METRICS.map((m) => <MetricPanel key={m} metric={m} />)}
<div className="flex flex-col gap-4">
<KpiStrip />
<div className="grid grid-cols-1 xl:grid-cols-2 gap-4">
{METRICS.map((m) => <MetricPanel key={m} metric={m} />)}
</div>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ function makeResult(overrides: Partial<HookResult> = {}): HookResult {
isEmpty: true,
fieldKeys: new Set(),
missingFields: [],
isPlaceholderData: false,
refetch: vi.fn(),
...overrides,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ function setHookResult(overrides: Partial<ReturnType<typeof useAnalyticsRecords>
isEmpty: true,
fieldKeys: new Set(),
missingFields: [],
isPlaceholderData: false,
refetch: vi.fn(),
...overrides,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ function setHookResult(rows: ReturnType<typeof makeTableSizeRows>) {
isEmpty: rows.length === 0,
fieldKeys: new Set(['size', 'database', 'table']),
missingFields: [],
isPlaceholderData: false,
refetch: vi.fn(),
});
}
Expand All @@ -63,6 +64,7 @@ describe('StorageTab', () => {
isEmpty: true,
fieldKeys: new Set(),
missingFields: [],
isPlaceholderData: false,
refetch: vi.fn(),
});
const { container } = render(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ function happyState() {
isEmpty: false,
fieldKeys: new Set(['count', 'mean', 'period', 'p50', 'p95', 'p99']),
missingFields: [],
isPlaceholderData: false,
refetch: vi.fn(),
});
}
Expand All @@ -42,6 +43,7 @@ function loadingState() {
isEmpty: true,
fieldKeys: new Set(),
missingFields: [],
isPlaceholderData: false,
refetch: vi.fn(),
});
}
Expand All @@ -55,6 +57,7 @@ function emptyMissingFieldsState(missing: string[]) {
isEmpty: true,
fieldKeys: new Set(),
missingFields: missing,
isPlaceholderData: false,
refetch: vi.fn(),
});
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import type { KpiPoint } from './kpiMath';

interface Props {
points: KpiPoint[];
/** Full current-window bounds, so a half-empty window draws half a line
* instead of stretching the data to fill the tile. */
xDomain: [number, number];
}

const VIEW_W = 120;
const VIEW_H = 32;
/** Inset so the stroke isn't clipped at the extremes. */
const PAD_Y = 2;

/** Axis-less inline-SVG sparkline for the KPI tiles. Decorative only
* (aria-hidden) — the tile's value + delta carry the information. Drawn in
* the muted de-emphasis hue with the final segment in the accent chart hue
* so "now" reads at a glance in both themes. */
export function KpiSparkline({ points, xDomain }: Props) {
if (points.length < 2) { return <div className="h-8" aria-hidden="true" />; }

const [x0, x1] = xDomain;
const xSpan = x1 - x0 || 1;
let yMin = Infinity;
let yMax = -Infinity;
for (const p of points) {
if (p.y < yMin) { yMin = p.y; }
if (p.y > yMax) { yMax = p.y; }
}
// Flat series: center the line rather than dividing by zero.
const ySpan = yMax - yMin || 1;

const toXY = (p: KpiPoint): [number, number] => [
((p.x - x0) / xSpan) * VIEW_W,
yMax === yMin
? VIEW_H / 2
: PAD_Y + (1 - (p.y - yMin) / ySpan) * (VIEW_H - 2 * PAD_Y),
];

const coords = points.map(toXY);
const toPath = (cs: [number, number][]): string =>
cs.map(([x, y], i) => `${i === 0 ? 'M' : 'L'}${x.toFixed(2)},${y.toFixed(2)}`).join(' ');

return (
<svg
viewBox={`0 0 ${VIEW_W} ${VIEW_H}`}
preserveAspectRatio="none"
className="h-8 w-full text-muted-foreground/60"
aria-hidden="true"
focusable="false"
>
{
/* Two points = only the accent segment below; a one-coord path
would be an invisible MoveTo-only element. */
}
{points.length > 2 && (
<path
d={toPath(coords.slice(0, -1))}
fill="none"
stroke="currentColor"
strokeWidth={1.5}
vectorEffect="non-scaling-stroke"
strokeLinejoin="round"
strokeLinecap="round"
/>
)}
<path
d={toPath(coords.slice(-2))}
fill="none"
stroke="var(--chart-node-1)"
strokeWidth={1.5}
vectorEffect="non-scaling-stroke"
strokeLinejoin="round"
strokeLinecap="round"
/>
</svg>
);
}
12 changes: 12 additions & 0 deletions src/features/instance/status/analytics/tabs/kpi/KpiStrip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { KpiTile } from './KpiTile';
import { KPI_TILES } from './kpiTiles';

/** The Health tab's at-a-glance vitals row: five stat tiles above the panel
* grid (see kpiTiles.ts for the metric definitions). */
export function KpiStrip() {
return (
<div className="grid grid-cols-2 gap-4 sm:grid-cols-3 xl:grid-cols-5" data-testid="kpi-strip">
{KPI_TILES.map((def) => <KpiTile key={def.id} def={def} />)}
</div>
);
}
Loading