@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,7 +1,11 @@
|
|
|
1
1
|
import {render, screen, waitFor, within} from '@testing-library/react';
|
|
2
2
|
import userEvent from '@testing-library/user-event';
|
|
3
3
|
import type {workflowRunDetailDto} from '#test/fixtures/workflow-run.js';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
workflowJobDto,
|
|
6
|
+
workflowJobExecutionDto,
|
|
7
|
+
workflowRunDetail,
|
|
8
|
+
} from '#test/fixtures/workflow-run.js';
|
|
5
9
|
import {WorkflowRunSummary} from './workflow-run-summary.js';
|
|
6
10
|
|
|
7
11
|
const RUN_ID = '66666666-6666-4666-8666-666666666666';
|
|
@@ -125,9 +129,9 @@ describe('WorkflowRunSummary', () => {
|
|
|
125
129
|
const heading = await screen.findByRole('heading', {name: runName});
|
|
126
130
|
|
|
127
131
|
expect(heading).toHaveAttribute('data-slot', 'tooltip-trigger');
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
);
|
|
132
|
+
// The Radix tooltip is the only name affordance: a native `title` beside it would fire a
|
|
133
|
+
// second, unstyled OS tooltip with the same text.
|
|
134
|
+
await waitFor(() => expect(within(heading).getByText(runName)).not.toHaveAttribute('title'));
|
|
131
135
|
});
|
|
132
136
|
|
|
133
137
|
test('shows the selected attempt duration, not the top-level run duration', async () => {
|
|
@@ -174,6 +178,61 @@ describe('WorkflowRunSummary', () => {
|
|
|
174
178
|
expect(duration).toHaveAttribute('aria-label', 'running 2m 14s');
|
|
175
179
|
});
|
|
176
180
|
|
|
181
|
+
test('reads a running run whose jobs have not started as queued', async () => {
|
|
182
|
+
vi.spyOn(Date, 'now').mockReturnValue(Date.parse('2026-05-07T01:02:14.000Z'));
|
|
183
|
+
renderSummary({
|
|
184
|
+
jobs: [workflowJobDto({status: 'pending'})],
|
|
185
|
+
run_attempt: runningAttemptDto(),
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
const summary = await screen.findByRole('region', {name: 'deploy-web'});
|
|
189
|
+
|
|
190
|
+
expect(within(summary).getAllByText('Queued')).not.toHaveLength(0);
|
|
191
|
+
expect(within(summary).queryByText('Running')).not.toBeInTheDocument();
|
|
192
|
+
expect(within(summary).getByText('2m 14s')).toHaveAttribute('aria-label', 'queued 2m 14s');
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test('names the job a queued run is waiting on when there is more than one', async () => {
|
|
196
|
+
renderSummary({
|
|
197
|
+
jobs: [
|
|
198
|
+
workflowJobDto({
|
|
199
|
+
name: 'lint',
|
|
200
|
+
status: 'pending',
|
|
201
|
+
job_executions: [workflowJobExecutionDto({queued_at: '2026-05-07T01:00:00.000Z'})],
|
|
202
|
+
}),
|
|
203
|
+
workflowJobDto({
|
|
204
|
+
name: 'build',
|
|
205
|
+
status: 'pending',
|
|
206
|
+
job_executions: [workflowJobExecutionDto({queued_at: '2026-05-07T00:59:00.000Z'})],
|
|
207
|
+
}),
|
|
208
|
+
],
|
|
209
|
+
run_attempt: runningAttemptDto(),
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
const summary = await screen.findByRole('region', {name: 'deploy-web'});
|
|
213
|
+
|
|
214
|
+
expect(within(summary).getByText('waiting on')).toBeInTheDocument();
|
|
215
|
+
expect(within(summary).getByText('build')).toBeInTheDocument();
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
test('leaves the waiting job unnamed when the run has a single job', async () => {
|
|
219
|
+
renderSummary({
|
|
220
|
+
jobs: [
|
|
221
|
+
workflowJobDto({
|
|
222
|
+
name: 'build',
|
|
223
|
+
status: 'pending',
|
|
224
|
+
job_executions: [workflowJobExecutionDto({queued_at: '2026-05-07T01:00:00.000Z'})],
|
|
225
|
+
}),
|
|
226
|
+
],
|
|
227
|
+
run_attempt: runningAttemptDto(),
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
const summary = await screen.findByRole('region', {name: 'deploy-web'});
|
|
231
|
+
|
|
232
|
+
expect(within(summary).getAllByText('Queued')).not.toHaveLength(0);
|
|
233
|
+
expect(within(summary).queryByText('waiting on')).not.toBeInTheDocument();
|
|
234
|
+
});
|
|
235
|
+
|
|
177
236
|
test('does not render a whole-run source control', async () => {
|
|
178
237
|
renderSummary();
|
|
179
238
|
|
|
@@ -250,7 +309,9 @@ describe('WorkflowRunSummary', () => {
|
|
|
250
309
|
const onRerun = vi.fn();
|
|
251
310
|
renderSummary({status: 'failed', jobs: [workflowJobDto({status: 'failed'})]}, {onRerun});
|
|
252
311
|
|
|
253
|
-
|
|
312
|
+
const rerunButton = await screen.findByRole('button', {name: 'Re-run jobs'});
|
|
313
|
+
expect(rerunButton).toHaveClass('bg-background-neutral-base');
|
|
314
|
+
await user.click(rerunButton);
|
|
254
315
|
expect(await screen.findByRole('menuitem', {name: 'Re-run all jobs'})).toBeInTheDocument();
|
|
255
316
|
await user.click(await screen.findByRole('menuitem', {name: 'Re-run failed jobs'}));
|
|
256
317
|
|
|
@@ -328,6 +389,19 @@ function renderSummary(
|
|
|
328
389
|
render(<WorkflowRunSummary run={run} {...props} />);
|
|
329
390
|
}
|
|
330
391
|
|
|
392
|
+
function runningAttemptDto() {
|
|
393
|
+
return {
|
|
394
|
+
id: '11111111-1111-4111-8111-000000000001',
|
|
395
|
+
workflow_run_id: RUN_ID,
|
|
396
|
+
attempt: 1,
|
|
397
|
+
status: 'running' as const,
|
|
398
|
+
created_at: '2026-05-07T01:01:00.000Z',
|
|
399
|
+
started_at: '2026-05-07T01:00:00.000Z',
|
|
400
|
+
finished_at: null,
|
|
401
|
+
rerun_mode: null,
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
|
|
331
405
|
function setElementWidths({scrollWidth, clientWidth}: {scrollWidth: number; clientWidth: number}) {
|
|
332
406
|
Object.defineProperty(window.HTMLElement.prototype, 'scrollWidth', {
|
|
333
407
|
configurable: true,
|
|
@@ -11,8 +11,7 @@ import {useIsTextTruncated} from '@shipfox/react-ui/hooks';
|
|
|
11
11
|
import {RelativeTime} from '@shipfox/react-ui/relative-time';
|
|
12
12
|
import {TimeTickerProvider} from '@shipfox/react-ui/time-ticker';
|
|
13
13
|
import {Tooltip, TooltipContent, TooltipTrigger} from '@shipfox/react-ui/tooltip';
|
|
14
|
-
import {Header, Text} from '@shipfox/react-ui/typography';
|
|
15
|
-
import {Link} from '@tanstack/react-router';
|
|
14
|
+
import {Code, Header, Text} from '@shipfox/react-ui/typography';
|
|
16
15
|
import {useId} from 'react';
|
|
17
16
|
import {WorkflowRunNumberLabel} from '#components/workflow-run-number-label.js';
|
|
18
17
|
import {
|
|
@@ -21,15 +20,20 @@ import {
|
|
|
21
20
|
WORKFLOW_RUN_STATUSES,
|
|
22
21
|
type WorkflowRunDetail,
|
|
23
22
|
type WorkflowRunRerunMode,
|
|
23
|
+
workflowRunBlockingJob,
|
|
24
|
+
workflowRunDetailDisplay,
|
|
24
25
|
} from '#core/workflow-run.js';
|
|
25
|
-
import {validateWorkflowRunsSearch, workflowRunListSearchParams} from '#routes/inputs.js';
|
|
26
26
|
import {WorkflowRunDurationLabel} from '../workflow-run-duration-label.js';
|
|
27
27
|
import {getWorkflowStatusVisual} from '../workflow-status/status-visuals.js';
|
|
28
28
|
import {WorkflowRunAttemptSwitcher} from './workflow-run-attempt-switcher.js';
|
|
29
29
|
|
|
30
30
|
const STATUS_BADGE_LABEL_WIDTH_CH = Math.max(
|
|
31
|
-
...WORKFLOW_RUN_STATUSES.map(
|
|
31
|
+
...[...WORKFLOW_RUN_STATUSES, 'queued' as const].map(
|
|
32
|
+
(status) => getWorkflowStatusVisual(status).label.length,
|
|
33
|
+
),
|
|
32
34
|
);
|
|
35
|
+
const RERUN_BUTTON_SURFACE_CLASS_NAME =
|
|
36
|
+
'bg-background-neutral-base hover:bg-background-neutral-hover active:bg-background-neutral-pressed';
|
|
33
37
|
|
|
34
38
|
type WorkflowRunAction = 'cancel' | 'rerun-all' | 'rerun-menu' | 'none';
|
|
35
39
|
|
|
@@ -55,14 +59,19 @@ export function WorkflowRunSummary({
|
|
|
55
59
|
latestAttempt,
|
|
56
60
|
}: WorkflowRunSummaryProps) {
|
|
57
61
|
const headingId = useId();
|
|
58
|
-
const
|
|
62
|
+
const display = workflowRunDetailDisplay(run);
|
|
63
|
+
const status = getWorkflowStatusVisual(display.status);
|
|
59
64
|
const action = workflowRunActionForRun(run);
|
|
60
65
|
const hasAction = canRenderWorkflowRunAction(action, onCancel, onRerun);
|
|
61
66
|
const attemptSwitcher =
|
|
62
67
|
latestAttempt && latestAttempt > 1 && workspaceSlug && projectSlug
|
|
63
68
|
? {workspaceSlug, projectSlug, latestAttempt}
|
|
64
69
|
: null;
|
|
65
|
-
const displayDuration =
|
|
70
|
+
const displayDuration = display.duration;
|
|
71
|
+
// Named only while the run is waiting and has more than one job: with a single job the rail
|
|
72
|
+
// and the graph already say which one it is.
|
|
73
|
+
const blockingJob =
|
|
74
|
+
display.status === 'queued' && run.jobs.length > 1 ? workflowRunBlockingJob(run.jobs) : null;
|
|
66
75
|
const {ref: headingTextRef, isTruncated: isHeadingTruncated} =
|
|
67
76
|
useIsTextTruncated<HTMLSpanElement>(run.name);
|
|
68
77
|
|
|
@@ -70,61 +79,32 @@ export function WorkflowRunSummary({
|
|
|
70
79
|
<TimeTickerProvider intervalMs={1000} reducedMotionIntervalMs={10_000}>
|
|
71
80
|
<section aria-labelledby={headingId} className="bg-background-neutral-background px-16 py-12">
|
|
72
81
|
<div className="grid min-w-0 grid-cols-[minmax(0,1fr)_auto] items-center gap-x-12 gap-y-8 overflow-hidden max-[480px]:grid-cols-1">
|
|
73
|
-
<
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
<Link
|
|
81
|
-
to="/w/$workspaceSlug/p/$projectSlug/runs"
|
|
82
|
-
params={{workspaceSlug, projectSlug}}
|
|
83
|
-
search={
|
|
84
|
-
((previous: Record<string, unknown>) =>
|
|
85
|
-
workflowRunListSearchParams(validateWorkflowRunsSearch(previous))) as never
|
|
86
|
-
}
|
|
87
|
-
className="rounded-4 text-xs font-medium text-foreground-neutral-subtle underline decoration-border-neutral-strong underline-offset-4 outline-none hover:text-foreground-neutral-base focus-visible:shadow-border-interactive-with-active"
|
|
88
|
-
>
|
|
89
|
-
Runs
|
|
90
|
-
</Link>
|
|
91
|
-
</li>
|
|
92
|
-
) : null}
|
|
93
|
-
{workspaceSlug && projectSlug ? (
|
|
94
|
-
<li aria-hidden="true" className="shrink-0 text-xs text-foreground-neutral-subtle">
|
|
95
|
-
/
|
|
96
|
-
</li>
|
|
97
|
-
) : null}
|
|
98
|
-
<li aria-current="page" className="flex min-w-0 items-center gap-8">
|
|
99
|
-
<Badge variant={status.badge} size="xs">
|
|
100
|
-
<span className="text-center" style={{width: `${STATUS_BADGE_LABEL_WIDTH_CH}ch`}}>
|
|
101
|
-
{status.label}
|
|
102
|
-
</span>
|
|
103
|
-
</Badge>
|
|
82
|
+
<div className="col-start-1 row-start-1 min-w-0 max-[480px]:col-start-auto max-[480px]:row-start-auto">
|
|
83
|
+
<div className="flex min-w-0 items-center gap-8">
|
|
84
|
+
<Badge variant={status.badge} size="xs">
|
|
85
|
+
<span className="text-center" style={{width: `${STATUS_BADGE_LABEL_WIDTH_CH}ch`}}>
|
|
86
|
+
{status.label}
|
|
87
|
+
</span>
|
|
88
|
+
</Badge>
|
|
104
89
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
) : null}
|
|
124
|
-
</Tooltip>
|
|
125
|
-
</li>
|
|
126
|
-
</ol>
|
|
127
|
-
</nav>
|
|
90
|
+
<Tooltip>
|
|
91
|
+
<TooltipTrigger asChild>
|
|
92
|
+
<Header as="h1" id={headingId} variant="h3" className="min-w-0 truncate">
|
|
93
|
+
<span ref={headingTextRef} className="block min-w-0 truncate">
|
|
94
|
+
{run.name}
|
|
95
|
+
</span>
|
|
96
|
+
</Header>
|
|
97
|
+
</TooltipTrigger>
|
|
98
|
+
{isHeadingTruncated ? (
|
|
99
|
+
<TooltipContent>
|
|
100
|
+
<Text as="span" size="xs" className="max-w-[360px] break-words">
|
|
101
|
+
{run.name}
|
|
102
|
+
</Text>
|
|
103
|
+
</TooltipContent>
|
|
104
|
+
) : null}
|
|
105
|
+
</Tooltip>
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
128
108
|
|
|
129
109
|
{hasAction ? (
|
|
130
110
|
<div className="col-start-2 row-start-1 flex min-w-max items-center gap-6 justify-self-end max-[480px]:col-start-auto max-[480px]:row-start-auto max-[480px]:justify-self-start">
|
|
@@ -204,6 +184,20 @@ export function WorkflowRunSummary({
|
|
|
204
184
|
/>
|
|
205
185
|
</>
|
|
206
186
|
) : null}
|
|
187
|
+
|
|
188
|
+
{blockingJob ? (
|
|
189
|
+
<>
|
|
190
|
+
<MetadataSeparator />
|
|
191
|
+
<span className="flex min-w-0 items-center gap-4">
|
|
192
|
+
<Text as="span" size="xs" className="shrink-0">
|
|
193
|
+
waiting on
|
|
194
|
+
</Text>
|
|
195
|
+
<Code as="span" variant="label" className="min-w-0 truncate">
|
|
196
|
+
{blockingJob.displayName}
|
|
197
|
+
</Code>
|
|
198
|
+
</span>
|
|
199
|
+
</>
|
|
200
|
+
) : null}
|
|
207
201
|
</div>
|
|
208
202
|
</div>
|
|
209
203
|
</section>
|
|
@@ -251,6 +245,7 @@ function WorkflowRunActionSlot({
|
|
|
251
245
|
type="button"
|
|
252
246
|
variant="secondary"
|
|
253
247
|
size="xs"
|
|
248
|
+
className={RERUN_BUTTON_SURFACE_CLASS_NAME}
|
|
254
249
|
isLoading={rerunPending}
|
|
255
250
|
disabled={rerunPending}
|
|
256
251
|
onClick={() => onRerun('all')}
|
|
@@ -269,6 +264,7 @@ function WorkflowRunActionSlot({
|
|
|
269
264
|
type="button"
|
|
270
265
|
variant="secondary"
|
|
271
266
|
size="xs"
|
|
267
|
+
className={RERUN_BUTTON_SURFACE_CLASS_NAME}
|
|
272
268
|
iconRight="arrowDownSLine"
|
|
273
269
|
isLoading={rerunPending}
|
|
274
270
|
disabled={rerunPending}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
export {RunAnnotationCountChip} from './run-annotation-count-chip.js';
|
|
2
|
+
export {annotationElementId, RunAnnotationItem} from './run-annotation-item.js';
|
|
3
|
+
export {RunAnnotationList, type RunAnnotationListQuery} from './run-annotation-list.js';
|
|
1
4
|
export {RunAnnotationSummaryLine} from './run-annotation-summary-line.js';
|
|
2
5
|
export {RunAnnotationsEmpty} from './run-annotations-empty.js';
|
|
3
|
-
export {RunJobsList} from './run-jobs-list.js';
|
|
4
|
-
export {RunTabCount} from './run-tab-count.js';
|
|
5
|
-
export {RunTabStrip} from './run-tab-strip.js';
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import {Icon} from '@shipfox/react-ui/icon';
|
|
2
|
+
import {cn} from '@shipfox/react-ui/utils';
|
|
3
|
+
import {Link} from '@tanstack/react-router';
|
|
4
|
+
import {highestRunAnnotationSeverity, type RunAnnotationSummary} from '#core/run-annotation.js';
|
|
5
|
+
import {type WorkflowRunsSearch, workflowRunSearchParams} from '#routes/inputs.js';
|
|
6
|
+
import {SEVERITY_CHIP_TONE, SEVERITY_ICON} from './severity-visuals.js';
|
|
7
|
+
|
|
8
|
+
export interface RunAnnotationCountChipProps {
|
|
9
|
+
summary: RunAnnotationSummary | undefined;
|
|
10
|
+
workspaceSlug: string;
|
|
11
|
+
projectSlug: string;
|
|
12
|
+
workflowRunId: string;
|
|
13
|
+
runAttempt?: number | undefined;
|
|
14
|
+
/** Scopes the destination list to one job. */
|
|
15
|
+
jobId?: string | undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* A bounded reference into the run's Annotations section: a count, its highest severity, and one
|
|
20
|
+
* link. Never a body, so an annotation is rendered exactly once per page.
|
|
21
|
+
*/
|
|
22
|
+
export function RunAnnotationCountChip({
|
|
23
|
+
summary,
|
|
24
|
+
workspaceSlug,
|
|
25
|
+
projectSlug,
|
|
26
|
+
workflowRunId,
|
|
27
|
+
runAttempt,
|
|
28
|
+
jobId,
|
|
29
|
+
}: RunAnnotationCountChipProps) {
|
|
30
|
+
if (!summary || summary.total === 0) return null;
|
|
31
|
+
|
|
32
|
+
const severity = highestRunAnnotationSeverity(summary);
|
|
33
|
+
const count = `${summary.total}${summary.truncated ? '+' : ''}`;
|
|
34
|
+
const label = `${count} ${summary.total === 1 && !summary.truncated ? 'annotation' : 'annotations'}`;
|
|
35
|
+
const accessibleLabel = severity
|
|
36
|
+
? `View ${label}, highest severity ${severity}`
|
|
37
|
+
: `View ${label}`;
|
|
38
|
+
const search: WorkflowRunsSearch = {tab: 'annotations', ...(runAttempt ? {runAttempt} : {})};
|
|
39
|
+
if (jobId) search.jobId = jobId;
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<Link
|
|
43
|
+
to="/w/$workspaceSlug/p/$projectSlug/runs/$workflowRunId"
|
|
44
|
+
params={{workspaceSlug, projectSlug, workflowRunId}}
|
|
45
|
+
search={workflowRunSearchParams(search, search) as never}
|
|
46
|
+
aria-label={accessibleLabel}
|
|
47
|
+
className={cn(
|
|
48
|
+
'inline-flex h-20 shrink-0 items-center gap-4 rounded-4 border px-6 font-code text-xs leading-16 tabular-nums outline-none transition-colors hover:bg-background-neutral-hover focus-visible:shadow-border-interactive-with-active',
|
|
49
|
+
// The chip stays 20px optically so it sits level with the duration metadata beside it;
|
|
50
|
+
// the hit area grows to the 44px minimum only where the pointer is coarse.
|
|
51
|
+
'[@media(pointer:coarse)]:h-44 [@media(pointer:coarse)]:px-10',
|
|
52
|
+
severity ? SEVERITY_CHIP_TONE[severity] : 'border-border-neutral-base',
|
|
53
|
+
)}
|
|
54
|
+
>
|
|
55
|
+
<Icon
|
|
56
|
+
name={severity ? SEVERITY_ICON[severity] : 'fileTextLine'}
|
|
57
|
+
size={12}
|
|
58
|
+
aria-hidden="true"
|
|
59
|
+
/>
|
|
60
|
+
<span className="text-foreground-neutral-base">{count}</span>
|
|
61
|
+
</Link>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import {AnnotationCard} from '@shipfox/client-ui';
|
|
2
|
+
import {Button} from '@shipfox/react-ui/button';
|
|
3
|
+
import {Icon} from '@shipfox/react-ui/icon';
|
|
4
|
+
import {Text} from '@shipfox/react-ui/typography';
|
|
5
|
+
import {cn} from '@shipfox/react-ui/utils';
|
|
6
|
+
import {Link} from '@tanstack/react-router';
|
|
7
|
+
import {useEffect, useRef} from 'react';
|
|
8
|
+
import type {RunAnnotationEntry} from '#core/run-annotation.js';
|
|
9
|
+
import {workflowJobSearchParams} from '#routes/inputs.js';
|
|
10
|
+
|
|
11
|
+
export interface RunAnnotationItemProps {
|
|
12
|
+
entry: RunAnnotationEntry;
|
|
13
|
+
workspaceSlug?: string | undefined;
|
|
14
|
+
projectSlug?: string | undefined;
|
|
15
|
+
workflowRunId: string;
|
|
16
|
+
runAttempt?: number | undefined;
|
|
17
|
+
/** Deep-link target from `?annotation=`, which takes focus rather than only being scrolled to. */
|
|
18
|
+
selected?: boolean | undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* One annotation, and the only place in the product that renders an annotation body.
|
|
23
|
+
*
|
|
24
|
+
* The title is the annotation's `context`, which is what the emitting step named the block.
|
|
25
|
+
* Everything below it exists so a reader can get from a diagnostic back to its cause.
|
|
26
|
+
*/
|
|
27
|
+
export function RunAnnotationItem({
|
|
28
|
+
entry,
|
|
29
|
+
workspaceSlug,
|
|
30
|
+
projectSlug,
|
|
31
|
+
workflowRunId,
|
|
32
|
+
runAttempt,
|
|
33
|
+
selected = false,
|
|
34
|
+
}: RunAnnotationItemProps) {
|
|
35
|
+
const {annotation, origin} = entry;
|
|
36
|
+
const canLink = Boolean(origin && workspaceSlug && projectSlug);
|
|
37
|
+
const itemRef = useRef<HTMLLIElement>(null);
|
|
38
|
+
const focusedRef = useRef(false);
|
|
39
|
+
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
if (!selected) {
|
|
42
|
+
focusedRef.current = false;
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (focusedRef.current) return;
|
|
46
|
+
|
|
47
|
+
// A deep link that only scrolls leaves a keyboard or screen-reader user at the document
|
|
48
|
+
// start while the page moves under them.
|
|
49
|
+
focusedRef.current = true;
|
|
50
|
+
itemRef.current?.focus({preventScroll: true});
|
|
51
|
+
itemRef.current?.scrollIntoView({block: 'nearest'});
|
|
52
|
+
}, [selected]);
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<li
|
|
56
|
+
ref={itemRef}
|
|
57
|
+
tabIndex={-1}
|
|
58
|
+
aria-current={selected ? 'true' : undefined}
|
|
59
|
+
className={cn(
|
|
60
|
+
'rounded-8 outline-none focus-visible:shadow-border-interactive-with-active',
|
|
61
|
+
selected && 'shadow-border-interactive-with-active',
|
|
62
|
+
)}
|
|
63
|
+
>
|
|
64
|
+
<AnnotationCard
|
|
65
|
+
id={annotationElementId(annotation.id)}
|
|
66
|
+
style={annotation.style}
|
|
67
|
+
title={annotation.context}
|
|
68
|
+
titleAs="h3"
|
|
69
|
+
provenance={<RunAnnotationProvenance entry={entry} />}
|
|
70
|
+
body={annotation.body}
|
|
71
|
+
action={
|
|
72
|
+
canLink && origin ? (
|
|
73
|
+
<Button
|
|
74
|
+
asChild
|
|
75
|
+
size="xs"
|
|
76
|
+
variant="transparent"
|
|
77
|
+
className="[@media(pointer:coarse)]:min-h-44"
|
|
78
|
+
>
|
|
79
|
+
<Link
|
|
80
|
+
to="/w/$workspaceSlug/p/$projectSlug/runs/$workflowRunId/jobs/$jobId"
|
|
81
|
+
params={{
|
|
82
|
+
workspaceSlug: workspaceSlug ?? '',
|
|
83
|
+
projectSlug: projectSlug ?? '',
|
|
84
|
+
workflowRunId,
|
|
85
|
+
jobId: origin.jobId,
|
|
86
|
+
}}
|
|
87
|
+
search={
|
|
88
|
+
workflowJobSearchParams({
|
|
89
|
+
jobExecutionId: origin.jobExecutionId,
|
|
90
|
+
stepId: origin.stepId,
|
|
91
|
+
stepAttemptId: origin.stepAttemptId,
|
|
92
|
+
runAttempt,
|
|
93
|
+
}) as never
|
|
94
|
+
}
|
|
95
|
+
>
|
|
96
|
+
Open step
|
|
97
|
+
<Icon name="arrowRightLine" size={12} aria-hidden="true" />
|
|
98
|
+
</Link>
|
|
99
|
+
</Button>
|
|
100
|
+
) : null
|
|
101
|
+
}
|
|
102
|
+
/>
|
|
103
|
+
</li>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** `job · execution #2 · run tests · attempt 1`, dropping any part the run no longer resolves. */
|
|
108
|
+
function RunAnnotationProvenance({entry}: {entry: RunAnnotationEntry}) {
|
|
109
|
+
const parts = [entry.jobName, entry.executionLabel, entry.stepLabel, entry.attemptLabel].filter(
|
|
110
|
+
(part): part is string => Boolean(part),
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
<Text as="p" size="xs" className="min-w-0 break-words font-code text-foreground-neutral-subtle">
|
|
115
|
+
{parts.join(' · ')}
|
|
116
|
+
</Text>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Stable element id so `?annotation=<id>` can scroll to and highlight one annotation. */
|
|
121
|
+
export function annotationElementId(annotationId: string): string {
|
|
122
|
+
return `run-annotation-${annotationId}`;
|
|
123
|
+
}
|