@shipfox/client-workflows 12.0.2 → 13.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.storybook/preview.tsx +3 -3
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +56 -0
- package/dist/components/job-graph/job-duration-format.d.ts +2 -2
- package/dist/components/job-graph/job-duration-format.d.ts.map +1 -1
- package/dist/components/job-graph/job-duration-format.js +4 -2
- package/dist/components/job-graph/job-duration-format.js.map +1 -1
- package/dist/components/job-graph/job-node.d.ts +1 -1
- package/dist/components/job-graph/job-node.d.ts.map +1 -1
- package/dist/components/job-graph/job-node.js +5 -4
- package/dist/components/job-graph/job-node.js.map +1 -1
- package/dist/components/step-list/step-list.d.ts +2 -2
- package/dist/components/step-list/step-list.d.ts.map +1 -1
- package/dist/components/step-list/step-list.js.map +1 -1
- package/dist/components/workflow-run-list/job-status-strip.d.ts +21 -0
- package/dist/components/workflow-run-list/job-status-strip.d.ts.map +1 -0
- package/dist/components/workflow-run-list/job-status-strip.js +185 -0
- package/dist/components/workflow-run-list/job-status-strip.js.map +1 -0
- package/dist/components/workflow-run-list/run-display.d.ts +27 -3
- package/dist/components/workflow-run-list/run-display.d.ts.map +1 -1
- package/dist/components/workflow-run-list/run-display.js +78 -7
- package/dist/components/workflow-run-list/run-display.js.map +1 -1
- package/dist/components/workflow-run-list/types.d.ts +23 -24
- package/dist/components/workflow-run-list/types.d.ts.map +1 -1
- package/dist/components/workflow-run-list/types.js.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-filter-menu.d.ts +22 -0
- package/dist/components/workflow-run-list/workflow-run-filter-menu.d.ts.map +1 -0
- package/dist/components/workflow-run-list/workflow-run-filter-menu.js +68 -0
- package/dist/components/workflow-run-list/workflow-run-filter-menu.js.map +1 -0
- package/dist/components/workflow-run-list/workflow-run-filters.d.ts +18 -0
- package/dist/components/workflow-run-list/workflow-run-filters.d.ts.map +1 -0
- package/dist/components/workflow-run-list/workflow-run-filters.js +236 -0
- package/dist/components/workflow-run-list/workflow-run-filters.js.map +1 -0
- package/dist/components/workflow-run-list/workflow-run-list-content.d.ts +8 -4
- package/dist/components/workflow-run-list/workflow-run-list-content.d.ts.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-list-content.js +40 -12
- package/dist/components/workflow-run-list/workflow-run-list-content.js.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-list-states.d.ts +29 -2
- package/dist/components/workflow-run-list/workflow-run-list-states.d.ts.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-list-states.js +103 -44
- package/dist/components/workflow-run-list/workflow-run-list-states.js.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-list-view.d.ts +1 -1
- package/dist/components/workflow-run-list/workflow-run-list-view.d.ts.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-list-view.js +60 -43
- package/dist/components/workflow-run-list/workflow-run-list-view.js.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-list.d.ts +1 -1
- package/dist/components/workflow-run-list/workflow-run-list.d.ts.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-list.js +20 -8
- package/dist/components/workflow-run-list/workflow-run-list.js.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-row.d.ts +19 -9
- package/dist/components/workflow-run-list/workflow-run-row.d.ts.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-row.js +179 -112
- package/dist/components/workflow-run-list/workflow-run-row.js.map +1 -1
- package/dist/components/workflow-run-number-label.d.ts +15 -0
- package/dist/components/workflow-run-number-label.d.ts.map +1 -0
- package/dist/components/workflow-run-number-label.js +41 -0
- package/dist/components/workflow-run-number-label.js.map +1 -0
- package/dist/components/workflow-run-summary/workflow-run-attempt-switcher.d.ts +3 -3
- package/dist/components/workflow-run-summary/workflow-run-attempt-switcher.d.ts.map +1 -1
- package/dist/components/workflow-run-summary/workflow-run-attempt-switcher.js +8 -7
- package/dist/components/workflow-run-summary/workflow-run-attempt-switcher.js.map +1 -1
- package/dist/components/workflow-run-summary/workflow-run-summary.d.ts +3 -3
- package/dist/components/workflow-run-summary/workflow-run-summary.d.ts.map +1 -1
- package/dist/components/workflow-run-summary/workflow-run-summary.js +16 -7
- package/dist/components/workflow-run-summary/workflow-run-summary.js.map +1 -1
- package/dist/components/workflow-run-view/agent-config-failure-callout.d.ts +2 -2
- package/dist/components/workflow-run-view/agent-config-failure-callout.d.ts.map +1 -1
- package/dist/components/workflow-run-view/agent-config-failure-callout.js +4 -4
- package/dist/components/workflow-run-view/agent-config-failure-callout.js.map +1 -1
- package/dist/components/workflow-run-view/job-card.d.ts +2 -2
- package/dist/components/workflow-run-view/job-card.d.ts.map +1 -1
- package/dist/components/workflow-run-view/job-card.js +51 -32
- package/dist/components/workflow-run-view/job-card.js.map +1 -1
- package/dist/components/workflow-run-view/job-execution-switcher.d.ts +1 -1
- package/dist/components/workflow-run-view/job-execution-switcher.d.ts.map +1 -1
- package/dist/components/workflow-run-view/job-execution-switcher.js +20 -8
- package/dist/components/workflow-run-view/job-execution-switcher.js.map +1 -1
- package/dist/components/workflow-run-view/workflow-run-view.d.ts +3 -2
- package/dist/components/workflow-run-view/workflow-run-view.d.ts.map +1 -1
- package/dist/components/workflow-run-view/workflow-run-view.js +15 -11
- package/dist/components/workflow-run-view/workflow-run-view.js.map +1 -1
- package/dist/components/workflow-status/status-visuals.d.ts +4 -4
- package/dist/components/workflow-status/status-visuals.d.ts.map +1 -1
- package/dist/components/workflow-status/status-visuals.js +7 -0
- package/dist/components/workflow-status/status-visuals.js.map +1 -1
- package/dist/components/workflow-status/workflow-status-icon.d.ts +3 -4
- package/dist/components/workflow-status/workflow-status-icon.d.ts.map +1 -1
- package/dist/components/workflow-status/workflow-status-icon.js +6 -4
- package/dist/components/workflow-status/workflow-status-icon.js.map +1 -1
- package/dist/core/entities/job-execution.d.ts +10 -0
- package/dist/core/entities/job-execution.d.ts.map +1 -1
- package/dist/core/entities/job-execution.js +11 -0
- package/dist/core/entities/job-execution.js.map +1 -1
- package/dist/core/entities/job.d.ts +4 -2
- package/dist/core/entities/job.d.ts.map +1 -1
- package/dist/core/entities/job.js +8 -1
- package/dist/core/entities/job.js.map +1 -1
- package/dist/core/entities/workflow-run.d.ts +52 -4
- package/dist/core/entities/workflow-run.d.ts.map +1 -1
- package/dist/core/entities/workflow-run.js +22 -3
- package/dist/core/entities/workflow-run.js.map +1 -1
- package/dist/core/workflow-run.d.ts +6 -6
- package/dist/core/workflow-run.d.ts.map +1 -1
- package/dist/core/workflow-run.js +3 -3
- package/dist/core/workflow-run.js.map +1 -1
- package/dist/feature.d.ts +7 -7
- package/dist/feature.js +5 -5
- package/dist/feature.js.map +1 -1
- package/dist/hooks/api/workflow-run-mapper.d.ts +4 -3
- package/dist/hooks/api/workflow-run-mapper.d.ts.map +1 -1
- package/dist/hooks/api/workflow-run-mapper.js +31 -3
- package/dist/hooks/api/workflow-run-mapper.js.map +1 -1
- package/dist/hooks/api/workflow-runs.d.ts +4 -4
- package/dist/hooks/api/workflow-runs.d.ts.map +1 -1
- package/dist/hooks/api/workflow-runs.js +12 -4
- package/dist/hooks/api/workflow-runs.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/pages/project-workflows-page.js +46 -1
- package/dist/pages/project-workflows-page.js.map +1 -1
- package/dist/pages/workflow-run-detail-page.d.ts +11 -0
- package/dist/pages/workflow-run-detail-page.d.ts.map +1 -0
- package/dist/pages/workflow-run-detail-page.js +30 -0
- package/dist/pages/workflow-run-detail-page.js.map +1 -0
- package/dist/pages/workflow-run-list-page.d.ts +10 -0
- package/dist/pages/workflow-run-list-page.d.ts.map +1 -0
- package/dist/pages/workflow-run-list-page.js +43 -0
- package/dist/pages/workflow-run-list-page.js.map +1 -0
- package/dist/routes/inputs.d.ts +61 -5
- package/dist/routes/inputs.d.ts.map +1 -1
- package/dist/routes/inputs.js +150 -13
- package/dist/routes/inputs.js.map +1 -1
- package/dist/routes/project-route.d.ts +6 -0
- package/dist/routes/project-route.d.ts.map +1 -0
- package/dist/routes/project-route.js +25 -0
- package/dist/routes/project-route.js.map +1 -0
- package/dist/routes/run-detail.d.ts.map +1 -1
- package/dist/routes/run-detail.js +12 -7
- package/dist/routes/run-detail.js.map +1 -1
- package/dist/routes/runs.d.ts.map +1 -1
- package/dist/routes/runs.js +11 -6
- package/dist/routes/runs.js.map +1 -1
- package/dist/routes/workflows.d.ts.map +1 -1
- package/dist/routes/workflows.js +6 -5
- package/dist/routes/workflows.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +9 -9
- package/src/components/job-graph/job-duration-format.ts +11 -2
- package/src/components/job-graph/job-graph-view.test.tsx +19 -8
- package/src/components/job-graph/job-graph.stories.tsx +23 -19
- package/src/components/job-graph/job-node.stories.tsx +10 -5
- package/src/components/job-graph/job-node.test.tsx +55 -10
- package/src/components/job-graph/job-node.tsx +16 -9
- package/src/components/step-list/step-list.stories.tsx +2 -3
- package/src/components/step-list/step-list.tsx +3 -2
- package/src/components/workflow-run-list/job-status-strip.tsx +189 -0
- package/src/components/workflow-run-list/run-display.test.ts +169 -30
- package/src/components/workflow-run-list/run-display.ts +116 -9
- package/src/components/workflow-run-list/types.ts +21 -17
- package/src/components/workflow-run-list/workflow-run-filter-menu.tsx +100 -0
- package/src/components/workflow-run-list/workflow-run-filters.tsx +243 -0
- package/src/components/workflow-run-list/workflow-run-list-content.tsx +44 -16
- package/src/components/workflow-run-list/workflow-run-list-states.tsx +128 -27
- package/src/components/workflow-run-list/workflow-run-list-view.test.tsx +441 -104
- package/src/components/workflow-run-list/workflow-run-list-view.tsx +73 -43
- package/src/components/workflow-run-list/workflow-run-list.stories.tsx +140 -20
- package/src/components/workflow-run-list/workflow-run-list.tsx +20 -10
- package/src/components/workflow-run-list/workflow-run-row.tsx +173 -106
- package/src/components/workflow-run-number-label.test.tsx +45 -0
- package/src/components/workflow-run-number-label.tsx +47 -0
- package/src/components/workflow-run-summary/workflow-run-attempt-switcher.test.tsx +7 -8
- package/src/components/workflow-run-summary/workflow-run-attempt-switcher.tsx +13 -12
- package/src/components/workflow-run-summary/workflow-run-summary.stories.tsx +6 -8
- package/src/components/workflow-run-summary/workflow-run-summary.test.tsx +27 -2
- package/src/components/workflow-run-summary/workflow-run-summary.tsx +19 -9
- package/src/components/workflow-run-view/agent-config-failure-callout.stories.tsx +2 -3
- package/src/components/workflow-run-view/agent-config-failure-callout.tsx +4 -4
- package/src/components/workflow-run-view/job-card.stories.tsx +11 -10
- package/src/components/workflow-run-view/job-card.test.tsx +115 -0
- package/src/components/workflow-run-view/job-card.tsx +74 -35
- package/src/components/workflow-run-view/job-execution-switcher.tsx +31 -9
- package/src/components/workflow-run-view/workflow-run-view.test.tsx +88 -30
- package/src/components/workflow-run-view/workflow-run-view.tsx +24 -14
- package/src/components/workflow-status/status-visuals.test.ts +9 -0
- package/src/components/workflow-status/status-visuals.ts +6 -4
- package/src/components/workflow-status/workflow-status-icon.stories.tsx +10 -2
- package/src/components/workflow-status/workflow-status-icon.test.tsx +18 -0
- package/src/components/workflow-status/workflow-status-icon.tsx +17 -24
- package/src/core/entities/job-execution.ts +22 -0
- package/src/core/entities/job-status.test.ts +104 -0
- package/src/core/entities/job.ts +18 -3
- package/src/core/entities/workflow-run.ts +76 -7
- package/src/core/workflow-run.test.ts +24 -9
- package/src/core/workflow-run.ts +17 -2
- package/src/feature.ts +5 -5
- package/src/hooks/api/workflow-run-mapper.ts +30 -3
- package/src/hooks/api/workflow-runs.test.tsx +2 -0
- package/src/hooks/api/workflow-runs.ts +12 -7
- package/src/index.ts +7 -2
- package/src/page-harness-budget.test.ts +1 -1
- package/src/pages/project-workflows-page.test.tsx +59 -5
- package/src/pages/project-workflows-page.tsx +34 -1
- package/src/pages/workflow-run-detail-page.tsx +45 -0
- package/src/pages/workflow-run-list-page.tsx +65 -0
- package/src/pages/{workflow-run-page.test.tsx → workflow-run-pages.test.tsx} +211 -47
- package/src/routes/inputs.test.ts +193 -12
- package/src/routes/inputs.ts +170 -11
- package/src/routes/project-route.tsx +25 -0
- package/src/routes/run-detail.tsx +15 -8
- package/src/routes/runs.tsx +14 -7
- package/src/routes/workflows.tsx +5 -4
- package/test/fixtures/workflow-run.ts +76 -7
- package/test/pages.tsx +18 -8
- package/test/render.tsx +8 -2
- package/tsconfig.build.tsbuildinfo +1 -1
- package/dist/components/workflow-run-list/workflow-run-list-header.d.ts +0 -10
- package/dist/components/workflow-run-list/workflow-run-list-header.d.ts.map +0 -1
- package/dist/components/workflow-run-list/workflow-run-list-header.js +0 -55
- package/dist/components/workflow-run-list/workflow-run-list-header.js.map +0 -1
- package/dist/pages/workflow-run-first-time-use.d.ts +0 -6
- package/dist/pages/workflow-run-first-time-use.d.ts.map +0 -1
- package/dist/pages/workflow-run-first-time-use.js +0 -17
- package/dist/pages/workflow-run-first-time-use.js.map +0 -1
- package/dist/pages/workflow-run-page.d.ts +0 -10
- package/dist/pages/workflow-run-page.d.ts.map +0 -1
- package/dist/pages/workflow-run-page.js +0 -90
- package/dist/pages/workflow-run-page.js.map +0 -1
- package/src/components/workflow-run-list/workflow-run-list-header.tsx +0 -53
- package/src/pages/workflow-run-first-time-use.tsx +0 -17
- package/src/pages/workflow-run-page.tsx +0 -94
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shipfox/client-workflows",
|
|
3
3
|
"license": "MIT",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "13.0.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/ShipfoxHQ/shipfox.git",
|
|
@@ -27,15 +27,15 @@
|
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"@swc/helpers": "^0.5.17",
|
|
29
29
|
"date-fns": "^4.1.0",
|
|
30
|
-
"@shipfox/api-
|
|
31
|
-
"@shipfox/api-workflows-dto": "10.0.0",
|
|
30
|
+
"@shipfox/api-workflows-dto": "12.0.0",
|
|
32
31
|
"@shipfox/client-api": "6.0.1",
|
|
33
|
-
"@shipfox/client-logs": "
|
|
34
|
-
"@shipfox/
|
|
35
|
-
"@shipfox/client-
|
|
36
|
-
"@shipfox/client-
|
|
37
|
-
"@shipfox/client-
|
|
38
|
-
"@shipfox/react-ui": "0.
|
|
32
|
+
"@shipfox/client-logs": "13.0.0",
|
|
33
|
+
"@shipfox/api-definitions-dto": "12.0.0",
|
|
34
|
+
"@shipfox/client-projects": "13.0.0",
|
|
35
|
+
"@shipfox/client-shell": "13.0.0",
|
|
36
|
+
"@shipfox/client-triggers": "13.0.0",
|
|
37
|
+
"@shipfox/react-ui": "0.4.0",
|
|
38
|
+
"@shipfox/client-ui": "13.0.0"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
41
|
"@tanstack/react-query": "^5.101.0",
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import {humanDuration} from '@shipfox/react-ui/utils';
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
JobDisplayDuration,
|
|
4
|
+
JobExecutionDisplayStatus,
|
|
5
|
+
JobExecutionTime,
|
|
6
|
+
} from '#core/workflow-run.js';
|
|
3
7
|
|
|
4
8
|
export function formatJobExecutionTimeLabel(time: JobExecutionTime): string {
|
|
5
9
|
switch (time.state) {
|
|
@@ -16,6 +20,7 @@ export function formatJobExecutionTimeLabel(time: JobExecutionTime): string {
|
|
|
16
20
|
|
|
17
21
|
export function formatJobDurationAccessibleLabel(
|
|
18
22
|
duration: JobDisplayDuration | null,
|
|
23
|
+
displayStatus?: JobExecutionDisplayStatus,
|
|
19
24
|
): string | undefined {
|
|
20
25
|
if (duration === null) return undefined;
|
|
21
26
|
|
|
@@ -24,7 +29,11 @@ export function formatJobDurationAccessibleLabel(
|
|
|
24
29
|
case 'queue':
|
|
25
30
|
return duration.state === 'live' ? `queueing ${label}` : `queued ${label}`;
|
|
26
31
|
case 'run':
|
|
27
|
-
|
|
32
|
+
if (duration.state !== 'live') return `ran ${label}`;
|
|
33
|
+
if (displayStatus === 'running') return `running ${label}`;
|
|
34
|
+
return displayStatus === 'pending'
|
|
35
|
+
? `runner assigned ${label}`
|
|
36
|
+
: `execution elapsed ${label}`;
|
|
28
37
|
default: {
|
|
29
38
|
const exhaustive: never = duration;
|
|
30
39
|
return exhaustive;
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
workflowJob,
|
|
7
7
|
workflowJobExecutionDto,
|
|
8
8
|
workflowRunDetail,
|
|
9
|
+
workflowStepDto,
|
|
9
10
|
} from '#test/fixtures/workflow-run.js';
|
|
10
11
|
import {JobGraph} from './job-graph.js';
|
|
11
12
|
|
|
@@ -46,10 +47,7 @@ describe('JobGraph', () => {
|
|
|
46
47
|
render(
|
|
47
48
|
<JobGraph
|
|
48
49
|
run={makeRun({
|
|
49
|
-
jobs: [
|
|
50
|
-
makeJob({name: 'linux', status: 'running'}),
|
|
51
|
-
makeJob({name: 'macos', status: 'running', position: 1}),
|
|
52
|
-
],
|
|
50
|
+
jobs: [makeRunningJob({name: 'linux'}), makeRunningJob({name: 'macos', position: 1})],
|
|
53
51
|
})}
|
|
54
52
|
/>,
|
|
55
53
|
);
|
|
@@ -99,7 +97,7 @@ describe('JobGraph', () => {
|
|
|
99
97
|
run={makeRun({
|
|
100
98
|
jobs: [
|
|
101
99
|
makeJob({name: 'build'}),
|
|
102
|
-
|
|
100
|
+
makeRunningJob({name: 'deploy', position: 1, dependencies: ['build']}),
|
|
103
101
|
],
|
|
104
102
|
})}
|
|
105
103
|
/>,
|
|
@@ -161,7 +159,7 @@ describe('JobGraph', () => {
|
|
|
161
159
|
/>,
|
|
162
160
|
);
|
|
163
161
|
|
|
164
|
-
const build = screen.getByRole('button', {name: 'build,
|
|
162
|
+
const build = screen.getByRole('button', {name: 'build, Succeeded, 2 executions'});
|
|
165
163
|
const deploy = screen.getByRole('button', {name: 'deploy, Pending'});
|
|
166
164
|
expect(within(build).getByText('2')).toBeInTheDocument();
|
|
167
165
|
expect(within(build).queryByText('2 exec')).not.toBeInTheDocument();
|
|
@@ -224,7 +222,7 @@ describe('JobGraph', () => {
|
|
|
224
222
|
dependencies: ['build'],
|
|
225
223
|
status: 'succeeded',
|
|
226
224
|
});
|
|
227
|
-
const testJob =
|
|
225
|
+
const testJob = makeRunningJob({
|
|
228
226
|
name: 'test',
|
|
229
227
|
position: 2,
|
|
230
228
|
dependencies: ['build'],
|
|
@@ -257,7 +255,7 @@ describe('JobGraph', () => {
|
|
|
257
255
|
test('routes skipped-column join edges away from intermediate nodes', () => {
|
|
258
256
|
const packageJob = makeJob({name: 'package', position: 0, status: 'succeeded'});
|
|
259
257
|
const securityScan = makeJob({name: 'security-scan', position: 1, status: 'succeeded'});
|
|
260
|
-
const smokeTests =
|
|
258
|
+
const smokeTests = makeRunningJob({
|
|
261
259
|
name: 'smoke-tests',
|
|
262
260
|
position: 2,
|
|
263
261
|
dependencies: ['package'],
|
|
@@ -296,3 +294,16 @@ function makeRun(overrides: Partial<WorkflowRunDetail> = {}): WorkflowRunDetail
|
|
|
296
294
|
function makeJob(overrides: Partial<WorkflowRunJobDetailDto> & {name: string}): Job {
|
|
297
295
|
return workflowJob(overrides);
|
|
298
296
|
}
|
|
297
|
+
|
|
298
|
+
function makeRunningJob(overrides: Partial<WorkflowRunJobDetailDto> & {name: string}): Job {
|
|
299
|
+
return makeJob({
|
|
300
|
+
...overrides,
|
|
301
|
+
status: 'running',
|
|
302
|
+
job_executions: [
|
|
303
|
+
workflowJobExecutionDto({
|
|
304
|
+
status: 'running',
|
|
305
|
+
steps: [workflowStepDto({status: 'running'})],
|
|
306
|
+
}),
|
|
307
|
+
],
|
|
308
|
+
});
|
|
309
|
+
}
|
|
@@ -2,7 +2,7 @@ import type {WorkflowRunJobDetailDto} from '@shipfox/api-workflows-dto';
|
|
|
2
2
|
import type {Meta, StoryObj} from '@storybook/react';
|
|
3
3
|
import {within} from 'storybook/test';
|
|
4
4
|
import type {Job, WorkflowRunDetail} from '#core/workflow-run.js';
|
|
5
|
-
import {workflowJob, workflowRunDetail} from '#test/fixtures/workflow-run.js';
|
|
5
|
+
import {workflowJob, workflowRunDetail, workflowStepDto} from '#test/fixtures/workflow-run.js';
|
|
6
6
|
import {JobGraph} from './job-graph.js';
|
|
7
7
|
|
|
8
8
|
const meta = {
|
|
@@ -93,6 +93,23 @@ export const Empty: Story = {
|
|
|
93
93
|
args: {run: makeRun({jobs: []})},
|
|
94
94
|
};
|
|
95
95
|
|
|
96
|
+
export const LargeWideAndTall: Story = {
|
|
97
|
+
args: {
|
|
98
|
+
run: makeRun({
|
|
99
|
+
jobs: [
|
|
100
|
+
...linearJobs(10),
|
|
101
|
+
...Array.from({length: 10}, (_, index) =>
|
|
102
|
+
makeJob({
|
|
103
|
+
name: `parallel-${String(index + 1).padStart(2, '0')}`,
|
|
104
|
+
position: 20 + index,
|
|
105
|
+
status: index < 2 ? 'running' : 'pending',
|
|
106
|
+
}),
|
|
107
|
+
),
|
|
108
|
+
],
|
|
109
|
+
}),
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
|
|
96
113
|
export const Selected: Story = {
|
|
97
114
|
args: {
|
|
98
115
|
run: makeRun({
|
|
@@ -111,23 +128,6 @@ export const Selected: Story = {
|
|
|
111
128
|
},
|
|
112
129
|
};
|
|
113
130
|
|
|
114
|
-
export const LargeWideAndTall: Story = {
|
|
115
|
-
args: {
|
|
116
|
-
run: makeRun({
|
|
117
|
-
jobs: [
|
|
118
|
-
...linearJobs(10),
|
|
119
|
-
...Array.from({length: 10}, (_, index) =>
|
|
120
|
-
makeJob({
|
|
121
|
-
name: `parallel-${String(index + 1).padStart(2, '0')}`,
|
|
122
|
-
position: 20 + index,
|
|
123
|
-
status: index < 2 ? 'running' : 'pending',
|
|
124
|
-
}),
|
|
125
|
-
),
|
|
126
|
-
],
|
|
127
|
-
}),
|
|
128
|
-
},
|
|
129
|
-
};
|
|
130
|
-
|
|
131
131
|
function linearJobs(count: number): Job[] {
|
|
132
132
|
return Array.from({length: count}, (_, index) =>
|
|
133
133
|
makeJob({
|
|
@@ -178,5 +178,9 @@ function makeRun(overrides: Partial<WorkflowRunDetail> = {}): WorkflowRunDetail
|
|
|
178
178
|
}
|
|
179
179
|
|
|
180
180
|
function makeJob(overrides: Partial<WorkflowRunJobDetailDto> & {name: string}): Job {
|
|
181
|
-
|
|
181
|
+
const job = workflowJob(overrides);
|
|
182
|
+
if (overrides.status === 'running' && job.jobExecutions.length === 0) {
|
|
183
|
+
return workflowJob({...overrides, steps: [workflowStepDto({status: 'running'})]});
|
|
184
|
+
}
|
|
185
|
+
return job;
|
|
182
186
|
}
|
|
@@ -2,7 +2,11 @@ import type {WorkflowRunJobDetailDto} from '@shipfox/api-workflows-dto';
|
|
|
2
2
|
import type {Meta, StoryObj} from '@storybook/react';
|
|
3
3
|
import type {KeyboardEventHandler} from 'react';
|
|
4
4
|
import type {JobExecutionStatus, JobStatus} from '#core/workflow-run.js';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
workflowJob,
|
|
7
|
+
workflowJobExecutionDto,
|
|
8
|
+
workflowStepDto,
|
|
9
|
+
} from '#test/fixtures/workflow-run.js';
|
|
6
10
|
import type {JobGraphNode} from './graph-model.js';
|
|
7
11
|
import {JobNode} from './job-node.js';
|
|
8
12
|
|
|
@@ -183,7 +187,7 @@ export const ListeningProgression: Story = {
|
|
|
183
187
|
<JobNode
|
|
184
188
|
node={makeNode({
|
|
185
189
|
id: 'job-listening-running',
|
|
186
|
-
label: '
|
|
190
|
+
label: 'listening, 0 executions',
|
|
187
191
|
mode: 'listening',
|
|
188
192
|
status: 'running',
|
|
189
193
|
listenerStatus: 'listening',
|
|
@@ -200,7 +204,7 @@ export const ListeningProgression: Story = {
|
|
|
200
204
|
<JobNode
|
|
201
205
|
node={makeNode({
|
|
202
206
|
id: 'job-one-running-execution',
|
|
203
|
-
label: '
|
|
207
|
+
label: 'listening, 1 running execution',
|
|
204
208
|
mode: 'listening',
|
|
205
209
|
status: 'running',
|
|
206
210
|
listenerStatus: 'listening',
|
|
@@ -217,7 +221,7 @@ export const ListeningProgression: Story = {
|
|
|
217
221
|
<JobNode
|
|
218
222
|
node={makeNode({
|
|
219
223
|
id: 'job-mixed-executions',
|
|
220
|
-
label: '
|
|
224
|
+
label: 'listening, mixed executions',
|
|
221
225
|
mode: 'listening',
|
|
222
226
|
status: 'running',
|
|
223
227
|
listenerStatus: 'listening',
|
|
@@ -330,6 +334,7 @@ function makeNode({
|
|
|
330
334
|
queued_at: queuedAt,
|
|
331
335
|
started_at: startedAt,
|
|
332
336
|
finished_at: finishedAt,
|
|
337
|
+
...(status === 'running' ? {steps: [workflowStepDto({status: 'running'})]} : {}),
|
|
333
338
|
}),
|
|
334
339
|
]
|
|
335
340
|
: jobExecutions,
|
|
@@ -359,6 +364,6 @@ function makeExecutionsByStatus(
|
|
|
359
364
|
timed_out_at: null,
|
|
360
365
|
created_at: '2026-06-26T11:54:00.000Z',
|
|
361
366
|
updated_at: '2026-06-26T12:00:00.000Z',
|
|
362
|
-
steps: [],
|
|
367
|
+
steps: status === 'running' ? [workflowStepDto({status: 'running'})] : [],
|
|
363
368
|
}));
|
|
364
369
|
}
|
|
@@ -2,14 +2,18 @@ import type {WorkflowRunJobDetailDto} from '@shipfox/api-workflows-dto';
|
|
|
2
2
|
import {TimeTickerProvider} from '@shipfox/react-ui/time-ticker';
|
|
3
3
|
import {act, render, screen, within} from '@testing-library/react';
|
|
4
4
|
import userEvent from '@testing-library/user-event';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
workflowJob,
|
|
7
|
+
workflowJobExecutionDto,
|
|
8
|
+
workflowStepDto,
|
|
9
|
+
} from '#test/fixtures/workflow-run.js';
|
|
6
10
|
import type {JobGraphNode} from './graph-model.js';
|
|
7
11
|
import {JobNode} from './job-node.js';
|
|
8
12
|
|
|
9
13
|
const NOW = Date.parse('2026-06-26T12:00:00.000Z');
|
|
10
14
|
|
|
11
15
|
type NodeOverrides = Omit<Partial<WorkflowRunJobDetailDto>, 'job_executions'> & {
|
|
12
|
-
name
|
|
16
|
+
name?: string | null;
|
|
13
17
|
job_executions?: WorkflowRunJobDetailDto['job_executions'];
|
|
14
18
|
queued_at?: string | null;
|
|
15
19
|
started_at?: string | null;
|
|
@@ -32,6 +36,9 @@ function makeNode(overrides: NodeOverrides): JobGraphNode {
|
|
|
32
36
|
queued_at: queued_at ?? null,
|
|
33
37
|
started_at: started_at ?? null,
|
|
34
38
|
finished_at: finished_at ?? null,
|
|
39
|
+
...(jobOverrides.status === 'running'
|
|
40
|
+
? {steps: [workflowStepDto({status: 'running'})]}
|
|
41
|
+
: {}),
|
|
35
42
|
}),
|
|
36
43
|
];
|
|
37
44
|
} else if (job_executions !== undefined) {
|
|
@@ -200,7 +207,7 @@ describe('JobNode duration', () => {
|
|
|
200
207
|
renderNode(node);
|
|
201
208
|
|
|
202
209
|
expect(screen.queryByText('2m 14s')).not.toBeInTheDocument();
|
|
203
|
-
expect(screen.getByRole('button', {name: 'deploy-window,
|
|
210
|
+
expect(screen.getByRole('button', {name: 'deploy-window, Listening'})).toBeInTheDocument();
|
|
204
211
|
});
|
|
205
212
|
});
|
|
206
213
|
|
|
@@ -227,12 +234,12 @@ describe('JobNode listening indicator', () => {
|
|
|
227
234
|
|
|
228
235
|
renderNode(node);
|
|
229
236
|
|
|
230
|
-
expect(screen.getByRole('button', {name: 'deploy-window,
|
|
237
|
+
expect(screen.getByRole('button', {name: 'deploy-window, Listening'})).toBeInTheDocument();
|
|
231
238
|
expect(screen.queryByLabelText('Waiting for events to start job')).not.toBeInTheDocument();
|
|
232
239
|
|
|
233
|
-
await user.hover(screen.getByRole('img', {name: '
|
|
240
|
+
await user.hover(screen.getByRole('img', {name: 'Listening'}));
|
|
234
241
|
|
|
235
|
-
expect(await screen.findByRole('tooltip')).toHaveTextContent('
|
|
242
|
+
expect(await screen.findByRole('tooltip')).toHaveTextContent('Listening');
|
|
236
243
|
});
|
|
237
244
|
|
|
238
245
|
test('does not show the listener icon before the listener is armed or after it resolves', () => {
|
|
@@ -277,6 +284,12 @@ describe('JobNode status indicator', () => {
|
|
|
277
284
|
const node = makeNode({
|
|
278
285
|
name: 'deploy',
|
|
279
286
|
status: 'running',
|
|
287
|
+
job_executions: [
|
|
288
|
+
workflowJobExecutionDto({
|
|
289
|
+
status: 'running',
|
|
290
|
+
steps: [workflowStepDto({status: 'running'})],
|
|
291
|
+
}),
|
|
292
|
+
],
|
|
280
293
|
});
|
|
281
294
|
|
|
282
295
|
renderNode(node);
|
|
@@ -286,7 +299,15 @@ describe('JobNode status indicator', () => {
|
|
|
286
299
|
expect(await screen.findByRole('tooltip')).toHaveTextContent('Running');
|
|
287
300
|
});
|
|
288
301
|
|
|
289
|
-
test('
|
|
302
|
+
test('uses the job key when the static name is missing', () => {
|
|
303
|
+
const node = makeNode({key: 'deploy-prod', name: null, status: 'succeeded'});
|
|
304
|
+
|
|
305
|
+
renderNode(node);
|
|
306
|
+
|
|
307
|
+
expect(screen.getByRole('button', {name: 'deploy-prod, Succeeded'})).toBeInTheDocument();
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
test('shows a running lifecycle without a running step as pending', () => {
|
|
290
311
|
const node = makeNode({
|
|
291
312
|
name: 'deploy',
|
|
292
313
|
status: 'pending',
|
|
@@ -295,6 +316,23 @@ describe('JobNode status indicator', () => {
|
|
|
295
316
|
|
|
296
317
|
renderNode(node);
|
|
297
318
|
|
|
319
|
+
expect(screen.getByRole('button', {name: 'deploy, Pending'})).toBeInTheDocument();
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
test('shows a pending lifecycle as running while a step is active', () => {
|
|
323
|
+
const node = makeNode({
|
|
324
|
+
name: 'deploy',
|
|
325
|
+
status: 'pending',
|
|
326
|
+
job_executions: [
|
|
327
|
+
workflowJobExecutionDto({
|
|
328
|
+
status: 'pending',
|
|
329
|
+
steps: [workflowStepDto({status: 'running'})],
|
|
330
|
+
}),
|
|
331
|
+
],
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
renderNode(node);
|
|
335
|
+
|
|
298
336
|
expect(screen.getByRole('button', {name: 'deploy, Running'})).toBeInTheDocument();
|
|
299
337
|
});
|
|
300
338
|
});
|
|
@@ -317,10 +355,17 @@ describe('JobNode execution count indicator', () => {
|
|
|
317
355
|
name: 'release-gates',
|
|
318
356
|
mode: 'listening',
|
|
319
357
|
status: 'running',
|
|
358
|
+
listener_status: 'listening',
|
|
320
359
|
job_executions: [
|
|
321
360
|
workflowJobExecutionDto({status: 'pending'}),
|
|
322
|
-
workflowJobExecutionDto({
|
|
323
|
-
|
|
361
|
+
workflowJobExecutionDto({
|
|
362
|
+
status: 'running',
|
|
363
|
+
steps: [workflowStepDto({status: 'running'})],
|
|
364
|
+
}),
|
|
365
|
+
workflowJobExecutionDto({
|
|
366
|
+
status: 'running',
|
|
367
|
+
steps: [workflowStepDto({status: 'running'})],
|
|
368
|
+
}),
|
|
324
369
|
workflowJobExecutionDto({status: 'succeeded'}),
|
|
325
370
|
workflowJobExecutionDto({status: 'succeeded'}),
|
|
326
371
|
workflowJobExecutionDto({status: 'succeeded'}),
|
|
@@ -331,7 +376,7 @@ describe('JobNode execution count indicator', () => {
|
|
|
331
376
|
|
|
332
377
|
renderNode(node);
|
|
333
378
|
|
|
334
|
-
const button = screen.getByRole('button', {name: 'release-gates,
|
|
379
|
+
const button = screen.getByRole('button', {name: 'release-gates, Listening, 8 executions'});
|
|
335
380
|
expect(within(button).getByText('8')).toBeInTheDocument();
|
|
336
381
|
expect(within(button).queryByText('8 exec')).not.toBeInTheDocument();
|
|
337
382
|
expect(button.querySelector('[data-execution-status-segment]')).not.toBeInTheDocument();
|
|
@@ -9,7 +9,14 @@ import {cn} from '@shipfox/react-ui/utils';
|
|
|
9
9
|
import type {KeyboardEventHandler, Ref} from 'react';
|
|
10
10
|
import {getWorkflowStatusVisual} from '#components/workflow-status/status-visuals.js';
|
|
11
11
|
import {WorkflowStatusIcon} from '#components/workflow-status/workflow-status-icon.js';
|
|
12
|
-
import
|
|
12
|
+
import {
|
|
13
|
+
defaultJobExecution,
|
|
14
|
+
deriveJobDisplayStatus,
|
|
15
|
+
deriveJobExecutionDisplayStatus,
|
|
16
|
+
type JobExecution,
|
|
17
|
+
type JobExecutionStatus,
|
|
18
|
+
type WorkflowRunDetail,
|
|
19
|
+
} from '#core/workflow-run.js';
|
|
13
20
|
import type {JobGraphNode} from './graph-model.js';
|
|
14
21
|
import {formatJobDurationAccessibleLabel} from './job-duration-format.js';
|
|
15
22
|
import {JobDurationLabel} from './job-duration-label.js';
|
|
@@ -69,16 +76,16 @@ export function JobNode({
|
|
|
69
76
|
ref?: Ref<HTMLButtonElement>;
|
|
70
77
|
}) {
|
|
71
78
|
useTimeTick();
|
|
72
|
-
const status =
|
|
73
|
-
|
|
74
|
-
node.jobExecutions.some((jobExecution) => jobExecution.status === 'running')
|
|
75
|
-
? 'running'
|
|
76
|
-
: node.status;
|
|
79
|
+
const status = deriveJobDisplayStatus(node);
|
|
80
|
+
const execution = defaultJobExecution(node);
|
|
77
81
|
const visual = getWorkflowStatusVisual(status);
|
|
78
82
|
const accessibleLabel = [
|
|
79
83
|
node.displayName,
|
|
80
84
|
visual.label,
|
|
81
|
-
formatJobDurationAccessibleLabel(
|
|
85
|
+
formatJobDurationAccessibleLabel(
|
|
86
|
+
node.displayDuration,
|
|
87
|
+
execution ? deriveJobExecutionDisplayStatus(execution) : undefined,
|
|
88
|
+
),
|
|
82
89
|
node.executionCountVisible
|
|
83
90
|
? executionCountAccessibleLabel(node.jobExecutions.length)
|
|
84
91
|
: undefined,
|
|
@@ -111,7 +118,7 @@ export function JobNode({
|
|
|
111
118
|
/>
|
|
112
119
|
) : null}
|
|
113
120
|
<div className="flex min-w-0 flex-1 items-center gap-8">
|
|
114
|
-
<WorkflowStatusIcon status={status}
|
|
121
|
+
<WorkflowStatusIcon status={status} size={14} />
|
|
115
122
|
<JobLabel label={node.displayName} />
|
|
116
123
|
</div>
|
|
117
124
|
<JobDurationLabel duration={node.displayDuration} />
|
|
@@ -199,7 +206,7 @@ const EXECUTION_STATUSES = [
|
|
|
199
206
|
function executionStatusCounts(executions: JobExecution[]) {
|
|
200
207
|
return executions.reduce(
|
|
201
208
|
(counts, execution) => {
|
|
202
|
-
counts[execution
|
|
209
|
+
counts[deriveJobExecutionDisplayStatus(execution)] += 1;
|
|
203
210
|
return counts;
|
|
204
211
|
},
|
|
205
212
|
{pending: 0, running: 0, succeeded: 0, failed: 0, cancelled: 0},
|
|
@@ -21,7 +21,6 @@ import {
|
|
|
21
21
|
import {AgentConfigFailureCallout as AgentConfigFailureCalloutView} from '../workflow-run-view/agent-config-failure-callout.js';
|
|
22
22
|
import {StepList} from './step-list.js';
|
|
23
23
|
|
|
24
|
-
const WORKSPACE_ID = '44444444-4444-4444-8444-444444444444';
|
|
25
24
|
const AGENTS_LINK_NAME = 'Configure Agents';
|
|
26
25
|
|
|
27
26
|
const meta = {
|
|
@@ -70,7 +69,7 @@ const withAgentSettingsRoute: Decorator = (Story) => {
|
|
|
70
69
|
});
|
|
71
70
|
const agentSettingsRoute = createRoute({
|
|
72
71
|
getParentRoute: () => rootRoute,
|
|
73
|
-
path: '/
|
|
72
|
+
path: '/w/$workspaceSlug/settings/agents',
|
|
74
73
|
component: () => null,
|
|
75
74
|
});
|
|
76
75
|
const router = createRouter({
|
|
@@ -390,7 +389,7 @@ function renderAgentConfigFailureCallout() {
|
|
|
390
389
|
defaultSelectedAttemptId={attempt.id}
|
|
391
390
|
renderExpandedStep={() => (
|
|
392
391
|
<AgentConfigFailureCalloutView
|
|
393
|
-
|
|
392
|
+
workspaceSlug="acme"
|
|
394
393
|
config={{provider: 'anthropic', model: 'claude-opus-4-8', thinking: 'high'}}
|
|
395
394
|
error={{
|
|
396
395
|
message: 'Model provider credentials are not configured',
|
|
@@ -18,6 +18,7 @@ import {WorkflowStatusIcon} from '#components/workflow-status/workflow-status-ic
|
|
|
18
18
|
import {
|
|
19
19
|
isWorkflowStatus,
|
|
20
20
|
type Job,
|
|
21
|
+
type JobDisplayStatus,
|
|
21
22
|
type JobExecution,
|
|
22
23
|
type Step,
|
|
23
24
|
type StepSourceLocation,
|
|
@@ -47,7 +48,7 @@ export interface StepExpandedContext {
|
|
|
47
48
|
export interface StepListEmptyState {
|
|
48
49
|
title: string;
|
|
49
50
|
description: string;
|
|
50
|
-
status?:
|
|
51
|
+
status?: JobDisplayStatus | undefined;
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
export interface StepListProps {
|
|
@@ -267,7 +268,7 @@ function StepListEmptyStateView({
|
|
|
267
268
|
);
|
|
268
269
|
}
|
|
269
270
|
|
|
270
|
-
function StepListEmptyStateIcon({status}: {status:
|
|
271
|
+
function StepListEmptyStateIcon({status}: {status: JobDisplayStatus}) {
|
|
271
272
|
if (status !== 'running') {
|
|
272
273
|
return (
|
|
273
274
|
<div className="flex size-32 items-center justify-center rounded-6 border border-border-neutral-strong bg-background-neutral-base p-8">
|