@shipfox/client-workflows 12.0.2 → 13.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/.storybook/preview.tsx +3 -3
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +56 -0
- package/dist/components/job-graph/job-duration-format.d.ts +2 -2
- package/dist/components/job-graph/job-duration-format.d.ts.map +1 -1
- package/dist/components/job-graph/job-duration-format.js +4 -2
- package/dist/components/job-graph/job-duration-format.js.map +1 -1
- package/dist/components/job-graph/job-node.d.ts +1 -1
- package/dist/components/job-graph/job-node.d.ts.map +1 -1
- package/dist/components/job-graph/job-node.js +5 -4
- package/dist/components/job-graph/job-node.js.map +1 -1
- package/dist/components/step-list/step-list.d.ts +2 -2
- package/dist/components/step-list/step-list.d.ts.map +1 -1
- package/dist/components/step-list/step-list.js.map +1 -1
- package/dist/components/workflow-run-list/job-status-strip.d.ts +21 -0
- package/dist/components/workflow-run-list/job-status-strip.d.ts.map +1 -0
- package/dist/components/workflow-run-list/job-status-strip.js +185 -0
- package/dist/components/workflow-run-list/job-status-strip.js.map +1 -0
- package/dist/components/workflow-run-list/run-display.d.ts +27 -3
- package/dist/components/workflow-run-list/run-display.d.ts.map +1 -1
- package/dist/components/workflow-run-list/run-display.js +78 -7
- package/dist/components/workflow-run-list/run-display.js.map +1 -1
- package/dist/components/workflow-run-list/types.d.ts +23 -24
- package/dist/components/workflow-run-list/types.d.ts.map +1 -1
- package/dist/components/workflow-run-list/types.js.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-filter-menu.d.ts +22 -0
- package/dist/components/workflow-run-list/workflow-run-filter-menu.d.ts.map +1 -0
- package/dist/components/workflow-run-list/workflow-run-filter-menu.js +68 -0
- package/dist/components/workflow-run-list/workflow-run-filter-menu.js.map +1 -0
- package/dist/components/workflow-run-list/workflow-run-filters.d.ts +18 -0
- package/dist/components/workflow-run-list/workflow-run-filters.d.ts.map +1 -0
- package/dist/components/workflow-run-list/workflow-run-filters.js +236 -0
- package/dist/components/workflow-run-list/workflow-run-filters.js.map +1 -0
- package/dist/components/workflow-run-list/workflow-run-list-content.d.ts +8 -4
- package/dist/components/workflow-run-list/workflow-run-list-content.d.ts.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-list-content.js +40 -12
- package/dist/components/workflow-run-list/workflow-run-list-content.js.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-list-states.d.ts +29 -2
- package/dist/components/workflow-run-list/workflow-run-list-states.d.ts.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-list-states.js +103 -44
- package/dist/components/workflow-run-list/workflow-run-list-states.js.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-list-view.d.ts +1 -1
- package/dist/components/workflow-run-list/workflow-run-list-view.d.ts.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-list-view.js +60 -43
- package/dist/components/workflow-run-list/workflow-run-list-view.js.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-list.d.ts +1 -1
- package/dist/components/workflow-run-list/workflow-run-list.d.ts.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-list.js +20 -8
- package/dist/components/workflow-run-list/workflow-run-list.js.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-row.d.ts +19 -9
- package/dist/components/workflow-run-list/workflow-run-row.d.ts.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-row.js +179 -112
- package/dist/components/workflow-run-list/workflow-run-row.js.map +1 -1
- package/dist/components/workflow-run-number-label.d.ts +15 -0
- package/dist/components/workflow-run-number-label.d.ts.map +1 -0
- package/dist/components/workflow-run-number-label.js +41 -0
- package/dist/components/workflow-run-number-label.js.map +1 -0
- package/dist/components/workflow-run-summary/workflow-run-attempt-switcher.d.ts +3 -3
- package/dist/components/workflow-run-summary/workflow-run-attempt-switcher.d.ts.map +1 -1
- package/dist/components/workflow-run-summary/workflow-run-attempt-switcher.js +8 -7
- package/dist/components/workflow-run-summary/workflow-run-attempt-switcher.js.map +1 -1
- package/dist/components/workflow-run-summary/workflow-run-summary.d.ts +3 -3
- package/dist/components/workflow-run-summary/workflow-run-summary.d.ts.map +1 -1
- package/dist/components/workflow-run-summary/workflow-run-summary.js +16 -7
- package/dist/components/workflow-run-summary/workflow-run-summary.js.map +1 -1
- package/dist/components/workflow-run-view/agent-config-failure-callout.d.ts +2 -2
- package/dist/components/workflow-run-view/agent-config-failure-callout.d.ts.map +1 -1
- package/dist/components/workflow-run-view/agent-config-failure-callout.js +4 -4
- package/dist/components/workflow-run-view/agent-config-failure-callout.js.map +1 -1
- package/dist/components/workflow-run-view/job-card.d.ts +2 -2
- package/dist/components/workflow-run-view/job-card.d.ts.map +1 -1
- package/dist/components/workflow-run-view/job-card.js +51 -32
- package/dist/components/workflow-run-view/job-card.js.map +1 -1
- package/dist/components/workflow-run-view/job-execution-switcher.d.ts +1 -1
- package/dist/components/workflow-run-view/job-execution-switcher.d.ts.map +1 -1
- package/dist/components/workflow-run-view/job-execution-switcher.js +20 -8
- package/dist/components/workflow-run-view/job-execution-switcher.js.map +1 -1
- package/dist/components/workflow-run-view/workflow-run-view.d.ts +3 -2
- package/dist/components/workflow-run-view/workflow-run-view.d.ts.map +1 -1
- package/dist/components/workflow-run-view/workflow-run-view.js +15 -11
- package/dist/components/workflow-run-view/workflow-run-view.js.map +1 -1
- package/dist/components/workflow-status/status-visuals.d.ts +4 -4
- package/dist/components/workflow-status/status-visuals.d.ts.map +1 -1
- package/dist/components/workflow-status/status-visuals.js +7 -0
- package/dist/components/workflow-status/status-visuals.js.map +1 -1
- package/dist/components/workflow-status/workflow-status-icon.d.ts +3 -4
- package/dist/components/workflow-status/workflow-status-icon.d.ts.map +1 -1
- package/dist/components/workflow-status/workflow-status-icon.js +6 -4
- package/dist/components/workflow-status/workflow-status-icon.js.map +1 -1
- package/dist/core/entities/job-execution.d.ts +10 -0
- package/dist/core/entities/job-execution.d.ts.map +1 -1
- package/dist/core/entities/job-execution.js +11 -0
- package/dist/core/entities/job-execution.js.map +1 -1
- package/dist/core/entities/job.d.ts +4 -2
- package/dist/core/entities/job.d.ts.map +1 -1
- package/dist/core/entities/job.js +8 -1
- package/dist/core/entities/job.js.map +1 -1
- package/dist/core/entities/workflow-run.d.ts +52 -4
- package/dist/core/entities/workflow-run.d.ts.map +1 -1
- package/dist/core/entities/workflow-run.js +22 -3
- package/dist/core/entities/workflow-run.js.map +1 -1
- package/dist/core/workflow-run.d.ts +6 -6
- package/dist/core/workflow-run.d.ts.map +1 -1
- package/dist/core/workflow-run.js +3 -3
- package/dist/core/workflow-run.js.map +1 -1
- package/dist/feature.d.ts +7 -7
- package/dist/feature.js +5 -5
- package/dist/feature.js.map +1 -1
- package/dist/hooks/api/workflow-run-mapper.d.ts +4 -3
- package/dist/hooks/api/workflow-run-mapper.d.ts.map +1 -1
- package/dist/hooks/api/workflow-run-mapper.js +31 -3
- package/dist/hooks/api/workflow-run-mapper.js.map +1 -1
- package/dist/hooks/api/workflow-runs.d.ts +4 -4
- package/dist/hooks/api/workflow-runs.d.ts.map +1 -1
- package/dist/hooks/api/workflow-runs.js +12 -4
- package/dist/hooks/api/workflow-runs.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/pages/project-workflows-page.js +46 -1
- package/dist/pages/project-workflows-page.js.map +1 -1
- package/dist/pages/workflow-run-detail-page.d.ts +11 -0
- package/dist/pages/workflow-run-detail-page.d.ts.map +1 -0
- package/dist/pages/workflow-run-detail-page.js +30 -0
- package/dist/pages/workflow-run-detail-page.js.map +1 -0
- package/dist/pages/workflow-run-list-page.d.ts +10 -0
- package/dist/pages/workflow-run-list-page.d.ts.map +1 -0
- package/dist/pages/workflow-run-list-page.js +43 -0
- package/dist/pages/workflow-run-list-page.js.map +1 -0
- package/dist/routes/inputs.d.ts +61 -5
- package/dist/routes/inputs.d.ts.map +1 -1
- package/dist/routes/inputs.js +150 -13
- package/dist/routes/inputs.js.map +1 -1
- package/dist/routes/project-route.d.ts +6 -0
- package/dist/routes/project-route.d.ts.map +1 -0
- package/dist/routes/project-route.js +25 -0
- package/dist/routes/project-route.js.map +1 -0
- package/dist/routes/run-detail.d.ts.map +1 -1
- package/dist/routes/run-detail.js +12 -7
- package/dist/routes/run-detail.js.map +1 -1
- package/dist/routes/runs.d.ts.map +1 -1
- package/dist/routes/runs.js +11 -6
- package/dist/routes/runs.js.map +1 -1
- package/dist/routes/workflows.d.ts.map +1 -1
- package/dist/routes/workflows.js +6 -5
- package/dist/routes/workflows.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +9 -9
- package/src/components/job-graph/job-duration-format.ts +11 -2
- package/src/components/job-graph/job-graph-view.test.tsx +19 -8
- package/src/components/job-graph/job-graph.stories.tsx +23 -19
- package/src/components/job-graph/job-node.stories.tsx +10 -5
- package/src/components/job-graph/job-node.test.tsx +55 -10
- package/src/components/job-graph/job-node.tsx +16 -9
- package/src/components/step-list/step-list.stories.tsx +2 -3
- package/src/components/step-list/step-list.tsx +3 -2
- package/src/components/workflow-run-list/job-status-strip.tsx +189 -0
- package/src/components/workflow-run-list/run-display.test.ts +169 -30
- package/src/components/workflow-run-list/run-display.ts +116 -9
- package/src/components/workflow-run-list/types.ts +21 -17
- package/src/components/workflow-run-list/workflow-run-filter-menu.tsx +100 -0
- package/src/components/workflow-run-list/workflow-run-filters.tsx +243 -0
- package/src/components/workflow-run-list/workflow-run-list-content.tsx +44 -16
- package/src/components/workflow-run-list/workflow-run-list-states.tsx +128 -27
- package/src/components/workflow-run-list/workflow-run-list-view.test.tsx +441 -104
- package/src/components/workflow-run-list/workflow-run-list-view.tsx +73 -43
- package/src/components/workflow-run-list/workflow-run-list.stories.tsx +140 -20
- package/src/components/workflow-run-list/workflow-run-list.tsx +20 -10
- package/src/components/workflow-run-list/workflow-run-row.tsx +173 -106
- package/src/components/workflow-run-number-label.test.tsx +45 -0
- package/src/components/workflow-run-number-label.tsx +47 -0
- package/src/components/workflow-run-summary/workflow-run-attempt-switcher.test.tsx +7 -8
- package/src/components/workflow-run-summary/workflow-run-attempt-switcher.tsx +13 -12
- package/src/components/workflow-run-summary/workflow-run-summary.stories.tsx +6 -8
- package/src/components/workflow-run-summary/workflow-run-summary.test.tsx +27 -2
- package/src/components/workflow-run-summary/workflow-run-summary.tsx +19 -9
- package/src/components/workflow-run-view/agent-config-failure-callout.stories.tsx +2 -3
- package/src/components/workflow-run-view/agent-config-failure-callout.tsx +4 -4
- package/src/components/workflow-run-view/job-card.stories.tsx +11 -10
- package/src/components/workflow-run-view/job-card.test.tsx +115 -0
- package/src/components/workflow-run-view/job-card.tsx +74 -35
- package/src/components/workflow-run-view/job-execution-switcher.tsx +31 -9
- package/src/components/workflow-run-view/workflow-run-view.test.tsx +88 -30
- package/src/components/workflow-run-view/workflow-run-view.tsx +24 -14
- package/src/components/workflow-status/status-visuals.test.ts +9 -0
- package/src/components/workflow-status/status-visuals.ts +6 -4
- package/src/components/workflow-status/workflow-status-icon.stories.tsx +10 -2
- package/src/components/workflow-status/workflow-status-icon.test.tsx +18 -0
- package/src/components/workflow-status/workflow-status-icon.tsx +17 -24
- package/src/core/entities/job-execution.ts +22 -0
- package/src/core/entities/job-status.test.ts +104 -0
- package/src/core/entities/job.ts +18 -3
- package/src/core/entities/workflow-run.ts +76 -7
- package/src/core/workflow-run.test.ts +24 -9
- package/src/core/workflow-run.ts +17 -2
- package/src/feature.ts +5 -5
- package/src/hooks/api/workflow-run-mapper.ts +30 -3
- package/src/hooks/api/workflow-runs.test.tsx +2 -0
- package/src/hooks/api/workflow-runs.ts +12 -7
- package/src/index.ts +7 -2
- package/src/page-harness-budget.test.ts +1 -1
- package/src/pages/project-workflows-page.test.tsx +59 -5
- package/src/pages/project-workflows-page.tsx +34 -1
- package/src/pages/workflow-run-detail-page.tsx +45 -0
- package/src/pages/workflow-run-list-page.tsx +65 -0
- package/src/pages/{workflow-run-page.test.tsx → workflow-run-pages.test.tsx} +211 -47
- package/src/routes/inputs.test.ts +193 -12
- package/src/routes/inputs.ts +170 -11
- package/src/routes/project-route.tsx +25 -0
- package/src/routes/run-detail.tsx +15 -8
- package/src/routes/runs.tsx +14 -7
- package/src/routes/workflows.tsx +5 -4
- package/test/fixtures/workflow-run.ts +76 -7
- package/test/pages.tsx +18 -8
- package/test/render.tsx +8 -2
- package/tsconfig.build.tsbuildinfo +1 -1
- package/dist/components/workflow-run-list/workflow-run-list-header.d.ts +0 -10
- package/dist/components/workflow-run-list/workflow-run-list-header.d.ts.map +0 -1
- package/dist/components/workflow-run-list/workflow-run-list-header.js +0 -55
- package/dist/components/workflow-run-list/workflow-run-list-header.js.map +0 -1
- package/dist/pages/workflow-run-first-time-use.d.ts +0 -6
- package/dist/pages/workflow-run-first-time-use.d.ts.map +0 -1
- package/dist/pages/workflow-run-first-time-use.js +0 -17
- package/dist/pages/workflow-run-first-time-use.js.map +0 -1
- package/dist/pages/workflow-run-page.d.ts +0 -10
- package/dist/pages/workflow-run-page.d.ts.map +0 -1
- package/dist/pages/workflow-run-page.js +0 -90
- package/dist/pages/workflow-run-page.js.map +0 -1
- package/src/components/workflow-run-list/workflow-run-list-header.tsx +0 -53
- package/src/pages/workflow-run-first-time-use.tsx +0 -17
- package/src/pages/workflow-run-page.tsx +0 -94
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
workflowStepAttemptDto,
|
|
16
16
|
workflowStepDto,
|
|
17
17
|
} from '#test/fixtures/workflow-run.js';
|
|
18
|
-
import {jsonResponse,
|
|
18
|
+
import {jsonResponse, PROJECT_TEST_WSLUG, renderProjectPage} from '#test/pages.js';
|
|
19
19
|
import {WorkflowRunView} from './workflow-run-view.js';
|
|
20
20
|
|
|
21
21
|
const RUN_ID = '66666666-6666-4666-8666-666666666666';
|
|
@@ -29,6 +29,9 @@ const RERUN_BUTTON_NAME = /^Re-run/;
|
|
|
29
29
|
const ATTEMPT_2_PATTERN = /Attempt 2/;
|
|
30
30
|
const COPY_RUN_BUTTON_NAME = /Copy run/;
|
|
31
31
|
const EXECUTION_ONE_MENU_ITEM_PATTERN = /#1/;
|
|
32
|
+
const LISTENING_EXECUTION_SWITCHER_NAME =
|
|
33
|
+
'Switch job execution, currently execution 2: Deployment status event';
|
|
34
|
+
const JOB_TEMPLATE_NAME = ['Implement $', '{{ event.issue.identifier }}'].join('');
|
|
32
35
|
|
|
33
36
|
describe('WorkflowRunView', () => {
|
|
34
37
|
test('renders the run summary, jobs graph, and selected job step attempts when a run loads', async () => {
|
|
@@ -55,6 +58,37 @@ describe('WorkflowRunView', () => {
|
|
|
55
58
|
).toBeInTheDocument();
|
|
56
59
|
});
|
|
57
60
|
|
|
61
|
+
test('uses the selected job execution name as the job card title', async () => {
|
|
62
|
+
configureApiClient({
|
|
63
|
+
fetchImpl: vi.fn(() =>
|
|
64
|
+
Promise.resolve(
|
|
65
|
+
jsonResponse(
|
|
66
|
+
workflowRunViewDetailDto({
|
|
67
|
+
jobs: [
|
|
68
|
+
workflowJobDto({
|
|
69
|
+
id: BUILD_JOB_ID,
|
|
70
|
+
run_attempt_id: RUN_ID,
|
|
71
|
+
name: JOB_TEMPLATE_NAME,
|
|
72
|
+
job_executions: [
|
|
73
|
+
workflowJobExecutionDto({
|
|
74
|
+
job_id: BUILD_JOB_ID,
|
|
75
|
+
name: 'Implement ENG-123',
|
|
76
|
+
}),
|
|
77
|
+
],
|
|
78
|
+
}),
|
|
79
|
+
],
|
|
80
|
+
}),
|
|
81
|
+
),
|
|
82
|
+
),
|
|
83
|
+
),
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
renderView();
|
|
87
|
+
|
|
88
|
+
expect(await screen.findByRole('region', {name: 'Implement ENG-123'})).toBeInTheDocument();
|
|
89
|
+
expect(screen.queryByRole('region', {name: JOB_TEMPLATE_NAME})).not.toBeInTheDocument();
|
|
90
|
+
});
|
|
91
|
+
|
|
58
92
|
test('renders active step attempt logs inline when the selected job is running', async () => {
|
|
59
93
|
const stepId = '99999999-9999-4999-8999-000000000001';
|
|
60
94
|
const fetchImpl = vi.fn((input: RequestInfo | URL) => {
|
|
@@ -125,6 +159,7 @@ describe('WorkflowRunView', () => {
|
|
|
125
159
|
id: '77777777-7777-4777-8777-000000000001',
|
|
126
160
|
job_id: BUILD_JOB_ID,
|
|
127
161
|
sequence: 1,
|
|
162
|
+
name: 'Pull request event',
|
|
128
163
|
status: 'succeeded',
|
|
129
164
|
trigger_events: [
|
|
130
165
|
{
|
|
@@ -132,6 +167,10 @@ describe('WorkflowRunView', () => {
|
|
|
132
167
|
event: 'pull_request',
|
|
133
168
|
delivery_id: 'delivery-1',
|
|
134
169
|
received_at: '2026-05-07T01:00:00.000Z',
|
|
170
|
+
project: null,
|
|
171
|
+
repository: null,
|
|
172
|
+
ref: null,
|
|
173
|
+
commit: null,
|
|
135
174
|
data: {number: 12},
|
|
136
175
|
},
|
|
137
176
|
],
|
|
@@ -155,6 +194,7 @@ describe('WorkflowRunView', () => {
|
|
|
155
194
|
id: '77777777-7777-4777-8777-000000000002',
|
|
156
195
|
job_id: BUILD_JOB_ID,
|
|
157
196
|
sequence: 2,
|
|
197
|
+
name: 'Deployment status event',
|
|
158
198
|
status: 'failed',
|
|
159
199
|
status_reason: 'step_failed',
|
|
160
200
|
trigger_events: [
|
|
@@ -163,6 +203,10 @@ describe('WorkflowRunView', () => {
|
|
|
163
203
|
event: 'deployment_status',
|
|
164
204
|
delivery_id: 'delivery-2',
|
|
165
205
|
received_at: '2026-05-07T01:01:00.000Z',
|
|
206
|
+
project: null,
|
|
207
|
+
repository: null,
|
|
208
|
+
ref: null,
|
|
209
|
+
commit: null,
|
|
166
210
|
data: {state: 'success'},
|
|
167
211
|
},
|
|
168
212
|
{
|
|
@@ -170,6 +214,10 @@ describe('WorkflowRunView', () => {
|
|
|
170
214
|
event: 'check_run',
|
|
171
215
|
delivery_id: 'delivery-3',
|
|
172
216
|
received_at: '2026-05-07T01:01:10.000Z',
|
|
217
|
+
project: null,
|
|
218
|
+
repository: null,
|
|
219
|
+
ref: null,
|
|
220
|
+
commit: null,
|
|
173
221
|
data: {status: 'completed'},
|
|
174
222
|
},
|
|
175
223
|
],
|
|
@@ -200,18 +248,20 @@ describe('WorkflowRunView', () => {
|
|
|
200
248
|
});
|
|
201
249
|
|
|
202
250
|
renderView();
|
|
251
|
+
expect(
|
|
252
|
+
await screen.findByRole('region', {name: 'Deployment status event'}),
|
|
253
|
+
).toBeInTheDocument();
|
|
203
254
|
expect(await screen.findByText('second-event')).toBeInTheDocument();
|
|
204
255
|
expect(
|
|
205
256
|
await screen.findByRole('button', {name: 'github · deployment_status (2 events)'}),
|
|
206
257
|
).toBeInTheDocument();
|
|
207
258
|
expect(screen.getByText('deployment_status (2)')).toBeInTheDocument();
|
|
208
259
|
|
|
209
|
-
await user.click(
|
|
210
|
-
screen.getByRole('button', {name: 'Switch job execution, currently execution 2'}),
|
|
211
|
-
);
|
|
260
|
+
await user.click(screen.getByRole('button', {name: LISTENING_EXECUTION_SWITCHER_NAME}));
|
|
212
261
|
await user.click(screen.getByRole('menuitem', {name: EXECUTION_ONE_MENU_ITEM_PATTERN}));
|
|
213
262
|
|
|
214
263
|
expect(await screen.findByText('first-event')).toBeInTheDocument();
|
|
264
|
+
expect(screen.getByRole('region', {name: 'Pull request event'})).toBeInTheDocument();
|
|
215
265
|
expect(screen.queryByText('second-event')).not.toBeInTheDocument();
|
|
216
266
|
expect(await screen.findByRole('button', {name: 'github · pull_request'})).toBeInTheDocument();
|
|
217
267
|
expect(screen.getByText('pull_request')).toBeInTheDocument();
|
|
@@ -301,7 +351,7 @@ describe('WorkflowRunView', () => {
|
|
|
301
351
|
).toBeInTheDocument();
|
|
302
352
|
expect(screen.getByRole('link', {name: 'Configure Agents'})).toHaveAttribute(
|
|
303
353
|
'href',
|
|
304
|
-
`/
|
|
354
|
+
`/w/${PROJECT_TEST_WSLUG}/settings/agents`,
|
|
305
355
|
);
|
|
306
356
|
});
|
|
307
357
|
|
|
@@ -378,7 +428,7 @@ describe('WorkflowRunView', () => {
|
|
|
378
428
|
expect(screen.getByText('Configure credentials for anthropic')).toBeInTheDocument();
|
|
379
429
|
expect(screen.getByRole('link', {name: 'Configure Agents'})).toHaveAttribute(
|
|
380
430
|
'href',
|
|
381
|
-
`/
|
|
431
|
+
`/w/${PROJECT_TEST_WSLUG}/settings/agents`,
|
|
382
432
|
);
|
|
383
433
|
});
|
|
384
434
|
|
|
@@ -413,7 +463,7 @@ describe('WorkflowRunView', () => {
|
|
|
413
463
|
expect(screen.queryByText('No steps recorded')).not.toBeInTheDocument();
|
|
414
464
|
});
|
|
415
465
|
|
|
416
|
-
test('
|
|
466
|
+
test('keeps a terminal job status over inconsistent zero-step execution activity', async () => {
|
|
417
467
|
configureApiClient({
|
|
418
468
|
fetchImpl: vi.fn(() =>
|
|
419
469
|
Promise.resolve(
|
|
@@ -445,11 +495,9 @@ describe('WorkflowRunView', () => {
|
|
|
445
495
|
|
|
446
496
|
renderView();
|
|
447
497
|
|
|
448
|
-
expect(await screen.findByText('
|
|
449
|
-
expect(
|
|
450
|
-
|
|
451
|
-
).toBeInTheDocument();
|
|
452
|
-
expect(screen.queryByText('No steps recorded')).not.toBeInTheDocument();
|
|
498
|
+
expect(await screen.findByText('No steps recorded')).toBeInTheDocument();
|
|
499
|
+
expect(screen.getByText('Execution #2 finished without recorded steps.')).toBeInTheDocument();
|
|
500
|
+
expect(screen.queryByText('Waiting for the first step')).not.toBeInTheDocument();
|
|
453
501
|
});
|
|
454
502
|
|
|
455
503
|
test('renders pending zero-step jobs as waiting to start', async () => {
|
|
@@ -480,7 +528,7 @@ describe('WorkflowRunView', () => {
|
|
|
480
528
|
expect(screen.queryByText('No steps recorded')).not.toBeInTheDocument();
|
|
481
529
|
});
|
|
482
530
|
|
|
483
|
-
test('renders
|
|
531
|
+
test('renders claimed idle jobs as runner preparation', async () => {
|
|
484
532
|
configureApiClient({
|
|
485
533
|
fetchImpl: vi.fn(() =>
|
|
486
534
|
Promise.resolve(
|
|
@@ -509,10 +557,13 @@ describe('WorkflowRunView', () => {
|
|
|
509
557
|
|
|
510
558
|
renderView();
|
|
511
559
|
|
|
512
|
-
expect(await screen.findByText('
|
|
560
|
+
expect(await screen.findByText('Runner preparing job')).toBeInTheDocument();
|
|
513
561
|
expect(
|
|
514
|
-
screen.getByText('
|
|
562
|
+
screen.getByText('A runner is assigned. Steps will appear here when work begins.'),
|
|
515
563
|
).toBeInTheDocument();
|
|
564
|
+
expect(
|
|
565
|
+
screen.queryByText('This job is running, but no steps have started yet.'),
|
|
566
|
+
).not.toBeInTheDocument();
|
|
516
567
|
expect(screen.queryByText('No steps recorded')).not.toBeInTheDocument();
|
|
517
568
|
});
|
|
518
569
|
|
|
@@ -898,7 +949,7 @@ describe('WorkflowRunView', () => {
|
|
|
898
949
|
expect(successSpy).toHaveBeenCalledWith('Re-run started');
|
|
899
950
|
await waitFor(() =>
|
|
900
951
|
expect(router.state.location.pathname).toBe(
|
|
901
|
-
`/
|
|
952
|
+
`/w/${PROJECT_TEST_WSLUG}/p/project/runs/${rerunId}`,
|
|
902
953
|
),
|
|
903
954
|
);
|
|
904
955
|
expect((router.state.location.search as Record<string, unknown>).runAttempt).toBeUndefined();
|
|
@@ -1008,11 +1059,12 @@ describe('WorkflowRunView', () => {
|
|
|
1008
1059
|
configureApiClient({fetchImpl: fetchImpl as typeof fetch});
|
|
1009
1060
|
|
|
1010
1061
|
const {router} = renderProjectPage(
|
|
1011
|
-
`/
|
|
1062
|
+
`/w/${PROJECT_TEST_WSLUG}/p/project/runs/${RUN_ID}?job=${BUILD_JOB_ID}&step=${CHECKOUT_STEP_ID}&stepAttempt=${CHECKOUT_ATTEMPT_ID}`,
|
|
1012
1063
|
() => (
|
|
1013
1064
|
<WorkflowRunView
|
|
1014
|
-
workspaceId={PROJECT_TEST_WID}
|
|
1015
1065
|
projectId={PROJECT_ID}
|
|
1066
|
+
workspaceSlug={PROJECT_TEST_WSLUG}
|
|
1067
|
+
projectSlug="project"
|
|
1016
1068
|
workflowRunId={RUN_ID}
|
|
1017
1069
|
/>
|
|
1018
1070
|
),
|
|
@@ -1023,7 +1075,7 @@ describe('WorkflowRunView', () => {
|
|
|
1023
1075
|
|
|
1024
1076
|
await waitFor(() =>
|
|
1025
1077
|
expect(router.state.location.pathname).toBe(
|
|
1026
|
-
`/
|
|
1078
|
+
`/w/${PROJECT_TEST_WSLUG}/p/project/runs/${RUN_ID}`,
|
|
1027
1079
|
),
|
|
1028
1080
|
);
|
|
1029
1081
|
expect(router.state.location.search).toEqual({runAttempt: 2});
|
|
@@ -1086,17 +1138,15 @@ describe('WorkflowRunView', () => {
|
|
|
1086
1138
|
});
|
|
1087
1139
|
|
|
1088
1140
|
function renderView(props: Partial<Parameters<typeof WorkflowRunView>[0]> = {}, search = '') {
|
|
1089
|
-
return renderProjectPage(
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
),
|
|
1099
|
-
);
|
|
1141
|
+
return renderProjectPage(`/w/${PROJECT_TEST_WSLUG}/p/project/runs/${RUN_ID}${search}`, () => (
|
|
1142
|
+
<WorkflowRunView
|
|
1143
|
+
projectId={PROJECT_ID}
|
|
1144
|
+
workspaceSlug={PROJECT_TEST_WSLUG}
|
|
1145
|
+
projectSlug="project"
|
|
1146
|
+
workflowRunId={RUN_ID}
|
|
1147
|
+
{...props}
|
|
1148
|
+
/>
|
|
1149
|
+
));
|
|
1100
1150
|
}
|
|
1101
1151
|
|
|
1102
1152
|
function requestUrl(input: RequestInfo | URL): URL {
|
|
@@ -1207,6 +1257,14 @@ function workflowRunViewDetailDto(
|
|
|
1207
1257
|
status: 'running',
|
|
1208
1258
|
position: 1,
|
|
1209
1259
|
dependencies: ['build'],
|
|
1260
|
+
job_executions: [
|
|
1261
|
+
workflowJobExecutionDto({
|
|
1262
|
+
job_id: DEPLOY_JOB_ID,
|
|
1263
|
+
name: 'deploy',
|
|
1264
|
+
status: 'running',
|
|
1265
|
+
steps: [workflowStepDto({status: 'running'})],
|
|
1266
|
+
}),
|
|
1267
|
+
],
|
|
1210
1268
|
}),
|
|
1211
1269
|
],
|
|
1212
1270
|
...overrides,
|
|
@@ -36,8 +36,9 @@ interface WorkflowSourceFocus {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
export interface WorkflowRunViewProps {
|
|
39
|
-
workspaceId: string;
|
|
40
39
|
projectId: string;
|
|
40
|
+
workspaceSlug?: string | undefined;
|
|
41
|
+
projectSlug?: string | undefined;
|
|
41
42
|
workflowRunId?: string | undefined;
|
|
42
43
|
selection?: WorkflowRunSelectionInput | undefined;
|
|
43
44
|
onSelectionChange?: ((selection: WorkflowRunSelectionInput) => void) | undefined;
|
|
@@ -49,8 +50,9 @@ export interface WorkflowRunViewProps {
|
|
|
49
50
|
* loading state itself.
|
|
50
51
|
*/
|
|
51
52
|
export function WorkflowRunView({
|
|
52
|
-
workspaceId,
|
|
53
53
|
projectId,
|
|
54
|
+
workspaceSlug,
|
|
55
|
+
projectSlug,
|
|
54
56
|
workflowRunId,
|
|
55
57
|
selection,
|
|
56
58
|
onSelectionChange,
|
|
@@ -62,8 +64,8 @@ export function WorkflowRunView({
|
|
|
62
64
|
<RelativeTimeProvider>
|
|
63
65
|
<div className="flex min-h-0 min-w-0 flex-1 overflow-hidden">
|
|
64
66
|
<RunViewContent
|
|
65
|
-
|
|
66
|
-
|
|
67
|
+
workspaceSlug={workspaceSlug}
|
|
68
|
+
projectSlug={projectSlug}
|
|
67
69
|
query={runQuery}
|
|
68
70
|
rerunMutation={rerunMutation}
|
|
69
71
|
selection={selection}
|
|
@@ -75,15 +77,15 @@ export function WorkflowRunView({
|
|
|
75
77
|
}
|
|
76
78
|
|
|
77
79
|
function RunViewContent({
|
|
78
|
-
|
|
79
|
-
|
|
80
|
+
workspaceSlug,
|
|
81
|
+
projectSlug,
|
|
80
82
|
query,
|
|
81
83
|
rerunMutation,
|
|
82
84
|
selection,
|
|
83
85
|
onSelectionChange,
|
|
84
86
|
}: {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
+
workspaceSlug: string | undefined;
|
|
88
|
+
projectSlug: string | undefined;
|
|
87
89
|
query: ReturnType<typeof useWorkflowRunAttemptQuery>;
|
|
88
90
|
rerunMutation: ReturnType<typeof useRerunWorkflowRunMutation>;
|
|
89
91
|
selection: WorkflowRunSelectionInput | undefined;
|
|
@@ -161,12 +163,20 @@ function RunViewContent({
|
|
|
161
163
|
sourceFocus?.location ?? resolvedSelection?.step?.sourceLocation ?? null;
|
|
162
164
|
const sourceSnapshot = runData.sourceSnapshot;
|
|
163
165
|
async function rerun(mode: WorkflowRunRerunMode) {
|
|
166
|
+
if (!workspaceSlug || !projectSlug) {
|
|
167
|
+
toast.error('Could not start re-run from this route.');
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
164
170
|
try {
|
|
165
171
|
const run = await rerunMutation.mutateAsync({workflowRunId: runData.id, mode});
|
|
166
172
|
toast.success('Re-run started');
|
|
167
173
|
await navigate({
|
|
168
|
-
to: '/
|
|
169
|
-
params: {
|
|
174
|
+
to: '/w/$workspaceSlug/p/$projectSlug/runs/$workflowRunId',
|
|
175
|
+
params: {
|
|
176
|
+
workspaceSlug,
|
|
177
|
+
projectSlug,
|
|
178
|
+
workflowRunId: run.id,
|
|
179
|
+
},
|
|
170
180
|
search: ((previous: Record<string, unknown>) =>
|
|
171
181
|
withoutWorkflowRunSelectionSearch(previous)) as never,
|
|
172
182
|
});
|
|
@@ -274,8 +284,8 @@ function RunViewContent({
|
|
|
274
284
|
<>
|
|
275
285
|
<div className="flex min-w-0 flex-1 flex-col">
|
|
276
286
|
<WorkflowRunSummary
|
|
277
|
-
|
|
278
|
-
|
|
287
|
+
workspaceSlug={workspaceSlug}
|
|
288
|
+
projectSlug={projectSlug}
|
|
279
289
|
run={runData}
|
|
280
290
|
sourceAvailable={sourceAvailable}
|
|
281
291
|
sourceOpen={sourcePanelOpen && sourceFocus === null}
|
|
@@ -290,7 +300,7 @@ function RunViewContent({
|
|
|
290
300
|
/>
|
|
291
301
|
{query.isError ? <WorkflowRunStaleError query={query} /> : null}
|
|
292
302
|
<div className="min-h-0 flex-1 overflow-auto bg-background-neutral-base p-16">
|
|
293
|
-
<div className="mx-auto flex w-full max-w-[
|
|
303
|
+
<div className="mx-auto flex w-full max-w-[1120px] flex-col gap-16">
|
|
294
304
|
<JobGraph
|
|
295
305
|
run={runData}
|
|
296
306
|
selectedJobId={selectedJob?.id}
|
|
@@ -298,7 +308,7 @@ function RunViewContent({
|
|
|
298
308
|
/>
|
|
299
309
|
{selectedJob ? (
|
|
300
310
|
<JobCard
|
|
301
|
-
|
|
311
|
+
workspaceSlug={workspaceSlug}
|
|
302
312
|
job={selectedJob}
|
|
303
313
|
selectedJobExecution={selectedJobExecution}
|
|
304
314
|
selectedAttemptId={selectedJob.carriedOver ? undefined : selectedAttemptId}
|
|
@@ -35,4 +35,13 @@ describe('getWorkflowStatusVisual', () => {
|
|
|
35
35
|
|
|
36
36
|
expect(visual).toEqual({kind: 'running', label: 'Running', dot: 'info', badge: 'info'});
|
|
37
37
|
});
|
|
38
|
+
|
|
39
|
+
test('returns the active listener visual', () => {
|
|
40
|
+
expect(getWorkflowStatusVisual('listening')).toEqual({
|
|
41
|
+
kind: 'listening',
|
|
42
|
+
label: 'Listening',
|
|
43
|
+
dot: 'info',
|
|
44
|
+
badge: 'info',
|
|
45
|
+
});
|
|
46
|
+
});
|
|
38
47
|
});
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type {BadgeVariant} from '@shipfox/react-ui/badge';
|
|
2
2
|
import type {DotVariant} from '@shipfox/react-ui/dot';
|
|
3
|
-
import type {
|
|
3
|
+
import type {WorkflowDisplayStatus} from '#core/workflow-run.js';
|
|
4
4
|
|
|
5
|
-
export type {
|
|
5
|
+
export type {WorkflowDisplayStatus};
|
|
6
6
|
|
|
7
7
|
export interface WorkflowStatusVisual {
|
|
8
|
-
kind:
|
|
8
|
+
kind: WorkflowDisplayStatus;
|
|
9
9
|
label: string;
|
|
10
10
|
dot: DotVariant;
|
|
11
11
|
badge: BadgeVariant;
|
|
@@ -15,12 +15,14 @@ export interface WorkflowStatusVisual {
|
|
|
15
15
|
// WorkflowStatusIcon (which renders the glyph per kind). The exhaustive switch turns any new
|
|
16
16
|
// status the API grows into (DESIGN.md section 9) into a compile error; reserve the `warning`
|
|
17
17
|
// tone for the queued/awaiting-* states when they land.
|
|
18
|
-
export function getWorkflowStatusVisual(status:
|
|
18
|
+
export function getWorkflowStatusVisual(status: WorkflowDisplayStatus): WorkflowStatusVisual {
|
|
19
19
|
switch (status) {
|
|
20
20
|
case 'pending':
|
|
21
21
|
return {kind: 'pending', label: 'Pending', dot: 'neutral', badge: 'neutral'};
|
|
22
22
|
case 'running':
|
|
23
23
|
return {kind: 'running', label: 'Running', dot: 'info', badge: 'info'};
|
|
24
|
+
case 'listening':
|
|
25
|
+
return {kind: 'listening', label: 'Listening', dot: 'info', badge: 'info'};
|
|
24
26
|
case 'succeeded':
|
|
25
27
|
return {kind: 'succeeded', label: 'Succeeded', dot: 'success', badge: 'success'};
|
|
26
28
|
case 'failed':
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import {Code, Text} from '@shipfox/react-ui/typography';
|
|
2
2
|
import type {Meta, StoryObj} from '@storybook/react';
|
|
3
|
-
import type {
|
|
3
|
+
import type {WorkflowDisplayStatus} from '#core/workflow-run.js';
|
|
4
4
|
import {getWorkflowStatusVisual} from './status-visuals.js';
|
|
5
5
|
import {WorkflowStatusIcon} from './workflow-status-icon.js';
|
|
6
6
|
|
|
7
|
-
const statuses:
|
|
7
|
+
const statuses: WorkflowDisplayStatus[] = [
|
|
8
|
+
'pending',
|
|
9
|
+
'running',
|
|
10
|
+
'listening',
|
|
11
|
+
'succeeded',
|
|
12
|
+
'failed',
|
|
13
|
+
'cancelled',
|
|
14
|
+
'skipped',
|
|
15
|
+
];
|
|
8
16
|
|
|
9
17
|
const meta = {
|
|
10
18
|
title: 'Workflows/StatusIcon',
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import {render, screen} from '@testing-library/react';
|
|
2
|
+
import {WorkflowStatusIcon} from './workflow-status-icon.js';
|
|
3
|
+
|
|
4
|
+
describe('WorkflowStatusIcon', () => {
|
|
5
|
+
test('keeps a running status as a dot', () => {
|
|
6
|
+
const {container} = render(<WorkflowStatusIcon status="running" tooltip={false} />);
|
|
7
|
+
|
|
8
|
+
expect(screen.getByRole('img', {name: 'Running'})).toBeInTheDocument();
|
|
9
|
+
expect(container.querySelector('svg')).toBeNull();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test('uses the pulse glyph for the listening status', () => {
|
|
13
|
+
const {container} = render(<WorkflowStatusIcon status="listening" tooltip={false} />);
|
|
14
|
+
|
|
15
|
+
expect(screen.getByRole('img', {name: 'Listening'})).toBeInTheDocument();
|
|
16
|
+
expect(container.querySelector('svg')).not.toBeNull();
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -3,7 +3,7 @@ import {Icon, type IconName} from '@shipfox/react-ui/icon';
|
|
|
3
3
|
import {Tooltip, TooltipContent, TooltipTrigger} from '@shipfox/react-ui/tooltip';
|
|
4
4
|
import {cn} from '@shipfox/react-ui/utils';
|
|
5
5
|
import type {ReactNode} from 'react';
|
|
6
|
-
import {getWorkflowStatusVisual, type
|
|
6
|
+
import {getWorkflowStatusVisual, type WorkflowDisplayStatus} from './status-visuals.js';
|
|
7
7
|
|
|
8
8
|
// Status glyphs use the same saturated accent tokens as IconBadge/StatusBadge.
|
|
9
9
|
const toneByVariant: Record<DotVariant, string> = {
|
|
@@ -17,7 +17,7 @@ const toneByVariant: Record<DotVariant, string> = {
|
|
|
17
17
|
|
|
18
18
|
// Terminal states are self-contained solid discs; the shape names the state. Running and
|
|
19
19
|
// pending render their own shapes (the live Dot and a bold ring), so they aren't here.
|
|
20
|
-
const glyphByKind: Partial<Record<
|
|
20
|
+
const glyphByKind: Partial<Record<WorkflowDisplayStatus, IconName>> = {
|
|
21
21
|
succeeded: 'checkCircleSolid',
|
|
22
22
|
failed: 'xCircleSolid',
|
|
23
23
|
cancelled: 'forbid2Fill',
|
|
@@ -45,8 +45,7 @@ const dotSizeClass: Record<number, string> = {
|
|
|
45
45
|
const PENDING_RING_MASK = 'radial-gradient(circle closest-side, transparent 0 52%, #000 56%)';
|
|
46
46
|
|
|
47
47
|
export interface WorkflowStatusIconProps {
|
|
48
|
-
status:
|
|
49
|
-
jobMode?: 'one_shot' | 'listening';
|
|
48
|
+
status: WorkflowDisplayStatus;
|
|
50
49
|
/** Optical diameter in px: 14 in the DAG node and run row. */
|
|
51
50
|
size?: number;
|
|
52
51
|
/** Pulsing halo for the running state. */
|
|
@@ -66,7 +65,6 @@ export interface WorkflowStatusIconProps {
|
|
|
66
65
|
*/
|
|
67
66
|
export function WorkflowStatusIcon({
|
|
68
67
|
status,
|
|
69
|
-
jobMode = 'one_shot',
|
|
70
68
|
size = 14,
|
|
71
69
|
ripple = true,
|
|
72
70
|
tooltip = true,
|
|
@@ -76,25 +74,20 @@ export function WorkflowStatusIcon({
|
|
|
76
74
|
const box = dotSizeClass[size] ?? 'size-14';
|
|
77
75
|
|
|
78
76
|
let glyph: ReactNode;
|
|
79
|
-
if (visual.kind === '
|
|
80
|
-
glyph =
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
/>
|
|
94
|
-
</span>
|
|
95
|
-
) : (
|
|
96
|
-
<Dot variant="info" ripple={ripple} className={box} />
|
|
97
|
-
);
|
|
77
|
+
if (visual.kind === 'listening') {
|
|
78
|
+
glyph = (
|
|
79
|
+
<span
|
|
80
|
+
className={cn(
|
|
81
|
+
'inline-flex shrink-0 items-center justify-center rounded-full bg-current',
|
|
82
|
+
box,
|
|
83
|
+
toneByVariant.info,
|
|
84
|
+
)}
|
|
85
|
+
>
|
|
86
|
+
<Icon name="pulseLine" size={Math.max(8, Math.round(size * 0.7))} className="text-white" />
|
|
87
|
+
</span>
|
|
88
|
+
);
|
|
89
|
+
} else if (visual.kind === 'running') {
|
|
90
|
+
glyph = <Dot variant="info" ripple={ripple} className={box} />;
|
|
98
91
|
} else if (visual.kind === 'pending') {
|
|
99
92
|
glyph = (
|
|
100
93
|
<span
|
|
@@ -2,11 +2,16 @@ import {type Duration, intervalToDuration} from 'date-fns';
|
|
|
2
2
|
import type {Step} from './step.js';
|
|
3
3
|
|
|
4
4
|
export type JobExecutionStatus = 'pending' | 'running' | 'succeeded' | 'failed' | 'cancelled';
|
|
5
|
+
export type JobExecutionDisplayStatus = JobExecutionStatus;
|
|
5
6
|
export interface WorkflowExecutionEvent {
|
|
6
7
|
source: string;
|
|
7
8
|
event: string;
|
|
8
9
|
deliveryId: string;
|
|
9
10
|
receivedAt: string;
|
|
11
|
+
project: {id: string} | null;
|
|
12
|
+
repository: string | null;
|
|
13
|
+
ref: string | null;
|
|
14
|
+
commit: string | null;
|
|
10
15
|
data: unknown;
|
|
11
16
|
}
|
|
12
17
|
export type JobExecutionTime =
|
|
@@ -53,6 +58,10 @@ export class JobExecution {
|
|
|
53
58
|
Object.assign(this, fields);
|
|
54
59
|
}
|
|
55
60
|
|
|
61
|
+
get displayName(): string {
|
|
62
|
+
return this.name;
|
|
63
|
+
}
|
|
64
|
+
|
|
56
65
|
get queueTime(): JobExecutionTime | null {
|
|
57
66
|
return jobExecutionQueueTimeFromTimestamps(this);
|
|
58
67
|
}
|
|
@@ -66,6 +75,19 @@ export class JobExecution {
|
|
|
66
75
|
}
|
|
67
76
|
}
|
|
68
77
|
|
|
78
|
+
export function deriveJobExecutionDisplayStatus({
|
|
79
|
+
status,
|
|
80
|
+
steps,
|
|
81
|
+
}: Pick<JobExecution, 'status' | 'steps'>): JobExecutionDisplayStatus {
|
|
82
|
+
if (isTerminalJobExecutionStatus(status)) return status;
|
|
83
|
+
if (steps.some((step) => step.status === 'running')) return 'running';
|
|
84
|
+
return 'pending';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function isTerminalJobExecutionStatus(status: JobExecutionStatus): boolean {
|
|
88
|
+
return status === 'succeeded' || status === 'failed' || status === 'cancelled';
|
|
89
|
+
}
|
|
90
|
+
|
|
69
91
|
export function jobExecutionQueueTimeFromTimestamps({
|
|
70
92
|
queuedAt,
|
|
71
93
|
startedAt,
|