( groups: WorkflowGroup[], beforeRow: TableRow, patch: Partial<RowData> )
| 118 | * Returns `null` when nothing changed, so callers can short-circuit. |
| 119 | */ |
| 120 | export function optimisticallyScheduleNewlyEligibleGroups( |
| 121 | groups: WorkflowGroup[], |
| 122 | beforeRow: TableRow, |
| 123 | patch: Partial<RowData> |
| 124 | ): RowExecutions | null { |
| 125 | if (groups.length === 0) return null |
| 126 | |
| 127 | const afterRow: TableRow = { |
| 128 | ...beforeRow, |
| 129 | data: { ...beforeRow.data, ...patch } as RowData, |
| 130 | } |
| 131 | const patchedColumns = new Set(Object.keys(patch)) |
| 132 | |
| 133 | let next: RowExecutions | null = null |
| 134 | let flipped = 0 |
| 135 | let skipped = 0 |
| 136 | for (const group of groups) { |
| 137 | if (group.autoRun === false) { |
| 138 | skipped++ |
| 139 | continue |
| 140 | } |
| 141 | if (!areGroupDepsSatisfied(group, afterRow)) { |
| 142 | skipped++ |
| 143 | continue |
| 144 | } |
| 145 | |
| 146 | const exec = beforeRow.executions?.[group.id] |
| 147 | if (exec?.status === 'pending' && exec.jobId) { |
| 148 | skipped++ |
| 149 | continue |
| 150 | } |
| 151 | |
| 152 | const isStaleCompleted = exec?.status === 'completed' && !areOutputsFilled(group, afterRow) |
| 153 | const wasSatisfied = areGroupDepsSatisfied(group, beforeRow) |
| 154 | const becameSatisfied = !wasSatisfied |
| 155 | const isRetryable = exec?.status === 'cancelled' || exec?.status === 'error' |
| 156 | // Dep-column touched: the server clears terminal entries + cancels in- |
| 157 | // flight downstream groups, so optimistically flip to `pending` |
| 158 | // regardless of current exec status (queued/running included — they're |
| 159 | // about to be cancelled and re-run). |
| 160 | const depTouched = (group.dependencies?.columns ?? []).some((d) => patchedColumns.has(d)) |
| 161 | |
| 162 | if (!depTouched && (exec?.status === 'queued' || exec?.status === 'running')) { |
| 163 | skipped++ |
| 164 | continue |
| 165 | } |
| 166 | if (!becameSatisfied && !isStaleCompleted && !isRetryable && !depTouched && exec) { |
| 167 | skipped++ |
| 168 | continue |
| 169 | } |
| 170 | |
| 171 | flipped++ |
| 172 | if (next === null) next = { ...(beforeRow.executions ?? {}) } |
| 173 | const pending: RowExecutionMetadata = { |
| 174 | status: 'pending', |
| 175 | executionId: exec?.executionId ?? null, |
| 176 | jobId: null, |
| 177 | workflowId: exec?.workflowId ?? group.workflowId, |
no test coverage detected