({ workspaceId, tableId }: RowMutationContext)
| 949 | * Batch update multiple rows by ID. Uses optimistic updates for instant UI feedback. |
| 950 | */ |
| 951 | export function useBatchUpdateTableRows({ workspaceId, tableId }: RowMutationContext) { |
| 952 | const queryClient = useQueryClient() |
| 953 | |
| 954 | return useMutation({ |
| 955 | mutationKey: tableKeys.rowWrites(tableId), |
| 956 | mutationFn: async ({ updates }: BatchUpdateTableRowsParams) => { |
| 957 | return requestJson(batchUpdateTableRowsContract, { |
| 958 | params: { tableId }, |
| 959 | body: { |
| 960 | workspaceId, |
| 961 | updates: updates.map((update) => ({ ...update, data: update.data as RowData })), |
| 962 | }, |
| 963 | }) |
| 964 | }, |
| 965 | onMutate: async ({ updates }) => { |
| 966 | await queryClient.cancelQueries({ queryKey: tableKeys.rowsRoot(tableId) }) |
| 967 | |
| 968 | const previousQueries = queryClient.getQueriesData< |
| 969 | InfiniteData<TableRowsResponse, TableRowsPageParam> |
| 970 | >({ |
| 971 | queryKey: tableKeys.rowsRoot(tableId), |
| 972 | }) |
| 973 | |
| 974 | const updateMap = new Map(updates.map((u) => [u.rowId, u.data])) |
| 975 | const groups = |
| 976 | queryClient.getQueryData<TableDefinition>(tableKeys.detail(tableId))?.schema |
| 977 | .workflowGroups ?? [] |
| 978 | |
| 979 | const stampedByRow: Record<string, number> = {} |
| 980 | patchCachedRows(queryClient, tableId, (row) => { |
| 981 | const raw = updateMap.get(row.id) |
| 982 | if (!raw) return row |
| 983 | const patch = raw as Partial<RowData> |
| 984 | const nextExecutions = optimisticallyScheduleNewlyEligibleGroups(groups, row, patch) |
| 985 | if (nextExecutions) { |
| 986 | stampedByRow[row.id] = countNewlyInFlight(row.executions ?? {}, nextExecutions) |
| 987 | } |
| 988 | return { |
| 989 | ...row, |
| 990 | data: { ...row.data, ...patch } as RowData, |
| 991 | ...(nextExecutions ? { executions: nextExecutions } : {}), |
| 992 | } |
| 993 | }) |
| 994 | |
| 995 | const bumped = bumpRunState(queryClient, tableId, stampedByRow) |
| 996 | return { |
| 997 | previousQueries, |
| 998 | runStateSnapshot: bumped?.snapshot, |
| 999 | didBumpRunState: bumped !== null, |
| 1000 | } |
| 1001 | }, |
| 1002 | onError: (error, _vars, context) => { |
| 1003 | if (context?.previousQueries) { |
| 1004 | for (const [queryKey, data] of context.previousQueries) { |
| 1005 | queryClient.setQueryData(queryKey, data) |
| 1006 | } |
| 1007 | } |
| 1008 | if (context?.didBumpRunState) { |
no test coverage detected