@shipfox/client-workflows 17.0.0 → 18.0.0
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.
- package/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-type.log +0 -1
- package/CHANGELOG.md +12 -0
- package/dist/components/job-detail/job-context-panel.d.ts.map +1 -1
- package/dist/components/job-detail/job-context-panel.js +5 -1
- package/dist/components/job-detail/job-context-panel.js.map +1 -1
- package/dist/components/job-detail/job-detail-view.d.ts.map +1 -1
- package/dist/components/job-detail/job-detail-view.js +34 -27
- package/dist/components/job-detail/job-detail-view.js.map +1 -1
- package/dist/components/job-detail/job-empty-states.d.ts +4 -0
- package/dist/components/job-detail/job-empty-states.d.ts.map +1 -1
- package/dist/components/job-detail/job-empty-states.js +43 -3
- package/dist/components/job-detail/job-empty-states.js.map +1 -1
- package/dist/components/job-detail/step-troubleshooting.js +4 -0
- package/dist/components/job-detail/step-troubleshooting.js.map +1 -1
- package/dist/components/step-list/step-list-model.d.ts.map +1 -1
- package/dist/components/step-list/step-list-model.js +1 -0
- package/dist/components/step-list/step-list-model.js.map +1 -1
- package/dist/components/workflow-run-view/run-workspace-nav.d.ts.map +1 -1
- package/dist/components/workflow-run-view/run-workspace-nav.js +6 -5
- package/dist/components/workflow-run-view/run-workspace-nav.js.map +1 -1
- package/dist/core/entities/job-execution.d.ts +2 -0
- package/dist/core/entities/job-execution.d.ts.map +1 -1
- package/dist/core/entities/job-execution.js.map +1 -1
- package/dist/core/entities/job.d.ts +1 -1
- package/dist/core/entities/job.d.ts.map +1 -1
- package/dist/core/entities/job.js.map +1 -1
- package/dist/hooks/api/workflow-run-mapper.d.ts.map +1 -1
- package/dist/hooks/api/workflow-run-mapper.js +1 -0
- package/dist/hooks/api/workflow-run-mapper.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/components/job-detail/job-context-panel.tsx +4 -0
- package/src/components/job-detail/job-detail-view.tsx +37 -33
- package/src/components/job-detail/job-empty-states.test.ts +105 -1
- package/src/components/job-detail/job-empty-states.tsx +56 -2
- package/src/components/job-detail/step-troubleshooting.tsx +4 -0
- package/src/components/step-list/step-list-model.ts +1 -0
- package/src/components/workflow-run-view/run-workspace-nav.tsx +13 -6
- package/src/core/entities/job-execution.ts +2 -0
- package/src/core/entities/job.ts +3 -1
- package/src/hooks/api/workflow-run-mapper.ts +1 -0
- package/tsconfig.build.tsbuildinfo +1 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shipfox/client-workflows",
|
|
3
3
|
"license": "MIT",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "18.0.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/ShipfoxHQ/shipfox.git",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"date-fns": "^4.1.0",
|
|
30
30
|
"@shipfox/annotations-dto": "12.3.0",
|
|
31
31
|
"@shipfox/api-definitions-dto": "12.3.0",
|
|
32
|
-
"@shipfox/api-workflows-dto": "12.
|
|
32
|
+
"@shipfox/api-workflows-dto": "12.5.0",
|
|
33
33
|
"@shipfox/client-api": "6.0.1",
|
|
34
34
|
"@shipfox/client-logs": "17.0.0",
|
|
35
35
|
"@shipfox/client-projects": "17.0.0",
|
|
@@ -27,6 +27,7 @@ export function JobContextPanel({job, execution}: {job: Job; execution: JobExecu
|
|
|
27
27
|
execution.triggerEvents.length ||
|
|
28
28
|
job.success ||
|
|
29
29
|
statusReason ||
|
|
30
|
+
execution.statusReasonMessage ||
|
|
30
31
|
trace?.length ||
|
|
31
32
|
hasTiming,
|
|
32
33
|
);
|
|
@@ -114,6 +115,9 @@ function JobContextSheet({
|
|
|
114
115
|
{statusReason ? (
|
|
115
116
|
<ContextValue label="Status reason" value={humanize(statusReason)} />
|
|
116
117
|
) : null}
|
|
118
|
+
{execution.statusReasonMessage ? (
|
|
119
|
+
<ContextValue label="Failure details" value={execution.statusReasonMessage} />
|
|
120
|
+
) : null}
|
|
117
121
|
{execution.triggerEvents.length ? (
|
|
118
122
|
<JsonCode
|
|
119
123
|
title={`Trigger events (${execution.triggerEvents.length})`}
|
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
emptyStateForJob,
|
|
34
34
|
emptyStateForMissingExecution,
|
|
35
35
|
jobSucceededSummary,
|
|
36
|
+
MaterializedOutputFailureNotice,
|
|
36
37
|
} from './job-empty-states.js';
|
|
37
38
|
import {
|
|
38
39
|
resolveWorkflowJobSelection,
|
|
@@ -258,39 +259,42 @@ export function JobDetailView({
|
|
|
258
259
|
Logs
|
|
259
260
|
</Text>
|
|
260
261
|
{selectedJobExecution ? (
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
262
|
+
<>
|
|
263
|
+
<MaterializedOutputFailureNotice jobExecution={selectedJobExecution} />
|
|
264
|
+
<StepList
|
|
265
|
+
job={job}
|
|
266
|
+
jobExecution={selectedJobExecution}
|
|
267
|
+
selectedAttemptId={selectedAttemptId}
|
|
268
|
+
defaultSelectedAttemptId={landingSelection?.attemptId}
|
|
269
|
+
onSelectedAttemptChange={selectAttempt}
|
|
270
|
+
inspectorOpenAttemptId={inspectorOpenAttemptId}
|
|
271
|
+
onInspectorOpenChange={onInspectorOpenChange}
|
|
272
|
+
autoSelectActiveAttempt
|
|
273
|
+
emptyState={emptyStateForJob(job, selectedJobExecution)}
|
|
274
|
+
showHeader={false}
|
|
275
|
+
className="rounded-none border-0 bg-transparent"
|
|
276
|
+
renderExpandedStep={(context) => (
|
|
277
|
+
<ExpandedStep context={context} pageScrollRef={pageScrollRef} />
|
|
278
|
+
)}
|
|
279
|
+
renderInspector={(entry) => (
|
|
280
|
+
<StepInspectorSheet
|
|
281
|
+
entry={entry}
|
|
282
|
+
open
|
|
283
|
+
onOpenChange={(open) => onInspectorOpenChange(open ? entry.id : null)}
|
|
284
|
+
workspaceSlug={workspaceSlug}
|
|
285
|
+
projectSlug={projectSlug}
|
|
286
|
+
workflowRunId={run.id}
|
|
287
|
+
runAttempt={run.runAttempt.attempt}
|
|
288
|
+
jobId={job.id}
|
|
289
|
+
annotationCount={annotationCountForStep(
|
|
290
|
+
annotationSummaryQuery.data,
|
|
291
|
+
entry.step.id,
|
|
292
|
+
entry.attempt,
|
|
293
|
+
)}
|
|
294
|
+
/>
|
|
295
|
+
)}
|
|
296
|
+
/>
|
|
297
|
+
</>
|
|
294
298
|
) : (
|
|
295
299
|
<EmptyStateForMissingExecution job={job} />
|
|
296
300
|
)}
|
|
@@ -3,7 +3,13 @@ import {
|
|
|
3
3
|
workflowJobExecutionDto,
|
|
4
4
|
workflowStepDto,
|
|
5
5
|
} from '#test/fixtures/workflow-run.js';
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
emptyStateForJob,
|
|
8
|
+
emptyStateForMissingExecution,
|
|
9
|
+
jobSucceededSummary,
|
|
10
|
+
outputFailureDescriptionForExecution,
|
|
11
|
+
skippedJobDescription,
|
|
12
|
+
} from './job-empty-states.js';
|
|
7
13
|
|
|
8
14
|
describe('jobSucceededSummary', () => {
|
|
9
15
|
test('counts only succeeded steps when skipped steps are present', () => {
|
|
@@ -25,3 +31,101 @@ describe('jobSucceededSummary', () => {
|
|
|
25
31
|
expect(jobSucceededSummary(job, execution)).toBe('1 step succeeded');
|
|
26
32
|
});
|
|
27
33
|
});
|
|
34
|
+
|
|
35
|
+
describe('skippedJobDescription', () => {
|
|
36
|
+
test('explains when materialized output exceeds the configured size limit', () => {
|
|
37
|
+
expect(skippedJobDescription('output_too_large')).toBe(
|
|
38
|
+
'The materialized job output exceeded its configured size limit.',
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe('materialized output failure descriptions', () => {
|
|
44
|
+
const fallback =
|
|
45
|
+
'A materialized job output could not be persisted: it exceeded a size or entry cap, contained a non-JSON-safe value, or referenced an unresolved value. Check the output mapping and values before re-running the workflow.';
|
|
46
|
+
|
|
47
|
+
test('uses the server-authored message for an execution with recorded steps', () => {
|
|
48
|
+
const job = workflowJob({
|
|
49
|
+
status: 'failed',
|
|
50
|
+
status_reason: 'output_invalid',
|
|
51
|
+
job_executions: [
|
|
52
|
+
workflowJobExecutionDto({
|
|
53
|
+
status: 'failed',
|
|
54
|
+
status_reason: 'output_invalid',
|
|
55
|
+
status_reason_message: 'Job output "payload" cannot be persisted as JSON: undefined.',
|
|
56
|
+
steps: [workflowStepDto({status: 'succeeded'})],
|
|
57
|
+
}),
|
|
58
|
+
],
|
|
59
|
+
});
|
|
60
|
+
const execution = job.jobExecutions[0];
|
|
61
|
+
if (!execution) throw new Error('Expected a job execution');
|
|
62
|
+
|
|
63
|
+
expect(outputFailureDescriptionForExecution(execution)).toBe(
|
|
64
|
+
'Job output "payload" cannot be persisted as JSON: undefined.',
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('keeps the generic fallback for old output-invalid rows without a message', () => {
|
|
69
|
+
const job = workflowJob({
|
|
70
|
+
status: 'failed',
|
|
71
|
+
status_reason: 'output_invalid',
|
|
72
|
+
job_executions: [
|
|
73
|
+
workflowJobExecutionDto({
|
|
74
|
+
status: 'failed',
|
|
75
|
+
status_reason: 'output_invalid',
|
|
76
|
+
steps: [workflowStepDto({status: 'succeeded'})],
|
|
77
|
+
}),
|
|
78
|
+
],
|
|
79
|
+
});
|
|
80
|
+
const execution = job.jobExecutions[0];
|
|
81
|
+
if (!execution) throw new Error('Expected a job execution');
|
|
82
|
+
|
|
83
|
+
expect(outputFailureDescriptionForExecution(execution)).toBe(fallback);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test('uses the message in the empty state when output materialization failed before steps', () => {
|
|
87
|
+
const job = workflowJob({
|
|
88
|
+
status: 'failed',
|
|
89
|
+
status_reason: 'output_invalid',
|
|
90
|
+
job_executions: [
|
|
91
|
+
workflowJobExecutionDto({
|
|
92
|
+
status: 'failed',
|
|
93
|
+
status_reason: 'output_invalid',
|
|
94
|
+
status_reason_message: 'Job outputs cannot define more than 10 entries (found 11)',
|
|
95
|
+
steps: [],
|
|
96
|
+
}),
|
|
97
|
+
],
|
|
98
|
+
});
|
|
99
|
+
const execution = job.jobExecutions[0];
|
|
100
|
+
if (!execution) throw new Error('Expected a job execution');
|
|
101
|
+
|
|
102
|
+
expect(emptyStateForJob(job, execution)).toMatchObject({
|
|
103
|
+
description: 'Job outputs cannot define more than 10 entries (found 11)',
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('keeps output failures scoped to the selected execution', () => {
|
|
108
|
+
const job = workflowJob({
|
|
109
|
+
status: 'failed',
|
|
110
|
+
status_reason: 'output_invalid',
|
|
111
|
+
job_executions: [
|
|
112
|
+
workflowJobExecutionDto({
|
|
113
|
+
status: 'succeeded',
|
|
114
|
+
status_reason: null,
|
|
115
|
+
steps: [workflowStepDto({status: 'succeeded'})],
|
|
116
|
+
}),
|
|
117
|
+
],
|
|
118
|
+
});
|
|
119
|
+
const execution = job.jobExecutions[0];
|
|
120
|
+
if (!execution) throw new Error('Expected a job execution');
|
|
121
|
+
|
|
122
|
+
expect(outputFailureDescriptionForExecution(execution)).toBeUndefined();
|
|
123
|
+
expect(
|
|
124
|
+
emptyStateForMissingExecution(
|
|
125
|
+
workflowJob({status: 'failed', status_reason: 'output_invalid'}),
|
|
126
|
+
),
|
|
127
|
+
).toMatchObject({
|
|
128
|
+
description: fallback,
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {Callout, CalloutContent, CalloutDescription, CalloutTitle} from '@shipfox/react-ui/callout';
|
|
1
2
|
import {EmptyState} from '@shipfox/react-ui/empty-state';
|
|
2
3
|
import type {Job, JobExecution, Step, StepError} from '#core/workflow-run.js';
|
|
3
4
|
import {
|
|
@@ -9,6 +10,44 @@ import {
|
|
|
9
10
|
import type {StepListEmptyState} from '../step-list/index.js';
|
|
10
11
|
import {formatJobExecutionTime} from './job-execution-time-text.js';
|
|
11
12
|
|
|
13
|
+
const MATERIALIZED_OUTPUT_FAILURE_DESCRIPTION =
|
|
14
|
+
'A materialized job output could not be persisted: it exceeded a size or entry cap, contained a non-JSON-safe value, or referenced an unresolved value. Check the output mapping and values before re-running the workflow.';
|
|
15
|
+
const OUTPUT_TOO_LARGE_FAILURE_DESCRIPTION =
|
|
16
|
+
'The materialized job output exceeded its configured size limit. Review the failure details before re-running the workflow.';
|
|
17
|
+
|
|
18
|
+
export function outputFailureDescriptionForExecution(
|
|
19
|
+
jobExecution: JobExecution,
|
|
20
|
+
): string | undefined {
|
|
21
|
+
if (jobExecution.steps.length === 0) return undefined;
|
|
22
|
+
|
|
23
|
+
if (jobExecution.statusReason === 'output_invalid') {
|
|
24
|
+
return jobExecution.statusReasonMessage || MATERIALIZED_OUTPUT_FAILURE_DESCRIPTION;
|
|
25
|
+
}
|
|
26
|
+
if (jobExecution.statusReason === 'output_too_large') {
|
|
27
|
+
return jobExecution.statusReasonMessage || OUTPUT_TOO_LARGE_FAILURE_DESCRIPTION;
|
|
28
|
+
}
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function MaterializedOutputFailureNotice({jobExecution}: {jobExecution: JobExecution}) {
|
|
33
|
+
const description = outputFailureDescriptionForExecution(jobExecution);
|
|
34
|
+
if (!description) return null;
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<Callout
|
|
38
|
+
role="alert"
|
|
39
|
+
type="error"
|
|
40
|
+
variant="secondary"
|
|
41
|
+
className="border-b border-border-neutral-base px-row py-row"
|
|
42
|
+
>
|
|
43
|
+
<CalloutContent>
|
|
44
|
+
<CalloutTitle>Job output could not be persisted</CalloutTitle>
|
|
45
|
+
<CalloutDescription>{description}</CalloutDescription>
|
|
46
|
+
</CalloutContent>
|
|
47
|
+
</Callout>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
12
51
|
export function emptyStateForJob(
|
|
13
52
|
job: Job,
|
|
14
53
|
jobExecution: JobExecution,
|
|
@@ -64,7 +103,11 @@ export function emptyStateForJob(
|
|
|
64
103
|
if (displayStatus === 'failed') {
|
|
65
104
|
return {
|
|
66
105
|
title: 'Job failed before its first step started',
|
|
67
|
-
description: preStepFailureDescription(
|
|
106
|
+
description: preStepFailureDescription(
|
|
107
|
+
jobExecution.statusReason ?? job.statusReason,
|
|
108
|
+
runner,
|
|
109
|
+
jobExecution.statusReasonMessage,
|
|
110
|
+
),
|
|
68
111
|
status: displayStatus,
|
|
69
112
|
};
|
|
70
113
|
}
|
|
@@ -159,14 +202,21 @@ export function skippedJobDescription(reason: Job['statusReason']): string {
|
|
|
159
202
|
case 'run_cancelled':
|
|
160
203
|
case 'timed_out':
|
|
161
204
|
case 'runner_lost':
|
|
205
|
+
case 'output_invalid':
|
|
162
206
|
case 'step_failed':
|
|
163
207
|
case 'unknown':
|
|
164
208
|
case null:
|
|
165
209
|
return 'This job did not start.';
|
|
210
|
+
case 'output_too_large':
|
|
211
|
+
return 'The materialized job output exceeded its configured size limit.';
|
|
166
212
|
}
|
|
167
213
|
}
|
|
168
214
|
|
|
169
|
-
function preStepFailureDescription(
|
|
215
|
+
function preStepFailureDescription(
|
|
216
|
+
reason: string | null,
|
|
217
|
+
runner: string[] | null = null,
|
|
218
|
+
statusReasonMessage: string | null = null,
|
|
219
|
+
): string {
|
|
170
220
|
const runnerCopy = runner?.length ? ` Required runner labels: ${runner.join(', ')}.` : '';
|
|
171
221
|
|
|
172
222
|
switch (reason) {
|
|
@@ -179,6 +229,10 @@ function preStepFailureDescription(reason: string | null, runner: string[] | nul
|
|
|
179
229
|
return 'The execution ended while the run was being cancelled.';
|
|
180
230
|
case 'step_failed':
|
|
181
231
|
return `The execution failed before step details were recorded.${runnerCopy} Review run annotations before re-running the workflow.`;
|
|
232
|
+
case 'output_too_large':
|
|
233
|
+
return statusReasonMessage || OUTPUT_TOO_LARGE_FAILURE_DESCRIPTION;
|
|
234
|
+
case 'output_invalid':
|
|
235
|
+
return statusReasonMessage || MATERIALIZED_OUTPUT_FAILURE_DESCRIPTION;
|
|
182
236
|
case 'condition_errored':
|
|
183
237
|
return 'The job condition could not be evaluated. Review run annotations before re-running the workflow.';
|
|
184
238
|
case 'dependency_not_completed':
|
|
@@ -442,6 +442,8 @@ function failureTitle(reason: string | JobStatusReason): string {
|
|
|
442
442
|
return 'Agent harness was unavailable';
|
|
443
443
|
case 'runner_lost':
|
|
444
444
|
return 'Runner stopped responding';
|
|
445
|
+
case 'output_too_large':
|
|
446
|
+
return 'Job output exceeded its size limit';
|
|
445
447
|
case 'timed_out':
|
|
446
448
|
return 'Step timed out';
|
|
447
449
|
case 'step_failed':
|
|
@@ -489,6 +491,8 @@ function failureDescription(reason: string | JobStatusReason): string {
|
|
|
489
491
|
return 'The runner could not start the agent harness.';
|
|
490
492
|
case 'runner_lost':
|
|
491
493
|
return 'The runner stopped responding before the step completed.';
|
|
494
|
+
case 'output_too_large':
|
|
495
|
+
return 'The materialized job output exceeded its configured size limit.';
|
|
492
496
|
case 'timed_out':
|
|
493
497
|
return 'The step exceeded its configured time limit.';
|
|
494
498
|
case 'dependency_not_completed':
|
|
@@ -77,6 +77,7 @@ export function emptyJobExecutionForJob(job: Job): JobExecution {
|
|
|
77
77
|
name: job.name ?? job.key,
|
|
78
78
|
status: job.status === 'skipped' ? 'cancelled' : job.status,
|
|
79
79
|
statusReason: job.statusReason,
|
|
80
|
+
statusReasonMessage: null,
|
|
80
81
|
runner: job.runner,
|
|
81
82
|
outputs: job.outputs,
|
|
82
83
|
triggerEvents: [],
|
|
@@ -53,8 +53,12 @@ export function RunWorkspaceNav({
|
|
|
53
53
|
|
|
54
54
|
return (
|
|
55
55
|
<TimeTickerProvider intervalMs={1000} reducedMotionIntervalMs={10_000}>
|
|
56
|
-
<aside className="w-full shrink-0 border-b border-border-neutral-base bg-background-neutral-background min-[768px]:w-240 min-[768px]:border-b-0 min-[768px]:border-r">
|
|
57
|
-
<Collapsible
|
|
56
|
+
<aside className="flex min-h-0 w-full shrink-0 flex-col border-b border-border-neutral-base bg-background-neutral-background min-[768px]:w-240 min-[768px]:border-b-0 min-[768px]:border-r">
|
|
57
|
+
<Collapsible
|
|
58
|
+
open={mobileOpen}
|
|
59
|
+
onOpenChange={setMobileOpen}
|
|
60
|
+
className="flex min-h-0 flex-col min-[768px]:flex-1"
|
|
61
|
+
>
|
|
58
62
|
<CollapsibleTrigger asChild>
|
|
59
63
|
<button
|
|
60
64
|
type="button"
|
|
@@ -82,7 +86,7 @@ export function RunWorkspaceNav({
|
|
|
82
86
|
</CollapsibleTrigger>
|
|
83
87
|
<div
|
|
84
88
|
className={cn(
|
|
85
|
-
'max-h-[50vh] overflow-y-auto p-tight min-[768px]:
|
|
89
|
+
'flex min-h-0 max-h-[50vh] flex-col overflow-y-auto p-tight scrollbar min-[768px]:min-h-0 min-[768px]:max-h-none min-[768px]:flex-1 min-[768px]:flex min-[768px]:overflow-hidden min-[768px]:p-[12px]',
|
|
86
90
|
!mobileOpen && 'hidden',
|
|
87
91
|
)}
|
|
88
92
|
>
|
|
@@ -136,7 +140,7 @@ function RunWorkspaceNavContent({
|
|
|
136
140
|
}, [currentJobId, mobileOpen]);
|
|
137
141
|
|
|
138
142
|
return (
|
|
139
|
-
<nav aria-label="Run workspace" className="flex min-w-0 flex-col gap-group">
|
|
143
|
+
<nav aria-label="Run workspace" className="flex min-h-0 min-w-0 flex-1 flex-col gap-group">
|
|
140
144
|
<ul>
|
|
141
145
|
<li>
|
|
142
146
|
<RunSectionLink
|
|
@@ -151,7 +155,10 @@ function RunWorkspaceNavContent({
|
|
|
151
155
|
</li>
|
|
152
156
|
</ul>
|
|
153
157
|
|
|
154
|
-
<section
|
|
158
|
+
<section
|
|
159
|
+
aria-labelledby="run-workspace-jobs-heading"
|
|
160
|
+
className="flex min-h-0 min-w-0 flex-1 flex-col"
|
|
161
|
+
>
|
|
155
162
|
<div className="flex items-center justify-between gap-inline px-tight pb-[6px]">
|
|
156
163
|
<Text
|
|
157
164
|
as="h2"
|
|
@@ -166,7 +173,7 @@ function RunWorkspaceNavContent({
|
|
|
166
173
|
{jobs.length}
|
|
167
174
|
</Text>
|
|
168
175
|
</div>
|
|
169
|
-
<ol className="
|
|
176
|
+
<ol className="min-h-0 flex-1 overflow-y-auto scrollbar">
|
|
170
177
|
{jobs.map((job) => {
|
|
171
178
|
const current = job.id === currentJobId;
|
|
172
179
|
const execution = defaultJobExecution(job);
|
|
@@ -29,6 +29,7 @@ interface JobExecutionFields {
|
|
|
29
29
|
name: string;
|
|
30
30
|
status: JobExecutionStatus;
|
|
31
31
|
statusReason: string | null;
|
|
32
|
+
statusReasonMessage: string | null;
|
|
32
33
|
runner: string[] | null;
|
|
33
34
|
outputs: Record<string, unknown> | null;
|
|
34
35
|
triggerEvents: WorkflowExecutionEvent[];
|
|
@@ -49,6 +50,7 @@ export class JobExecution {
|
|
|
49
50
|
name!: string;
|
|
50
51
|
status!: JobExecutionStatus;
|
|
51
52
|
statusReason!: string | null;
|
|
53
|
+
statusReasonMessage!: string | null;
|
|
52
54
|
runner!: string[] | null;
|
|
53
55
|
outputs!: Record<string, unknown> | null;
|
|
54
56
|
triggerEvents!: WorkflowExecutionEvent[];
|
package/src/core/entities/job.ts
CHANGED
|
@@ -20,8 +20,10 @@ export type JobStatusReason =
|
|
|
20
20
|
| 'run_cancelled'
|
|
21
21
|
| 'timed_out'
|
|
22
22
|
| 'runner_lost'
|
|
23
|
+
| 'output_too_large'
|
|
23
24
|
| 'step_failed'
|
|
24
|
-
| 'unknown'
|
|
25
|
+
| 'unknown'
|
|
26
|
+
| 'output_invalid';
|
|
25
27
|
export interface JobListening {
|
|
26
28
|
on: Array<{
|
|
27
29
|
source: string;
|
|
@@ -165,6 +165,7 @@ export function toJobExecution(dto: WorkflowRunJobExecutionDetailDto): JobExecut
|
|
|
165
165
|
name: dto.name,
|
|
166
166
|
status: dto.status,
|
|
167
167
|
statusReason: dto.status_reason,
|
|
168
|
+
statusReasonMessage: dto.status_reason_message ?? null,
|
|
168
169
|
runner: dto.runner ?? null,
|
|
169
170
|
outputs: dto.outputs ?? null,
|
|
170
171
|
triggerEvents: (dto.trigger_events ?? []).map(toWorkflowExecutionEvent),
|