|
1 | 1 | import { db } from '@sim/db' |
2 | | -import { pausedExecutions, workflowExecutionLogs } from '@sim/db/schema' |
| 2 | +import { pausedExecutions, resumeQueue, workflowExecutionLogs } from '@sim/db/schema' |
3 | 3 | import { and, eq } from 'drizzle-orm' |
4 | 4 | import type { WorkflowExecutionStatusResponse } from '@/lib/api/contracts/workflows' |
5 | 5 | import { getJobQueue } from '@/lib/core/async-jobs' |
| 6 | +import type { Job } from '@/lib/core/async-jobs/types' |
6 | 7 | import { |
7 | 8 | collectFunctionalBlockOutputs, |
8 | 9 | type FunctionalExecutionDataSource, |
@@ -86,6 +87,38 @@ function extractError(executionData: unknown): string | null { |
86 | 87 | return null |
87 | 88 | } |
88 | 89 |
|
| 90 | +function extractJobFinalOutput(output: unknown): unknown | null { |
| 91 | + if (!output || typeof output !== 'object' || !('output' in output)) return null |
| 92 | + return (output as Record<string, unknown>).output ?? null |
| 93 | +} |
| 94 | + |
| 95 | +function projectQueueJob( |
| 96 | + job: Job, |
| 97 | + input: Pick<GetWorkflowExecutionStatusInput, 'executionId' | 'includeOutput' | 'workflowId'> |
| 98 | +): WorkflowExecutionStatusResponse { |
| 99 | + const status: WorkflowExecutionStatusResponse['status'] = |
| 100 | + job.status === 'pending' ? 'queued' : job.status === 'processing' ? 'running' : job.status |
| 101 | + const startedAt = job.startedAt ?? job.createdAt |
| 102 | + const endedAt = job.completedAt ?? null |
| 103 | + |
| 104 | + return { |
| 105 | + executionId: input.executionId, |
| 106 | + workflowId: input.workflowId, |
| 107 | + status, |
| 108 | + trigger: job.metadata.correlation?.triggerType ?? 'api', |
| 109 | + level: status === 'failed' ? 'error' : 'info', |
| 110 | + startedAt: startedAt.toISOString(), |
| 111 | + endedAt: endedAt?.toISOString() ?? null, |
| 112 | + totalDurationMs: endedAt ? endedAt.getTime() - startedAt.getTime() : null, |
| 113 | + paused: null, |
| 114 | + cost: null, |
| 115 | + error: status === 'failed' ? (job.error ?? 'Execution failed') : null, |
| 116 | + finalOutput: |
| 117 | + input.includeOutput && status === 'completed' ? extractJobFinalOutput(job.output) : null, |
| 118 | + blockOutputs: null, |
| 119 | + } |
| 120 | +} |
| 121 | + |
89 | 122 | export interface GetWorkflowExecutionStatusInput { |
90 | 123 | workflowId: string |
91 | 124 | executionId: string |
@@ -121,42 +154,34 @@ export async function getWorkflowExecutionStatus( |
121 | 154 | ) |
122 | 155 | .limit(1) |
123 | 156 |
|
124 | | - if (!logRow) { |
125 | | - const jobQueue = await getJobQueue() |
126 | | - const jobIds = [ |
127 | | - `${WORKFLOW_EXECUTION_JOB_ID_PREFIX}${executionId}`, |
128 | | - `${RESUME_EXECUTION_JOB_ID_PREFIX}${executionId}`, |
129 | | - ] |
| 157 | + const [activeResume] = await db |
| 158 | + .select({ id: resumeQueue.id }) |
| 159 | + .from(resumeQueue) |
| 160 | + .where( |
| 161 | + and( |
| 162 | + eq(resumeQueue.parentExecutionId, executionId), |
| 163 | + eq(resumeQueue.newExecutionId, executionId), |
| 164 | + eq(resumeQueue.status, 'claimed') |
| 165 | + ) |
| 166 | + ) |
| 167 | + .limit(1) |
| 168 | + |
| 169 | + const queueJobIds = [ |
| 170 | + ...(activeResume ? [`${RESUME_EXECUTION_JOB_ID_PREFIX}${activeResume.id}`] : []), |
| 171 | + ...(!logRow ? [`${WORKFLOW_EXECUTION_JOB_ID_PREFIX}${executionId}`] : []), |
| 172 | + ] |
130 | 173 |
|
131 | | - for (const jobId of jobIds) { |
| 174 | + if (queueJobIds.length > 0) { |
| 175 | + const jobQueue = await getJobQueue() |
| 176 | + for (const jobId of queueJobIds) { |
132 | 177 | const job = await jobQueue.getJob(jobId) |
133 | 178 | if (!job || job.metadata.workflowId !== workflowId) continue |
134 | | - |
135 | | - const status: WorkflowExecutionStatusResponse['status'] = |
136 | | - job.status === 'pending' ? 'queued' : job.status === 'processing' ? 'running' : job.status |
137 | | - const startedAt = job.startedAt ?? job.createdAt |
138 | | - const endedAt = job.completedAt ?? null |
139 | | - |
140 | | - return { |
141 | | - executionId, |
142 | | - workflowId, |
143 | | - status, |
144 | | - trigger: job.metadata.correlation?.triggerType ?? 'api', |
145 | | - level: status === 'failed' ? 'error' : 'info', |
146 | | - startedAt: startedAt.toISOString(), |
147 | | - endedAt: endedAt?.toISOString() ?? null, |
148 | | - totalDurationMs: endedAt ? endedAt.getTime() - startedAt.getTime() : null, |
149 | | - paused: null, |
150 | | - cost: null, |
151 | | - error: status === 'failed' ? (job.error ?? 'Execution failed') : null, |
152 | | - finalOutput: null, |
153 | | - blockOutputs: null, |
154 | | - } |
| 179 | + return projectQueueJob(job, { executionId, includeOutput, workflowId }) |
155 | 180 | } |
156 | | - |
157 | | - return null |
158 | 181 | } |
159 | 182 |
|
| 183 | + if (!logRow) return null |
| 184 | + |
160 | 185 | const [pausedRow] = await db |
161 | 186 | .select({ |
162 | 187 | id: pausedExecutions.id, |
|
0 commit comments