@xtr-dev/payload-automation 0.0.21 → 0.0.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +73 -2
  2. package/dist/collections/Workflow.js +19 -7
  3. package/dist/collections/Workflow.js.map +1 -1
  4. package/dist/collections/WorkflowRuns.js +14 -7
  5. package/dist/collections/WorkflowRuns.js.map +1 -1
  6. package/dist/components/StatusCell.d.ts +6 -0
  7. package/dist/components/StatusCell.js +75 -0
  8. package/dist/components/StatusCell.js.map +1 -0
  9. package/dist/components/WorkflowExecutionStatus.d.ts +6 -0
  10. package/dist/components/WorkflowExecutionStatus.js +287 -0
  11. package/dist/components/WorkflowExecutionStatus.js.map +1 -0
  12. package/dist/core/workflow-executor.d.ts +32 -0
  13. package/dist/core/workflow-executor.js +162 -7
  14. package/dist/core/workflow-executor.js.map +1 -1
  15. package/dist/exports/client.d.ts +2 -0
  16. package/dist/exports/client.js +4 -1
  17. package/dist/exports/client.js.map +1 -1
  18. package/dist/plugin/index.js +131 -42
  19. package/dist/plugin/index.js.map +1 -1
  20. package/dist/plugin/init-step-tasks.js +15 -1
  21. package/dist/plugin/init-step-tasks.js.map +1 -1
  22. package/dist/steps/create-document.js +1 -1
  23. package/dist/steps/create-document.js.map +1 -1
  24. package/dist/steps/delete-document.js +2 -2
  25. package/dist/steps/delete-document.js.map +1 -1
  26. package/dist/steps/http-request-handler.js +229 -10
  27. package/dist/steps/http-request-handler.js.map +1 -1
  28. package/dist/steps/http-request.d.ts +147 -4
  29. package/dist/steps/http-request.js +189 -3
  30. package/dist/steps/http-request.js.map +1 -1
  31. package/dist/steps/read-document.js +2 -2
  32. package/dist/steps/read-document.js.map +1 -1
  33. package/dist/steps/send-email.js +5 -5
  34. package/dist/steps/send-email.js.map +1 -1
  35. package/dist/steps/update-document.js +2 -2
  36. package/dist/steps/update-document.js.map +1 -1
  37. package/dist/test/create-document-step.test.js +378 -0
  38. package/dist/test/create-document-step.test.js.map +1 -0
  39. package/dist/test/http-request-step.test.js +361 -0
  40. package/dist/test/http-request-step.test.js.map +1 -0
  41. package/dist/test/workflow-executor.test.js +530 -0
  42. package/dist/test/workflow-executor.test.js.map +1 -0
  43. package/package.json +4 -1
  44. package/dist/test/basic.test.d.ts +0 -1
