Pin a chart to the last few runs of a flow
This commit is contained in:
@@ -9,6 +9,7 @@ import {
|
||||
PanelTitle,
|
||||
SidePanel,
|
||||
} from "@/components/Flow/SidePanel"
|
||||
import { runOverviewQueryOptions } from "@/components/Runs/queries"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import {
|
||||
Dialog,
|
||||
@@ -144,10 +145,158 @@ function MessagePicker({
|
||||
)
|
||||
}
|
||||
|
||||
/** Which runs a pinned chart draws, and which of their metrics. */
|
||||
type RunsPick = {
|
||||
metric?: string
|
||||
flow?: string
|
||||
group?: string
|
||||
ids?: string[]
|
||||
latest?: number
|
||||
}
|
||||
|
||||
/** How a pinned chart says which runs it means. */
|
||||
const RUN_PICKS = [
|
||||
["latest", "Latest"],
|
||||
["group", "Sweep"],
|
||||
["ids", "Named"],
|
||||
] as const
|
||||
|
||||
/**
|
||||
* The runs a chart is pinned to.
|
||||
*
|
||||
* The flows offered are the ones that have actually run — a flow with no runs
|
||||
* has no curve to draw, and the run tables already know which those are.
|
||||
*/
|
||||
function RunsSourceFields({
|
||||
runs,
|
||||
refreshS,
|
||||
onChange,
|
||||
onRefresh,
|
||||
}: {
|
||||
runs: RunsPick
|
||||
refreshS: unknown
|
||||
onChange: (next: RunsPick) => void
|
||||
onRefresh: (seconds: number) => void
|
||||
}) {
|
||||
const { data: flows } = useQuery(runOverviewQueryOptions())
|
||||
const mode = runs.ids?.length ? "ids" : runs.group ? "group" : "latest"
|
||||
|
||||
return (
|
||||
<div className="grid gap-2">
|
||||
<div className="grid gap-1.5">
|
||||
<Label className="font-normal text-sm">Of flow</Label>
|
||||
<Select
|
||||
value={runs.flow ?? ""}
|
||||
onValueChange={(flow) => onChange({ ...runs, flow })}
|
||||
>
|
||||
<SelectTrigger data-testid="runs-flow">
|
||||
<SelectValue placeholder="Pick a flow" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{(flows ?? []).map((row) => (
|
||||
<SelectItem key={row.flow} value={row.flow}>
|
||||
{row.flow}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<MessagePicker
|
||||
kind="chart"
|
||||
value={runs.metric ?? ""}
|
||||
label="Draws"
|
||||
testId="runs-metric"
|
||||
placeholder="Pick a metric"
|
||||
filter={(message) =>
|
||||
(message.dtype === "float" || message.dtype === "int") &&
|
||||
(!runs.flow || message.name.startsWith(`${runs.flow}.`))
|
||||
}
|
||||
onPick={(metric) => onChange({ ...runs, metric })}
|
||||
/>
|
||||
|
||||
<Segmented
|
||||
value={mode}
|
||||
options={RUN_PICKS}
|
||||
label="Which runs"
|
||||
testId="runs-pick"
|
||||
onChange={(picked) =>
|
||||
onChange({
|
||||
...runs,
|
||||
group: undefined,
|
||||
ids: undefined,
|
||||
latest: picked === "latest" ? (runs.latest ?? 3) : undefined,
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
||||
{mode === "latest" && (
|
||||
<div className="grid gap-1.5">
|
||||
<Label className="font-normal text-sm">How many</Label>
|
||||
<Input
|
||||
type="number"
|
||||
min={1}
|
||||
max={MAX_SERIES}
|
||||
value={runs.latest ?? 3}
|
||||
aria-label="How many runs"
|
||||
onChange={(event) =>
|
||||
onChange({ ...runs, latest: Number(event.target.value) })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{mode === "group" && (
|
||||
<div className="grid gap-1.5">
|
||||
<Label className="font-normal text-sm">Sweep</Label>
|
||||
<Input
|
||||
value={runs.group ?? ""}
|
||||
placeholder="A sweep's group id"
|
||||
onChange={(event) =>
|
||||
onChange({ ...runs, group: event.target.value })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{mode === "ids" && (
|
||||
<div className="grid gap-1.5">
|
||||
<Label className="font-normal text-sm">Runs</Label>
|
||||
<Input
|
||||
value={(runs.ids ?? []).join(",")}
|
||||
placeholder="Run ids, comma separated"
|
||||
onChange={(event) =>
|
||||
onChange({
|
||||
...runs,
|
||||
ids: event.target.value
|
||||
.split(",")
|
||||
.map((one) => one.trim())
|
||||
.filter(Boolean),
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid gap-1.5">
|
||||
<Label className="font-normal text-sm">Refresh (seconds)</Label>
|
||||
<Input
|
||||
type="number"
|
||||
min={15}
|
||||
value={Number(refreshS) || 60}
|
||||
aria-label="Refresh seconds"
|
||||
onChange={(event) => onRefresh(Number(event.target.value))}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/** Where a chart's lines come from: what the engine kept, or what it asks for. */
|
||||
const CHART_SOURCES = [
|
||||
["live", "Live"],
|
||||
["query", "Query"],
|
||||
["runs", "Runs"],
|
||||
] as const
|
||||
|
||||
/**
|
||||
@@ -278,6 +427,7 @@ export function WidgetPanel({
|
||||
onChange({ config: { ...cfg, ...changes } })
|
||||
|
||||
const series = seriesOf(widget)
|
||||
const runsPick = (cfg.runs ?? {}) as RunsPick
|
||||
const setSeries = (next: Series[]) => set({ series: next })
|
||||
// A bar's readings. An empty row stands in for none, so a fresh bar offers
|
||||
// the picker rather than only a button.
|
||||
@@ -305,6 +455,7 @@ export function WidgetPanel({
|
||||
const options = (cfg.options ?? []) as { label?: string; value?: unknown }[]
|
||||
const setOptions = (next: typeof options) => set({ options: next })
|
||||
const querying = cfg.source === "query"
|
||||
const pinnedRuns = cfg.source === "runs"
|
||||
// What this chart would refresh at with nothing configured. The viewer can
|
||||
// pick another window on the widget, which moves it.
|
||||
const paced = refreshFor(
|
||||
@@ -375,13 +526,20 @@ export function WidgetPanel({
|
||||
) : widget.type === "chart" ? (
|
||||
<div className="grid gap-3">
|
||||
<Segmented
|
||||
value={querying ? "query" : "live"}
|
||||
value={pinnedRuns ? "runs" : querying ? "query" : "live"}
|
||||
options={CHART_SOURCES}
|
||||
label="Where the chart's data comes from"
|
||||
testId="chart-source"
|
||||
onChange={(source) => set({ source })}
|
||||
/>
|
||||
{querying ? (
|
||||
{pinnedRuns ? (
|
||||
<RunsSourceFields
|
||||
runs={runsPick}
|
||||
refreshS={cfg.refresh_s}
|
||||
onChange={(runs) => set({ runs })}
|
||||
onRefresh={(refresh_s) => set({ refresh_s })}
|
||||
/>
|
||||
) : querying ? (
|
||||
<div className="grid gap-2">
|
||||
<MessagePicker
|
||||
kind="chart"
|
||||
|
||||
Reference in New Issue
Block a user