diff --git a/backend/fluksio/api/routes/runs.py b/backend/fluksio/api/routes/runs.py index 54bb5e4..c2dc375 100644 --- a/backend/fluksio/api/routes/runs.py +++ b/backend/fluksio/api/routes/runs.py @@ -82,6 +82,9 @@ class RunRow(BaseModel): #: decorators. Empty for one drawn on the canvas, where `commit` is the #: whole answer to what produced the number. origin_commit: str = "" + #: The flow store's own commit. Short, unlike `result`, so the list + #: carries it: "what code produced this" is a question asked of a table. + commit: str = "" seed: int | None group_id: str | None labels: list[str] @@ -94,7 +97,6 @@ class RunRow(BaseModel): class RunDetail(RunRow): result: dict[str, Any] = Field(default_factory=dict) - commit: str = "" flow_version: int = 1 nodes: list[RunNodeRow] = Field(default_factory=list) artifacts: list[ArtifactRow] = Field(default_factory=list) diff --git a/frontend/scripts/capture-screenshots.mjs b/frontend/scripts/capture-screenshots.mjs index 7b08326..d15208a 100644 --- a/frontend/scripts/capture-screenshots.mjs +++ b/frontend/scripts/capture-screenshots.mjs @@ -89,16 +89,73 @@ for (const theme of ["light", "dark"]) { await captureFlows(page, dir) await captureDashboards(page, dir) + await captureRuns(page, dir) await context.close() console.log( - ` wrote ${dir}/{website-hero,app-login,app-dashboard,app-flows,app-flow-panel,app-panel}.png`, + ` wrote ${dir}/{website-hero,app-login,app-dashboard,app-flows,app-flow-panel,app-panel,app-runs,app-run,app-run-context}.png`, ) } } await browser.close() +/** + * The experiment log: the table, one run in full, and a dashboard read against + * the runs someone picked. Skipped on an instance that has never run anything, + * where all three would photograph the same empty state. + */ +async function captureRuns(page, dir) { + await page.goto(`${APP_URL}/runs`, { waitUntil: "networkidle" }) + await page.waitForTimeout(1000) + await page.screenshot({ path: `${dir}/app-runs.png` }) + + const rows = page.getByTestId("run-row") + if (!(await rows.count())) return + + // Two runs compared, which is the whole point of the screen. + const boxes = page.getByTestId("run-select") + await boxes.nth(0).click() + if ((await boxes.count()) > 1) await boxes.nth(1).click() + await page.waitForTimeout(1500) + await page.screenshot({ path: `${dir}/app-runs-compare.png`, fullPage: true }) + + // A dashboard read against those two runs: the same page a live run is + // watched on, showing finished ones. This is the seam the feature exists for. + const picked = await page + .getByTestId("run-link") + .evaluateAll((links) => links.slice(0, 2).map((a) => a.getAttribute("href"))) + const ids = picked + .map((href) => (href || "").split("/").pop()) + .filter(Boolean) + // The API is its own host here; the SPA's origin does not proxy /api. + const apiUrl = APP_URL.replace("//app.", "//api.") + const results = await page.evaluate(async (base) => { + const answer = await fetch(`${base}/api/v1/dashboards/`, { + headers: { Authorization: `Bearer ${localStorage.getItem("access_token")}` }, + }) + if (!answer.ok) return null + const body = await answer.json() + return (body.data || []) + .map((one) => one.name) + .find((n) => n.endsWith("_results")) + }, apiUrl) + if (results && ids.length) { + await page.goto(`${APP_URL}/view/${results}?runs=${ids.join(",")}`, { + waitUntil: "networkidle", + }) + await page.waitForTimeout(2000) + await page.screenshot({ path: `${dir}/app-run-context.png` }) + } + + await page.goto(`${APP_URL}/runs`, { waitUntil: "networkidle" }) + await page.getByTestId("run-link").first().click() + await page.waitForURL(/\/runs\/.+/, { timeout: 15000 }) + await page.waitForLoadState("networkidle") + await page.waitForTimeout(1500) + await page.screenshot({ path: `${dir}/app-run.png`, fullPage: true }) +} + /** * A dashboard as a wall panel sees it. Seeds one if the instance has none, so * the shot shows the grid rather than an empty-state message. diff --git a/frontend/src/client/schemas.gen.ts b/frontend/src/client/schemas.gen.ts index 8c1f6c9..aae31b8 100644 --- a/frontend/src/client/schemas.gen.ts +++ b/frontend/src/client/schemas.gen.ts @@ -2491,6 +2491,11 @@ export const RunDetailSchema = { title: 'Origin Commit', default: '' }, + commit: { + type: 'string', + title: 'Commit', + default: '' + }, seed: { anyOf: [ { @@ -2542,11 +2547,6 @@ export const RunDetailSchema = { type: 'object', title: 'Result' }, - commit: { - type: 'string', - title: 'Commit', - default: '' - }, flow_version: { type: 'integer', title: 'Flow Version', @@ -3619,6 +3619,11 @@ export const fluksio__api__routes__runs__RunRowSchema = { title: 'Origin Commit', default: '' }, + commit: { + type: 'string', + title: 'Commit', + default: '' + }, seed: { anyOf: [ { diff --git a/frontend/src/client/types.gen.ts b/frontend/src/client/types.gen.ts index 5a630b3..208dfb6 100644 --- a/frontend/src/client/types.gen.ts +++ b/frontend/src/client/types.gen.ts @@ -426,6 +426,7 @@ export type fluksio__api__routes__runs__RunRow = { }; params_digest: string; origin_commit?: string; + commit?: string; seed: (number | null); group_id: (string | null); labels: Array<(string)>; @@ -898,6 +899,7 @@ export type RunDetail = { }; params_digest: string; origin_commit?: string; + commit?: string; seed: (number | null); group_id: (string | null); labels: Array<(string)>; @@ -909,7 +911,6 @@ export type RunDetail = { result?: { [key: string]: unknown; }; - commit?: string; flow_version?: number; nodes?: Array; artifacts?: Array; diff --git a/frontend/src/components/Runs/MetricChart.tsx b/frontend/src/components/Runs/MetricChart.tsx index 1bf71df..568ba02 100644 --- a/frontend/src/components/Runs/MetricChart.tsx +++ b/frontend/src/components/Runs/MetricChart.tsx @@ -53,7 +53,7 @@ export function RunMetricChart({ const drawn = plots.reduce((total, plot) => total + plot.length, 0) return ( -
+
)} {runs.map((run) => ( - + onToggle(run.id)} aria-label={`Compare ${run.id}`} + data-testid="run-select" /> @@ -303,6 +304,7 @@ function RunsTable({ to="/runs/$id" params={{ id: run.id }} className="font-mono text-sm hover:underline" + data-testid="run-link" > {shortId(run.id)} @@ -342,8 +344,9 @@ function RunsTable({ {/* The user's own repository when there is one: for a flow declared in code, the store's commit names a generated - shim rather than anything anyone wrote. */} - {shortCommit(run.origin_commit ?? "") || "—"} + shim rather than anything anyone wrote. A canvas flow has + only the store's, which is then the whole answer. */} + {shortCommit(run.origin_commit || run.commit || "") || "—"} {run.duration_ms ? dur(run.duration_ms) : "—"}