@@ -0,0 +1,287 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import React, { useState, useEffect } from 'react';
4
+ import { Button } from '@payloadcms/ui';
5
+ export const WorkflowExecutionStatus = ({ workflowId })=>{
6
+ const [runs, setRuns] = useState([]);
7
+ const [loading, setLoading] = useState(true);
8
+ const [expanded, setExpanded] = useState(false);
9
+ useEffect(()=>{
10
+ const fetchRecentRuns = async ()=>{
11
+ try {
12
+ const response = await fetch(`/api/workflow-runs?where[workflow][equals]=${workflowId}&limit=5&sort=-startedAt`);
13
+ if (response.ok) {
14
+ const data = await response.json();
15
+ setRuns(data.docs || []);
16
+ }
17
+ } catch (error) {
18
+ console.warn('Failed to fetch workflow runs:', error);
19
+ } finally{
20
+ setLoading(false);
21
+ }
22
+ };
23
+ fetchRecentRuns();
24
+ }, [
25
+ workflowId
26
+ ]);
27
+ if (loading) {
28
+ return /*#__PURE__*/ _jsx("div", {
29
+ style: {
30
+ padding: '16px',
31
+ color: '#6B7280'
32
+ },
33
+ children: "Loading execution history..."
34
+ });
35
+ }
36
+ if (runs.length === 0) {
37
+ return /*#__PURE__*/ _jsxs("div", {
38
+ style: {
39
+ padding: '16px',
40
+ backgroundColor: '#F9FAFB',
41
+ border: '1px solid #E5E7EB',
42
+ borderRadius: '8px',
43
+ color: '#6B7280',
44
+ textAlign: 'center'
45
+ },
46
+ children: [
47
+ "📋 No execution history yet",
48
+ /*#__PURE__*/ _jsx("br", {}),
49
+ /*#__PURE__*/ _jsx("small", {
50
+ children: "This workflow hasn't been triggered yet."
51
+ })
52
+ ]
53
+ });
54
+ }
55
+ const getStatusIcon = (status)=>{
56
+ switch(status){
57
+ case 'pending':
58
+ return 'âŗ';
59
+ case 'running':
60
+ return '🔄';
61
+ case 'completed':
62
+ return '✅';
63
+ case 'failed':
64
+ return '❌';
65
+ case 'cancelled':
66
+ return 'âšī¸';
67
+ default:
68
+ return '❓';
69
+ }
70
+ };
71
+ const getStatusColor = (status)=>{
72
+ switch(status){
73
+ case 'pending':
74
+ return '#6B7280';
75
+ case 'running':
76
+ return '#3B82F6';
77
+ case 'completed':
78
+ return '#10B981';
79
+ case 'failed':
80
+ return '#EF4444';
81
+ case 'cancelled':
82
+ return '#F59E0B';
83
+ default:
84
+ return '#6B7280';
85
+ }
86
+ };
87
+ const formatDate = (dateString)=>{
88
+ const date = new Date(dateString);
89
+ const now = new Date();
90
+ const diffMs = now.getTime() - date.getTime();
91
+ if (diffMs < 60000) {
92
+ return 'Just now';
93
+ } else if (diffMs < 3600000) {
94
+ return `${Math.floor(diffMs / 60000)} min ago`;
95
+ } else if (diffMs < 86400000) {
96
+ return `${Math.floor(diffMs / 3600000)} hrs ago`;
97
+ } else {
98
+ return date.toLocaleDateString();
99
+ }
100
+ };
101
+ const getDuration = (startedAt, completedAt)=>{
102
+ const start = new Date(startedAt);
103
+ const end = completedAt ? new Date(completedAt) : new Date();
104
+ const diffMs = end.getTime() - start.getTime();
105
+ if (diffMs < 1000) return '<1s';
106
+ if (diffMs < 60000) return `${Math.floor(diffMs / 1000)}s`;
107
+ if (diffMs < 3600000) return `${Math.floor(diffMs / 60000)}m ${Math.floor(diffMs % 60000 / 1000)}s`;
108
+ return `${Math.floor(diffMs / 3600000)}h ${Math.floor(diffMs % 3600000 / 60000)}m`;
109
+ };
110
+ const recentRun = runs[0];
111
+ const recentStatus = getStatusIcon(recentRun.status);
112
+ const recentColor = getStatusColor(recentRun.status);
113
+ return /*#__PURE__*/ _jsxs("div", {
114
+ style: {
115
+ border: '1px solid #E5E7EB',
116
+ borderRadius: '8px',
117
+ backgroundColor: '#FAFAFA'
118
+ },
119
+ children: [
120
+ /*#__PURE__*/ _jsxs("div", {
121
+ style: {
122
+ padding: '16px',
123
+ borderBottom: expanded ? '1px solid #E5E7EB' : 'none',
124
+ display: 'flex',
125
+ justifyContent: 'space-between',
126
+ alignItems: 'center'
127
+ },
128
+ children: [
129
+ /*#__PURE__*/ _jsxs("div", {
130
+ style: {
131
+ display: 'flex',
132
+ alignItems: 'center',
133
+ gap: '12px'
134
+ },
135
+ children: [
136
+ /*#__PURE__*/ _jsx("span", {
137
+ style: {
138
+ fontSize: '20px'
139
+ },
140
+ children: recentStatus
141
+ }),
142
+ /*#__PURE__*/ _jsxs("div", {
143
+ children: [
144
+ /*#__PURE__*/ _jsxs("div", {
145
+ style: {
146
+ fontWeight: '600',
147
+ color: recentColor
148
+ },
149
+ children: [
150
+ "Last run: ",
151
+ recentRun.status
152
+ ]
153
+ }),
154
+ /*#__PURE__*/ _jsxs("div", {
155
+ style: {
156
+ fontSize: '13px',
157
+ color: '#6B7280'
158
+ },
159
+ children: [
160
+ formatDate(recentRun.startedAt),
161
+ " â€ĸ Duration: ",
162
+ getDuration(recentRun.startedAt, recentRun.completedAt)
163
+ ]
164
+ })
165
+ ]
166
+ })
167
+ ]
168
+ }),
169
+ /*#__PURE__*/ _jsxs(Button, {
170
+ onClick: ()=>setExpanded(!expanded),
171
+ size: "small",
172
+ buttonStyle: "secondary",
173
+ children: [
174
+ expanded ? 'Hide' : 'Show',
175
+ " History (",
176
+ runs.length,
177
+ ")"
178
+ ]
179
+ })
180
+ ]
181
+ }),
182
+ expanded && /*#__PURE__*/ _jsxs("div", {
183
+ style: {
184
+ padding: '16px'
185
+ },
186
+ children: [
187
+ /*#__PURE__*/ _jsx("h4", {
188
+ style: {
189
+ margin: '0 0 12px 0',
190
+ fontSize: '14px',
191
+ fontWeight: '600'
192
+ },
193
+ children: "Recent Executions"
194
+ }),
195
+ runs.map((run, index)=>/*#__PURE__*/ _jsxs("div", {
196
+ style: {
197
+ display: 'flex',
198
+ justifyContent: 'space-between',
199
+ alignItems: 'center',
200
+ padding: '8px 12px',
201
+ marginBottom: index < runs.length - 1 ? '8px' : '0',
202
+ backgroundColor: 'white',
203
+ border: '1px solid #E5E7EB',
204
+ borderRadius: '6px'
205
+ },
206
+ children: [
207
+ /*#__PURE__*/ _jsxs("div", {
208
+ style: {
209
+ display: 'flex',
210
+ alignItems: 'center',
211
+ gap: '10px'
212
+ },
213
+ children: [
214
+ /*#__PURE__*/ _jsx("span", {
215
+ style: {
216
+ fontSize: '16px'
217
+ },
218
+ children: getStatusIcon(run.status)
219
+ }),
220
+ /*#__PURE__*/ _jsxs("div", {
221
+ children: [
222
+ /*#__PURE__*/ _jsx("div", {
223
+ style: {
224
+ fontSize: '13px',
225
+ fontWeight: '500',
226
+ color: getStatusColor(run.status)
227
+ },
228
+ children: run.status.charAt(0).toUpperCase() + run.status.slice(1)
229
+ }),
230
+ /*#__PURE__*/ _jsxs("div", {
231
+ style: {
232
+ fontSize: '12px',
233
+ color: '#6B7280'
234
+ },
235
+ children: [
236
+ formatDate(run.startedAt),
237
+ " â€ĸ ",
238
+ run.triggeredBy
239
+ ]
240
+ })
241
+ ]
242
+ })
243
+ ]
244
+ }),
245
+ /*#__PURE__*/ _jsxs("div", {
246
+ style: {
247
+ fontSize: '12px',
248
+ color: '#6B7280',
249
+ textAlign: 'right'
250
+ },
251
+ children: [
252
+ /*#__PURE__*/ _jsx("div", {
253
+ children: getDuration(run.startedAt, run.completedAt)
254
+ }),
255
+ run.error && /*#__PURE__*/ _jsx("div", {
256
+ style: {
257
+ color: '#EF4444',
258
+ marginTop: '2px'
259
+ },
260
+ children: "Error"
261
+ })
262
+ ]
263
+ })
264
+ ]
265
+ }, run.id)),
266
+ /*#__PURE__*/ _jsx("div", {
267
+ style: {
268
+ marginTop: '12px',
269
+ textAlign: 'center'
270
+ },
271
+ children: /*#__PURE__*/ _jsx(Button, {
272
+ onClick: ()=>{
273
+ // Navigate to workflow runs filtered by this workflow
274
+ window.location.href = `/admin/collections/workflow-runs?where[workflow][equals]=${workflowId}`;
275
+ },
276
+ size: "small",
277
+ buttonStyle: "secondary",
278
+ children: "View All Runs →"
279
+ })
280
+ })
281
+ ]
282
+ })
283
+ ]
284
+ });
285
+ };
286
+
287
+ //# sourceMappingURL=WorkflowExecutionStatus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/WorkflowExecutionStatus.tsx"],"sourcesContent":["'use client'\n\nimport React, { useState, useEffect } from 'react'\nimport { Button } from '@payloadcms/ui'\n\ninterface WorkflowRun {\n id: string\n status: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled'\n startedAt: string\n completedAt?: string\n error?: string\n triggeredBy: string\n}\n\ninterface WorkflowExecutionStatusProps {\n workflowId: string | number\n}\n\nexport const WorkflowExecutionStatus: React.FC<WorkflowExecutionStatusProps> = ({ workflowId }) => {\n const [runs, setRuns] = useState<WorkflowRun[]>([])\n const [loading, setLoading] = useState(true)\n const [expanded, setExpanded] = useState(false)\n\n useEffect(() => {\n const fetchRecentRuns = async () => {\n try {\n const response = await fetch(`/api/workflow-runs?where[workflow][equals]=${workflowId}&limit=5&sort=-startedAt`)\n if (response.ok) {\n const data = await response.json()\n setRuns(data.docs || [])\n }\n } catch (error) {\n console.warn('Failed to fetch workflow runs:', error)\n } finally {\n setLoading(false)\n }\n }\n\n fetchRecentRuns()\n }, [workflowId])\n\n if (loading) {\n return (\n <div style={{ padding: '16px', color: '#6B7280' }}>\n Loading execution history...\n </div>\n )\n }\n\n if (runs.length === 0) {\n return (\n <div style={{ \n padding: '16px',\n backgroundColor: '#F9FAFB',\n border: '1px solid #E5E7EB',\n borderRadius: '8px',\n color: '#6B7280',\n textAlign: 'center'\n }}>\n 📋 No execution history yet\n <br />\n <small>This workflow hasn't been triggered yet.</small>\n </div>\n )\n }\n\n const getStatusIcon = (status: string) => {\n switch (status) {\n case 'pending': return 'âŗ'\n case 'running': return '🔄'\n case 'completed': return '✅'\n case 'failed': return '❌'\n case 'cancelled': return 'âšī¸'\n default: return '❓'\n }\n }\n\n const getStatusColor = (status: string) => {\n switch (status) {\n case 'pending': return '#6B7280'\n case 'running': return '#3B82F6'\n case 'completed': return '#10B981'\n case 'failed': return '#EF4444'\n case 'cancelled': return '#F59E0B'\n default: return '#6B7280'\n }\n }\n\n const formatDate = (dateString: string) => {\n const date = new Date(dateString)\n const now = new Date()\n const diffMs = now.getTime() - date.getTime()\n \n if (diffMs < 60000) { // Less than 1 minute\n return 'Just now'\n } else if (diffMs < 3600000) { // Less than 1 hour\n return `${Math.floor(diffMs / 60000)} min ago`\n } else if (diffMs < 86400000) { // Less than 1 day\n return `${Math.floor(diffMs / 3600000)} hrs ago`\n } else {\n return date.toLocaleDateString()\n }\n }\n\n const getDuration = (startedAt: string, completedAt?: string) => {\n const start = new Date(startedAt)\n const end = completedAt ? new Date(completedAt) : new Date()\n const diffMs = end.getTime() - start.getTime()\n \n if (diffMs < 1000) return '<1s'\n if (diffMs < 60000) return `${Math.floor(diffMs / 1000)}s`\n if (diffMs < 3600000) return `${Math.floor(diffMs / 60000)}m ${Math.floor((diffMs % 60000) / 1000)}s`\n return `${Math.floor(diffMs / 3600000)}h ${Math.floor((diffMs % 3600000) / 60000)}m`\n }\n\n const recentRun = runs[0]\n const recentStatus = getStatusIcon(recentRun.status)\n const recentColor = getStatusColor(recentRun.status)\n\n return (\n <div style={{\n border: '1px solid #E5E7EB',\n borderRadius: '8px',\n backgroundColor: '#FAFAFA'\n }}>\n {/* Summary Header */}\n <div style={{\n padding: '16px',\n borderBottom: expanded ? '1px solid #E5E7EB' : 'none',\n display: 'flex',\n justifyContent: 'space-between',\n alignItems: 'center'\n }}>\n <div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>\n <span style={{ fontSize: '20px' }}>{recentStatus}</span>\n <div>\n <div style={{ fontWeight: '600', color: recentColor }}>\n Last run: {recentRun.status}\n </div>\n <div style={{ fontSize: '13px', color: '#6B7280' }}>\n {formatDate(recentRun.startedAt)} â€ĸ Duration: {getDuration(recentRun.startedAt, recentRun.completedAt)}\n </div>\n </div>\n </div>\n \n <Button\n onClick={() => setExpanded(!expanded)}\n size=\"small\"\n buttonStyle=\"secondary\"\n >\n {expanded ? 'Hide' : 'Show'} History ({runs.length})\n </Button>\n </div>\n\n {/* Detailed History */}\n {expanded && (\n <div style={{ padding: '16px' }}>\n <h4 style={{ margin: '0 0 12px 0', fontSize: '14px', fontWeight: '600' }}>\n Recent Executions\n </h4>\n \n {runs.map((run, index) => (\n <div\n key={run.id}\n style={{\n display: 'flex',\n justifyContent: 'space-between',\n alignItems: 'center',\n padding: '8px 12px',\n marginBottom: index < runs.length - 1 ? '8px' : '0',\n backgroundColor: 'white',\n border: '1px solid #E5E7EB',\n borderRadius: '6px'\n }}\n >\n <div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>\n <span style={{ fontSize: '16px' }}>\n {getStatusIcon(run.status)}\n </span>\n \n <div>\n <div style={{ \n fontSize: '13px', \n fontWeight: '500',\n color: getStatusColor(run.status)\n }}>\n {run.status.charAt(0).toUpperCase() + run.status.slice(1)}\n </div>\n <div style={{ fontSize: '12px', color: '#6B7280' }}>\n {formatDate(run.startedAt)} â€ĸ {run.triggeredBy}\n </div>\n </div>\n </div>\n \n <div style={{ \n fontSize: '12px', \n color: '#6B7280',\n textAlign: 'right'\n }}>\n <div>\n {getDuration(run.startedAt, run.completedAt)}\n </div>\n {run.error && (\n <div style={{ color: '#EF4444', marginTop: '2px' }}>\n Error\n </div>\n )}\n </div>\n </div>\n ))}\n \n <div style={{ \n marginTop: '12px', \n textAlign: 'center' \n }}>\n <Button\n onClick={() => {\n // Navigate to workflow runs filtered by this workflow\n window.location.href = `/admin/collections/workflow-runs?where[workflow][equals]=${workflowId}`\n }}\n size=\"small\"\n buttonStyle=\"secondary\"\n >\n View All Runs →\n </Button>\n </div>\n </div>\n )}\n </div>\n )\n}"],"names":["React","useState","useEffect","Button","WorkflowExecutionStatus","workflowId","runs","setRuns","loading","setLoading","expanded","setExpanded","fetchRecentRuns","response","fetch","ok","data","json","docs","error","console","warn","div","style","padding","color","length","backgroundColor","border","borderRadius","textAlign","br","small","getStatusIcon","status","getStatusColor","formatDate","dateString","date","Date","now","diffMs","getTime","Math","floor","toLocaleDateString","getDuration","startedAt","completedAt","start","end","recentRun","recentStatus","recentColor","borderBottom","display","justifyContent","alignItems","gap","span","fontSize","fontWeight","onClick","size","buttonStyle","h4","margin","map","run","index","marginBottom","charAt","toUpperCase","slice","triggeredBy","marginTop","id","window","location","href"],"mappings":"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"}
@@ -32,6 +32,30 @@ export interface ExecutionContext {
32
32
  input: unknown;
33
33
  output: unknown;
34
34
  state: 'failed' | 'pending' | 'running' | 'succeeded';
35
+ _startTime?: number;
36
+ executionInfo?: {
37
+ completed: boolean;
38
+ success: boolean;
39
+ executedAt: string;
40
+ duration: number;
41
+ failureReason?: string;
42
+ };
43
+ errorDetails?: {
44
+ stepId: string;
45
+ errorType: string;
46
+ duration: number;
47
+ attempts: number;
48
+ finalError: string;
49
+ context: {
50
+ url?: string;
51
+ method?: string;
52
+ timeout?: number;
53
+ statusCode?: number;
54
+ headers?: Record<string, string>;
55
+ [key: string]: any;
56
+ };
57
+ timestamp: string;
58
+ };
35
59
  }>;
