feat(dashboard): surface per-attempt job runs and frozen progress - #225
Conversation
Foreman keeps outcome per attempt: job.result holds only the winning attempt's payload, and current_run is non-null exclusively while an attempt holds the lease. So a failed attempt's error, result, and progress had nowhere to surface — including the partial-failure payloads handlers now attach to a failure. Add an Attempts card to the job details page, fetching /jobs/:id/runs and rendering one collapsible row per attempt with its worker, duration, progress, error, and result. Anything that didn't succeed starts expanded, as does the latest attempt; toggles are keyed by run id so a new attempt landing over SSE doesn't reopen what was collapsed. Switch the jobs list and the details progress/worker fields onto last_run, so pending-after-retry and terminal rows keep their final reading instead of showing an em dash. This makes progressBarClass's pending and terminal branches reachable for the first time — they were dead code, written against a last-run fallback that current_run never provided. Moved to lib/job-stream so both views share it. Requires foreman's include=last_run (Gaucho-Racing/Foreman#1).
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 366bdf7c72
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // into its row so the table renders progress without a second | ||
| // fetch per row — and unlike current_run it stays populated after | ||
| // the attempt finishes, so pending and terminal rows keep theirs. | ||
| params.set("include", "last_run"); |
There was a problem hiding this comment.
Preserve current-run progress until Foreman is upgraded
In the checked docker-compose.yaml configuration, Foreman remains pinned to 2.0.2, whose repository-side client only supports include=current_run; the old server therefore returns bare jobs for this new value. Running the dashboard against the committed local stack makes job.last_run undefined and removes progress from every row, including active jobs. Upgrade the pinned Foreman deployment in lockstep or retain a compatible fallback before switching this query.
Useful? React with 👍 / 👎.
| useEffect(() => { | ||
| void fetchRuns(); | ||
| }, [fetchRuns, attemptCount, status]); |
There was a problem hiding this comment.
Refresh the active attempt when heartbeat progress changes
While an attempt is running, heartbeat SSE events can change its progress and message without changing either attemptCount or status, so this effect never refetches and the expanded attempt row remains at the values captured when the card first loaded. On the same page the main progress card updates from the stream, producing contradictory readings until the attempt terminates or another attempt starts; pass the streamed run/update timestamp through or otherwise refresh on heartbeat updates.
Useful? React with 👍 / 👎.
Important
Depends on Gaucho-Racing/Foreman#1. Do not deploy this before Foreman ships
include=last_run— see Deploy ordering below.JobRunsCard: an Attempts section on the job details page, fetchingGET /foreman/jobs/:id/runs, newest-first, one collapsible row per attempt with worker, duration, progress, error, and result JSONlast_runto theJobmodelcurrent_runtolast_run, so pending and terminal rows keep their final readinglast_run(lease stays oncurrent_run— only an in-flight run holds one)progressBarClassintolib/job-streamand use it in both viewsWhy
current_runfiltersstatus = 'running'server-side, and run/job transitions are atomic, so it's non-null iff the job is active. That left three gaps:errorandresultwere unreachable —job.resultis reserved for the winning attempt, so partial-failure payloads were invisible.—in the Progress column.progressBarClass'spending → grayandterminal → white (frozen final reading)branches were unreachable dead code — the only call site sits behind atotal > 0guard that required a non-nullcurrent_run. They were written expecting a last-run fallback that Foreman never had. Readinglast_runmakes them work as intended.Deploy ordering
Foreman must ship first. Old Foreman compares
includeby exact string (c.Query("include") != "current_run"), so it answersinclude=last_runwith bare jobs and no run data at all — the list's Progress column would go empty for every row, active ones included. There's no way to degrade gracefully around that from the client, so the ordering is a hard requirement.The details page does degrade gracefully (
job.last_run ?? job.current_run), since SSE still carriescurrent_runon old Foreman.Notes
JobsPage.tsx; that file was already failingnpm run checkonmain, so this fixes it rather than adding churn.Test plan
tsc --noEmitreports nothing in the touched files; ESLint clean at--max-warnings 0; Prettier cleanmainand unrelated:tscfails to resolveecharts/chrono-node, sonpm run buildis red regardless of this branch — worth annpm ci