@shipfox/client-workflows 14.0.1 → 16.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.css +4 -0
- package/.storybook/preview.tsx +7 -2
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +42 -0
- package/dist/components/job-detail/agent-config-failure-callout.d.ts.map +1 -0
- package/dist/components/{workflow-run-view → job-detail}/agent-config-failure-callout.js +2 -2
- package/dist/components/job-detail/agent-config-failure-callout.js.map +1 -0
- package/dist/components/job-detail/job-detail-header.d.ts +15 -0
- package/dist/components/job-detail/job-detail-header.d.ts.map +1 -0
- package/dist/components/job-detail/job-detail-header.js +124 -0
- package/dist/components/job-detail/job-detail-header.js.map +1 -0
- package/dist/components/job-detail/job-detail-view.d.ts +16 -0
- package/dist/components/job-detail/job-detail-view.d.ts.map +1 -0
- package/dist/components/job-detail/job-detail-view.js +420 -0
- package/dist/components/job-detail/job-detail-view.js.map +1 -0
- package/dist/components/job-detail/job-empty-states.d.ts +10 -0
- package/dist/components/job-detail/job-empty-states.d.ts.map +1 -0
- package/dist/components/job-detail/job-empty-states.js +195 -0
- package/dist/components/job-detail/job-empty-states.js.map +1 -0
- package/dist/components/job-detail/job-execution-switcher.d.ts.map +1 -0
- package/dist/components/job-detail/job-execution-switcher.js.map +1 -0
- package/dist/components/job-detail/job-execution-time-text.d.ts.map +1 -0
- package/dist/components/job-detail/job-execution-time-text.js.map +1 -0
- package/dist/components/job-detail/job-selection.d.ts +24 -0
- package/dist/components/job-detail/job-selection.d.ts.map +1 -0
- package/dist/components/job-detail/job-selection.js +66 -0
- package/dist/components/job-detail/job-selection.js.map +1 -0
- package/dist/components/job-detail/step-attempt-log-panel.d.ts +15 -0
- package/dist/components/job-detail/step-attempt-log-panel.d.ts.map +1 -0
- package/dist/components/{workflow-run-view → job-detail}/step-attempt-log-panel.js +61 -16
- package/dist/components/job-detail/step-attempt-log-panel.js.map +1 -0
- package/dist/components/job-graph/job-graph-content.js +1 -1
- package/dist/components/job-graph/job-graph-content.js.map +1 -1
- package/dist/components/step-list/step-list-model.js +2 -2
- package/dist/components/step-list/step-list-model.js.map +1 -1
- package/dist/components/step-list/step-list.d.ts +1 -0
- package/dist/components/step-list/step-list.d.ts.map +1 -1
- package/dist/components/step-list/step-list.js +29 -4
- package/dist/components/step-list/step-list.js.map +1 -1
- package/dist/components/workflow-run-duration-label.d.ts +9 -4
- package/dist/components/workflow-run-duration-label.d.ts.map +1 -1
- package/dist/components/workflow-run-duration-label.js +27 -7
- package/dist/components/workflow-run-duration-label.js.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-row.d.ts.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-row.js +6 -5
- package/dist/components/workflow-run-list/workflow-run-row.js.map +1 -1
- package/dist/components/workflow-run-summary/workflow-run-summary.d.ts.map +1 -1
- package/dist/components/workflow-run-summary/workflow-run-summary.js +69 -64
- package/dist/components/workflow-run-summary/workflow-run-summary.js.map +1 -1
- package/dist/components/workflow-run-tabs/index.d.ts +3 -3
- package/dist/components/workflow-run-tabs/index.d.ts.map +1 -1
- package/dist/components/workflow-run-tabs/index.js +3 -3
- package/dist/components/workflow-run-tabs/index.js.map +1 -1
- package/dist/components/workflow-run-tabs/run-annotation-count-chip.d.ts +16 -0
- package/dist/components/workflow-run-tabs/run-annotation-count-chip.d.ts.map +1 -0
- package/dist/components/workflow-run-tabs/run-annotation-count-chip.js +50 -0
- package/dist/components/workflow-run-tabs/run-annotation-count-chip.js.map +1 -0
- package/dist/components/workflow-run-tabs/run-annotation-item.d.ts +20 -0
- package/dist/components/workflow-run-tabs/run-annotation-item.d.ts.map +1 -0
- package/dist/components/workflow-run-tabs/run-annotation-item.js +102 -0
- package/dist/components/workflow-run-tabs/run-annotation-item.js.map +1 -0
- package/dist/components/workflow-run-tabs/run-annotation-list.d.ts +32 -0
- package/dist/components/workflow-run-tabs/run-annotation-list.d.ts.map +1 -0
- package/dist/components/workflow-run-tabs/run-annotation-list.js +202 -0
- package/dist/components/workflow-run-tabs/run-annotation-list.js.map +1 -0
- package/dist/components/workflow-run-tabs/run-annotation-summary-line.d.ts +1 -1
- package/dist/components/workflow-run-tabs/run-annotation-summary-line.d.ts.map +1 -1
- package/dist/components/workflow-run-tabs/run-annotation-summary-line.js +87 -22
- package/dist/components/workflow-run-tabs/run-annotation-summary-line.js.map +1 -1
- package/dist/components/workflow-run-tabs/run-annotations-empty.d.ts +3 -1
- package/dist/components/workflow-run-tabs/run-annotations-empty.d.ts.map +1 -1
- package/dist/components/workflow-run-tabs/run-annotations-empty.js +2 -2
- package/dist/components/workflow-run-tabs/run-annotations-empty.js.map +1 -1
- package/dist/components/workflow-run-tabs/severity-visuals.d.ts +20 -0
- package/dist/components/workflow-run-tabs/severity-visuals.d.ts.map +1 -0
- package/dist/components/workflow-run-tabs/severity-visuals.js +31 -0
- package/dist/components/workflow-run-tabs/severity-visuals.js.map +1 -0
- package/dist/components/workflow-run-view/index.d.ts +0 -1
- package/dist/components/workflow-run-view/index.d.ts.map +1 -1
- package/dist/components/workflow-run-view/index.js +0 -1
- package/dist/components/workflow-run-view/index.js.map +1 -1
- package/dist/components/workflow-run-view/run-workspace-nav.d.ts +20 -0
- package/dist/components/workflow-run-view/run-workspace-nav.d.ts.map +1 -0
- package/dist/components/workflow-run-view/run-workspace-nav.js +277 -0
- package/dist/components/workflow-run-view/run-workspace-nav.js.map +1 -0
- package/dist/components/workflow-run-view/workflow-run-selection.d.ts.map +1 -1
- package/dist/components/workflow-run-view/workflow-run-selection.js +1 -14
- package/dist/components/workflow-run-view/workflow-run-selection.js.map +1 -1
- package/dist/components/workflow-run-view/workflow-run-view.d.ts +10 -10
- package/dist/components/workflow-run-view/workflow-run-view.d.ts.map +1 -1
- package/dist/components/workflow-run-view/workflow-run-view.js +351 -239
- package/dist/components/workflow-run-view/workflow-run-view.js.map +1 -1
- package/dist/components/workflow-status/status-visuals.d.ts.map +1 -1
- package/dist/components/workflow-status/status-visuals.js +10 -2
- package/dist/components/workflow-status/status-visuals.js.map +1 -1
- package/dist/components/workflow-status/workflow-status-icon.d.ts +3 -1
- package/dist/components/workflow-status/workflow-status-icon.d.ts.map +1 -1
- package/dist/components/workflow-status/workflow-status-icon.js +13 -2
- package/dist/components/workflow-status/workflow-status-icon.js.map +1 -1
- package/dist/core/entities/job-execution.d.ts +9 -0
- package/dist/core/entities/job-execution.d.ts.map +1 -1
- package/dist/core/entities/job-execution.js +13 -3
- package/dist/core/entities/job-execution.js.map +1 -1
- package/dist/core/entities/job.d.ts.map +1 -1
- package/dist/core/entities/step-attempt.d.ts.map +1 -1
- package/dist/core/entities/step.d.ts +2 -0
- package/dist/core/entities/step.d.ts.map +1 -1
- package/dist/core/entities/step.js +13 -0
- package/dist/core/entities/step.js.map +1 -1
- package/dist/core/entities/workflow-run-attempt.d.ts.map +1 -1
- package/dist/core/entities/workflow-run.d.ts +56 -1
- package/dist/core/entities/workflow-run.d.ts.map +1 -1
- package/dist/core/entities/workflow-run.js +98 -0
- package/dist/core/entities/workflow-run.js.map +1 -1
- package/dist/core/run-annotation.d.ts +73 -0
- package/dist/core/run-annotation.d.ts.map +1 -0
- package/dist/core/run-annotation.js +125 -0
- package/dist/core/run-annotation.js.map +1 -0
- package/dist/core/workflow-run.d.ts +4 -4
- 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 +20 -16
- package/dist/feature.d.ts.map +1 -1
- package/dist/feature.js +5 -0
- package/dist/feature.js.map +1 -1
- package/dist/hooks/api/run-annotation-mapper.d.ts +10 -0
- package/dist/hooks/api/run-annotation-mapper.d.ts.map +1 -0
- package/dist/hooks/api/run-annotation-mapper.js +22 -0
- package/dist/hooks/api/run-annotation-mapper.js.map +1 -0
- package/dist/hooks/api/run-annotations.d.ts +25 -0
- package/dist/hooks/api/run-annotations.d.ts.map +1 -0
- package/dist/hooks/api/run-annotations.js +78 -0
- package/dist/hooks/api/run-annotations.js.map +1 -0
- package/dist/hooks/api/workflow-runs.d.ts +5 -3
- package/dist/hooks/api/workflow-runs.d.ts.map +1 -1
- package/dist/hooks/api/workflow-runs.js +5 -4
- package/dist/hooks/api/workflow-runs.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/pages/workflow-job-detail-page.d.ts +11 -0
- package/dist/pages/workflow-job-detail-page.d.ts.map +1 -0
- package/dist/pages/workflow-job-detail-page.js +66 -0
- package/dist/pages/workflow-job-detail-page.js.map +1 -0
- package/dist/pages/workflow-run-detail-page.d.ts.map +1 -1
- package/dist/pages/workflow-run-detail-page.js +4 -37
- package/dist/pages/workflow-run-detail-page.js.map +1 -1
- package/dist/routes/inputs.d.ts +52 -34
- package/dist/routes/inputs.d.ts.map +1 -1
- package/dist/routes/inputs.js +74 -43
- package/dist/routes/inputs.js.map +1 -1
- package/dist/routes/job-detail.d.ts +10 -0
- package/dist/routes/job-detail.d.ts.map +1 -0
- package/dist/routes/job-detail.js +27 -0
- package/dist/routes/job-detail.js.map +1 -0
- package/dist/routes/project-route.d.ts +1 -1
- package/dist/routes/run-detail.d.ts.map +1 -1
- package/dist/routes/runs.d.ts.map +1 -1
- package/dist/routes/workflows.d.ts.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +10 -9
- package/src/components/{workflow-run-view → job-detail}/agent-config-failure-callout.stories.tsx +1 -1
- package/src/components/{workflow-run-view → job-detail}/agent-config-failure-callout.tsx +2 -2
- package/src/components/job-detail/job-detail-header.test.tsx +34 -0
- package/src/components/job-detail/job-detail-header.tsx +136 -0
- package/src/components/job-detail/job-detail-view.stories.tsx +531 -0
- package/src/components/job-detail/job-detail-view.tsx +467 -0
- package/src/components/job-detail/job-empty-states.test.ts +27 -0
- package/src/components/job-detail/job-empty-states.tsx +248 -0
- package/src/components/job-detail/job-selection.test.ts +188 -0
- package/src/components/job-detail/job-selection.ts +96 -0
- package/src/components/{workflow-run-view → job-detail}/step-attempt-log-panel.test.tsx +32 -9
- package/src/components/{workflow-run-view → job-detail}/step-attempt-log-panel.tsx +52 -18
- package/src/components/job-graph/job-graph-content.tsx +1 -1
- package/src/components/step-list/step-list-model.test.ts +1 -1
- package/src/components/step-list/step-list-model.ts +2 -2
- package/src/components/step-list/step-list.stories.tsx +1 -1
- package/src/components/step-list/step-list.test.tsx +33 -0
- package/src/components/step-list/step-list.tsx +40 -9
- package/src/components/workflow-run-duration-label.tsx +50 -12
- package/src/components/workflow-run-list/workflow-run-row.tsx +6 -4
- package/src/components/workflow-run-summary/workflow-run-summary.stories.tsx +38 -0
- package/src/components/workflow-run-summary/workflow-run-summary.test.tsx +79 -5
- package/src/components/workflow-run-summary/workflow-run-summary.tsx +56 -60
- package/src/components/workflow-run-tabs/index.ts +3 -3
- package/src/components/workflow-run-tabs/run-annotation-count-chip.tsx +63 -0
- package/src/components/workflow-run-tabs/run-annotation-item.tsx +123 -0
- package/src/components/workflow-run-tabs/run-annotation-list.tsx +258 -0
- package/src/components/workflow-run-tabs/run-annotation-summary-line.test.tsx +77 -3
- package/src/components/workflow-run-tabs/run-annotation-summary-line.tsx +92 -17
- package/src/components/workflow-run-tabs/run-annotations-empty.tsx +6 -2
- package/src/components/workflow-run-tabs/run-annotations.stories.tsx +293 -0
- package/src/components/workflow-run-tabs/severity-visuals.ts +37 -0
- package/src/components/workflow-run-view/index.ts +0 -1
- package/src/components/workflow-run-view/run-workspace-nav.test.tsx +286 -0
- package/src/components/workflow-run-view/run-workspace-nav.tsx +314 -0
- package/src/components/workflow-run-view/workflow-run-selection.ts +1 -23
- package/src/components/workflow-run-view/workflow-run-view.test.tsx +237 -1085
- package/src/components/workflow-run-view/workflow-run-view.tsx +450 -323
- package/src/components/workflow-status/status-visuals.ts +5 -2
- package/src/components/workflow-status/workflow-status-icon.stories.tsx +1 -0
- package/src/components/workflow-status/workflow-status-icon.tsx +26 -1
- package/src/core/entities/job-execution.ts +17 -2
- package/src/core/entities/step.ts +26 -0
- package/src/core/entities/workflow-run-display.test.ts +159 -0
- package/src/core/entities/workflow-run.ts +139 -1
- package/src/core/run-annotation.test.ts +315 -0
- package/src/core/run-annotation.ts +238 -0
- package/src/core/workflow-run.ts +16 -1
- package/src/feature.ts +5 -0
- package/src/hooks/api/run-annotation-mapper.ts +30 -0
- package/src/hooks/api/run-annotations.ts +137 -0
- package/src/hooks/api/workflow-runs.ts +6 -2
- package/src/index.ts +1 -0
- package/src/page-harness-budget.test.ts +1 -0
- package/src/pages/workflow-job-detail-page.test.tsx +605 -0
- package/src/pages/workflow-job-detail-page.tsx +84 -0
- package/src/pages/workflow-run-detail-page.tsx +3 -43
- package/src/pages/workflow-run-pages.test.tsx +77 -332
- package/src/routes/inputs.test.ts +56 -6
- package/src/routes/inputs.ts +71 -19
- package/src/routes/job-detail.tsx +29 -0
- package/test/pages.tsx +25 -3
- package/test/render.tsx +6 -1
- package/tsconfig.build.tsbuildinfo +1 -1
- package/dist/components/workflow-run-tabs/run-jobs-list.d.ts +0 -18
- package/dist/components/workflow-run-tabs/run-jobs-list.d.ts.map +0 -1
- package/dist/components/workflow-run-tabs/run-jobs-list.js +0 -107
- package/dist/components/workflow-run-tabs/run-jobs-list.js.map +0 -1
- package/dist/components/workflow-run-tabs/run-tab-count.d.ts +0 -6
- package/dist/components/workflow-run-tabs/run-tab-count.d.ts.map +0 -1
- package/dist/components/workflow-run-tabs/run-tab-count.js +0 -27
- package/dist/components/workflow-run-tabs/run-tab-count.js.map +0 -1
- package/dist/components/workflow-run-tabs/run-tab-strip.d.ts +0 -8
- package/dist/components/workflow-run-tabs/run-tab-strip.d.ts.map +0 -1
- package/dist/components/workflow-run-tabs/run-tab-strip.js +0 -82
- package/dist/components/workflow-run-tabs/run-tab-strip.js.map +0 -1
- package/dist/components/workflow-run-view/agent-config-failure-callout.d.ts.map +0 -1
- package/dist/components/workflow-run-view/agent-config-failure-callout.js.map +0 -1
- package/dist/components/workflow-run-view/job-card.d.ts +0 -17
- package/dist/components/workflow-run-view/job-card.d.ts.map +0 -1
- package/dist/components/workflow-run-view/job-card.js +0 -496
- package/dist/components/workflow-run-view/job-card.js.map +0 -1
- package/dist/components/workflow-run-view/job-execution-switcher.d.ts.map +0 -1
- package/dist/components/workflow-run-view/job-execution-switcher.js.map +0 -1
- package/dist/components/workflow-run-view/job-execution-time-text.d.ts.map +0 -1
- package/dist/components/workflow-run-view/job-execution-time-text.js.map +0 -1
- package/dist/components/workflow-run-view/step-attempt-log-panel.d.ts +0 -9
- package/dist/components/workflow-run-view/step-attempt-log-panel.d.ts.map +0 -1
- package/dist/components/workflow-run-view/step-attempt-log-panel.js.map +0 -1
- package/dist/core/workflow-run-tabs.d.ts +0 -9
- package/dist/core/workflow-run-tabs.d.ts.map +0 -1
- package/dist/core/workflow-run-tabs.js +0 -3
- package/dist/core/workflow-run-tabs.js.map +0 -1
- package/src/components/workflow-run-tabs/run-jobs-list.tsx +0 -151
- package/src/components/workflow-run-tabs/run-tab-count.tsx +0 -28
- package/src/components/workflow-run-tabs/run-tab-strip.stories.tsx +0 -52
- package/src/components/workflow-run-tabs/run-tab-strip.test.tsx +0 -117
- package/src/components/workflow-run-tabs/run-tab-strip.tsx +0 -97
- package/src/components/workflow-run-view/job-card.stories.tsx +0 -789
- package/src/components/workflow-run-view/job-card.test.tsx +0 -115
- package/src/components/workflow-run-view/job-card.tsx +0 -614
- package/src/core/workflow-run-tabs.ts +0 -8
- /package/dist/components/{workflow-run-view → job-detail}/agent-config-failure-callout.d.ts +0 -0
- /package/dist/components/{workflow-run-view → job-detail}/job-execution-switcher.d.ts +0 -0
- /package/dist/components/{workflow-run-view → job-detail}/job-execution-switcher.js +0 -0
- /package/dist/components/{workflow-run-view → job-detail}/job-execution-time-text.d.ts +0 -0
- /package/dist/components/{workflow-run-view → job-detail}/job-execution-time-text.js +0 -0
- /package/src/components/{workflow-run-view → job-detail}/job-execution-switcher.tsx +0 -0
- /package/src/components/{workflow-run-view → job-detail}/job-execution-time-text.tsx +0 -0
|
@@ -1,17 +1,12 @@
|
|
|
1
|
+
import type {AnnotationDto} from '@shipfox/annotations-dto';
|
|
1
2
|
import type {WorkflowRunDetailResponseDto} from '@shipfox/api-workflows-dto';
|
|
2
3
|
import {configureApiClient} from '@shipfox/client-api';
|
|
3
|
-
import {toast} from '@shipfox/react-ui/toast';
|
|
4
4
|
import {screen, waitFor, within} from '@testing-library/react';
|
|
5
5
|
import userEvent from '@testing-library/user-event';
|
|
6
|
-
import {workflowRunsQueryKeys} from '#hooks/api/workflow-runs.js';
|
|
7
|
-
import {inlineLogBody, outputLine} from '#test/fixtures/logs.js';
|
|
8
6
|
import {
|
|
9
|
-
runAttemptsResponseDto,
|
|
10
7
|
workflowJobDto,
|
|
11
8
|
workflowJobExecutionDto,
|
|
12
|
-
workflowRunAttemptDto,
|
|
13
9
|
workflowRunDetailDto,
|
|
14
|
-
workflowRunDto,
|
|
15
10
|
workflowStepAttemptDto,
|
|
16
11
|
workflowStepDto,
|
|
17
12
|
} from '#test/fixtures/workflow-run.js';
|
|
@@ -20,700 +15,275 @@ import {WorkflowRunView} from './workflow-run-view.js';
|
|
|
20
15
|
|
|
21
16
|
const RUN_ID = '66666666-6666-4666-8666-666666666666';
|
|
22
17
|
const PROJECT_ID = '44444444-4444-4444-8444-444444444444';
|
|
23
|
-
const DEFINITION_ID = '55555555-5555-4555-8555-555555555555';
|
|
24
18
|
const BUILD_JOB_ID = '77777777-7777-4777-8777-777777777777';
|
|
25
19
|
const DEPLOY_JOB_ID = '88888888-8888-4888-8888-888888888888';
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
const
|
|
33
|
-
const EXECUTION_ONE_MENU_ITEM_PATTERN = /#1/;
|
|
34
|
-
const LISTENING_EXECUTION_SWITCHER_NAME =
|
|
35
|
-
'Switch job execution, currently execution 2: Deployment status event';
|
|
36
|
-
const JOB_TEMPLATE_NAME = ['Implement $', '{{ event.issue.identifier }}'].join('');
|
|
20
|
+
const BUILD_EXECUTION_ID = '99999999-9999-4999-8999-000000000001';
|
|
21
|
+
const BUILD_STEP_ID = '55555555-5555-4555-8555-000000000001';
|
|
22
|
+
const BUILD_ATTEMPT_ID = '66666666-6666-4666-8666-000000000001';
|
|
23
|
+
const ANNOTATION_ID_ONE = 'aaaaaaaa-aaaa-4aaa-8aaa-000000000001';
|
|
24
|
+
const ANNOTATION_ID_TWO = 'aaaaaaaa-aaaa-4aaa-8aaa-000000000002';
|
|
25
|
+
const TASK_NINE_PATTERN = /Task nine/;
|
|
26
|
+
const SHOW_MORE_PATTERN = /Show \d+ more/;
|
|
37
27
|
|
|
38
28
|
describe('WorkflowRunView', () => {
|
|
39
|
-
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
Element.prototype.scrollIntoView = vi.fn();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('reserves the shared run workspace while the run is loading', async () => {
|
|
40
34
|
configureApiClient({fetchImpl: vi.fn(() => new Promise<Response>(() => undefined))});
|
|
41
35
|
|
|
42
|
-
renderView(
|
|
36
|
+
renderView();
|
|
43
37
|
|
|
44
|
-
expect(await screen.findByRole('
|
|
45
|
-
expect(screen.
|
|
46
|
-
expect(screen.getByRole('tab', {name: 'Annotations'})).toBeInTheDocument();
|
|
47
|
-
expect(screen.getByRole('tab', {name: 'Source'})).toBeInTheDocument();
|
|
38
|
+
expect(await screen.findByRole('region', {name: 'Loading workflow run'})).toBeInTheDocument();
|
|
39
|
+
expect(screen.getByLabelText('Loading run navigation')).toBeInTheDocument();
|
|
48
40
|
expect(screen.getByRole('region', {name: 'Loading workflow run content'})).toBeInTheDocument();
|
|
41
|
+
expect(screen.queryByRole('tab', {name: 'Jobs'})).not.toBeInTheDocument();
|
|
49
42
|
});
|
|
50
43
|
|
|
51
|
-
test('
|
|
52
|
-
|
|
53
|
-
fetchImpl: vi.fn(() => Promise.resolve(jsonResponse(workflowRunViewDetailDto()))),
|
|
54
|
-
});
|
|
44
|
+
test('opens the all-jobs Summary on the dependency graph by default', async () => {
|
|
45
|
+
configureRunFetch();
|
|
55
46
|
|
|
56
|
-
renderView();
|
|
47
|
+
const {container} = renderView();
|
|
57
48
|
|
|
58
49
|
const summary = await screen.findByRole('region', {name: 'deploy-web'});
|
|
59
|
-
|
|
60
50
|
expect(within(summary).getByRole('heading', {name: 'deploy-web'})).toBeInTheDocument();
|
|
61
|
-
expect(
|
|
62
|
-
expect(within(summary).getByText('fire')).toBeInTheDocument();
|
|
51
|
+
expect(screen.getByRole('navigation', {name: 'Run workspace'})).toBeInTheDocument();
|
|
63
52
|
expect(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
expect(screen.getByRole('
|
|
69
|
-
expect(screen.getByRole('
|
|
70
|
-
|
|
71
|
-
|
|
53
|
+
screen
|
|
54
|
+
.getByRole('navigation', {name: 'Run workspace'})
|
|
55
|
+
.closest('[data-run-workspace-layout]'),
|
|
56
|
+
).toHaveClass('border-t', 'border-border-neutral-base', 'min-[768px]:flex-row');
|
|
57
|
+
expect(screen.getByRole('link', {name: 'Summary'})).toHaveAttribute('aria-current', 'page');
|
|
58
|
+
expect(screen.getByRole('heading', {name: 'Jobs'})).toBeInTheDocument();
|
|
59
|
+
expect(screen.getByRole('heading', {name: 'Run details'})).toBeInTheDocument();
|
|
60
|
+
expect(screen.getByRole('region', {name: 'All jobs summary'})).toBeInTheDocument();
|
|
61
|
+
expect(screen.getByRole('region', {name: 'Workflow jobs'})).toBeInTheDocument();
|
|
62
|
+
expect(container.querySelector('[data-run-workspace-content]')).toHaveClass(
|
|
63
|
+
'bg-background-neutral-base',
|
|
64
|
+
'flex-1',
|
|
72
65
|
);
|
|
73
|
-
expect(screen.
|
|
74
|
-
expect(screen.getByRole('region', {name: 'build'})).toBeInTheDocument();
|
|
75
|
-
expect(
|
|
76
|
-
screen.getByRole('button', {name: 'checkout, Succeeded, attempt 1'}),
|
|
77
|
-
).toBeInTheDocument();
|
|
66
|
+
expect(screen.queryByRole('tab', {name: 'Jobs'})).not.toBeInTheDocument();
|
|
78
67
|
});
|
|
79
68
|
|
|
80
|
-
test('
|
|
81
|
-
|
|
82
|
-
fetchImpl: vi.fn(() =>
|
|
83
|
-
Promise.resolve(
|
|
84
|
-
jsonResponse(
|
|
85
|
-
workflowRunViewDetailDto({
|
|
86
|
-
jobs: [
|
|
87
|
-
workflowJobDto({
|
|
88
|
-
id: BUILD_JOB_ID,
|
|
89
|
-
run_attempt_id: RUN_ID,
|
|
90
|
-
name: JOB_TEMPLATE_NAME,
|
|
91
|
-
job_executions: [
|
|
92
|
-
workflowJobExecutionDto({
|
|
93
|
-
job_id: BUILD_JOB_ID,
|
|
94
|
-
name: 'Implement ENG-123',
|
|
95
|
-
}),
|
|
96
|
-
],
|
|
97
|
-
}),
|
|
98
|
-
],
|
|
99
|
-
}),
|
|
100
|
-
),
|
|
101
|
-
),
|
|
102
|
-
),
|
|
103
|
-
});
|
|
69
|
+
test('treats the removed Jobs tab URL as the graph Summary', async () => {
|
|
70
|
+
configureRunFetch();
|
|
104
71
|
|
|
105
|
-
renderView();
|
|
72
|
+
renderView({tab: 'jobs'});
|
|
106
73
|
|
|
107
|
-
expect(await screen.findByRole('region', {name: '
|
|
108
|
-
expect(screen.
|
|
74
|
+
expect(await screen.findByRole('region', {name: 'All jobs summary'})).toBeInTheDocument();
|
|
75
|
+
expect(screen.getByRole('link', {name: 'Summary'})).toHaveAttribute('aria-current', 'page');
|
|
109
76
|
});
|
|
110
77
|
|
|
111
|
-
test('
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
const url = requestUrl(input);
|
|
115
|
-
if (url.pathname === `/steps/${stepId}/attempts/1/logs`) {
|
|
116
|
-
return Promise.resolve(jsonResponse(inlineLogBody(outputLine('live output\n'), 1)));
|
|
117
|
-
}
|
|
118
|
-
return Promise.resolve(
|
|
119
|
-
jsonResponse(
|
|
120
|
-
workflowRunViewDetailDto({
|
|
121
|
-
jobs: [
|
|
122
|
-
workflowJobDto({
|
|
123
|
-
id: BUILD_JOB_ID,
|
|
124
|
-
run_attempt_id: RUN_ID,
|
|
125
|
-
name: 'build',
|
|
126
|
-
status: 'running',
|
|
127
|
-
steps: [
|
|
128
|
-
workflowStepDto({
|
|
129
|
-
id: stepId,
|
|
130
|
-
key: 'test',
|
|
131
|
-
name: 'test',
|
|
132
|
-
status: 'running',
|
|
133
|
-
attempts: [
|
|
134
|
-
workflowStepAttemptDto({
|
|
135
|
-
id: 'aaaaaaaa-aaaa-4aaa-8aaa-000000000001',
|
|
136
|
-
step_id: stepId,
|
|
137
|
-
status: 'running',
|
|
138
|
-
exit_code: null,
|
|
139
|
-
finished_at: null,
|
|
140
|
-
}),
|
|
141
|
-
],
|
|
142
|
-
}),
|
|
143
|
-
],
|
|
144
|
-
}),
|
|
145
|
-
],
|
|
146
|
-
}),
|
|
147
|
-
),
|
|
148
|
-
);
|
|
149
|
-
});
|
|
150
|
-
configureApiClient({fetchImpl: fetchImpl as typeof fetch});
|
|
78
|
+
test('navigates graph and rail jobs to their dedicated job routes', async () => {
|
|
79
|
+
const user = userEvent.setup();
|
|
80
|
+
configureRunFetch();
|
|
151
81
|
|
|
152
|
-
renderView();
|
|
82
|
+
const {router} = renderView();
|
|
83
|
+
await user.click(await screen.findByRole('button', {name: 'deploy, Running'}));
|
|
153
84
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
85
|
+
await waitFor(() =>
|
|
86
|
+
expect(router.state.location.pathname).toBe(
|
|
87
|
+
`/w/${PROJECT_TEST_WSLUG}/p/project/runs/${RUN_ID}/jobs/${DEPLOY_JOB_ID}`,
|
|
88
|
+
),
|
|
89
|
+
);
|
|
159
90
|
});
|
|
160
91
|
|
|
161
|
-
test('
|
|
92
|
+
test('keeps run Annotations and Source in the workspace navigation', async () => {
|
|
162
93
|
const user = userEvent.setup();
|
|
163
|
-
|
|
164
|
-
fetchImpl: vi.fn(() =>
|
|
165
|
-
Promise.resolve(
|
|
166
|
-
jsonResponse(
|
|
167
|
-
workflowRunViewDetailDto({
|
|
168
|
-
jobs: [
|
|
169
|
-
workflowJobDto({
|
|
170
|
-
id: BUILD_JOB_ID,
|
|
171
|
-
run_attempt_id: RUN_ID,
|
|
172
|
-
name: 'release-gates',
|
|
173
|
-
mode: 'listening',
|
|
174
|
-
status: 'running',
|
|
175
|
-
listener_status: 'listening',
|
|
176
|
-
job_executions: [
|
|
177
|
-
workflowJobExecutionDto({
|
|
178
|
-
id: '77777777-7777-4777-8777-000000000001',
|
|
179
|
-
job_id: BUILD_JOB_ID,
|
|
180
|
-
sequence: 1,
|
|
181
|
-
name: 'Pull request event',
|
|
182
|
-
status: 'succeeded',
|
|
183
|
-
trigger_events: [
|
|
184
|
-
{
|
|
185
|
-
source: 'github',
|
|
186
|
-
event: 'pull_request',
|
|
187
|
-
delivery_id: 'delivery-1',
|
|
188
|
-
received_at: '2026-05-07T01:00:00.000Z',
|
|
189
|
-
project: null,
|
|
190
|
-
repository: null,
|
|
191
|
-
ref: null,
|
|
192
|
-
commit: null,
|
|
193
|
-
data: {number: 12},
|
|
194
|
-
},
|
|
195
|
-
],
|
|
196
|
-
started_at: '2026-05-07T01:01:00.000Z',
|
|
197
|
-
finished_at: '2026-05-07T01:01:20.000Z',
|
|
198
|
-
steps: [
|
|
199
|
-
workflowStepDto({
|
|
200
|
-
key: 'first-event',
|
|
201
|
-
name: 'first-event',
|
|
202
|
-
status: 'succeeded',
|
|
203
|
-
attempts: [
|
|
204
|
-
workflowStepAttemptDto({
|
|
205
|
-
status: 'succeeded',
|
|
206
|
-
finished_at: '2026-05-07T01:01:20.000Z',
|
|
207
|
-
}),
|
|
208
|
-
],
|
|
209
|
-
}),
|
|
210
|
-
],
|
|
211
|
-
}),
|
|
212
|
-
workflowJobExecutionDto({
|
|
213
|
-
id: '77777777-7777-4777-8777-000000000002',
|
|
214
|
-
job_id: BUILD_JOB_ID,
|
|
215
|
-
sequence: 2,
|
|
216
|
-
name: 'Deployment status event',
|
|
217
|
-
status: 'failed',
|
|
218
|
-
status_reason: 'step_failed',
|
|
219
|
-
trigger_events: [
|
|
220
|
-
{
|
|
221
|
-
source: 'github',
|
|
222
|
-
event: 'deployment_status',
|
|
223
|
-
delivery_id: 'delivery-2',
|
|
224
|
-
received_at: '2026-05-07T01:01:00.000Z',
|
|
225
|
-
project: null,
|
|
226
|
-
repository: null,
|
|
227
|
-
ref: null,
|
|
228
|
-
commit: null,
|
|
229
|
-
data: {state: 'success'},
|
|
230
|
-
},
|
|
231
|
-
{
|
|
232
|
-
source: 'github',
|
|
233
|
-
event: 'check_run',
|
|
234
|
-
delivery_id: 'delivery-3',
|
|
235
|
-
received_at: '2026-05-07T01:01:10.000Z',
|
|
236
|
-
project: null,
|
|
237
|
-
repository: null,
|
|
238
|
-
ref: null,
|
|
239
|
-
commit: null,
|
|
240
|
-
data: {status: 'completed'},
|
|
241
|
-
},
|
|
242
|
-
],
|
|
243
|
-
started_at: '2026-05-07T01:02:00.000Z',
|
|
244
|
-
finished_at: '2026-05-07T01:02:20.000Z',
|
|
245
|
-
steps: [
|
|
246
|
-
workflowStepDto({
|
|
247
|
-
key: 'second-event',
|
|
248
|
-
name: 'second-event',
|
|
249
|
-
status: 'failed',
|
|
250
|
-
attempts: [
|
|
251
|
-
workflowStepAttemptDto({
|
|
252
|
-
status: 'failed',
|
|
253
|
-
exit_code: 1,
|
|
254
|
-
finished_at: '2026-05-07T01:02:20.000Z',
|
|
255
|
-
}),
|
|
256
|
-
],
|
|
257
|
-
}),
|
|
258
|
-
],
|
|
259
|
-
}),
|
|
260
|
-
],
|
|
261
|
-
}),
|
|
262
|
-
],
|
|
263
|
-
}),
|
|
264
|
-
),
|
|
265
|
-
),
|
|
266
|
-
),
|
|
267
|
-
});
|
|
94
|
+
configureRunFetch();
|
|
268
95
|
|
|
269
|
-
renderView();
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
).toBeInTheDocument();
|
|
273
|
-
expect(await screen.findByText('second-event')).toBeInTheDocument();
|
|
274
|
-
expect(
|
|
275
|
-
await screen.findByRole('button', {name: 'github · deployment_status (2 events)'}),
|
|
276
|
-
).toBeInTheDocument();
|
|
277
|
-
expect(screen.getByText('deployment_status (2)')).toBeInTheDocument();
|
|
278
|
-
|
|
279
|
-
await user.click(screen.getByRole('button', {name: LISTENING_EXECUTION_SWITCHER_NAME}));
|
|
280
|
-
await user.click(screen.getByRole('menuitem', {name: EXECUTION_ONE_MENU_ITEM_PATTERN}));
|
|
96
|
+
const {router} = renderView();
|
|
97
|
+
await screen.findByRole('region', {name: 'Workflow jobs'});
|
|
98
|
+
await user.click(screen.getByRole('link', {name: 'Annotations'}));
|
|
281
99
|
|
|
282
|
-
expect(
|
|
283
|
-
expect(screen.getByRole('region', {name: 'Pull request event'})).toBeInTheDocument();
|
|
284
|
-
expect(screen.queryByText('second-event')).not.toBeInTheDocument();
|
|
285
|
-
expect(await screen.findByRole('button', {name: 'github · pull_request'})).toBeInTheDocument();
|
|
286
|
-
expect(screen.getByText('pull_request')).toBeInTheDocument();
|
|
287
|
-
expect(screen.queryByText('deployment_status (2)')).not.toBeInTheDocument();
|
|
100
|
+
await waitFor(() => expect(router.state.location.search).toMatchObject({tab: 'annotations'}));
|
|
288
101
|
});
|
|
289
102
|
|
|
290
|
-
test('
|
|
291
|
-
|
|
292
|
-
const stepId = '99999999-9999-4999-8999-000000000004';
|
|
293
|
-
const attemptId = 'aaaaaaaa-aaaa-4aaa-8aaa-000000000004';
|
|
294
|
-
const fetchImpl = vi.fn((input: RequestInfo | URL) => {
|
|
295
|
-
const url = requestUrl(input);
|
|
296
|
-
if (url.pathname === `/steps/${stepId}/attempts/1/logs`) {
|
|
297
|
-
return Promise.resolve(
|
|
298
|
-
jsonResponse(inlineLogBody(outputLine('configuration failed\n'), 1)),
|
|
299
|
-
);
|
|
300
|
-
}
|
|
301
|
-
return Promise.resolve(
|
|
302
|
-
jsonResponse(
|
|
303
|
-
workflowRunViewDetailDto({
|
|
304
|
-
jobs: [
|
|
305
|
-
workflowJobDto({
|
|
306
|
-
id: BUILD_JOB_ID,
|
|
307
|
-
run_attempt_id: RUN_ID,
|
|
308
|
-
name: 'build',
|
|
309
|
-
status: 'failed',
|
|
310
|
-
steps: [
|
|
311
|
-
workflowStepDto({
|
|
312
|
-
id: stepId,
|
|
313
|
-
key: 'implement',
|
|
314
|
-
name: 'Fix the failing tests.',
|
|
315
|
-
type: 'agent',
|
|
316
|
-
status: 'failed',
|
|
317
|
-
config: {
|
|
318
|
-
provider: 'anthropic',
|
|
319
|
-
model: 'claude-opus-4-8',
|
|
320
|
-
thinking: 'high',
|
|
321
|
-
prompt: 'Fix the failing tests.',
|
|
322
|
-
},
|
|
323
|
-
error: {
|
|
324
|
-
message: 'Model provider request failed',
|
|
325
|
-
reason: 'agent_invocation_failed',
|
|
326
|
-
category: 'user',
|
|
327
|
-
},
|
|
328
|
-
attempts: [
|
|
329
|
-
workflowStepAttemptDto({
|
|
330
|
-
id: attemptId,
|
|
331
|
-
step_id: stepId,
|
|
332
|
-
status: 'failed',
|
|
333
|
-
exit_code: 1,
|
|
334
|
-
error: {
|
|
335
|
-
message: 'Model provider credentials are not configured',
|
|
336
|
-
reason: 'agent_config_invalid',
|
|
337
|
-
agentConfigIssue: 'provider_not_configured',
|
|
338
|
-
},
|
|
339
|
-
finished_at: '2026-05-07T01:01:20.000Z',
|
|
340
|
-
}),
|
|
341
|
-
],
|
|
342
|
-
}),
|
|
343
|
-
],
|
|
344
|
-
}),
|
|
345
|
-
],
|
|
346
|
-
}),
|
|
347
|
-
),
|
|
348
|
-
);
|
|
349
|
-
});
|
|
350
|
-
configureApiClient({fetchImpl: fetchImpl as typeof fetch});
|
|
103
|
+
test('filters the single run-level Annotations page by job', async () => {
|
|
104
|
+
configureRunFetch();
|
|
351
105
|
|
|
352
|
-
renderView();
|
|
353
|
-
await user.click(
|
|
354
|
-
await screen.findByRole('button', {
|
|
355
|
-
name: 'Fix the failing tests., Failed, attempt 1, User',
|
|
356
|
-
}),
|
|
357
|
-
);
|
|
106
|
+
renderView({tab: 'annotations', selection: {jobId: BUILD_JOB_ID}});
|
|
358
107
|
|
|
359
108
|
expect(
|
|
360
|
-
screen.
|
|
361
|
-
).
|
|
362
|
-
expect(screen.
|
|
363
|
-
expect(screen.
|
|
364
|
-
expect(screen.queryByText('high')).not.toBeInTheDocument();
|
|
365
|
-
expect(screen.getByText('Configure credentials for anthropic')).toBeInTheDocument();
|
|
366
|
-
expect(
|
|
367
|
-
screen.getByText(
|
|
368
|
-
'This step uses anthropic, but no workspace credentials are configured for that model provider. Configure anthropic in Agents, then re-run the workflow.',
|
|
369
|
-
),
|
|
370
|
-
).toBeInTheDocument();
|
|
371
|
-
expect(screen.getByRole('link', {name: 'Configure Agents'})).toHaveAttribute(
|
|
372
|
-
'href',
|
|
373
|
-
`/w/${PROJECT_TEST_WSLUG}/settings/agents`,
|
|
374
|
-
);
|
|
109
|
+
await screen.findByRole('combobox', {name: 'Filter annotations by job'}),
|
|
110
|
+
).toHaveTextContent('build');
|
|
111
|
+
expect(await screen.findByText('This run has no annotations to show.')).toBeInTheDocument();
|
|
112
|
+
expect(screen.queryByRole('tab', {name: 'Annotations'})).not.toBeInTheDocument();
|
|
375
113
|
});
|
|
376
114
|
|
|
377
|
-
test('
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
jsonResponse(inlineLogBody(outputLine('configuration failed\n'), 1)),
|
|
387
|
-
);
|
|
388
|
-
}
|
|
389
|
-
return Promise.resolve(
|
|
390
|
-
jsonResponse(
|
|
391
|
-
workflowRunViewDetailDto({
|
|
392
|
-
jobs: [
|
|
393
|
-
workflowJobDto({
|
|
394
|
-
id: BUILD_JOB_ID,
|
|
395
|
-
run_attempt_id: RUN_ID,
|
|
396
|
-
name: 'build',
|
|
397
|
-
status: 'failed',
|
|
398
|
-
steps: [
|
|
399
|
-
workflowStepDto({
|
|
400
|
-
id: stepId,
|
|
401
|
-
key: 'implement',
|
|
402
|
-
name: 'Fix the failing tests.',
|
|
403
|
-
type: 'agent',
|
|
404
|
-
status: 'failed',
|
|
405
|
-
config: {
|
|
406
|
-
provider: 'anthropic',
|
|
407
|
-
model: 'claude-opus-4-8',
|
|
408
|
-
thinking: 'high',
|
|
409
|
-
prompt: 'Fix the failing tests.',
|
|
410
|
-
},
|
|
411
|
-
error: {
|
|
412
|
-
message: 'Model provider credentials are not configured',
|
|
413
|
-
reason: 'agent_config_invalid',
|
|
414
|
-
agent_config_issue: 'provider_not_configured',
|
|
415
|
-
category: 'user',
|
|
416
|
-
},
|
|
417
|
-
attempts: [
|
|
418
|
-
workflowStepAttemptDto({
|
|
419
|
-
id: attemptId,
|
|
420
|
-
step_id: stepId,
|
|
421
|
-
status: 'failed',
|
|
422
|
-
exit_code: 1,
|
|
423
|
-
error: null,
|
|
424
|
-
finished_at: '2026-05-07T01:01:20.000Z',
|
|
425
|
-
}),
|
|
426
|
-
],
|
|
427
|
-
}),
|
|
428
|
-
],
|
|
429
|
-
}),
|
|
430
|
-
],
|
|
431
|
-
}),
|
|
432
|
-
),
|
|
433
|
-
);
|
|
115
|
+
test('renders annotation bodies once, severity first, with provenance', async () => {
|
|
116
|
+
configureRunFetch([
|
|
117
|
+
annotationDto({id: ANNOTATION_ID_ONE, context: 'coverage', style: 'info', sequence: 1}),
|
|
118
|
+
annotationDto({
|
|
119
|
+
id: ANNOTATION_ID_TWO,
|
|
120
|
+
context: 'smoke check',
|
|
121
|
+
style: 'error',
|
|
122
|
+
sequence: 2,
|
|
123
|
+
body: 'Task nine **failed**.',
|
|
434
124
|
}),
|
|
435
|
-
|
|
125
|
+
]);
|
|
436
126
|
|
|
437
|
-
renderView();
|
|
438
|
-
await user.click(
|
|
439
|
-
await screen.findByRole('button', {
|
|
440
|
-
name: 'Fix the failing tests., Failed, attempt 1, User',
|
|
441
|
-
}),
|
|
442
|
-
);
|
|
127
|
+
renderView({tab: 'annotations'});
|
|
443
128
|
|
|
444
|
-
|
|
445
|
-
expect(
|
|
446
|
-
expect(screen.
|
|
447
|
-
expect(screen.
|
|
448
|
-
expect(screen.
|
|
449
|
-
'href',
|
|
450
|
-
`/w/${PROJECT_TEST_WSLUG}/settings/agents`,
|
|
451
|
-
);
|
|
129
|
+
const headings = await screen.findAllByRole('heading', {level: 3});
|
|
130
|
+
expect(headings.map((heading) => heading.textContent)).toEqual(['smoke check', 'coverage']);
|
|
131
|
+
expect(screen.getByText(TASK_NINE_PATTERN)).toBeInTheDocument();
|
|
132
|
+
expect(screen.getAllByText('build · checkout · attempt 1')).toHaveLength(2);
|
|
133
|
+
expect(screen.getByText('2 annotations')).toBeInTheDocument();
|
|
452
134
|
});
|
|
453
135
|
|
|
454
|
-
test('
|
|
455
|
-
|
|
456
|
-
fetchImpl: vi.fn(() =>
|
|
457
|
-
Promise.resolve(
|
|
458
|
-
jsonResponse(
|
|
459
|
-
workflowRunViewDetailDto({
|
|
460
|
-
jobs: [
|
|
461
|
-
workflowJobDto({
|
|
462
|
-
id: DEPLOY_JOB_ID,
|
|
463
|
-
run_attempt_id: RUN_ID,
|
|
464
|
-
name: 'deploy',
|
|
465
|
-
status: 'skipped',
|
|
466
|
-
status_reason: 'dependency_not_completed',
|
|
467
|
-
job_executions: [],
|
|
468
|
-
}),
|
|
469
|
-
],
|
|
470
|
-
}),
|
|
471
|
-
),
|
|
472
|
-
),
|
|
473
|
-
),
|
|
474
|
-
});
|
|
136
|
+
test('links an annotation back to the step that emitted it', async () => {
|
|
137
|
+
configureRunFetch([annotationDto({id: ANNOTATION_ID_ONE, context: 'coverage'})]);
|
|
475
138
|
|
|
476
|
-
renderView();
|
|
139
|
+
renderView({tab: 'annotations'});
|
|
477
140
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
screen.getByText('A required job did not complete, so this job was skipped.'),
|
|
481
|
-
).toBeInTheDocument();
|
|
482
|
-
expect(screen.queryByText('No steps recorded')).not.toBeInTheDocument();
|
|
483
|
-
});
|
|
141
|
+
const href = (await screen.findByRole('link', {name: 'Open step'})).getAttribute('href') ?? '';
|
|
142
|
+
const [pathname, query = ''] = href.split('?');
|
|
484
143
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
jobs: [
|
|
492
|
-
workflowJobDto({
|
|
493
|
-
id: DEPLOY_JOB_ID,
|
|
494
|
-
run_attempt_id: RUN_ID,
|
|
495
|
-
name: 'deploy',
|
|
496
|
-
status: 'succeeded',
|
|
497
|
-
job_executions: [
|
|
498
|
-
workflowJobExecutionDto({
|
|
499
|
-
id: '77777777-7777-4777-8777-000000000003',
|
|
500
|
-
job_id: DEPLOY_JOB_ID,
|
|
501
|
-
sequence: 2,
|
|
502
|
-
name: 'retry',
|
|
503
|
-
status: 'running',
|
|
504
|
-
steps: [],
|
|
505
|
-
}),
|
|
506
|
-
],
|
|
507
|
-
}),
|
|
508
|
-
],
|
|
509
|
-
}),
|
|
510
|
-
),
|
|
511
|
-
),
|
|
512
|
-
),
|
|
144
|
+
expect(pathname).toBe(`/w/${PROJECT_TEST_WSLUG}/p/project/runs/${RUN_ID}/jobs/${BUILD_JOB_ID}`);
|
|
145
|
+
expect(Object.fromEntries(new URLSearchParams(query))).toEqual({
|
|
146
|
+
jobExecution: BUILD_EXECUTION_ID,
|
|
147
|
+
step: BUILD_STEP_ID,
|
|
148
|
+
stepAttempt: BUILD_ATTEMPT_ID,
|
|
149
|
+
runAttempt: '"1"',
|
|
513
150
|
});
|
|
514
|
-
|
|
515
|
-
renderView();
|
|
516
|
-
|
|
517
|
-
expect(await screen.findByText('No steps recorded')).toBeInTheDocument();
|
|
518
|
-
expect(screen.getByText('Execution #2 finished without recorded steps.')).toBeInTheDocument();
|
|
519
|
-
expect(screen.queryByText('Waiting for the first step')).not.toBeInTheDocument();
|
|
520
151
|
});
|
|
521
152
|
|
|
522
|
-
test('
|
|
153
|
+
test('shows a failed annotations read as an error, never as an empty run', async () => {
|
|
523
154
|
configureApiClient({
|
|
524
|
-
fetchImpl: vi.fn(() =>
|
|
155
|
+
fetchImpl: vi.fn((input: RequestInfo | URL) =>
|
|
525
156
|
Promise.resolve(
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
workflowJobDto({
|
|
530
|
-
id: DEPLOY_JOB_ID,
|
|
531
|
-
run_attempt_id: RUN_ID,
|
|
532
|
-
name: 'deploy',
|
|
533
|
-
status: 'pending',
|
|
534
|
-
steps: [],
|
|
535
|
-
}),
|
|
536
|
-
],
|
|
537
|
-
}),
|
|
538
|
-
),
|
|
157
|
+
requestUrl(input).includes('/annotations')
|
|
158
|
+
? jsonResponse({code: 'internal'}, {status: 500})
|
|
159
|
+
: jsonResponse(workflowRunViewDetailDto()),
|
|
539
160
|
),
|
|
540
161
|
),
|
|
541
162
|
});
|
|
542
163
|
|
|
543
|
-
renderView();
|
|
164
|
+
renderView({tab: 'annotations'});
|
|
544
165
|
|
|
545
|
-
expect(await screen.
|
|
546
|
-
expect(screen.
|
|
547
|
-
expect(screen.queryByText('No steps recorded')).not.toBeInTheDocument();
|
|
166
|
+
expect(await screen.findByRole('button', {name: 'Retry loading annotations'})).toBeVisible();
|
|
167
|
+
expect(screen.queryByText('This run has no annotations to show.')).not.toBeInTheDocument();
|
|
548
168
|
});
|
|
549
169
|
|
|
550
|
-
test('
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
id: DEPLOY_JOB_ID,
|
|
559
|
-
run_attempt_id: RUN_ID,
|
|
560
|
-
name: 'deploy',
|
|
561
|
-
status: 'running',
|
|
562
|
-
job_executions: [
|
|
563
|
-
workflowJobExecutionDto({
|
|
564
|
-
job_id: DEPLOY_JOB_ID,
|
|
565
|
-
status: 'running',
|
|
566
|
-
steps: [],
|
|
567
|
-
}),
|
|
568
|
-
],
|
|
569
|
-
}),
|
|
570
|
-
],
|
|
571
|
-
}),
|
|
572
|
-
),
|
|
573
|
-
),
|
|
170
|
+
test('bounds how many annotation bodies render at once', async () => {
|
|
171
|
+
configureRunFetch(
|
|
172
|
+
Array.from({length: 30}, (_unused, index) =>
|
|
173
|
+
annotationDto({
|
|
174
|
+
id: `aaaaaaaa-aaaa-4aaa-8aaa-${String(index).padStart(12, '0')}`,
|
|
175
|
+
context: `context-${index}`,
|
|
176
|
+
sequence: index + 1,
|
|
177
|
+
}),
|
|
574
178
|
),
|
|
575
|
-
|
|
179
|
+
);
|
|
576
180
|
|
|
577
|
-
renderView();
|
|
181
|
+
renderView({tab: 'annotations'});
|
|
578
182
|
|
|
579
|
-
expect(await screen.
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
).not.toBeInTheDocument();
|
|
586
|
-
expect(screen.queryByText('No steps recorded')).not.toBeInTheDocument();
|
|
183
|
+
expect(await screen.findAllByRole('heading', {level: 3})).toHaveLength(25);
|
|
184
|
+
const showMore = screen.getByRole('button', {name: 'Show 5 more of 30'});
|
|
185
|
+
|
|
186
|
+
await userEvent.click(showMore);
|
|
187
|
+
|
|
188
|
+
expect(screen.getAllByRole('heading', {level: 3})).toHaveLength(30);
|
|
189
|
+
expect(screen.queryByRole('button', {name: SHOW_MORE_PATTERN})).not.toBeInTheDocument();
|
|
587
190
|
});
|
|
588
191
|
|
|
589
|
-
test('
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
status: 'running',
|
|
602
|
-
listener_status: 'listening',
|
|
603
|
-
job_executions: [],
|
|
604
|
-
}),
|
|
605
|
-
],
|
|
606
|
-
}),
|
|
607
|
-
),
|
|
608
|
-
),
|
|
192
|
+
test('focuses a deep-linked annotation beyond the render window', async () => {
|
|
193
|
+
const deepLinkedId = 'aaaaaaaa-aaaa-4aaa-8aaa-000000000029';
|
|
194
|
+
configureRunFetch(
|
|
195
|
+
Array.from({length: 30}, (_unused, index) =>
|
|
196
|
+
annotationDto({
|
|
197
|
+
id:
|
|
198
|
+
index === 29
|
|
199
|
+
? deepLinkedId
|
|
200
|
+
: `aaaaaaaa-aaaa-4aaa-8aaa-${String(index).padStart(12, '0')}`,
|
|
201
|
+
context: `context-${index}`,
|
|
202
|
+
sequence: index + 1,
|
|
203
|
+
}),
|
|
609
204
|
),
|
|
610
|
-
|
|
205
|
+
);
|
|
611
206
|
|
|
612
|
-
renderView();
|
|
207
|
+
renderView({tab: 'annotations', selection: {annotation: deepLinkedId}});
|
|
613
208
|
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
).
|
|
618
|
-
expect(
|
|
209
|
+
const target = await screen.findByRole('heading', {level: 3, name: 'context-29'});
|
|
210
|
+
const selected = target.closest('li');
|
|
211
|
+
await waitFor(() => expect(selected).toHaveFocus());
|
|
212
|
+
expect(selected).toHaveAttribute('aria-current', 'true');
|
|
213
|
+
expect(selected).toHaveClass('shadow-border-interactive-with-active');
|
|
619
214
|
});
|
|
620
215
|
|
|
621
|
-
test('
|
|
622
|
-
|
|
623
|
-
fetchImpl: vi.fn(() =>
|
|
624
|
-
Promise.resolve(
|
|
625
|
-
jsonResponse(
|
|
626
|
-
workflowRunViewDetailDto({
|
|
627
|
-
jobs: [
|
|
628
|
-
workflowJobDto({
|
|
629
|
-
id: DEPLOY_JOB_ID,
|
|
630
|
-
run_attempt_id: RUN_ID,
|
|
631
|
-
name: 'deploy',
|
|
632
|
-
status: 'succeeded',
|
|
633
|
-
job_executions: [],
|
|
634
|
-
}),
|
|
635
|
-
],
|
|
636
|
-
}),
|
|
637
|
-
),
|
|
638
|
-
),
|
|
639
|
-
),
|
|
640
|
-
});
|
|
216
|
+
test('separates a filtered miss from a run with no annotations', async () => {
|
|
217
|
+
configureRunFetch([annotationDto({id: ANNOTATION_ID_ONE, context: 'coverage', style: 'info'})]);
|
|
641
218
|
|
|
642
|
-
renderView();
|
|
219
|
+
renderView({tab: 'annotations', selection: {severity: 'error'}});
|
|
643
220
|
|
|
644
|
-
expect(await screen.findByText('
|
|
221
|
+
expect(await screen.findByText('No matching annotations')).toBeInTheDocument();
|
|
645
222
|
expect(
|
|
646
|
-
screen.getByText('This
|
|
223
|
+
screen.getByText('This run has annotations, but none at error severity.'),
|
|
647
224
|
).toBeInTheDocument();
|
|
648
|
-
expect(screen.
|
|
225
|
+
expect(screen.getByRole('button', {name: 'Clear filters'})).toBeInTheDocument();
|
|
649
226
|
});
|
|
650
227
|
|
|
651
|
-
test('
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
jobs: [
|
|
658
|
-
workflowJobDto({
|
|
659
|
-
id: DEPLOY_JOB_ID,
|
|
660
|
-
run_attempt_id: RUN_ID,
|
|
661
|
-
name: 'deploy',
|
|
662
|
-
status: 'cancelled',
|
|
663
|
-
job_executions: [
|
|
664
|
-
workflowJobExecutionDto({
|
|
665
|
-
job_id: DEPLOY_JOB_ID,
|
|
666
|
-
status: 'cancelled',
|
|
667
|
-
steps: [],
|
|
668
|
-
}),
|
|
669
|
-
],
|
|
670
|
-
}),
|
|
671
|
-
],
|
|
672
|
-
}),
|
|
673
|
-
),
|
|
674
|
-
),
|
|
675
|
-
),
|
|
676
|
-
});
|
|
228
|
+
test('does not claim a filtered miss beyond the loaded annotation budget', async () => {
|
|
229
|
+
configureRunFetch(
|
|
230
|
+
[annotationDto({id: ANNOTATION_ID_ONE, context: 'coverage', style: 'info'})],
|
|
231
|
+
{},
|
|
232
|
+
{has_more: true, next_cursor: 'next-page'},
|
|
233
|
+
);
|
|
677
234
|
|
|
678
|
-
renderView();
|
|
235
|
+
renderView({tab: 'annotations', selection: {severity: 'error'}});
|
|
679
236
|
|
|
680
|
-
expect(await screen.findByText('
|
|
681
|
-
expect(
|
|
682
|
-
|
|
237
|
+
expect(await screen.findByText('No matches in loaded annotations')).toBeInTheDocument();
|
|
238
|
+
expect(
|
|
239
|
+
screen.getByText(
|
|
240
|
+
'None of the loaded annotations are at error severity. Load more annotations to continue searching.',
|
|
241
|
+
),
|
|
242
|
+
).toBeInTheDocument();
|
|
243
|
+
expect(screen.getByRole('button', {name: 'Load more annotations'})).toBeInTheDocument();
|
|
244
|
+
expect(screen.queryByText('No matching annotations')).not.toBeInTheDocument();
|
|
683
245
|
});
|
|
684
246
|
|
|
685
|
-
test('
|
|
686
|
-
|
|
687
|
-
|
|
247
|
+
test('clears annotation filters and legacy step selection together', async () => {
|
|
248
|
+
const user = userEvent.setup();
|
|
249
|
+
configureRunFetch([annotationDto({id: ANNOTATION_ID_ONE, context: 'coverage', style: 'info'})]);
|
|
250
|
+
|
|
251
|
+
const {router} = renderView({
|
|
252
|
+
tab: 'annotations',
|
|
253
|
+
selection: {
|
|
254
|
+
jobId: DEPLOY_JOB_ID,
|
|
255
|
+
jobExecutionId: BUILD_EXECUTION_ID,
|
|
256
|
+
stepId: BUILD_STEP_ID,
|
|
257
|
+
stepAttemptId: BUILD_ATTEMPT_ID,
|
|
258
|
+
severity: 'error',
|
|
259
|
+
annotation: ANNOTATION_ID_ONE,
|
|
260
|
+
},
|
|
688
261
|
});
|
|
689
262
|
|
|
690
|
-
|
|
263
|
+
await user.click(await screen.findByRole('button', {name: 'Clear filters'}));
|
|
691
264
|
|
|
692
|
-
|
|
265
|
+
await waitFor(() => expect(router.state.location.search).toEqual({tab: 'annotations'}));
|
|
693
266
|
});
|
|
694
267
|
|
|
695
|
-
test('
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
});
|
|
268
|
+
test('blames only the filters that are actually active', async () => {
|
|
269
|
+
// The annotation belongs to build, so filtering to deploy is a job-only miss. Naming
|
|
270
|
+
// severity here would send the reader to change a control they never touched.
|
|
271
|
+
configureRunFetch([annotationDto({id: ANNOTATION_ID_ONE, context: 'coverage', style: 'info'})]);
|
|
699
272
|
|
|
700
|
-
renderView();
|
|
273
|
+
renderView({tab: 'annotations', selection: {jobId: DEPLOY_JOB_ID}});
|
|
701
274
|
|
|
702
275
|
expect(
|
|
703
|
-
await screen.
|
|
276
|
+
await screen.findByText('This run has annotations, but none from deploy.'),
|
|
704
277
|
).toBeInTheDocument();
|
|
705
278
|
});
|
|
706
279
|
|
|
707
|
-
test('renders the
|
|
280
|
+
test('renders the captured workflow source in the Source section', async () => {
|
|
708
281
|
configureApiClient({
|
|
709
282
|
fetchImpl: vi.fn(() =>
|
|
710
283
|
Promise.resolve(
|
|
711
284
|
jsonResponse(
|
|
712
285
|
workflowRunViewDetailDto({
|
|
713
|
-
source_snapshot: {
|
|
714
|
-
format: 'yaml',
|
|
715
|
-
content: 'jobs:\n build:\n steps:\n - run: pnpm test',
|
|
716
|
-
},
|
|
286
|
+
source_snapshot: {format: 'yaml', content: 'jobs:\n build:\n steps: []'},
|
|
717
287
|
}),
|
|
718
288
|
),
|
|
719
289
|
),
|
|
@@ -722,496 +292,81 @@ describe('WorkflowRunView', () => {
|
|
|
722
292
|
|
|
723
293
|
renderView({tab: 'source'});
|
|
724
294
|
|
|
725
|
-
expect(await screen.
|
|
726
|
-
|
|
727
|
-
'true',
|
|
728
|
-
);
|
|
729
|
-
expect(await screen.findByText('pnpm test')).toBeInTheDocument();
|
|
730
|
-
expect(screen.queryByRole('dialog', {name: 'Workflow source'})).not.toBeInTheDocument();
|
|
731
|
-
});
|
|
732
|
-
|
|
733
|
-
test('highlights selected step source lines when the source panel opens', async () => {
|
|
734
|
-
const user = userEvent.setup();
|
|
735
|
-
const stepId = '99999999-9999-4999-8999-000000000003';
|
|
736
|
-
const stepAttemptId = 'aaaaaaaa-aaaa-4aaa-8aaa-000000000003';
|
|
737
|
-
configureApiClient({
|
|
738
|
-
fetchImpl: vi.fn(() =>
|
|
739
|
-
Promise.resolve(
|
|
740
|
-
jsonResponse(
|
|
741
|
-
workflowRunViewDetailDto({
|
|
742
|
-
source_snapshot: {
|
|
743
|
-
format: 'yaml',
|
|
744
|
-
content: 'jobs:\n deploy:\n steps:\n - run: ship',
|
|
745
|
-
},
|
|
746
|
-
jobs: [
|
|
747
|
-
workflowJobDto({
|
|
748
|
-
id: DEPLOY_JOB_ID,
|
|
749
|
-
run_attempt_id: RUN_ID,
|
|
750
|
-
name: 'deploy',
|
|
751
|
-
status: 'running',
|
|
752
|
-
job_executions: [
|
|
753
|
-
workflowJobExecutionDto({
|
|
754
|
-
job_id: DEPLOY_JOB_ID,
|
|
755
|
-
name: 'deploy',
|
|
756
|
-
status: 'running',
|
|
757
|
-
steps: [
|
|
758
|
-
workflowStepDto({
|
|
759
|
-
id: stepId,
|
|
760
|
-
key: 'deploy',
|
|
761
|
-
name: 'deploy',
|
|
762
|
-
source_location: {start_line: 2, end_line: 3},
|
|
763
|
-
status: 'running',
|
|
764
|
-
attempts: [
|
|
765
|
-
workflowStepAttemptDto({
|
|
766
|
-
id: stepAttemptId,
|
|
767
|
-
step_id: stepId,
|
|
768
|
-
status: 'running',
|
|
769
|
-
}),
|
|
770
|
-
],
|
|
771
|
-
}),
|
|
772
|
-
],
|
|
773
|
-
}),
|
|
774
|
-
],
|
|
775
|
-
}),
|
|
776
|
-
],
|
|
777
|
-
}),
|
|
778
|
-
),
|
|
779
|
-
),
|
|
780
|
-
),
|
|
781
|
-
});
|
|
782
|
-
|
|
783
|
-
renderView({tab: 'jobs', selection: {stepId, stepAttemptId}});
|
|
784
|
-
const jobRegion = await screen.findByRole('region', {name: 'deploy'});
|
|
785
|
-
await user.click(within(jobRegion).getByRole('button', {name: 'View source'}));
|
|
786
|
-
|
|
787
|
-
const sourceDialog = await screen.findByRole('dialog', {name: 'Workflow source'});
|
|
788
|
-
const highlightedLines = sourceDialog.querySelectorAll('.line.highlighted-line');
|
|
789
|
-
expect(highlightedLines).toHaveLength(2);
|
|
790
|
-
expect(highlightedLines[0]).toHaveTextContent('deploy:');
|
|
791
|
-
expect(highlightedLines[1]).toHaveTextContent('steps:');
|
|
295
|
+
expect(await screen.findByText('build:')).toBeInTheDocument();
|
|
296
|
+
expect(screen.getByRole('link', {name: 'Source'})).toHaveAttribute('aria-current', 'page');
|
|
792
297
|
});
|
|
793
298
|
|
|
794
299
|
test('explains when the run has no source snapshot', async () => {
|
|
795
|
-
|
|
796
|
-
fetchImpl: vi.fn(() =>
|
|
797
|
-
Promise.resolve(jsonResponse(workflowRunViewDetailDto({source_snapshot: null}))),
|
|
798
|
-
),
|
|
799
|
-
});
|
|
300
|
+
configureRunFetch();
|
|
800
301
|
|
|
801
302
|
renderView({tab: 'source'});
|
|
802
303
|
|
|
803
|
-
await screen.findByRole('region', {name: 'deploy-web'});
|
|
804
|
-
|
|
805
304
|
expect(await screen.findByText('Source snapshot unavailable')).toBeInTheDocument();
|
|
806
305
|
});
|
|
807
306
|
|
|
808
|
-
test('
|
|
809
|
-
const user = userEvent.setup();
|
|
810
|
-
configureApiClient({
|
|
811
|
-
fetchImpl: vi.fn(() => Promise.resolve(jsonResponse(locatedStepSourceDetail()))),
|
|
812
|
-
});
|
|
813
|
-
|
|
814
|
-
renderView();
|
|
815
|
-
|
|
816
|
-
const jobRegion = await screen.findByRole('region', {name: 'build'});
|
|
817
|
-
await user.click(screen.getByRole('button', {name: 'checkout, Succeeded, attempt 1'}));
|
|
818
|
-
const jobSourceButton = within(jobRegion).getByRole('button', {name: 'View source'});
|
|
819
|
-
expect(jobSourceButton).toHaveAttribute('aria-expanded', 'false');
|
|
820
|
-
expect(jobSourceButton).toHaveAttribute('aria-controls');
|
|
821
|
-
|
|
822
|
-
await user.click(jobSourceButton);
|
|
823
|
-
|
|
824
|
-
const sourceDialog = await screen.findByRole('dialog', {name: 'Workflow source'});
|
|
825
|
-
const highlighted = sourceDialog.querySelectorAll('.line.highlighted-line');
|
|
826
|
-
expect(highlighted).toHaveLength(2);
|
|
827
|
-
expect(highlighted[0]).toHaveTextContent('build:');
|
|
828
|
-
expect(highlighted[1]).toHaveTextContent('steps:');
|
|
829
|
-
expect(jobSourceButton).toHaveAttribute('aria-expanded', 'true');
|
|
830
|
-
});
|
|
831
|
-
|
|
832
|
-
test('returns focus to the job Source button on close and preserves the selected job', async () => {
|
|
833
|
-
const user = userEvent.setup();
|
|
834
|
-
configureApiClient({
|
|
835
|
-
fetchImpl: vi.fn(() => Promise.resolve(jsonResponse(locatedStepSourceDetail()))),
|
|
836
|
-
});
|
|
837
|
-
|
|
838
|
-
renderView();
|
|
839
|
-
|
|
840
|
-
const jobRegion = await screen.findByRole('region', {name: 'build'});
|
|
841
|
-
await user.click(screen.getByRole('button', {name: 'checkout, Succeeded, attempt 1'}));
|
|
842
|
-
const jobSourceButton = within(jobRegion).getByRole('button', {name: 'View source'});
|
|
843
|
-
|
|
844
|
-
await user.click(jobSourceButton);
|
|
845
|
-
await screen.findByRole('dialog', {name: 'Workflow source'});
|
|
846
|
-
await user.click(screen.getByRole('button', {name: 'Close source'}));
|
|
847
|
-
|
|
848
|
-
await waitFor(() => expect(jobSourceButton).toHaveFocus());
|
|
849
|
-
expect(jobSourceButton).toHaveAttribute('aria-expanded', 'false');
|
|
850
|
-
expect(screen.getByRole('region', {name: 'build'})).toBeInTheDocument();
|
|
851
|
-
});
|
|
852
|
-
|
|
853
|
-
test('closes the source panel when refetch removes the focused step source', async () => {
|
|
854
|
-
const user = userEvent.setup();
|
|
855
|
-
let detailRequests = 0;
|
|
856
|
-
const fetchImpl = vi.fn((input: RequestInfo | URL) => {
|
|
857
|
-
const url = requestUrl(input);
|
|
858
|
-
if (url.pathname === `/workflows/runs/${RUN_ID}`) {
|
|
859
|
-
detailRequests += 1;
|
|
860
|
-
return Promise.resolve(
|
|
861
|
-
jsonResponse(
|
|
862
|
-
detailRequests === 1
|
|
863
|
-
? locatedStepSourceDetail()
|
|
864
|
-
: locatedStepSourceDetail({
|
|
865
|
-
jobs: [
|
|
866
|
-
workflowJobDto({
|
|
867
|
-
id: BUILD_JOB_ID,
|
|
868
|
-
name: 'build',
|
|
869
|
-
status: 'succeeded',
|
|
870
|
-
steps: [
|
|
871
|
-
workflowStepDto({
|
|
872
|
-
id: CHECKOUT_STEP_ID,
|
|
873
|
-
name: 'checkout',
|
|
874
|
-
status: 'succeeded',
|
|
875
|
-
source_location: null,
|
|
876
|
-
attempts: [
|
|
877
|
-
workflowStepAttemptDto({
|
|
878
|
-
id: CHECKOUT_ATTEMPT_ID,
|
|
879
|
-
step_id: CHECKOUT_STEP_ID,
|
|
880
|
-
status: 'succeeded',
|
|
881
|
-
exit_code: 0,
|
|
882
|
-
finished_at: '2026-05-07T01:01:20.000Z',
|
|
883
|
-
}),
|
|
884
|
-
],
|
|
885
|
-
}),
|
|
886
|
-
],
|
|
887
|
-
}),
|
|
888
|
-
],
|
|
889
|
-
}),
|
|
890
|
-
),
|
|
891
|
-
);
|
|
892
|
-
}
|
|
893
|
-
return Promise.resolve(jsonResponse(inlineLogBody(outputLine('done\n'), 1)));
|
|
894
|
-
});
|
|
895
|
-
configureApiClient({fetchImpl: fetchImpl as typeof fetch});
|
|
896
|
-
|
|
897
|
-
const {queryClient} = renderView();
|
|
898
|
-
|
|
899
|
-
const jobRegion = await screen.findByRole('region', {name: 'build'});
|
|
900
|
-
await user.click(screen.getByRole('button', {name: 'checkout, Succeeded, attempt 1'}));
|
|
901
|
-
const jobSourceButton = within(jobRegion).getByRole('button', {name: 'View source'});
|
|
902
|
-
await user.click(jobSourceButton);
|
|
903
|
-
await screen.findByRole('dialog', {name: 'Workflow source'});
|
|
904
|
-
|
|
905
|
-
await queryClient.refetchQueries({queryKey: workflowRunsQueryKeys.detail(RUN_ID)});
|
|
906
|
-
await waitFor(() =>
|
|
907
|
-
expect(
|
|
908
|
-
within(jobRegion).queryByRole('button', {name: 'View source'}),
|
|
909
|
-
).not.toBeInTheDocument(),
|
|
910
|
-
);
|
|
911
|
-
expect(screen.queryByRole('dialog', {name: 'Workflow source'})).not.toBeInTheDocument();
|
|
912
|
-
});
|
|
913
|
-
|
|
914
|
-
test('re-runs all jobs from a succeeded run and navigates to the new run', async () => {
|
|
915
|
-
const user = userEvent.setup();
|
|
916
|
-
const rerunId = 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb';
|
|
917
|
-
const successSpy = vi.spyOn(toast, 'success').mockImplementation(() => 'toast-id');
|
|
918
|
-
const postBodies: unknown[] = [];
|
|
919
|
-
const fetchImpl = vi.fn(async (input: RequestInfo | URL) => {
|
|
920
|
-
const request = input as Request;
|
|
921
|
-
const url = requestUrl(input);
|
|
922
|
-
if (request.method === 'POST' && url.pathname === `/workflows/runs/${RUN_ID}/rerun`) {
|
|
923
|
-
postBodies.push(await request.clone().json());
|
|
924
|
-
return jsonResponse(workflowRunDto({id: rerunId, status: 'pending'}));
|
|
925
|
-
}
|
|
926
|
-
return jsonResponse(
|
|
927
|
-
workflowRunViewDetailDto({
|
|
928
|
-
status: 'succeeded',
|
|
929
|
-
jobs: [
|
|
930
|
-
workflowJobDto({
|
|
931
|
-
id: BUILD_JOB_ID,
|
|
932
|
-
run_attempt_id: RUN_ID,
|
|
933
|
-
name: 'build',
|
|
934
|
-
status: 'succeeded',
|
|
935
|
-
}),
|
|
936
|
-
],
|
|
937
|
-
}),
|
|
938
|
-
);
|
|
939
|
-
});
|
|
940
|
-
configureApiClient({fetchImpl: fetchImpl as typeof fetch});
|
|
941
|
-
|
|
942
|
-
const {router} = renderView({}, '?runAttempt=1');
|
|
943
|
-
await user.click(await screen.findByRole('button', {name: 'Re-run workflow'}));
|
|
944
|
-
|
|
945
|
-
const postRequest = await findRequest(fetchImpl, 'POST', `/workflows/runs/${RUN_ID}/rerun`);
|
|
946
|
-
expect(postRequest).toBeDefined();
|
|
947
|
-
expect(postBodies).toEqual([{mode: 'all'}]);
|
|
948
|
-
expect(successSpy).toHaveBeenCalledWith('Re-run started');
|
|
949
|
-
await waitFor(() =>
|
|
950
|
-
expect(router.state.location.pathname).toBe(
|
|
951
|
-
`/w/${PROJECT_TEST_WSLUG}/p/project/runs/${rerunId}`,
|
|
952
|
-
),
|
|
953
|
-
);
|
|
954
|
-
expect((router.state.location.search as Record<string, unknown>).runAttempt).toBeUndefined();
|
|
955
|
-
});
|
|
956
|
-
|
|
957
|
-
test('re-runs failed jobs from the dropdown', async () => {
|
|
958
|
-
const user = userEvent.setup();
|
|
959
|
-
const postBodies: unknown[] = [];
|
|
960
|
-
const fetchImpl = vi.fn(async (input: RequestInfo | URL) => {
|
|
961
|
-
const request = input as Request;
|
|
962
|
-
const url = requestUrl(input);
|
|
963
|
-
if (request.method === 'POST' && url.pathname === `/workflows/runs/${RUN_ID}/rerun`) {
|
|
964
|
-
postBodies.push(await request.clone().json());
|
|
965
|
-
return jsonResponse(workflowRunDto({id: 'cccccccc-cccc-4ccc-8ccc-cccccccccccc'}));
|
|
966
|
-
}
|
|
967
|
-
return jsonResponse(
|
|
968
|
-
workflowRunViewDetailDto({
|
|
969
|
-
status: 'failed',
|
|
970
|
-
jobs: [
|
|
971
|
-
workflowJobDto({
|
|
972
|
-
id: BUILD_JOB_ID,
|
|
973
|
-
run_attempt_id: RUN_ID,
|
|
974
|
-
name: 'build',
|
|
975
|
-
status: 'failed',
|
|
976
|
-
}),
|
|
977
|
-
workflowJobDto({
|
|
978
|
-
id: DEPLOY_JOB_ID,
|
|
979
|
-
run_attempt_id: RUN_ID,
|
|
980
|
-
name: 'deploy',
|
|
981
|
-
status: 'cancelled',
|
|
982
|
-
position: 1,
|
|
983
|
-
}),
|
|
984
|
-
],
|
|
985
|
-
}),
|
|
986
|
-
);
|
|
987
|
-
});
|
|
988
|
-
configureApiClient({fetchImpl: fetchImpl as typeof fetch});
|
|
989
|
-
|
|
990
|
-
renderView();
|
|
991
|
-
await user.click(await screen.findByRole('button', {name: 'Re-run jobs'}));
|
|
992
|
-
await user.click(await screen.findByRole('menuitem', {name: 'Re-run failed jobs'}));
|
|
993
|
-
|
|
994
|
-
const postRequest = await findRequest(fetchImpl, 'POST', `/workflows/runs/${RUN_ID}/rerun`);
|
|
995
|
-
expect(postRequest).toBeDefined();
|
|
996
|
-
expect(postBodies).toEqual([{mode: 'failed'}]);
|
|
997
|
-
});
|
|
998
|
-
|
|
999
|
-
test('shows an error toast when rerun creation fails', async () => {
|
|
1000
|
-
const user = userEvent.setup();
|
|
1001
|
-
const errorSpy = vi.spyOn(toast, 'error').mockImplementation(() => 'toast-id');
|
|
1002
|
-
const fetchImpl = vi.fn((input: RequestInfo | URL) => {
|
|
1003
|
-
const request = input as Request;
|
|
1004
|
-
const url = requestUrl(input);
|
|
1005
|
-
if (request.method === 'POST' && url.pathname === `/workflows/runs/${RUN_ID}/rerun`) {
|
|
1006
|
-
return Promise.resolve(
|
|
1007
|
-
jsonResponse({code: 'no-failed-jobs', message: 'Run has no failed jobs'}, {status: 409}),
|
|
1008
|
-
);
|
|
1009
|
-
}
|
|
1010
|
-
return Promise.resolve(jsonResponse(workflowRunViewDetailDto({status: 'succeeded'})));
|
|
1011
|
-
});
|
|
1012
|
-
configureApiClient({fetchImpl: fetchImpl as typeof fetch});
|
|
1013
|
-
|
|
1014
|
-
renderView();
|
|
1015
|
-
await user.click(await screen.findByRole('button', {name: 'Re-run workflow'}));
|
|
1016
|
-
|
|
1017
|
-
await waitFor(() => expect(errorSpy).toHaveBeenCalledWith('Run has no failed jobs'));
|
|
1018
|
-
});
|
|
1019
|
-
|
|
1020
|
-
test('selects another run attempt and clears job selection search', async () => {
|
|
1021
|
-
const user = userEvent.setup();
|
|
1022
|
-
const secondAttemptId = 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb';
|
|
1023
|
-
const fetchImpl = vi.fn((input: RequestInfo | URL) => {
|
|
1024
|
-
const url = requestUrl(input);
|
|
1025
|
-
if (url.pathname === `/workflows/runs/${RUN_ID}/attempts`) {
|
|
1026
|
-
return Promise.resolve(
|
|
1027
|
-
jsonResponse(
|
|
1028
|
-
runAttemptsResponseDto({
|
|
1029
|
-
attempts: [
|
|
1030
|
-
workflowRunAttemptDto({
|
|
1031
|
-
id: RUN_ID,
|
|
1032
|
-
attempt: 1,
|
|
1033
|
-
status: 'succeeded',
|
|
1034
|
-
created_at: '2026-05-07T01:01:00.000Z',
|
|
1035
|
-
}),
|
|
1036
|
-
workflowRunAttemptDto({
|
|
1037
|
-
id: secondAttemptId,
|
|
1038
|
-
attempt: 2,
|
|
1039
|
-
status: 'running',
|
|
1040
|
-
created_at: '2026-05-07T01:02:00.000Z',
|
|
1041
|
-
rerun_mode: 'all',
|
|
1042
|
-
}),
|
|
1043
|
-
],
|
|
1044
|
-
}),
|
|
1045
|
-
),
|
|
1046
|
-
);
|
|
1047
|
-
}
|
|
1048
|
-
return Promise.resolve(
|
|
1049
|
-
jsonResponse(
|
|
1050
|
-
workflowRunViewDetailDto({
|
|
1051
|
-
current_attempt: 1,
|
|
1052
|
-
latest_attempt: 2,
|
|
1053
|
-
run_attempt: workflowRunAttemptDto({id: RUN_ID, workflow_run_id: RUN_ID, attempt: 1}),
|
|
1054
|
-
}),
|
|
1055
|
-
),
|
|
1056
|
-
);
|
|
1057
|
-
});
|
|
1058
|
-
configureApiClient({fetchImpl: fetchImpl as typeof fetch});
|
|
1059
|
-
|
|
1060
|
-
const {router} = renderProjectPage(
|
|
1061
|
-
`/w/${PROJECT_TEST_WSLUG}/p/project/runs/${RUN_ID}?job=${BUILD_JOB_ID}&step=${CHECKOUT_STEP_ID}&stepAttempt=${CHECKOUT_ATTEMPT_ID}`,
|
|
1062
|
-
() => (
|
|
1063
|
-
<WorkflowRunView
|
|
1064
|
-
projectId={PROJECT_ID}
|
|
1065
|
-
workspaceSlug={PROJECT_TEST_WSLUG}
|
|
1066
|
-
projectSlug="project"
|
|
1067
|
-
workflowRunId={RUN_ID}
|
|
1068
|
-
/>
|
|
1069
|
-
),
|
|
1070
|
-
);
|
|
1071
|
-
|
|
1072
|
-
await user.click(await screen.findByRole('button', {name: 'Switch attempt, currently 1 of 2'}));
|
|
1073
|
-
await user.click(await screen.findByRole('menuitem', {name: ATTEMPT_2_PATTERN}));
|
|
1074
|
-
|
|
1075
|
-
await waitFor(() =>
|
|
1076
|
-
expect(router.state.location.pathname).toBe(
|
|
1077
|
-
`/w/${PROJECT_TEST_WSLUG}/p/project/runs/${RUN_ID}`,
|
|
1078
|
-
),
|
|
1079
|
-
);
|
|
1080
|
-
expect(router.state.location.search).toEqual({runAttempt: 2});
|
|
1081
|
-
});
|
|
1082
|
-
|
|
1083
|
-
test('does not render rerun controls for non-terminal runs', async () => {
|
|
307
|
+
test('shows the not-found surface when the run 404s', async () => {
|
|
1084
308
|
configureApiClient({
|
|
1085
|
-
fetchImpl: vi.fn(() => Promise.resolve(jsonResponse(
|
|
309
|
+
fetchImpl: vi.fn(() => Promise.resolve(jsonResponse({code: 'not-found'}, {status: 404}))),
|
|
1086
310
|
});
|
|
1087
311
|
|
|
1088
312
|
renderView();
|
|
1089
313
|
|
|
1090
|
-
await screen.
|
|
1091
|
-
expect(screen.queryByRole('button', {name: RERUN_BUTTON_NAME})).not.toBeInTheDocument();
|
|
1092
|
-
});
|
|
1093
|
-
|
|
1094
|
-
test('renders carried-over steps without requesting attempt logs', async () => {
|
|
1095
|
-
const user = userEvent.setup();
|
|
1096
|
-
const fetchImpl = vi.fn(() =>
|
|
1097
|
-
Promise.resolve(
|
|
1098
|
-
jsonResponse(
|
|
1099
|
-
workflowRunViewDetailDto({
|
|
1100
|
-
status: 'succeeded',
|
|
1101
|
-
jobs: [
|
|
1102
|
-
workflowJobDto({
|
|
1103
|
-
id: BUILD_JOB_ID,
|
|
1104
|
-
run_attempt_id: RUN_ID,
|
|
1105
|
-
name: 'build',
|
|
1106
|
-
status: 'succeeded',
|
|
1107
|
-
carried_over: true,
|
|
1108
|
-
steps: [
|
|
1109
|
-
workflowStepDto({
|
|
1110
|
-
id: CHECKOUT_STEP_ID,
|
|
1111
|
-
key: 'checkout',
|
|
1112
|
-
name: 'checkout',
|
|
1113
|
-
status: 'succeeded',
|
|
1114
|
-
attempts: [],
|
|
1115
|
-
}),
|
|
1116
|
-
],
|
|
1117
|
-
}),
|
|
1118
|
-
],
|
|
1119
|
-
}),
|
|
1120
|
-
),
|
|
1121
|
-
),
|
|
1122
|
-
);
|
|
1123
|
-
configureApiClient({fetchImpl: fetchImpl as typeof fetch});
|
|
1124
|
-
|
|
1125
|
-
renderView();
|
|
1126
|
-
expect(
|
|
1127
|
-
await screen.findByRole('button', {name: 'build, Succeeded, reused'}),
|
|
1128
|
-
).toBeInTheDocument();
|
|
1129
|
-
expect(screen.getAllByText('reused')).toHaveLength(1);
|
|
1130
|
-
await user.click(screen.getByRole('button', {name: 'checkout, Succeeded, attempt 1'}));
|
|
1131
|
-
|
|
1132
|
-
expect(await screen.findByText('Not executed in this run.')).toBeInTheDocument();
|
|
1133
|
-
expect(
|
|
1134
|
-
mockRequests(fetchImpl).some((request) => requestUrl(request).pathname.includes('/logs')),
|
|
1135
|
-
).toBe(false);
|
|
314
|
+
expect(await screen.findByText('Run not found')).toBeInTheDocument();
|
|
1136
315
|
});
|
|
1137
316
|
});
|
|
1138
317
|
|
|
1139
|
-
function renderView(props: Partial<Parameters<typeof WorkflowRunView>[0]> = {}
|
|
1140
|
-
return renderProjectPage(`/w/${PROJECT_TEST_WSLUG}/p/project/runs/${RUN_ID}
|
|
318
|
+
function renderView(props: Partial<Parameters<typeof WorkflowRunView>[0]> = {}) {
|
|
319
|
+
return renderProjectPage(`/w/${PROJECT_TEST_WSLUG}/p/project/runs/${RUN_ID}`, () => (
|
|
1141
320
|
<WorkflowRunView
|
|
1142
321
|
projectId={PROJECT_ID}
|
|
1143
322
|
workspaceSlug={PROJECT_TEST_WSLUG}
|
|
1144
323
|
projectSlug="project"
|
|
1145
324
|
workflowRunId={RUN_ID}
|
|
1146
|
-
tab="jobs"
|
|
1147
325
|
{...props}
|
|
1148
326
|
/>
|
|
1149
327
|
));
|
|
1150
328
|
}
|
|
1151
329
|
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
330
|
+
/** The API client hands `fetchImpl` a `Request`, whose URL only `.url` exposes. */
|
|
331
|
+
function requestUrl(input: RequestInfo | URL): string {
|
|
332
|
+
if (typeof input === 'string') return input;
|
|
333
|
+
return input instanceof URL ? input.href : input.url;
|
|
1155
334
|
}
|
|
1156
335
|
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
336
|
+
/** Routes the run detail and the annotations read, which are two independent fetches. */
|
|
337
|
+
function configureRunFetch(
|
|
338
|
+
annotations: AnnotationDto[] = [],
|
|
339
|
+
runOverrides: Partial<WorkflowRunDetailResponseDto> = {},
|
|
340
|
+
annotationPageOverrides: Partial<{has_more: boolean; next_cursor: string | null}> = {},
|
|
341
|
+
) {
|
|
342
|
+
configureApiClient({
|
|
343
|
+
fetchImpl: vi.fn((input: RequestInfo | URL) =>
|
|
344
|
+
Promise.resolve(
|
|
345
|
+
requestUrl(input).includes('/annotations')
|
|
346
|
+
? jsonResponse({
|
|
347
|
+
annotations,
|
|
348
|
+
has_more: false,
|
|
349
|
+
next_cursor: null,
|
|
350
|
+
...annotationPageOverrides,
|
|
351
|
+
})
|
|
352
|
+
: jsonResponse(workflowRunViewDetailDto(runOverrides)),
|
|
353
|
+
),
|
|
354
|
+
),
|
|
1167
355
|
});
|
|
1168
|
-
const match = mockRequests(fetchImpl).find(
|
|
1169
|
-
(request) => request.method === method && requestUrl(request).pathname === pathname,
|
|
1170
|
-
);
|
|
1171
|
-
if (!match) throw new Error(`Missing ${method} ${pathname}`);
|
|
1172
|
-
return match;
|
|
1173
|
-
}
|
|
1174
|
-
|
|
1175
|
-
function mockRequests(fetchImpl: ReturnType<typeof vi.fn>): Request[] {
|
|
1176
|
-
return (fetchImpl.mock.calls as unknown[][])
|
|
1177
|
-
.map((call) => call[0])
|
|
1178
|
-
.filter((input): input is Request => input instanceof Request);
|
|
1179
356
|
}
|
|
1180
357
|
|
|
1181
|
-
function
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
id: BUILD_JOB_ID,
|
|
1192
|
-
name: 'build',
|
|
1193
|
-
status: 'succeeded',
|
|
1194
|
-
steps: [
|
|
1195
|
-
workflowStepDto({
|
|
1196
|
-
id: CHECKOUT_STEP_ID,
|
|
1197
|
-
name: 'checkout',
|
|
1198
|
-
status: 'succeeded',
|
|
1199
|
-
source_location: {start_line: 2, end_line: 3},
|
|
1200
|
-
attempts: [
|
|
1201
|
-
workflowStepAttemptDto({
|
|
1202
|
-
id: CHECKOUT_ATTEMPT_ID,
|
|
1203
|
-
step_id: CHECKOUT_STEP_ID,
|
|
1204
|
-
status: 'succeeded',
|
|
1205
|
-
exit_code: 0,
|
|
1206
|
-
finished_at: '2026-05-07T01:01:20.000Z',
|
|
1207
|
-
}),
|
|
1208
|
-
],
|
|
1209
|
-
}),
|
|
1210
|
-
],
|
|
1211
|
-
}),
|
|
1212
|
-
],
|
|
358
|
+
function annotationDto(overrides: Partial<AnnotationDto> & {id: string}): AnnotationDto {
|
|
359
|
+
return {
|
|
360
|
+
job_id: BUILD_JOB_ID,
|
|
361
|
+
job_execution_id: BUILD_EXECUTION_ID,
|
|
362
|
+
origin_step_id: BUILD_STEP_ID,
|
|
363
|
+
origin_step_attempt: 1,
|
|
364
|
+
context: 'default',
|
|
365
|
+
style: 'default',
|
|
366
|
+
sequence: 1,
|
|
367
|
+
body: 'Body',
|
|
1213
368
|
...overrides,
|
|
1214
|
-
}
|
|
369
|
+
};
|
|
1215
370
|
}
|
|
1216
371
|
|
|
1217
372
|
function workflowRunViewDetailDto(
|
|
@@ -1220,7 +375,6 @@ function workflowRunViewDetailDto(
|
|
|
1220
375
|
return workflowRunDetailDto({
|
|
1221
376
|
id: RUN_ID,
|
|
1222
377
|
project_id: PROJECT_ID,
|
|
1223
|
-
definition_id: DEFINITION_ID,
|
|
1224
378
|
name: 'deploy-web',
|
|
1225
379
|
status: 'running',
|
|
1226
380
|
trigger_payload: {},
|
|
@@ -1232,19 +386,24 @@ function workflowRunViewDetailDto(
|
|
|
1232
386
|
run_attempt_id: RUN_ID,
|
|
1233
387
|
name: 'build',
|
|
1234
388
|
status: 'succeeded',
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
id:
|
|
1238
|
-
|
|
1239
|
-
name: 'checkout',
|
|
389
|
+
job_executions: [
|
|
390
|
+
workflowJobExecutionDto({
|
|
391
|
+
id: BUILD_EXECUTION_ID,
|
|
392
|
+
job_id: BUILD_JOB_ID,
|
|
1240
393
|
status: 'succeeded',
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
id:
|
|
1244
|
-
|
|
394
|
+
steps: [
|
|
395
|
+
workflowStepDto({
|
|
396
|
+
id: BUILD_STEP_ID,
|
|
397
|
+
name: 'checkout',
|
|
1245
398
|
status: 'succeeded',
|
|
1246
|
-
|
|
1247
|
-
|
|
399
|
+
attempts: [
|
|
400
|
+
workflowStepAttemptDto({
|
|
401
|
+
id: BUILD_ATTEMPT_ID,
|
|
402
|
+
step_id: BUILD_STEP_ID,
|
|
403
|
+
attempt: 1,
|
|
404
|
+
status: 'succeeded',
|
|
405
|
+
}),
|
|
406
|
+
],
|
|
1248
407
|
}),
|
|
1249
408
|
],
|
|
1250
409
|
}),
|
|
@@ -1257,14 +416,7 @@ function workflowRunViewDetailDto(
|
|
|
1257
416
|
status: 'running',
|
|
1258
417
|
position: 1,
|
|
1259
418
|
dependencies: ['build'],
|
|
1260
|
-
|
|
1261
|
-
workflowJobExecutionDto({
|
|
1262
|
-
job_id: DEPLOY_JOB_ID,
|
|
1263
|
-
name: 'deploy',
|
|
1264
|
-
status: 'running',
|
|
1265
|
-
steps: [workflowStepDto({status: 'running'})],
|
|
1266
|
-
}),
|
|
1267
|
-
],
|
|
419
|
+
steps: [workflowStepDto({name: 'deploy', status: 'running'})],
|
|
1268
420
|
}),
|
|
1269
421
|
],
|
|
1270
422
|
...overrides,
|