36
60
  trigger: {
37
61
  collection?: string;
@@ -75,6 +99,14 @@ export declare class WorkflowExecutor {
75
99
  * Safely serialize an object, handling circular references and non-serializable values
76
100
  */
77
101
  private safeSerialize;
102
+ /**
103
+ * Extracts detailed error information from job logs and input
104
+ */
105
+ private extractErrorDetailsFromJob;
106
+ /**
107
+ * Classifies error types based on error messages
108
+ */
109
+ private classifyErrorType;
78
110
  /**
79
111
  * Update workflow run with current context
80
112
  */
@@ -81,14 +81,29 @@ export class WorkflowExecutor {
81
81
  error: undefined,
82
82
  input: undefined,
83
83
  output: undefined,
84
- state: 'running'
84
+ state: 'running',
85
+ _startTime: Date.now() // Track execution start time for independent duration tracking
85
86
  };
86
87
  // Move taskSlug declaration outside try block so it's accessible in catch
87
88
  const taskSlug = step.step // Use the 'step' field for task type
88
89
  ;
89
90
  try {
91
+ // Extract input data from step - PayloadCMS flattens inputSchema fields to step level
92
+ const inputFields = {};
93
+ // Get all fields except the core step fields
94
+ const coreFields = [
95
+ 'step',
96
+ 'name',
97
+ 'dependencies',
98
+ 'condition'
99
+ ];
100
+ for (const [key, value] of Object.entries(step)){
101
+ if (!coreFields.includes(key)) {
102
+ inputFields[key] = value;
103
+ }
104
+ }
90
105
  // Resolve input data using JSONPath
91
- const resolvedInput = this.resolveStepInput(step.input || {}, context);
106
+ const resolvedInput = this.resolveStepInput(inputFields, context);
92
107
  context.steps[stepName].input = resolvedInput;
93
108
  if (!taskSlug) {
94
109
  throw new Error(`Step ${stepName} is missing a task type`);
@@ -104,17 +119,33 @@ export class WorkflowExecutor {
104
119
  req,
105
120
  task: taskSlug
106
121
  });
107
- // Run the job immediately
108
- await this.payload.jobs.run({
109
- limit: 1,
122
+ // Run the specific job immediately and wait for completion
123
+ this.logger.info({
124
+ jobId: job.id
125
+ }, 'Running job immediately using runByID');
126
+ const runResults = await this.payload.jobs.runByID({
127
+ id: job.id,
110
128
  req
111
129
  });
130
+ this.logger.info({
131
+ jobId: job.id,
132
+ runResult: runResults,
133
+ hasResult: !!runResults
134
+ }, 'Job run completed');
135
+ // Give a small delay to ensure job is fully processed
136
+ await new Promise((resolve)=>setTimeout(resolve, 100));
112
137
  // Get the job result
113
138
  const completedJob = await this.payload.findByID({
114
139
  id: job.id,
115
140
  collection: 'payload-jobs',
116
141
  req
117
142
  });
143
+ this.logger.info({
144
+ jobId: job.id,
145
+ totalTried: completedJob.totalTried,
146
+ hasError: completedJob.hasError,
147
+ taskStatus: completedJob.taskStatus ? Object.keys(completedJob.taskStatus) : 'null'
148
+ }, 'Retrieved job results');
118
149
  const taskStatus = completedJob.taskStatus?.[completedJob.taskSlug]?.[completedJob.totalTried];
119
150
  const isComplete = taskStatus?.complete === true;
120
151
  const hasError = completedJob.hasError || !isComplete;
@@ -130,9 +161,34 @@ export class WorkflowExecutor {
130
161
  if (!errorMessage && completedJob.error) {
131
162
  errorMessage = completedJob.error.message || completedJob.error;
132
163
  }
133
- // Final fallback to generic message
164
+ // Try to get error from task output if available
165
+ if (!errorMessage && taskStatus?.output?.error) {
166
+ errorMessage = taskStatus.output.error;
167
+ }
168
+ // Check if task handler returned with state='failed'
169
+ if (!errorMessage && taskStatus?.state === 'failed') {
170
+ errorMessage = 'Task handler returned a failed state';
171
+ // Try to get more specific error from output
172
+ if (taskStatus.output?.error) {
173
+ errorMessage = taskStatus.output.error;
174
+ }
175
+ }
176
+ // Check for network errors in the job data
177
+ if (!errorMessage && completedJob.result) {
178
+ const result = completedJob.result;
179
+ if (result.error) {
180
+ errorMessage = result.error;
181
+ }
182
+ }
183
+ // Final fallback to generic message with more detail
134
184
  if (!errorMessage) {
135
- errorMessage = `Task ${taskSlug} failed without detailed error information`;
185
+ const jobDetails = {
186
+ taskSlug,
187
+ hasError: completedJob.hasError,
188
+ taskStatus: taskStatus?.complete,
189
+ totalTried: completedJob.totalTried
190
+ };
191
+ errorMessage = `Task ${taskSlug} failed without detailed error information. Job details: ${JSON.stringify(jobDetails)}`;
136
192
  }
137
193
  }
138
194
  const result = {
@@ -146,6 +202,27 @@ export class WorkflowExecutor {
146
202
  if (result.error) {
147
203
  context.steps[stepName].error = result.error;
148
204
  }
205
+ // Independent execution tracking (not dependent on PayloadCMS task status)
206
+ context.steps[stepName].executionInfo = {
207
+ completed: true,
208
+ success: result.state === 'succeeded',
209
+ executedAt: new Date().toISOString(),
210
+ duration: Date.now() - (context.steps[stepName]._startTime || Date.now())
211
+ };
212
+ // For failed steps, try to extract detailed error information from the job logs
213
+ // This approach is more reliable than external storage and persists with the workflow
214
+ if (result.state === 'failed') {
215
+ const errorDetails = this.extractErrorDetailsFromJob(completedJob, context.steps[stepName], stepName);
216
+ if (errorDetails) {
217
+ context.steps[stepName].errorDetails = errorDetails;
218
+ this.logger.info({
219
+ stepName,
220
+ errorType: errorDetails.errorType,
221
+ duration: errorDetails.duration,
222
+ attempts: errorDetails.attempts
223
+ }, 'Extracted detailed error information for failed step');
224
+ }
225
+ }
149
226
  this.logger.debug({
150
227
  context
151
228
  }, 'Step execution context');
@@ -164,6 +241,14 @@ export class WorkflowExecutor {
164
241
  const errorMessage = error instanceof Error ? error.message : 'Unknown error';
165
242
  context.steps[stepName].state = 'failed';
166
243
  context.steps[stepName].error = errorMessage;
244
+ // Independent execution tracking for failed steps
245
+ context.steps[stepName].executionInfo = {
246
+ completed: true,
247
+ success: false,
248
+ executedAt: new Date().toISOString(),
249
+ duration: Date.now() - (context.steps[stepName]._startTime || Date.now()),
250
+ failureReason: errorMessage
251
+ };
167
252
  this.logger.error({
168
253
  error: errorMessage,
169
254
  input: context.steps[stepName].input,
@@ -325,6 +410,76 @@ export class WorkflowExecutor {
325
410
  return serialize(obj);
326
411
  }
327
412
  /**
413
+ * Extracts detailed error information from job logs and input
414
+ */ extractErrorDetailsFromJob(job, stepContext, stepName) {
415
+ try {
416
+ // Get error information from multiple sources
417
+ const input = stepContext.input || {};
418
+ const logs = job.log || [];
419
+ const latestLog = logs[logs.length - 1];
420
+ // Extract error message from job error or log
421
+ const errorMessage = job.error?.message || latestLog?.error?.message || 'Unknown error';
422
+ // For timeout scenarios, check if it's a timeout based on duration and timeout setting
423
+ let errorType = this.classifyErrorType(errorMessage);
424
+ // Special handling for HTTP timeouts - if task failed and duration exceeds timeout, it's likely a timeout
425
+ if (errorType === 'unknown' && input.timeout && stepContext.executionInfo?.duration) {
426
+ const timeoutMs = parseInt(input.timeout) || 30000;
427
+ const actualDuration = stepContext.executionInfo.duration;
428
+ // If execution duration is close to or exceeds timeout, classify as timeout
429
+ if (actualDuration >= timeoutMs * 0.9) {
430
+ errorType = 'timeout';
431
+ this.logger.debug({
432
+ timeoutMs,
433
+ actualDuration,
434
+ stepName
435
+ }, 'Classified error as timeout based on duration analysis');
436
+ }
437
+ }
438
+ // Calculate duration from execution info if available
439
+ const duration = stepContext.executionInfo?.duration || 0;
440
+ // Extract attempt count from logs
441
+ const attempts = job.totalTried || 1;
442
+ return {
443
+ stepId: `${stepName}-${Date.now()}`,
444
+ errorType,
445
+ duration,
446
+ attempts,
447
+ finalError: errorMessage,
448
+ context: {
449
+ url: input.url,
450
+ method: input.method,
451
+ timeout: input.timeout,
452
+ statusCode: latestLog?.output?.status,
453
+ headers: input.headers
454
+ },
455
+ timestamp: new Date().toISOString()
456
+ };
457
+ } catch (error) {
458
+ this.logger.warn({
459
+ error: error instanceof Error ? error.message : 'Unknown error',
460
+ stepName
461
+ }, 'Failed to extract error details from job');
462
+ return null;
463
+ }
464
+ }
465
+ /**
466
+ * Classifies error types based on error messages
467
+ */ classifyErrorType(errorMessage) {
468
+ if (errorMessage.includes('timeout') || errorMessage.includes('ETIMEDOUT')) {
469
+ return 'timeout';
470
+ }
471
+ if (errorMessage.includes('ENOTFOUND') || errorMessage.includes('getaddrinfo')) {
472
+ return 'dns';
473
+ }
474
+ if (errorMessage.includes('ECONNREFUSED') || errorMessage.includes('ECONNRESET')) {
475
+ return 'connection';
476
+ }
477
+ if (errorMessage.includes('network') || errorMessage.includes('fetch')) {
478
+ return 'network';
479
+ }
480
+ return 'unknown';
481
+ }
482
+ /**
328
483
  * Update workflow run with current context
329
484
  */ async updateWorkflowRunContext(workflowRunId, context, req) {
330
485
  const serializeContext = ()=>({