({ workspaceId, tableId }: RowMutationContext)
| 1981 | * to `pending`; refetch on settle reconciles. |
| 1982 | */ |
| 1983 | export function useRunColumn({ workspaceId, tableId }: RowMutationContext) { |
| 1984 | const queryClient = useQueryClient() |
| 1985 | |
| 1986 | return useMutation({ |
| 1987 | mutationFn: async ({ |
| 1988 | groupIds, |
| 1989 | runMode = 'all', |
| 1990 | rowIds, |
| 1991 | filter, |
| 1992 | excludeRowIds, |
| 1993 | limit, |
| 1994 | }: RunColumnVariables) => { |
| 1995 | return requestJson(runColumnContract, { |
| 1996 | params: { tableId }, |
| 1997 | body: { |
| 1998 | workspaceId, |
| 1999 | groupIds, |
| 2000 | runMode, |
| 2001 | ...(rowIds && rowIds.length > 0 ? { rowIds } : {}), |
| 2002 | ...(filter ? { filter } : {}), |
| 2003 | ...(excludeRowIds && excludeRowIds.length > 0 ? { excludeRowIds } : {}), |
| 2004 | ...(limit ? { limit } : {}), |
| 2005 | }, |
| 2006 | }) |
| 2007 | }, |
| 2008 | onMutate: async ({ groupIds, runMode = 'all', rowIds, filter, excludeRowIds, limit }) => { |
| 2009 | // Capped and filtered runs target a set we can't predict client-side (capped picks the first |
| 2010 | // N by position; filtered matches a server-evaluated predicate), so optimistic stamping is |
| 2011 | // skipped — the dispatcher's real pending stamps (cell SSE) drive the UI within the first |
| 2012 | // window. |
| 2013 | if (limit || filter) |
| 2014 | return { snapshots: undefined, runStateSnapshot: undefined, didBumpRunState: false } |
| 2015 | const targetRowIds = rowIds && rowIds.length > 0 ? new Set(rowIds) : null |
| 2016 | const excludedRowIds = |
| 2017 | excludeRowIds && excludeRowIds.length > 0 ? new Set(excludeRowIds) : null |
| 2018 | const targetGroupIds = new Set(groupIds) |
| 2019 | const groups = |
| 2020 | queryClient.getQueryData<TableDefinition>(tableKeys.detail(tableId))?.schema |
| 2021 | .workflowGroups ?? [] |
| 2022 | const groupsById = new Map(groups.map((g) => [g.id, g])) |
| 2023 | // Tally cells stamped per row to bump the run-state counter in lockstep. |
| 2024 | const stampedByRow: Record<string, number> = {} |
| 2025 | const snapshots = await snapshotAndMutateRows(queryClient, tableId, (r) => { |
| 2026 | if (targetRowIds && !targetRowIds.has(r.id)) return null |
| 2027 | if (excludedRowIds?.has(r.id)) return null |
| 2028 | const executions = r.executions ?? {} |
| 2029 | let stamped = 0 |
| 2030 | const next: RowExecutions = { ...executions } |
| 2031 | const nextData = { ...r.data } |
| 2032 | for (const groupId of targetGroupIds) { |
| 2033 | const exec = executions[groupId] as RowExecutionMetadata | undefined |
| 2034 | if (isExecInFlight(exec)) continue |
| 2035 | const group = groupsById.get(groupId) |
| 2036 | // Mirror server eligibility: rows with unmet deps are skipped by the |
| 2037 | // dispatcher regardless of mode. Stamping pending here would leave |
| 2038 | // the cell flashing Queued indefinitely (no SSE event will arrive). |
| 2039 | if (group && !areGroupDepsSatisfied(group, r)) continue |
| 2040 | // Mirror server eligibility for manual `mode: 'incomplete'`: a |
no test coverage detected