@shipfox/client-workflows 15.0.0 → 17.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/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-type.log +1 -0
- package/CHANGELOG.md +41 -0
- package/dist/components/identifier/identifier.js +2 -2
- package/dist/components/identifier/identifier.js.map +1 -1
- package/dist/components/job-detail/agent-config-failure-callout.d.ts.map +1 -1
- package/dist/components/job-detail/agent-config-failure-callout.js +1 -1
- package/dist/components/job-detail/agent-config-failure-callout.js.map +1 -1
- package/dist/components/job-detail/job-context-panel.d.ts +6 -0
- package/dist/components/job-detail/job-context-panel.d.ts.map +1 -0
- package/dist/components/job-detail/job-context-panel.js +224 -0
- package/dist/components/job-detail/job-context-panel.js.map +1 -0
- package/dist/components/job-detail/job-detail-header.d.ts +10 -1
- package/dist/components/job-detail/job-detail-header.d.ts.map +1 -1
- package/dist/components/job-detail/job-detail-header.js +58 -39
- package/dist/components/job-detail/job-detail-header.js.map +1 -1
- package/dist/components/job-detail/job-detail-view.d.ts +1 -1
- package/dist/components/job-detail/job-detail-view.d.ts.map +1 -1
- package/dist/components/job-detail/job-detail-view.js +92 -41
- package/dist/components/job-detail/job-detail-view.js.map +1 -1
- package/dist/components/job-detail/job-empty-states.d.ts.map +1 -1
- package/dist/components/job-detail/job-empty-states.js +19 -10
- package/dist/components/job-detail/job-empty-states.js.map +1 -1
- package/dist/components/job-detail/job-execution-switcher.js +4 -4
- package/dist/components/job-detail/job-execution-switcher.js.map +1 -1
- package/dist/components/job-detail/json-code.d.ts +14 -0
- package/dist/components/job-detail/json-code.d.ts.map +1 -0
- package/dist/components/job-detail/json-code.js +93 -0
- package/dist/components/job-detail/json-code.js.map +1 -0
- package/dist/components/job-detail/step-attempt-log-panel.d.ts.map +1 -1
- package/dist/components/job-detail/step-attempt-log-panel.js +9 -6
- package/dist/components/job-detail/step-attempt-log-panel.js.map +1 -1
- package/dist/components/job-detail/step-troubleshooting.d.ts +18 -0
- package/dist/components/job-detail/step-troubleshooting.d.ts.map +1 -0
- package/dist/components/job-detail/step-troubleshooting.js +493 -0
- package/dist/components/job-detail/step-troubleshooting.js.map +1 -0
- package/dist/components/job-graph/job-graph-content.d.ts.map +1 -1
- package/dist/components/job-graph/job-graph-content.js +5 -4
- package/dist/components/job-graph/job-graph-content.js.map +1 -1
- package/dist/components/job-graph/job-node.js +3 -3
- package/dist/components/job-graph/job-node.js.map +1 -1
- package/dist/components/step-list/index.d.ts +1 -0
- package/dist/components/step-list/index.d.ts.map +1 -1
- package/dist/components/step-list/index.js.map +1 -1
- package/dist/components/step-list/step-list-model.d.ts.map +1 -1
- package/dist/components/step-list/step-list-model.js +5 -0
- package/dist/components/step-list/step-list-model.js.map +1 -1
- package/dist/components/step-list/step-list.d.ts +5 -1
- package/dist/components/step-list/step-list.d.ts.map +1 -1
- package/dist/components/step-list/step-list.js +117 -82
- package/dist/components/step-list/step-list.js.map +1 -1
- package/dist/components/workflow-run-duration-label.js +1 -1
- package/dist/components/workflow-run-duration-label.js.map +1 -1
- package/dist/components/workflow-run-list/job-status-strip.js +6 -6
- package/dist/components/workflow-run-list/job-status-strip.js.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-filter-menu.js +1 -1
- package/dist/components/workflow-run-list/workflow-run-filter-menu.js.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-filters.js +4 -4
- package/dist/components/workflow-run-list/workflow-run-filters.js.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-list-states.js +6 -6
- package/dist/components/workflow-run-list/workflow-run-list-states.js.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-list-view.js +1 -1
- package/dist/components/workflow-run-list/workflow-run-list-view.js.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-row.js +7 -7
- package/dist/components/workflow-run-list/workflow-run-row.js.map +1 -1
- package/dist/components/workflow-run-summary/workflow-run-attempt-switcher.js +1 -1
- 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 +0 -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 +9 -13
- package/dist/components/workflow-run-summary/workflow-run-summary.js.map +1 -1
- package/dist/components/workflow-run-tabs/index.d.ts +3 -0
- package/dist/components/workflow-run-tabs/index.d.ts.map +1 -1
- package/dist/components/workflow-run-tabs/index.js +3 -0
- 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 +39 -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 +211 -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 +90 -25
- package/dist/components/workflow-run-tabs/run-annotation-summary-line.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/run-workspace-nav.d.ts +1 -1
- package/dist/components/workflow-run-view/run-workspace-nav.d.ts.map +1 -1
- package/dist/components/workflow-run-view/run-workspace-nav.js +16 -10
- package/dist/components/workflow-run-view/run-workspace-nav.js.map +1 -1
- package/dist/components/workflow-run-view/workflow-run-states.js +6 -6
- package/dist/components/workflow-run-view/workflow-run-states.js.map +1 -1
- package/dist/components/workflow-run-view/workflow-run-view.d.ts +1 -3
- package/dist/components/workflow-run-view/workflow-run-view.d.ts.map +1 -1
- package/dist/components/workflow-run-view/workflow-run-view.js +217 -59
- package/dist/components/workflow-run-view/workflow-run-view.js.map +1 -1
- package/dist/core/entities/job-execution.d.ts +7 -0
- package/dist/core/entities/job-execution.d.ts.map +1 -1
- package/dist/core/entities/job-execution.js.map +1 -1
- package/dist/core/entities/job.d.ts +9 -0
- package/dist/core/entities/job.d.ts.map +1 -1
- package/dist/core/entities/job.js.map +1 -1
- package/dist/core/entities/step-attempt.d.ts +29 -0
- package/dist/core/entities/step-attempt.d.ts.map +1 -1
- package/dist/core/entities/step-attempt.js.map +1 -1
- package/dist/core/entities/step.d.ts +3 -1
- package/dist/core/entities/step.d.ts.map +1 -1
- package/dist/core/entities/step.js.map +1 -1
- package/dist/core/run-annotation.d.ts +79 -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 +1 -1
- package/dist/core/workflow-run.d.ts.map +1 -1
- package/dist/core/workflow-run.js.map +1 -1
- package/dist/hooks/api/annotations.d.ts +63 -0
- package/dist/hooks/api/annotations.d.ts.map +1 -0
- package/dist/hooks/api/annotations.js +145 -0
- package/dist/hooks/api/annotations.js.map +1 -0
- 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/step-attempt-detail.d.ts +14 -0
- package/dist/hooks/api/step-attempt-detail.d.ts.map +1 -0
- package/dist/hooks/api/step-attempt-detail.js +43 -0
- package/dist/hooks/api/step-attempt-detail.js.map +1 -0
- package/dist/hooks/api/workflow-run-mapper.d.ts +9 -2
- package/dist/hooks/api/workflow-run-mapper.d.ts.map +1 -1
- package/dist/hooks/api/workflow-run-mapper.js +47 -0
- package/dist/hooks/api/workflow-run-mapper.js.map +1 -1
- package/dist/pages/project-workflows-page.js +19 -19
- package/dist/pages/project-workflows-page.js.map +1 -1
- package/dist/pages/workflow-run-list-page.js +1 -1
- package/dist/pages/workflow-run-list-page.js.map +1 -1
- package/dist/routes/inputs.d.ts +7 -2
- package/dist/routes/inputs.d.ts.map +1 -1
- package/dist/routes/inputs.js +5 -6
- package/dist/routes/inputs.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +10 -9
- package/src/components/identifier/identifier.tsx +2 -2
- package/src/components/job-detail/agent-config-failure-callout.tsx +5 -1
- package/src/components/job-detail/job-context-panel.test.tsx +72 -0
- package/src/components/job-detail/job-context-panel.tsx +237 -0
- package/src/components/job-detail/job-detail-header.test.tsx +3 -0
- package/src/components/job-detail/job-detail-header.tsx +65 -34
- package/src/components/job-detail/job-detail-view.stories.tsx +555 -48
- package/src/components/job-detail/job-detail-view.tsx +101 -42
- package/src/components/job-detail/job-empty-states.tsx +23 -10
- package/src/components/job-detail/job-execution-switcher.tsx +5 -5
- package/src/components/job-detail/json-code.tsx +120 -0
- package/src/components/job-detail/step-attempt-log-panel.test.tsx +1 -0
- package/src/components/job-detail/step-attempt-log-panel.tsx +17 -6
- package/src/components/job-detail/step-troubleshooting.stories.tsx +120 -0
- package/src/components/job-detail/step-troubleshooting.test.tsx +337 -0
- package/src/components/job-detail/step-troubleshooting.tsx +537 -0
- package/src/components/job-graph/job-graph-content.tsx +4 -3
- package/src/components/job-graph/job-node.tsx +3 -3
- package/src/components/step-list/index.ts +1 -0
- package/src/components/step-list/step-list-model.ts +5 -0
- package/src/components/step-list/step-list.test.tsx +75 -2
- package/src/components/step-list/step-list.tsx +73 -20
- package/src/components/workflow-run-duration-label.tsx +1 -1
- package/src/components/workflow-run-list/job-status-strip.tsx +6 -6
- package/src/components/workflow-run-list/workflow-run-filter-menu.tsx +1 -1
- package/src/components/workflow-run-list/workflow-run-filters.tsx +4 -4
- package/src/components/workflow-run-list/workflow-run-list-states.tsx +6 -6
- package/src/components/workflow-run-list/workflow-run-list-view.tsx +1 -1
- package/src/components/workflow-run-list/workflow-run-row.tsx +7 -7
- package/src/components/workflow-run-summary/workflow-run-attempt-switcher.tsx +1 -1
- package/src/components/workflow-run-summary/workflow-run-summary.test.tsx +1 -1
- package/src/components/workflow-run-summary/workflow-run-summary.tsx +12 -12
- package/src/components/workflow-run-tabs/index.ts +7 -0
- 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 +273 -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 +95 -20
- 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/run-workspace-nav.test.tsx +44 -0
- package/src/components/workflow-run-view/run-workspace-nav.tsx +22 -9
- package/src/components/workflow-run-view/workflow-run-states.tsx +6 -6
- package/src/components/workflow-run-view/workflow-run-view.test.tsx +246 -20
- package/src/components/workflow-run-view/workflow-run-view.tsx +255 -37
- package/src/core/entities/job-execution.ts +7 -0
- package/src/core/entities/job.ts +9 -0
- package/src/core/entities/step-attempt.ts +33 -0
- package/src/core/entities/step.ts +3 -1
- package/src/core/run-annotation.test.ts +315 -0
- package/src/core/run-annotation.ts +244 -0
- package/src/core/workflow-run.ts +4 -0
- package/src/hooks/api/annotations.test.ts +84 -0
- package/src/hooks/api/annotations.ts +240 -0
- package/src/hooks/api/run-annotation-mapper.ts +30 -0
- package/src/hooks/api/run-annotations.ts +137 -0
- package/src/hooks/api/step-attempt-detail.ts +67 -0
- package/src/hooks/api/workflow-run-mapper.ts +46 -0
- package/src/hooks/api/workflow-runs.test.tsx +61 -1
- package/src/pages/project-workflows-page.tsx +19 -19
- package/src/pages/workflow-job-detail-page.test.tsx +118 -1
- package/src/pages/workflow-run-list-page.tsx +1 -1
- package/src/routes/inputs.ts +7 -2
- package/test/fixtures/workflow-run.ts +7 -0
- package/tsconfig.build.tsbuildinfo +1 -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/core/workflow-run-tabs.ts +0 -8
|
@@ -9,10 +9,10 @@ export function WorkflowRunSkeleton() {
|
|
|
9
9
|
return (
|
|
10
10
|
<section
|
|
11
11
|
aria-label="Loading workflow run"
|
|
12
|
-
className="bg-background-neutral-background px-
|
|
12
|
+
className="bg-background-neutral-background px-row py-row"
|
|
13
13
|
>
|
|
14
|
-
<div className="flex min-w-0 flex-wrap items-center gap-x-
|
|
15
|
-
<div className="flex min-w-0 items-center gap-
|
|
14
|
+
<div className="flex min-w-0 flex-wrap items-center gap-x-cluster gap-y-inline">
|
|
15
|
+
<div className="flex min-w-0 items-center gap-inline">
|
|
16
16
|
<Skeleton className="size-8 rounded-full" />
|
|
17
17
|
<Skeleton className="h-24 w-180 rounded-6" />
|
|
18
18
|
</div>
|
|
@@ -35,7 +35,7 @@ export function WorkflowRunContentSkeleton() {
|
|
|
35
35
|
return (
|
|
36
36
|
<section
|
|
37
37
|
aria-label="Loading workflow run content"
|
|
38
|
-
className="min-h-160 rounded-8 border border-border-neutral-base bg-background-components-base p-
|
|
38
|
+
className="min-h-160 rounded-8 border border-border-neutral-base bg-background-components-base p-panel-compact"
|
|
39
39
|
>
|
|
40
40
|
<Skeleton className="h-160 w-full rounded-6" />
|
|
41
41
|
</section>
|
|
@@ -59,9 +59,9 @@ export function WorkflowRunNotFound() {
|
|
|
59
59
|
*/
|
|
60
60
|
export function WorkflowRunStaleError({query}: {query: QueryLoadErrorQuery}) {
|
|
61
61
|
return (
|
|
62
|
-
<div className="border-b border-border-neutral-base p-
|
|
62
|
+
<div className="border-b border-border-neutral-base p-tight">
|
|
63
63
|
<Callout role="alert" type="error">
|
|
64
|
-
<div className="flex items-center justify-between gap-
|
|
64
|
+
<div className="flex items-center justify-between gap-inline">
|
|
65
65
|
<Text size="xs">Could not refresh this run.</Text>
|
|
66
66
|
<Button
|
|
67
67
|
type="button"
|
|
@@ -1,10 +1,13 @@
|
|
|
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
4
|
import {screen, waitFor, within} from '@testing-library/react';
|
|
4
5
|
import userEvent from '@testing-library/user-event';
|
|
5
6
|
import {
|
|
6
7
|
workflowJobDto,
|
|
8
|
+
workflowJobExecutionDto,
|
|
7
9
|
workflowRunDetailDto,
|
|
10
|
+
workflowStepAttemptDto,
|
|
8
11
|
workflowStepDto,
|
|
9
12
|
} from '#test/fixtures/workflow-run.js';
|
|
10
13
|
import {jsonResponse, PROJECT_TEST_WSLUG, renderProjectPage} from '#test/pages.js';
|
|
@@ -14,6 +17,13 @@ const RUN_ID = '66666666-6666-4666-8666-666666666666';
|
|
|
14
17
|
const PROJECT_ID = '44444444-4444-4444-8444-444444444444';
|
|
15
18
|
const BUILD_JOB_ID = '77777777-7777-4777-8777-777777777777';
|
|
16
19
|
const DEPLOY_JOB_ID = '88888888-8888-4888-8888-888888888888';
|
|
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/;
|
|
17
27
|
|
|
18
28
|
describe('WorkflowRunView', () => {
|
|
19
29
|
beforeEach(() => {
|
|
@@ -32,9 +42,7 @@ describe('WorkflowRunView', () => {
|
|
|
32
42
|
});
|
|
33
43
|
|
|
34
44
|
test('opens the all-jobs Summary on the dependency graph by default', async () => {
|
|
35
|
-
|
|
36
|
-
fetchImpl: vi.fn(() => Promise.resolve(jsonResponse(workflowRunViewDetailDto()))),
|
|
37
|
-
});
|
|
45
|
+
configureRunFetch();
|
|
38
46
|
|
|
39
47
|
const {container} = renderView();
|
|
40
48
|
|
|
@@ -59,9 +67,7 @@ describe('WorkflowRunView', () => {
|
|
|
59
67
|
});
|
|
60
68
|
|
|
61
69
|
test('treats the removed Jobs tab URL as the graph Summary', async () => {
|
|
62
|
-
|
|
63
|
-
fetchImpl: vi.fn(() => Promise.resolve(jsonResponse(workflowRunViewDetailDto()))),
|
|
64
|
-
});
|
|
70
|
+
configureRunFetch();
|
|
65
71
|
|
|
66
72
|
renderView({tab: 'jobs'});
|
|
67
73
|
|
|
@@ -71,9 +77,7 @@ describe('WorkflowRunView', () => {
|
|
|
71
77
|
|
|
72
78
|
test('navigates graph and rail jobs to their dedicated job routes', async () => {
|
|
73
79
|
const user = userEvent.setup();
|
|
74
|
-
|
|
75
|
-
fetchImpl: vi.fn(() => Promise.resolve(jsonResponse(workflowRunViewDetailDto()))),
|
|
76
|
-
});
|
|
80
|
+
configureRunFetch();
|
|
77
81
|
|
|
78
82
|
const {router} = renderView();
|
|
79
83
|
await user.click(await screen.findByRole('button', {name: 'deploy, Running'}));
|
|
@@ -87,9 +91,7 @@ describe('WorkflowRunView', () => {
|
|
|
87
91
|
|
|
88
92
|
test('keeps run Annotations and Source in the workspace navigation', async () => {
|
|
89
93
|
const user = userEvent.setup();
|
|
90
|
-
|
|
91
|
-
fetchImpl: vi.fn(() => Promise.resolve(jsonResponse(workflowRunViewDetailDto()))),
|
|
92
|
-
});
|
|
94
|
+
configureRunFetch();
|
|
93
95
|
|
|
94
96
|
const {router} = renderView();
|
|
95
97
|
await screen.findByRole('region', {name: 'Workflow jobs'});
|
|
@@ -99,19 +101,182 @@ describe('WorkflowRunView', () => {
|
|
|
99
101
|
});
|
|
100
102
|
|
|
101
103
|
test('filters the single run-level Annotations page by job', async () => {
|
|
102
|
-
|
|
103
|
-
fetchImpl: vi.fn(() => Promise.resolve(jsonResponse(workflowRunViewDetailDto()))),
|
|
104
|
-
});
|
|
104
|
+
configureRunFetch();
|
|
105
105
|
|
|
106
106
|
renderView({tab: 'annotations', selection: {jobId: BUILD_JOB_ID}});
|
|
107
107
|
|
|
108
108
|
expect(
|
|
109
109
|
await screen.findByRole('combobox', {name: 'Filter annotations by job'}),
|
|
110
110
|
).toHaveTextContent('build');
|
|
111
|
-
expect(screen.
|
|
111
|
+
expect(await screen.findByText('This run has no annotations to show.')).toBeInTheDocument();
|
|
112
112
|
expect(screen.queryByRole('tab', {name: 'Annotations'})).not.toBeInTheDocument();
|
|
113
113
|
});
|
|
114
114
|
|
|
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**.',
|
|
124
|
+
}),
|
|
125
|
+
]);
|
|
126
|
+
|
|
127
|
+
renderView({tab: 'annotations'});
|
|
128
|
+
|
|
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();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test('links an annotation back to the step that emitted it', async () => {
|
|
137
|
+
configureRunFetch([annotationDto({id: ANNOTATION_ID_ONE, context: 'coverage'})]);
|
|
138
|
+
|
|
139
|
+
renderView({tab: 'annotations'});
|
|
140
|
+
|
|
141
|
+
const href = (await screen.findByRole('link', {name: 'Open step'})).getAttribute('href') ?? '';
|
|
142
|
+
const [pathname, query = ''] = href.split('?');
|
|
143
|
+
|
|
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"',
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test('shows a failed annotations read as an error, never as an empty run', async () => {
|
|
154
|
+
configureApiClient({
|
|
155
|
+
fetchImpl: vi.fn((input: RequestInfo | URL) =>
|
|
156
|
+
Promise.resolve(
|
|
157
|
+
requestUrl(input).includes('/annotations')
|
|
158
|
+
? jsonResponse({code: 'internal'}, {status: 500})
|
|
159
|
+
: jsonResponse(workflowRunViewDetailDto()),
|
|
160
|
+
),
|
|
161
|
+
),
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
renderView({tab: 'annotations'});
|
|
165
|
+
|
|
166
|
+
expect(await screen.findByRole('button', {name: 'Retry loading annotations'})).toBeVisible();
|
|
167
|
+
expect(screen.queryByText('This run has no annotations to show.')).not.toBeInTheDocument();
|
|
168
|
+
});
|
|
169
|
+
|
|
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
|
+
}),
|
|
178
|
+
),
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
renderView({tab: 'annotations'});
|
|
182
|
+
|
|
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();
|
|
190
|
+
});
|
|
191
|
+
|
|
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
|
+
}),
|
|
204
|
+
),
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
renderView({tab: 'annotations', selection: {annotation: deepLinkedId}});
|
|
208
|
+
|
|
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');
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test('separates a filtered miss from a run with no annotations', async () => {
|
|
217
|
+
configureRunFetch([annotationDto({id: ANNOTATION_ID_ONE, context: 'coverage', style: 'info'})]);
|
|
218
|
+
|
|
219
|
+
renderView({tab: 'annotations', selection: {severity: 'error'}});
|
|
220
|
+
|
|
221
|
+
expect(await screen.findByText('No matching annotations')).toBeInTheDocument();
|
|
222
|
+
expect(
|
|
223
|
+
screen.getByText('This run has annotations, but none at error severity.'),
|
|
224
|
+
).toBeInTheDocument();
|
|
225
|
+
expect(screen.getByRole('button', {name: 'Clear filters'})).toBeInTheDocument();
|
|
226
|
+
});
|
|
227
|
+
|
|
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
|
+
);
|
|
234
|
+
|
|
235
|
+
renderView({tab: 'annotations', selection: {severity: 'error'}});
|
|
236
|
+
|
|
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();
|
|
245
|
+
});
|
|
246
|
+
|
|
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
|
+
},
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
await user.click(await screen.findByRole('button', {name: 'Clear filters'}));
|
|
264
|
+
|
|
265
|
+
await waitFor(() => expect(router.state.location.search).toEqual({tab: 'annotations'}));
|
|
266
|
+
});
|
|
267
|
+
|
|
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'})]);
|
|
272
|
+
|
|
273
|
+
renderView({tab: 'annotations', selection: {jobId: DEPLOY_JOB_ID}});
|
|
274
|
+
|
|
275
|
+
expect(
|
|
276
|
+
await screen.findByText('This run has annotations, but none from deploy.'),
|
|
277
|
+
).toBeInTheDocument();
|
|
278
|
+
});
|
|
279
|
+
|
|
115
280
|
test('renders the captured workflow source in the Source section', async () => {
|
|
116
281
|
configureApiClient({
|
|
117
282
|
fetchImpl: vi.fn(() =>
|
|
@@ -132,9 +297,7 @@ describe('WorkflowRunView', () => {
|
|
|
132
297
|
});
|
|
133
298
|
|
|
134
299
|
test('explains when the run has no source snapshot', async () => {
|
|
135
|
-
|
|
136
|
-
fetchImpl: vi.fn(() => Promise.resolve(jsonResponse(workflowRunViewDetailDto()))),
|
|
137
|
-
});
|
|
300
|
+
configureRunFetch();
|
|
138
301
|
|
|
139
302
|
renderView({tab: 'source'});
|
|
140
303
|
|
|
@@ -164,6 +327,48 @@ function renderView(props: Partial<Parameters<typeof WorkflowRunView>[0]> = {})
|
|
|
164
327
|
));
|
|
165
328
|
}
|
|
166
329
|
|
|
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;
|
|
334
|
+
}
|
|
335
|
+
|
|
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
|
+
),
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
|
|
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',
|
|
368
|
+
...overrides,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
167
372
|
function workflowRunViewDetailDto(
|
|
168
373
|
overrides: Partial<WorkflowRunDetailResponseDto> = {},
|
|
169
374
|
): WorkflowRunDetailResponseDto {
|
|
@@ -181,7 +386,28 @@ function workflowRunViewDetailDto(
|
|
|
181
386
|
run_attempt_id: RUN_ID,
|
|
182
387
|
name: 'build',
|
|
183
388
|
status: 'succeeded',
|
|
184
|
-
|
|
389
|
+
job_executions: [
|
|
390
|
+
workflowJobExecutionDto({
|
|
391
|
+
id: BUILD_EXECUTION_ID,
|
|
392
|
+
job_id: BUILD_JOB_ID,
|
|
393
|
+
status: 'succeeded',
|
|
394
|
+
steps: [
|
|
395
|
+
workflowStepDto({
|
|
396
|
+
id: BUILD_STEP_ID,
|
|
397
|
+
name: 'checkout',
|
|
398
|
+
status: 'succeeded',
|
|
399
|
+
attempts: [
|
|
400
|
+
workflowStepAttemptDto({
|
|
401
|
+
id: BUILD_ATTEMPT_ID,
|
|
402
|
+
step_id: BUILD_STEP_ID,
|
|
403
|
+
attempt: 1,
|
|
404
|
+
status: 'succeeded',
|
|
405
|
+
}),
|
|
406
|
+
],
|
|
407
|
+
}),
|
|
408
|
+
],
|
|
409
|
+
}),
|
|
410
|
+
],
|
|
185
411
|
}),
|
|
186
412
|
workflowJobDto({
|
|
187
413
|
id: DEPLOY_JOB_ID,
|