Skip to content

Commit c1a7142

Browse files
fix(table): run counter + gutter Stop update instantly on Run
The "X running" badge, per-row gutter Stop button, and runningByRowId map stayed at zero after clicking Run until a manual refetch. useRunColumn optimistically stamped cells pending in the rows cache but never bumped the activeDispatches counter — so when the dispatcher's real pending SSE arrived, applyCell saw the cell was already in-flight (wasInFlight === isInFlight) and skipped the counter delta. The optimistic stamp ate the transition. - onMutate now bumps runningCellCount / runningByRowId by the cells it stamps, snapshotting prior run-state for rollback on error. - onSuccess seeds the dispatch into the overlay list from the response instead of invalidating activeDispatches (a refetch would reset the optimistic counter to the server's still-zero count before the dispatcher stamps).
1 parent 158fe3d commit c1a7142

1 file changed

Lines changed: 61 additions & 8 deletions

File tree

apps/sim/hooks/queries/tables.ts

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1336,10 +1336,13 @@ export function useRunColumn({ workspaceId, tableId }: RowMutationContext) {
13361336
queryClient.getQueryData<TableDefinition>(tableKeys.detail(tableId))?.schema
13371337
.workflowGroups ?? []
13381338
const groupsById = new Map(groups.map((g) => [g.id, g]))
1339+
// Tally cells flipped to pending per row so we can bump the run-state
1340+
// counter in lockstep with the optimistic cell stamps below.
1341+
const stampedByRow: Record<string, number> = {}
13391342
const snapshots = await snapshotAndMutateRows(queryClient, tableId, (r) => {
13401343
if (targetRowIds && !targetRowIds.has(r.id)) return null
13411344
const executions = r.executions ?? {}
1342-
let changed = false
1345+
let stamped = 0
13431346
const next: RowExecutions = { ...executions }
13441347
const nextData = { ...r.data }
13451348
for (const groupId of targetGroupIds) {
@@ -1367,20 +1370,70 @@ export function useRunColumn({ workspaceId, tableId }: RowMutationContext) {
13671370
if (o.columnName in nextData) nextData[o.columnName] = null
13681371
}
13691372
}
1370-
changed = true
1373+
stamped++
13711374
}
1372-
if (!changed) return null
1375+
if (stamped === 0) return null
1376+
stampedByRow[r.id] = stamped
13731377
return { ...r, data: nextData, executions: next }
13741378
})
1375-
return { snapshots }
1379+
1380+
// Bump the run-state counter to match the cells we just stamped. Without
1381+
// this the top-right "X running" badge and per-row gutter Stop button
1382+
// stay at zero until a refetch: the optimistic stamp marks the cell
1383+
// in-flight in the rows cache, so the dispatcher's real `pending` SSE
1384+
// event sees no `wasInFlight` transition and never bumps the counter.
1385+
const runStateSnapshot = queryClient.getQueryData<TableRunState>(
1386+
tableKeys.activeDispatches(tableId)
1387+
)
1388+
const totalStamped = Object.values(stampedByRow).reduce((s, n) => s + n, 0)
1389+
if (totalStamped > 0) {
1390+
queryClient.setQueryData<TableRunState>(tableKeys.activeDispatches(tableId), (prev) => {
1391+
const base = prev ?? { dispatches: [], runningCellCount: 0, runningByRowId: {} }
1392+
const nextByRow = { ...base.runningByRowId }
1393+
for (const [rid, n] of Object.entries(stampedByRow)) {
1394+
nextByRow[rid] = (nextByRow[rid] ?? 0) + n
1395+
}
1396+
return {
1397+
...base,
1398+
runningCellCount: base.runningCellCount + totalStamped,
1399+
runningByRowId: nextByRow,
1400+
}
1401+
})
1402+
}
1403+
return { snapshots, runStateSnapshot, didBumpRunState: totalStamped > 0 }
13761404
},
13771405
onError: (_err, _variables, context) => {
13781406
if (context?.snapshots) restoreCachedWorkflowCells(queryClient, context.snapshots)
1407+
// Roll back the optimistic counter bump to its pre-mutation value
1408+
// (possibly undefined, which clears the entry we created).
1409+
if (context?.didBumpRunState) {
1410+
queryClient.setQueryData(tableKeys.activeDispatches(tableId), context.runStateSnapshot)
1411+
}
13791412
},
1380-
onSuccess: () => {
1381-
// Seed the active-dispatch overlay immediately (insertDispatch ran
1382-
// server-side before responding); rows cache stays owned by SSE.
1383-
void queryClient.invalidateQueries({ queryKey: tableKeys.activeDispatches(tableId) })
1413+
onSuccess: (data, { groupIds, runMode = 'all', rowIds }) => {
1414+
// Seed the dispatch into the overlay list (drives resolveCellExec's
1415+
// queued overlay for ahead-of-cursor rows). Upsert directly from the
1416+
// response instead of refetching — a refetch would reset the
1417+
// optimistic counter to the server's still-zero count (the dispatcher
1418+
// hasn't stamped cells yet).
1419+
const dispatchId = data?.data?.dispatchId
1420+
if (!dispatchId) return
1421+
queryClient.setQueryData<TableRunState>(tableKeys.activeDispatches(tableId), (prev) => {
1422+
const base = prev ?? { dispatches: [], runningCellCount: 0, runningByRowId: {} }
1423+
if (base.dispatches.some((d) => d.id === dispatchId)) return base
1424+
const dispatch: ActiveDispatch = {
1425+
id: dispatchId,
1426+
status: 'pending',
1427+
mode: runMode,
1428+
isManualRun: true,
1429+
cursor: -1,
1430+
scope: {
1431+
groupIds,
1432+
...(rowIds && rowIds.length > 0 ? { rowIds } : {}),
1433+
},
1434+
}
1435+
return { ...base, dispatches: [...base.dispatches, dispatch] }
1436+
})
13841437
},
13851438
})
13861439
}

0 commit comments

Comments
 (0)