@shipfox/client-workflows 13.0.0 → 14.0.1
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/main.ts +16 -1
- package/.storybook/workspace-source.ts +31 -0
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +37 -0
- package/dist/components/job-graph/job-duration-label.js +1 -1
- package/dist/components/job-graph/job-duration-label.js.map +1 -1
- package/dist/components/job-graph/job-graph-content.d.ts +2 -1
- package/dist/components/job-graph/job-graph-content.d.ts.map +1 -1
- package/dist/components/job-graph/job-graph-content.js +3 -3
- package/dist/components/job-graph/job-graph-content.js.map +1 -1
- package/dist/components/job-graph/job-graph-view.d.ts +2 -1
- package/dist/components/job-graph/job-graph-view.d.ts.map +1 -1
- package/dist/components/job-graph/job-graph-view.js +2 -2
- package/dist/components/job-graph/job-graph-view.js.map +1 -1
- package/dist/components/job-graph/job-node.js +5 -5
- package/dist/components/job-graph/job-node.js.map +1 -1
- package/dist/components/job-graph/types.d.ts +2 -1
- package/dist/components/job-graph/types.d.ts.map +1 -1
- package/dist/components/job-graph/types.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-number-label.js +1 -1
- package/dist/components/workflow-run-number-label.js.map +1 -1
- package/dist/components/workflow-run-summary/workflow-run-attempt-switcher.js +2 -2
- 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 +2 -7
- package/dist/components/workflow-run-summary/workflow-run-summary.d.ts.map +1 -1
- package/dist/components/workflow-run-summary/workflow-run-summary.js +92 -68
- package/dist/components/workflow-run-summary/workflow-run-summary.js.map +1 -1
- package/dist/components/workflow-run-tabs/index.d.ts +6 -0
- package/dist/components/workflow-run-tabs/index.d.ts.map +1 -0
- package/dist/components/workflow-run-tabs/index.js +7 -0
- package/dist/components/workflow-run-tabs/index.js.map +1 -0
- package/dist/components/workflow-run-tabs/run-annotation-summary-line.d.ts +11 -0
- package/dist/components/workflow-run-tabs/run-annotation-summary-line.d.ts.map +1 -0
- package/dist/components/workflow-run-tabs/run-annotation-summary-line.js +71 -0
- package/dist/components/workflow-run-tabs/run-annotation-summary-line.js.map +1 -0
- package/dist/components/workflow-run-tabs/run-annotations-empty.d.ts +2 -0
- package/dist/components/workflow-run-tabs/run-annotations-empty.d.ts.map +1 -0
- package/dist/components/workflow-run-tabs/run-annotations-empty.js +11 -0
- package/dist/components/workflow-run-tabs/run-annotations-empty.js.map +1 -0
- package/dist/components/workflow-run-tabs/run-jobs-list.d.ts +18 -0
- package/dist/components/workflow-run-tabs/run-jobs-list.d.ts.map +1 -0
- package/dist/components/workflow-run-tabs/run-jobs-list.js +107 -0
- package/dist/components/workflow-run-tabs/run-jobs-list.js.map +1 -0
- package/dist/components/workflow-run-tabs/run-tab-count.d.ts +6 -0
- package/dist/components/workflow-run-tabs/run-tab-count.d.ts.map +1 -0
- package/dist/components/workflow-run-tabs/run-tab-count.js +27 -0
- package/dist/components/workflow-run-tabs/run-tab-count.js.map +1 -0
- package/dist/components/workflow-run-tabs/run-tab-strip.d.ts +8 -0
- package/dist/components/workflow-run-tabs/run-tab-strip.d.ts.map +1 -0
- package/dist/components/workflow-run-tabs/run-tab-strip.js +82 -0
- package/dist/components/workflow-run-tabs/run-tab-strip.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 +40 -1
- package/dist/components/workflow-run-view/workflow-run-selection.js.map +1 -1
- package/dist/components/workflow-run-view/workflow-run-states.d.ts +1 -0
- package/dist/components/workflow-run-view/workflow-run-states.d.ts.map +1 -1
- package/dist/components/workflow-run-view/workflow-run-states.js +10 -1
- package/dist/components/workflow-run-view/workflow-run-states.js.map +1 -1
- package/dist/components/workflow-run-view/workflow-run-view.d.ts +8 -4
- package/dist/components/workflow-run-view/workflow-run-view.d.ts.map +1 -1
- package/dist/components/workflow-run-view/workflow-run-view.js +125 -78
- package/dist/components/workflow-run-view/workflow-run-view.js.map +1 -1
- package/dist/components/workflow-source-panel/index.d.ts +1 -0
- package/dist/components/workflow-source-panel/index.d.ts.map +1 -1
- package/dist/components/workflow-source-panel/index.js +1 -0
- package/dist/components/workflow-source-panel/index.js.map +1 -1
- package/dist/components/workflow-source-panel/workflow-source-content.d.ts +12 -0
- package/dist/components/workflow-source-panel/workflow-source-content.d.ts.map +1 -0
- package/dist/components/workflow-source-panel/workflow-source-content.js +53 -0
- package/dist/components/workflow-source-panel/workflow-source-content.js.map +1 -0
- package/dist/components/workflow-source-panel/workflow-source-panel.d.ts +1 -1
- package/dist/components/workflow-source-panel/workflow-source-panel.d.ts.map +1 -1
- package/dist/components/workflow-source-panel/workflow-source-panel.js +7 -54
- package/dist/components/workflow-source-panel/workflow-source-panel.js.map +1 -1
- package/dist/core/entities/step.d.ts +1 -1
- package/dist/core/entities/step.d.ts.map +1 -1
- package/dist/core/entities/step.js +2 -0
- package/dist/core/entities/step.js.map +1 -1
- package/dist/core/workflow-run-tabs.d.ts +9 -0
- package/dist/core/workflow-run-tabs.d.ts.map +1 -0
- package/dist/core/workflow-run-tabs.js +3 -0
- package/dist/core/workflow-run-tabs.js.map +1 -0
- package/dist/pages/workflow-run-detail-page.d.ts.map +1 -1
- package/dist/pages/workflow-run-detail-page.js +28 -6
- package/dist/pages/workflow-run-detail-page.js.map +1 -1
- package/dist/routes/inputs.d.ts +33 -0
- package/dist/routes/inputs.d.ts.map +1 -1
- package/dist/routes/inputs.js +49 -0
- package/dist/routes/inputs.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +9 -9
- package/src/components/job-graph/job-duration-label.tsx +5 -1
- package/src/components/job-graph/job-graph-content.tsx +7 -4
- package/src/components/job-graph/job-graph-view.tsx +6 -3
- package/src/components/job-graph/job-graph.stories.tsx +1 -1
- package/src/components/job-graph/job-node.stories.tsx +1 -1
- package/src/components/job-graph/job-node.test.tsx +27 -2
- package/src/components/job-graph/job-node.tsx +6 -6
- package/src/components/job-graph/types.ts +5 -1
- package/src/components/workflow-run-duration-label.tsx +1 -1
- package/src/components/workflow-run-number-label.tsx +1 -1
- package/src/components/workflow-run-summary/workflow-run-attempt-switcher.tsx +2 -2
- package/src/components/workflow-run-summary/workflow-run-summary.stories.tsx +23 -30
- package/src/components/workflow-run-summary/workflow-run-summary.test.tsx +3 -22
- package/src/components/workflow-run-summary/workflow-run-summary.tsx +84 -69
- package/src/components/workflow-run-tabs/index.ts +5 -0
- package/src/components/workflow-run-tabs/run-annotation-summary-line.test.tsx +69 -0
- package/src/components/workflow-run-tabs/run-annotation-summary-line.tsx +97 -0
- package/src/components/workflow-run-tabs/run-annotations-empty.tsx +11 -0
- package/src/components/workflow-run-tabs/run-jobs-list.tsx +151 -0
- package/src/components/workflow-run-tabs/run-tab-count.tsx +28 -0
- package/src/components/workflow-run-tabs/run-tab-strip.stories.tsx +52 -0
- package/src/components/workflow-run-tabs/run-tab-strip.test.tsx +117 -0
- package/src/components/workflow-run-tabs/run-tab-strip.tsx +97 -0
- package/src/components/workflow-run-view/workflow-run-selection.test.ts +48 -0
- package/src/components/workflow-run-view/workflow-run-selection.ts +48 -1
- package/src/components/workflow-run-view/workflow-run-states.tsx +12 -1
- package/src/components/workflow-run-view/workflow-run-view.test.tsx +64 -64
- package/src/components/workflow-run-view/workflow-run-view.tsx +199 -85
- package/src/components/workflow-source-panel/index.ts +1 -0
- package/src/components/workflow-source-panel/workflow-source-content.tsx +84 -0
- package/src/components/workflow-source-panel/workflow-source-panel.tsx +14 -81
- package/src/core/entities/step.ts +4 -0
- package/src/core/workflow-run-tabs.ts +8 -0
- package/src/pages/workflow-run-detail-page.tsx +31 -4
- package/src/pages/workflow-run-pages.test.tsx +185 -45
- package/src/routes/inputs.test.ts +47 -0
- package/src/routes/inputs.ts +45 -0
- package/src/storybook-config.test.ts +27 -0
- package/tsconfig.build.tsbuildinfo +1 -1
- package/tsconfig.test.json +7 -1
|
@@ -82,6 +82,29 @@ describe('resolveWorkflowRunSelection', () => {
|
|
|
82
82
|
expect(resolved.selectedAttemptId).toBeNull();
|
|
83
83
|
});
|
|
84
84
|
|
|
85
|
+
test('finds a job from a legacy job execution selection', () => {
|
|
86
|
+
const execution = workflowJobExecutionDto({id: 'execution-deploy', sequence: 1});
|
|
87
|
+
const run = workflowRunDetail({
|
|
88
|
+
jobs: [
|
|
89
|
+
workflowJobDto({id: 'job-build', name: 'build'}),
|
|
90
|
+
workflowJobDto({
|
|
91
|
+
id: 'job-deploy',
|
|
92
|
+
name: 'deploy',
|
|
93
|
+
position: 1,
|
|
94
|
+
job_executions: [execution],
|
|
95
|
+
}),
|
|
96
|
+
],
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const resolved = resolveWorkflowRunSelection({
|
|
100
|
+
run,
|
|
101
|
+
selection: {jobExecutionId: 'execution-deploy'},
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
expect(resolved.job?.id).toBe('job-deploy');
|
|
105
|
+
expect(resolved.jobExecution?.id).toBe('execution-deploy');
|
|
106
|
+
});
|
|
107
|
+
|
|
85
108
|
test('falls back from an invalid job execution id to the running execution', () => {
|
|
86
109
|
const run = workflowRunDetail({
|
|
87
110
|
jobs: [
|
|
@@ -156,6 +179,31 @@ describe('resolveWorkflowRunSelection', () => {
|
|
|
156
179
|
expect(resolved.selectedAttemptId).toBe('attempt-2');
|
|
157
180
|
});
|
|
158
181
|
|
|
182
|
+
test('finds the step owner from a legacy step attempt selection', () => {
|
|
183
|
+
const attempt = workflowStepAttemptDto({id: 'attempt-deploy', step_id: 'step-deploy'});
|
|
184
|
+
const step = workflowStepDto({
|
|
185
|
+
id: 'step-deploy',
|
|
186
|
+
current_attempt: 1,
|
|
187
|
+
attempts: [attempt],
|
|
188
|
+
});
|
|
189
|
+
const run = workflowRunDetail({
|
|
190
|
+
jobs: [
|
|
191
|
+
workflowJobDto({id: 'job-build', name: 'build'}),
|
|
192
|
+
workflowJobDto({id: 'job-deploy', name: 'deploy', position: 1, steps: [step]}),
|
|
193
|
+
],
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
const resolved = resolveWorkflowRunSelection({
|
|
197
|
+
run,
|
|
198
|
+
selection: {stepAttemptId: 'attempt-deploy'},
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
expect(resolved.job?.id).toBe('job-deploy');
|
|
202
|
+
expect(resolved.step?.id).toBe('step-deploy');
|
|
203
|
+
expect(resolved.attempt?.id).toBe('attempt-deploy');
|
|
204
|
+
expect(resolved.selectedAttemptId).toBe('attempt-deploy');
|
|
205
|
+
});
|
|
206
|
+
|
|
159
207
|
test('lets a valid step override a mismatched job id', () => {
|
|
160
208
|
const step = workflowStepDto({
|
|
161
209
|
id: 'step-deploy',
|
|
@@ -25,6 +25,8 @@ export function resolveWorkflowRunSelection({
|
|
|
25
25
|
}): ResolvedWorkflowRunSelection {
|
|
26
26
|
const jobById = new Map(run.jobs.map((job) => [job.id, job]));
|
|
27
27
|
const stepMatch = findStep(run, selection.stepId);
|
|
28
|
+
const jobExecutionMatch = findJobExecution(run, selection.jobExecutionId);
|
|
29
|
+
const stepAttemptMatch = findStepAttempt(run, selection.stepAttemptId);
|
|
28
30
|
|
|
29
31
|
if (stepMatch) {
|
|
30
32
|
const attempt = resolveStepAttempt(stepMatch.step, selection.stepAttemptId);
|
|
@@ -37,7 +39,20 @@ export function resolveWorkflowRunSelection({
|
|
|
37
39
|
};
|
|
38
40
|
}
|
|
39
41
|
|
|
40
|
-
|
|
42
|
+
if (stepAttemptMatch) {
|
|
43
|
+
return {
|
|
44
|
+
job: stepAttemptMatch.job,
|
|
45
|
+
jobExecution: stepAttemptMatch.jobExecution,
|
|
46
|
+
step: stepAttemptMatch.step,
|
|
47
|
+
attempt: stepAttemptMatch.attempt,
|
|
48
|
+
selectedAttemptId: stepAttemptMatch.attempt.id,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const job =
|
|
53
|
+
(selection.jobId ? jobById.get(selection.jobId) : undefined) ??
|
|
54
|
+
jobExecutionMatch?.job ??
|
|
55
|
+
run.jobs.at(0);
|
|
41
56
|
const jobExecution = job ? resolveJobExecution(job, selection.jobExecutionId) : undefined;
|
|
42
57
|
|
|
43
58
|
return {
|
|
@@ -49,6 +64,38 @@ export function resolveWorkflowRunSelection({
|
|
|
49
64
|
};
|
|
50
65
|
}
|
|
51
66
|
|
|
67
|
+
function findJobExecution(
|
|
68
|
+
run: WorkflowRunDetail,
|
|
69
|
+
jobExecutionId: string | undefined,
|
|
70
|
+
): {job: Job; jobExecution: JobExecution} | undefined {
|
|
71
|
+
if (!jobExecutionId) return undefined;
|
|
72
|
+
|
|
73
|
+
for (const job of run.jobs) {
|
|
74
|
+
const jobExecution = job.jobExecutions.find((candidate) => candidate.id === jobExecutionId);
|
|
75
|
+
if (jobExecution) return {job, jobExecution};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function findStepAttempt(
|
|
82
|
+
run: WorkflowRunDetail,
|
|
83
|
+
stepAttemptId: string | undefined,
|
|
84
|
+
): {job: Job; jobExecution: JobExecution; step: Step; attempt: StepAttempt} | undefined {
|
|
85
|
+
if (!stepAttemptId) return undefined;
|
|
86
|
+
|
|
87
|
+
for (const job of run.jobs) {
|
|
88
|
+
for (const jobExecution of job.jobExecutions) {
|
|
89
|
+
for (const step of jobExecution.steps) {
|
|
90
|
+
const attempt = step.attempts.find((candidate) => candidate.id === stepAttemptId);
|
|
91
|
+
if (attempt) return {job, jobExecution, step, attempt};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
|
|
52
99
|
function findStep(
|
|
53
100
|
run: WorkflowRunDetail,
|
|
54
101
|
stepId: string | undefined,
|
|
@@ -9,7 +9,7 @@ export function WorkflowRunSkeleton() {
|
|
|
9
9
|
return (
|
|
10
10
|
<section
|
|
11
11
|
aria-label="Loading workflow run"
|
|
12
|
-
className="
|
|
12
|
+
className="bg-background-neutral-background px-16 py-12"
|
|
13
13
|
>
|
|
14
14
|
<div className="flex min-w-0 flex-wrap items-center gap-x-12 gap-y-8">
|
|
15
15
|
<div className="flex min-w-0 items-center gap-8">
|
|
@@ -31,6 +31,17 @@ export function WorkflowRunSkeleton() {
|
|
|
31
31
|
);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
export function WorkflowRunContentSkeleton() {
|
|
35
|
+
return (
|
|
36
|
+
<section
|
|
37
|
+
aria-label="Loading workflow run content"
|
|
38
|
+
className="min-h-160 rounded-8 border border-border-neutral-base bg-background-components-base p-16"
|
|
39
|
+
>
|
|
40
|
+
<Skeleton className="h-160 w-full rounded-6" />
|
|
41
|
+
</section>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
34
45
|
export function WorkflowRunNotFound() {
|
|
35
46
|
return (
|
|
36
47
|
<EmptyState
|
|
@@ -26,6 +26,8 @@ const DEPLOY_JOB_ID = '88888888-8888-4888-8888-888888888888';
|
|
|
26
26
|
const CHECKOUT_STEP_ID = '99999999-9999-4999-8999-999999999999';
|
|
27
27
|
const CHECKOUT_ATTEMPT_ID = 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa';
|
|
28
28
|
const RERUN_BUTTON_NAME = /^Re-run/;
|
|
29
|
+
const BUILD_JOB_BUTTON_NAME = 'build, Succeeded';
|
|
30
|
+
const DEPLOY_JOB_BUTTON_NAME = 'deploy, Running';
|
|
29
31
|
const ATTEMPT_2_PATTERN = /Attempt 2/;
|
|
30
32
|
const COPY_RUN_BUTTON_NAME = /Copy run/;
|
|
31
33
|
const EXECUTION_ONE_MENU_ITEM_PATTERN = /#1/;
|
|
@@ -34,7 +36,19 @@ const LISTENING_EXECUTION_SWITCHER_NAME =
|
|
|
34
36
|
const JOB_TEMPLATE_NAME = ['Implement $', '{{ event.issue.identifier }}'].join('');
|
|
35
37
|
|
|
36
38
|
describe('WorkflowRunView', () => {
|
|
37
|
-
test('
|
|
39
|
+
test('keeps the real tab strip visible while the run is loading', async () => {
|
|
40
|
+
configureApiClient({fetchImpl: vi.fn(() => new Promise<Response>(() => undefined))});
|
|
41
|
+
|
|
42
|
+
renderView({tab: 'summary'});
|
|
43
|
+
|
|
44
|
+
expect(await screen.findByRole('tab', {name: 'Summary'})).toBeInTheDocument();
|
|
45
|
+
expect(screen.getByRole('tab', {name: 'Jobs'})).toBeInTheDocument();
|
|
46
|
+
expect(screen.getByRole('tab', {name: 'Annotations'})).toBeInTheDocument();
|
|
47
|
+
expect(screen.getByRole('tab', {name: 'Source'})).toBeInTheDocument();
|
|
48
|
+
expect(screen.getByRole('region', {name: 'Loading workflow run content'})).toBeInTheDocument();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('renders the run summary, jobs list, and selected job step attempts when a run loads', async () => {
|
|
38
52
|
configureApiClient({
|
|
39
53
|
fetchImpl: vi.fn(() => Promise.resolve(jsonResponse(workflowRunViewDetailDto()))),
|
|
40
54
|
});
|
|
@@ -50,8 +64,13 @@ describe('WorkflowRunView', () => {
|
|
|
50
64
|
within(summary).queryByRole('button', {name: COPY_RUN_BUTTON_NAME}),
|
|
51
65
|
).not.toBeInTheDocument();
|
|
52
66
|
expect(await screen.findByRole('region', {name: 'Workflow jobs'})).toBeInTheDocument();
|
|
53
|
-
expect(screen.getByRole('button', {name:
|
|
54
|
-
expect(screen.getByRole('button', {name:
|
|
67
|
+
expect(screen.getByRole('button', {name: BUILD_JOB_BUTTON_NAME})).toBeInTheDocument();
|
|
68
|
+
expect(screen.getByRole('button', {name: DEPLOY_JOB_BUTTON_NAME})).toBeInTheDocument();
|
|
69
|
+
expect(screen.getByRole('button', {name: BUILD_JOB_BUTTON_NAME})).toHaveClass(
|
|
70
|
+
'bg-background-components-hover',
|
|
71
|
+
'hover:bg-background-components-pressed',
|
|
72
|
+
);
|
|
73
|
+
expect(screen.getByRole('tabpanel')).toHaveClass('max-w-[1120px]', 'px-24');
|
|
55
74
|
expect(screen.getByRole('region', {name: 'build'})).toBeInTheDocument();
|
|
56
75
|
expect(
|
|
57
76
|
screen.getByRole('button', {name: 'checkout, Succeeded, attempt 1'}),
|
|
@@ -685,8 +704,7 @@ describe('WorkflowRunView', () => {
|
|
|
685
704
|
).toBeInTheDocument();
|
|
686
705
|
});
|
|
687
706
|
|
|
688
|
-
test('
|
|
689
|
-
const user = userEvent.setup();
|
|
707
|
+
test('renders the full workflow source in the Source tab', async () => {
|
|
690
708
|
configureApiClient({
|
|
691
709
|
fetchImpl: vi.fn(() =>
|
|
692
710
|
Promise.resolve(
|
|
@@ -702,38 +720,20 @@ describe('WorkflowRunView', () => {
|
|
|
702
720
|
),
|
|
703
721
|
});
|
|
704
722
|
|
|
705
|
-
renderView();
|
|
706
|
-
|
|
707
|
-
const deployNode = await screen.findByRole('button', {name: 'deploy, Running'});
|
|
708
|
-
await user.click(deployNode);
|
|
709
|
-
expect(deployNode).toHaveAttribute('aria-pressed', 'true');
|
|
723
|
+
renderView({tab: 'source'});
|
|
710
724
|
|
|
711
|
-
|
|
712
|
-
'
|
|
713
|
-
|
|
725
|
+
expect(await screen.findByRole('tab', {name: 'Source'})).toHaveAttribute(
|
|
726
|
+
'aria-selected',
|
|
727
|
+
'true',
|
|
714
728
|
);
|
|
715
|
-
|
|
716
|
-
expect(
|
|
717
|
-
expect(sourceButton).toHaveAttribute('aria-expanded', 'false');
|
|
718
|
-
|
|
719
|
-
await user.click(sourceButton);
|
|
720
|
-
|
|
721
|
-
const sourcePanel = screen.getByRole('dialog', {name: 'Workflow source'});
|
|
722
|
-
expect(sourceButton).toHaveAttribute('aria-expanded', 'true');
|
|
723
|
-
expect(sourcePanel).toHaveAttribute('id', panelId);
|
|
724
|
-
expect(sourcePanel).toHaveTextContent('pnpm test');
|
|
725
|
-
|
|
726
|
-
await user.click(screen.getByRole('button', {name: 'Close source'}));
|
|
727
|
-
|
|
728
|
-
await waitFor(() => expect(sourceButton).toHaveFocus());
|
|
729
|
-
expect(sourceButton).toHaveAttribute('aria-expanded', 'false');
|
|
730
|
-
expect(deployNode).toHaveAttribute('aria-pressed', 'true');
|
|
731
|
-
expect(screen.getByRole('region', {name: 'deploy'})).toBeInTheDocument();
|
|
729
|
+
expect(await screen.findByText('pnpm test')).toBeInTheDocument();
|
|
730
|
+
expect(screen.queryByRole('dialog', {name: 'Workflow source'})).not.toBeInTheDocument();
|
|
732
731
|
});
|
|
733
732
|
|
|
734
733
|
test('highlights selected step source lines when the source panel opens', async () => {
|
|
735
734
|
const user = userEvent.setup();
|
|
736
735
|
const stepId = '99999999-9999-4999-8999-000000000003';
|
|
736
|
+
const stepAttemptId = 'aaaaaaaa-aaaa-4aaa-8aaa-000000000003';
|
|
737
737
|
configureApiClient({
|
|
738
738
|
fetchImpl: vi.fn(() =>
|
|
739
739
|
Promise.resolve(
|
|
@@ -749,13 +749,27 @@ describe('WorkflowRunView', () => {
|
|
|
749
749
|
run_attempt_id: RUN_ID,
|
|
750
750
|
name: 'deploy',
|
|
751
751
|
status: 'running',
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
key: 'deploy',
|
|
752
|
+
job_executions: [
|
|
753
|
+
workflowJobExecutionDto({
|
|
754
|
+
job_id: DEPLOY_JOB_ID,
|
|
756
755
|
name: 'deploy',
|
|
757
|
-
source_location: {start_line: 2, end_line: 3},
|
|
758
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
|
+
],
|
|
759
773
|
}),
|
|
760
774
|
],
|
|
761
775
|
}),
|
|
@@ -766,29 +780,29 @@ describe('WorkflowRunView', () => {
|
|
|
766
780
|
),
|
|
767
781
|
});
|
|
768
782
|
|
|
769
|
-
renderView({selection: {stepId}});
|
|
770
|
-
const
|
|
771
|
-
await user.click(within(
|
|
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'}));
|
|
772
786
|
|
|
773
|
-
await screen.findByRole('dialog', {name: 'Workflow source'});
|
|
774
|
-
const highlightedLines =
|
|
787
|
+
const sourceDialog = await screen.findByRole('dialog', {name: 'Workflow source'});
|
|
788
|
+
const highlightedLines = sourceDialog.querySelectorAll('.line.highlighted-line');
|
|
775
789
|
expect(highlightedLines).toHaveLength(2);
|
|
776
790
|
expect(highlightedLines[0]).toHaveTextContent('deploy:');
|
|
777
791
|
expect(highlightedLines[1]).toHaveTextContent('steps:');
|
|
778
792
|
});
|
|
779
793
|
|
|
780
|
-
test('
|
|
794
|
+
test('explains when the run has no source snapshot', async () => {
|
|
781
795
|
configureApiClient({
|
|
782
796
|
fetchImpl: vi.fn(() =>
|
|
783
797
|
Promise.resolve(jsonResponse(workflowRunViewDetailDto({source_snapshot: null}))),
|
|
784
798
|
),
|
|
785
799
|
});
|
|
786
800
|
|
|
787
|
-
renderView();
|
|
801
|
+
renderView({tab: 'source'});
|
|
788
802
|
|
|
789
803
|
await screen.findByRole('region', {name: 'deploy-web'});
|
|
790
804
|
|
|
791
|
-
expect(screen.
|
|
805
|
+
expect(await screen.findByText('Source snapshot unavailable')).toBeInTheDocument();
|
|
792
806
|
});
|
|
793
807
|
|
|
794
808
|
test('opens the source panel highlighting a step from the job header Source button', async () => {
|
|
@@ -802,25 +816,17 @@ describe('WorkflowRunView', () => {
|
|
|
802
816
|
const jobRegion = await screen.findByRole('region', {name: 'build'});
|
|
803
817
|
await user.click(screen.getByRole('button', {name: 'checkout, Succeeded, attempt 1'}));
|
|
804
818
|
const jobSourceButton = within(jobRegion).getByRole('button', {name: 'View source'});
|
|
805
|
-
const summaryButton = within(screen.getByRole('region', {name: 'deploy-web'})).getByRole(
|
|
806
|
-
'button',
|
|
807
|
-
{name: 'View source'},
|
|
808
|
-
);
|
|
809
819
|
expect(jobSourceButton).toHaveAttribute('aria-expanded', 'false');
|
|
810
|
-
expect(jobSourceButton).toHaveAttribute(
|
|
811
|
-
'aria-controls',
|
|
812
|
-
summaryButton.getAttribute('aria-controls'),
|
|
813
|
-
);
|
|
820
|
+
expect(jobSourceButton).toHaveAttribute('aria-controls');
|
|
814
821
|
|
|
815
822
|
await user.click(jobSourceButton);
|
|
816
823
|
|
|
817
|
-
await screen.findByRole('dialog', {name: 'Workflow source'});
|
|
818
|
-
const highlighted =
|
|
824
|
+
const sourceDialog = await screen.findByRole('dialog', {name: 'Workflow source'});
|
|
825
|
+
const highlighted = sourceDialog.querySelectorAll('.line.highlighted-line');
|
|
819
826
|
expect(highlighted).toHaveLength(2);
|
|
820
827
|
expect(highlighted[0]).toHaveTextContent('build:');
|
|
821
828
|
expect(highlighted[1]).toHaveTextContent('steps:');
|
|
822
829
|
expect(jobSourceButton).toHaveAttribute('aria-expanded', 'true');
|
|
823
|
-
expect(summaryButton).toHaveAttribute('aria-expanded', 'false');
|
|
824
830
|
});
|
|
825
831
|
|
|
826
832
|
test('returns focus to the job Source button on close and preserves the selected job', async () => {
|
|
@@ -844,7 +850,7 @@ describe('WorkflowRunView', () => {
|
|
|
844
850
|
expect(screen.getByRole('region', {name: 'build'})).toBeInTheDocument();
|
|
845
851
|
});
|
|
846
852
|
|
|
847
|
-
test('
|
|
853
|
+
test('closes the source panel when refetch removes the focused step source', async () => {
|
|
848
854
|
const user = userEvent.setup();
|
|
849
855
|
let detailRequests = 0;
|
|
850
856
|
const fetchImpl = vi.fn((input: RequestInfo | URL) => {
|
|
@@ -893,10 +899,6 @@ describe('WorkflowRunView', () => {
|
|
|
893
899
|
const jobRegion = await screen.findByRole('region', {name: 'build'});
|
|
894
900
|
await user.click(screen.getByRole('button', {name: 'checkout, Succeeded, attempt 1'}));
|
|
895
901
|
const jobSourceButton = within(jobRegion).getByRole('button', {name: 'View source'});
|
|
896
|
-
const summaryButton = within(screen.getByRole('region', {name: 'deploy-web'})).getByRole(
|
|
897
|
-
'button',
|
|
898
|
-
{name: 'View source'},
|
|
899
|
-
);
|
|
900
902
|
await user.click(jobSourceButton);
|
|
901
903
|
await screen.findByRole('dialog', {name: 'Workflow source'});
|
|
902
904
|
|
|
@@ -906,10 +908,7 @@ describe('WorkflowRunView', () => {
|
|
|
906
908
|
within(jobRegion).queryByRole('button', {name: 'View source'}),
|
|
907
909
|
).not.toBeInTheDocument(),
|
|
908
910
|
);
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
await waitFor(() => expect(summaryButton).toHaveFocus());
|
|
912
|
-
expect(summaryButton).toHaveAttribute('aria-expanded', 'false');
|
|
911
|
+
expect(screen.queryByRole('dialog', {name: 'Workflow source'})).not.toBeInTheDocument();
|
|
913
912
|
});
|
|
914
913
|
|
|
915
914
|
test('re-runs all jobs from a succeeded run and navigates to the new run', async () => {
|
|
@@ -1127,7 +1126,7 @@ describe('WorkflowRunView', () => {
|
|
|
1127
1126
|
expect(
|
|
1128
1127
|
await screen.findByRole('button', {name: 'build, Succeeded, reused'}),
|
|
1129
1128
|
).toBeInTheDocument();
|
|
1130
|
-
expect(screen.getAllByText('reused')).toHaveLength(
|
|
1129
|
+
expect(screen.getAllByText('reused')).toHaveLength(1);
|
|
1131
1130
|
await user.click(screen.getByRole('button', {name: 'checkout, Succeeded, attempt 1'}));
|
|
1132
1131
|
|
|
1133
1132
|
expect(await screen.findByText('Not executed in this run.')).toBeInTheDocument();
|
|
@@ -1144,6 +1143,7 @@ function renderView(props: Partial<Parameters<typeof WorkflowRunView>[0]> = {},
|
|
|
1144
1143
|
workspaceSlug={PROJECT_TEST_WSLUG}
|
|
1145
1144
|
projectSlug="project"
|
|
1146
1145
|
workflowRunId={RUN_ID}
|
|
1146
|
+
tab="jobs"
|
|
1147
1147
|
{...props}
|
|
1148
1148
|
/>
|
|
1149
1149
|
));
|