Skip to content

Commit b473264

Browse files
authored
fix(ui): show live run detail view immediately without config retries (#196)
## Summary Clicking a live run row previously stalled for ~6s before the live detail view appeared. `useRunConfig` would fetch `config.json` (which doesn't exist yet on the storage backend for live runs), get a 404, and then react-query would retry the default 3 times with exponential backoff before allowing the fallback branch to render `LiveRunDetailView`. This PR: - **Short-circuits the render flow**: when `useLiveRuns` returns a match, render `LiveRunDetailView` immediately — before the on-disk config/result fetches have a chance to run their retry budget. - **Disables `useRunConfig` / `useRunResult` entirely** when we already know the run is live. No point fetching a file we know doesn't exist. - **Reduces `useRunConfig` retry** from the default 3 → 1 for the fallback case. A 404 on `config.json` is the deterministic "not uploaded yet" state, not a transient error worth retrying with backoff. End result: live runs open in ~200ms (the latency of `useLiveRuns`) instead of ~6s. ## Test plan - [x] TypeScript + ESLint clean - [x] Manual: click a live run row, confirm `LiveRunDetailView` appears immediately - [x] Manual: click a completed indexed run row, confirm the normal `RunDetailPage` still loads (and still retries once on transient 404) - [x] Manual: navigate to a `runId` that doesn't exist at all — confirm the error state shows without excessive delay
1 parent 6dd7577 commit b473264

3 files changed

Lines changed: 34 additions & 17 deletions

File tree

ui/src/api/hooks/useRunConfig.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,14 @@ import { useQuery } from '@tanstack/react-query'
22
import { fetchData } from '../client'
33
import type { RunConfig } from '../types'
44

5-
export function useRunConfig(runId: string) {
5+
// useRunConfig fetches a run's config.json from the storage backend.
6+
// Pass enabled=false to skip the fetch entirely (e.g. when the caller
7+
// already knows the run is live and hasn't been uploaded yet).
8+
//
9+
// retry is intentionally low: a 404 on config.json is the deterministic
10+
// "file not uploaded yet" state, not a transient error, so multiple
11+
// retries with backoff just delay the fallback live view.
12+
export function useRunConfig(runId: string, enabled = true) {
613
return useQuery({
714
queryKey: ['run', runId, 'config'],
815
queryFn: async () => {
@@ -12,6 +19,7 @@ export function useRunConfig(runId: string) {
1219
}
1320
return data
1421
},
15-
enabled: !!runId,
22+
enabled: !!runId && enabled,
23+
retry: 1,
1624
})
1725
}

ui/src/api/hooks/useRunResult.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@ import { useQuery } from '@tanstack/react-query'
22
import { fetchData } from '../client'
33
import type { RunResult } from '../types'
44

5-
export function useRunResult(runId: string) {
5+
// useRunResult fetches a run's result.json from the storage backend.
6+
// Pass enabled=false to skip the fetch (e.g. for runs we already know
7+
// are still live and haven't been uploaded yet).
8+
export function useRunResult(runId: string, enabled = true) {
69
return useQuery({
710
queryKey: ['run', runId, 'result'],
811
queryFn: async () => {
912
const { data } = await fetchData<RunResult>(`runs/${runId}/result.json`)
1013
return data ?? null
1114
},
12-
enabled: !!runId,
15+
enabled: !!runId && enabled,
1316
})
1417
}

ui/src/pages/RunDetailPage.tsx

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -144,12 +144,17 @@ export function RunDetailPage() {
144144
const stepFilter = parseStepFilter(search.steps)
145145
const { sortBy = 'order', sortDir = 'asc', q = '', status = 'all', testModal, preRunModal, heatmapGroup, heatmapSort, ohFs = false, blFs = false, dlModal = false, dlFmt } = search
146146

147-
const { data: config, isLoading: configLoading, error: configError, refetch: refetchConfig } = useRunConfig(runId)
148-
const { data: result, isLoading: resultLoading, refetch: refetchResult } = useRunResult(runId)
147+
const { data: liveRuns, isLoading: liveRunsLoading } = useLiveRuns()
148+
const liveRun = liveRuns?.find((lr) => lr.run_id === runId)
149+
150+
// Skip fetching config/result on the on-disk backend while we know the
151+
// run is live — the files won't exist yet, and blocking the UI on three
152+
// retries of a 404'ing fetch delays the live view unnecessarily.
153+
const fetchOnDisk = !liveRun
154+
const { data: config, isLoading: configLoading, error: configError, refetch: refetchConfig } = useRunConfig(runId, fetchOnDisk)
155+
const { data: result, isLoading: resultLoading, refetch: refetchResult } = useRunResult(runId, fetchOnDisk)
149156
const { data: suite } = useSuite(config?.suite_hash ?? '')
150157
const { data: index } = useIndex()
151-
const { data: liveRuns } = useLiveRuns()
152-
const liveRun = liveRuns?.find((lr) => lr.run_id === runId)
153158
const { data: containerLogHead, isLoading: containerLogLoading } = useQuery({
154159
queryKey: ['run', runId, 'container-log-head'],
155160
queryFn: () => fetchHead(`runs/${runId}/container.log`),
@@ -162,7 +167,7 @@ export function RunDetailPage() {
162167
})
163168
const { data: blockLogs } = useBlockLogs(runId)
164169

165-
const isLoading = configLoading || resultLoading
170+
const isLoading = liveRunsLoading || configLoading || resultLoading
166171
const error = configError
167172

168173
const [compareMode, setCompareMode] = useState(false)
@@ -285,16 +290,17 @@ export function RunDetailPage() {
285290
updateSearch({ dlFmt: format !== 'curl' ? format : undefined })
286291
}
287292

288-
if (isLoading) {
289-
return <LoadingState message="Loading run details..." />
293+
// Short-circuit: if the ingest API is reporting this run as live and
294+
// we don't have a completed config.json yet, render the live view
295+
// immediately. This avoids waiting for on-disk config.json retries,
296+
// which would block the UI for several seconds while react-query
297+
// exhausts its retry budget against a 404.
298+
if (liveRun && !config) {
299+
return <LiveRunDetailView run={liveRun} />
290300
}
291301

292-
// If the run is being actively reported by a runner and its files aren't
293-
// yet on the storage backend, show the lightweight live view instead of
294-
// the error state. Once the run finishes and config.json lands, the next
295-
// refetch will drop us back into the normal detail view.
296-
if (!config && liveRun) {
297-
return <LiveRunDetailView run={liveRun} />
302+
if (isLoading) {
303+
return <LoadingState message="Loading run details..." />
298304
}
299305

300306
if (error) {

0 commit comments

Comments
 (0)