@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
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { EmptyState } from '@shipfox/react-ui/empty-state';
|
|
3
|
+
import { AGENT_CONFIG_ISSUES, deriveJobDisplayStatus, deriveJobExecutionDisplayStatus, STEP_ERROR_REASONS } from '#core/workflow-run.js';
|
|
4
|
+
import { formatJobExecutionTime } from './job-execution-time-text.js';
|
|
5
|
+
export function emptyStateForJob(job, jobExecution) {
|
|
6
|
+
if (job.carriedOver) {
|
|
7
|
+
return {
|
|
8
|
+
title: 'Carried over from a previous attempt',
|
|
9
|
+
description: 'This job did not execute in this run.',
|
|
10
|
+
status: 'succeeded'
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
const displayStatus = job.mode === 'listening' ? deriveJobExecutionDisplayStatus(jobExecution) : deriveJobDisplayStatus(job);
|
|
14
|
+
if (jobExecution.status === 'running' && displayStatus === 'pending') {
|
|
15
|
+
return {
|
|
16
|
+
title: 'Runner preparing job',
|
|
17
|
+
description: 'A runner is assigned. Steps will appear here when work begins.',
|
|
18
|
+
status: displayStatus
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
if (displayStatus === 'pending') {
|
|
22
|
+
return {
|
|
23
|
+
title: 'Waiting for this job to start',
|
|
24
|
+
description: 'Steps will appear here once the job starts.',
|
|
25
|
+
status: displayStatus
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
if (displayStatus === 'running') {
|
|
29
|
+
return {
|
|
30
|
+
title: 'Waiting for the first step',
|
|
31
|
+
description: 'The first running step will appear here shortly.',
|
|
32
|
+
status: displayStatus
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
if (displayStatus === 'cancelled') {
|
|
36
|
+
return {
|
|
37
|
+
title: 'Cancelled before start',
|
|
38
|
+
description: 'This job was cancelled before any step started.',
|
|
39
|
+
status: displayStatus
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
if (displayStatus === 'failed') {
|
|
43
|
+
return {
|
|
44
|
+
title: 'Job failed before its first step started',
|
|
45
|
+
description: preStepFailureDescription(jobExecution.statusReason ?? job.statusReason),
|
|
46
|
+
status: displayStatus
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
if (displayStatus === 'succeeded') {
|
|
50
|
+
return {
|
|
51
|
+
title: 'Completed without recorded steps',
|
|
52
|
+
description: `Execution #${jobExecution.sequence} succeeded before any step output was recorded.`,
|
|
53
|
+
status: displayStatus
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
export function emptyStateForMissingExecution(job) {
|
|
59
|
+
if (job.carriedOver) {
|
|
60
|
+
return {
|
|
61
|
+
title: 'Carried over from a previous attempt',
|
|
62
|
+
description: 'This job did not execute in this run.',
|
|
63
|
+
status: 'succeeded'
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
if (job.mode === 'listening' && job.listenerStatus === 'listening') {
|
|
67
|
+
return {
|
|
68
|
+
title: 'Waiting for trigger events',
|
|
69
|
+
description: 'Matching trigger events will create job executions here.',
|
|
70
|
+
status: deriveJobDisplayStatus(job)
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
if (job.mode === 'listening' && job.listenerStatus === 'resolved') {
|
|
74
|
+
return {
|
|
75
|
+
title: 'Listener resolved without executions',
|
|
76
|
+
description: 'No matching trigger event created a job execution before the listener stopped.',
|
|
77
|
+
status: job.status
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
if (job.status === 'pending') {
|
|
81
|
+
return {
|
|
82
|
+
title: 'Waiting for this job to start',
|
|
83
|
+
description: 'No execution has been created for this job yet.',
|
|
84
|
+
status: 'pending'
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
if (job.status === 'skipped') {
|
|
88
|
+
return {
|
|
89
|
+
title: 'This job was skipped',
|
|
90
|
+
description: skippedJobDescription(job.statusReason),
|
|
91
|
+
status: 'skipped'
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
if (job.status === 'cancelled') {
|
|
95
|
+
return {
|
|
96
|
+
title: 'Cancelled before start',
|
|
97
|
+
description: 'This job was cancelled before an execution was created.',
|
|
98
|
+
status: 'cancelled'
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
title: 'Execution details unavailable',
|
|
103
|
+
description: 'This job finished, but no job execution record is available.',
|
|
104
|
+
status: job.status
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
export function skippedJobDescription(reason) {
|
|
108
|
+
switch(reason){
|
|
109
|
+
case 'dependency_not_completed':
|
|
110
|
+
return 'A required job did not complete, so this job was skipped.';
|
|
111
|
+
case 'default_gate_rejected':
|
|
112
|
+
return 'A required job did not succeed, so this job was skipped.';
|
|
113
|
+
case 'condition_false':
|
|
114
|
+
case 'condition_rejected':
|
|
115
|
+
return 'The job condition did not match, so this job was skipped.';
|
|
116
|
+
case 'condition_errored':
|
|
117
|
+
return 'The job condition could not be evaluated, so this job was skipped.';
|
|
118
|
+
case 'user_cancelled':
|
|
119
|
+
case 'run_cancelled':
|
|
120
|
+
case 'timed_out':
|
|
121
|
+
case 'runner_lost':
|
|
122
|
+
case 'step_failed':
|
|
123
|
+
case 'unknown':
|
|
124
|
+
case null:
|
|
125
|
+
return 'This job did not start.';
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function preStepFailureDescription(reason) {
|
|
129
|
+
switch(reason){
|
|
130
|
+
case 'runner_lost':
|
|
131
|
+
return 'The runner stopped responding before work began. Check runner availability before re-running the workflow.';
|
|
132
|
+
case 'timed_out':
|
|
133
|
+
return 'The job timed out before work began. Check runner capacity and timeout configuration before re-running the workflow.';
|
|
134
|
+
case 'user_cancelled':
|
|
135
|
+
case 'run_cancelled':
|
|
136
|
+
return 'The execution ended while the run was being cancelled.';
|
|
137
|
+
case 'step_failed':
|
|
138
|
+
return 'The execution failed before step details were recorded. Review run annotations before re-running the workflow.';
|
|
139
|
+
case 'condition_errored':
|
|
140
|
+
return 'The job condition could not be evaluated. Review run annotations before re-running the workflow.';
|
|
141
|
+
case 'dependency_not_completed':
|
|
142
|
+
case 'default_gate_rejected':
|
|
143
|
+
return 'A required job did not complete successfully. Resolve that failure before re-running the workflow.';
|
|
144
|
+
case 'condition_false':
|
|
145
|
+
case 'condition_rejected':
|
|
146
|
+
return 'The job condition did not match, so no work started.';
|
|
147
|
+
case 'unknown':
|
|
148
|
+
case null:
|
|
149
|
+
return 'Shipfox did not record a failure reason. Re-run only after checking the runner and workflow configuration.';
|
|
150
|
+
default:
|
|
151
|
+
return `The execution ended because ${humanizeFailureReason(reason)}. Resolve that condition before re-running the workflow.`;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function humanizeFailureReason(reason) {
|
|
155
|
+
return reason.replaceAll('_', ' ').replaceAll('-', ' ');
|
|
156
|
+
}
|
|
157
|
+
export function CarriedOverStepPanel() {
|
|
158
|
+
return /*#__PURE__*/ _jsx(EmptyState, {
|
|
159
|
+
className: "min-h-120 rounded-8 border border-border-neutral-base bg-background-components-base px-16 py-20",
|
|
160
|
+
icon: "componentLine",
|
|
161
|
+
title: "Carried over from a previous attempt",
|
|
162
|
+
description: "Not executed in this run.",
|
|
163
|
+
variant: "compact"
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
export function toSelectedAttemptError(step, error) {
|
|
167
|
+
if (error === null) return null;
|
|
168
|
+
const rawReason = error.reason;
|
|
169
|
+
const parsedReason = typeof rawReason === 'string' && STEP_ERROR_REASONS.has(rawReason) ? rawReason : undefined;
|
|
170
|
+
const rawAgentConfigIssue = error.agentConfigIssue ?? error.agent_config_issue;
|
|
171
|
+
const agentConfigIssue = typeof rawAgentConfigIssue === 'string' && AGENT_CONFIG_ISSUES.has(rawAgentConfigIssue) ? rawAgentConfigIssue : undefined;
|
|
172
|
+
const exitCode = error.exitCode ?? error.exit_code;
|
|
173
|
+
const resolvedReason = parsedReason ? parsedReason : agentConfigIssue ? 'agent_config_invalid' : undefined;
|
|
174
|
+
if (resolvedReason === undefined) return null;
|
|
175
|
+
return {
|
|
176
|
+
message: typeof error.message === 'string' ? error.message : '',
|
|
177
|
+
exitCode: exitCode === null || typeof exitCode === 'number' ? exitCode : null,
|
|
178
|
+
signal: typeof error.signal === 'string' ? error.signal : undefined,
|
|
179
|
+
reason: resolvedReason,
|
|
180
|
+
agentConfigIssue,
|
|
181
|
+
category: step.type === 'setup' || step.type === 'checkout' ? 'setup' : 'user'
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
export function isAgentConfigFailure(step, error) {
|
|
185
|
+
return step.type === 'agent' && error?.reason === 'agent_config_invalid';
|
|
186
|
+
}
|
|
187
|
+
export function jobSucceededSummary(job, execution) {
|
|
188
|
+
if (job.carriedOver || execution.status !== 'succeeded') return undefined;
|
|
189
|
+
const stepCount = execution.steps.filter((step)=>step.status === 'succeeded').length;
|
|
190
|
+
if (stepCount === 0) return undefined;
|
|
191
|
+
const duration = execution.displayDuration;
|
|
192
|
+
return `${stepCount} step${stepCount === 1 ? '' : 's'} succeeded${duration ? ` in ${formatJobExecutionTime(duration)}` : ''}`;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
//# sourceMappingURL=job-empty-states.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/job-detail/job-empty-states.tsx"],"sourcesContent":["import {EmptyState} from '@shipfox/react-ui/empty-state';\nimport type {Job, JobExecution, Step, StepError} from '#core/workflow-run.js';\nimport {\n AGENT_CONFIG_ISSUES,\n deriveJobDisplayStatus,\n deriveJobExecutionDisplayStatus,\n STEP_ERROR_REASONS,\n} from '#core/workflow-run.js';\nimport type {StepListEmptyState} from '../step-list/index.js';\nimport {formatJobExecutionTime} from './job-execution-time-text.js';\n\nexport function emptyStateForJob(\n job: Job,\n jobExecution: JobExecution,\n): StepListEmptyState | undefined {\n if (job.carriedOver) {\n return {\n title: 'Carried over from a previous attempt',\n description: 'This job did not execute in this run.',\n status: 'succeeded',\n };\n }\n\n const displayStatus =\n job.mode === 'listening'\n ? deriveJobExecutionDisplayStatus(jobExecution)\n : deriveJobDisplayStatus(job);\n\n if (jobExecution.status === 'running' && displayStatus === 'pending') {\n return {\n title: 'Runner preparing job',\n description: 'A runner is assigned. Steps will appear here when work begins.',\n status: displayStatus,\n };\n }\n\n if (displayStatus === 'pending') {\n return {\n title: 'Waiting for this job to start',\n description: 'Steps will appear here once the job starts.',\n status: displayStatus,\n };\n }\n\n if (displayStatus === 'running') {\n return {\n title: 'Waiting for the first step',\n description: 'The first running step will appear here shortly.',\n status: displayStatus,\n };\n }\n\n if (displayStatus === 'cancelled') {\n return {\n title: 'Cancelled before start',\n description: 'This job was cancelled before any step started.',\n status: displayStatus,\n };\n }\n\n if (displayStatus === 'failed') {\n return {\n title: 'Job failed before its first step started',\n description: preStepFailureDescription(jobExecution.statusReason ?? job.statusReason),\n status: displayStatus,\n };\n }\n\n if (displayStatus === 'succeeded') {\n return {\n title: 'Completed without recorded steps',\n description: `Execution #${jobExecution.sequence} succeeded before any step output was recorded.`,\n status: displayStatus,\n };\n }\n\n return undefined;\n}\n\nexport function emptyStateForMissingExecution(job: Job): StepListEmptyState {\n if (job.carriedOver) {\n return {\n title: 'Carried over from a previous attempt',\n description: 'This job did not execute in this run.',\n status: 'succeeded',\n };\n }\n\n if (job.mode === 'listening' && job.listenerStatus === 'listening') {\n return {\n title: 'Waiting for trigger events',\n description: 'Matching trigger events will create job executions here.',\n status: deriveJobDisplayStatus(job),\n };\n }\n\n if (job.mode === 'listening' && job.listenerStatus === 'resolved') {\n return {\n title: 'Listener resolved without executions',\n description: 'No matching trigger event created a job execution before the listener stopped.',\n status: job.status,\n };\n }\n\n if (job.status === 'pending') {\n return {\n title: 'Waiting for this job to start',\n description: 'No execution has been created for this job yet.',\n status: 'pending',\n };\n }\n\n if (job.status === 'skipped') {\n return {\n title: 'This job was skipped',\n description: skippedJobDescription(job.statusReason),\n status: 'skipped',\n };\n }\n\n if (job.status === 'cancelled') {\n return {\n title: 'Cancelled before start',\n description: 'This job was cancelled before an execution was created.',\n status: 'cancelled',\n };\n }\n\n return {\n title: 'Execution details unavailable',\n description: 'This job finished, but no job execution record is available.',\n status: job.status,\n };\n}\n\nexport function skippedJobDescription(reason: Job['statusReason']): string {\n switch (reason) {\n case 'dependency_not_completed':\n return 'A required job did not complete, so this job was skipped.';\n case 'default_gate_rejected':\n return 'A required job did not succeed, so this job was skipped.';\n case 'condition_false':\n case 'condition_rejected':\n return 'The job condition did not match, so this job was skipped.';\n case 'condition_errored':\n return 'The job condition could not be evaluated, so this job was skipped.';\n case 'user_cancelled':\n case 'run_cancelled':\n case 'timed_out':\n case 'runner_lost':\n case 'step_failed':\n case 'unknown':\n case null:\n return 'This job did not start.';\n }\n}\n\nfunction preStepFailureDescription(reason: string | null): string {\n switch (reason) {\n case 'runner_lost':\n return 'The runner stopped responding before work began. Check runner availability before re-running the workflow.';\n case 'timed_out':\n return 'The job timed out before work began. Check runner capacity and timeout configuration before re-running the workflow.';\n case 'user_cancelled':\n case 'run_cancelled':\n return 'The execution ended while the run was being cancelled.';\n case 'step_failed':\n return 'The execution failed before step details were recorded. Review run annotations before re-running the workflow.';\n case 'condition_errored':\n return 'The job condition could not be evaluated. Review run annotations before re-running the workflow.';\n case 'dependency_not_completed':\n case 'default_gate_rejected':\n return 'A required job did not complete successfully. Resolve that failure before re-running the workflow.';\n case 'condition_false':\n case 'condition_rejected':\n return 'The job condition did not match, so no work started.';\n case 'unknown':\n case null:\n return 'Shipfox did not record a failure reason. Re-run only after checking the runner and workflow configuration.';\n default:\n return `The execution ended because ${humanizeFailureReason(reason)}. Resolve that condition before re-running the workflow.`;\n }\n}\n\nfunction humanizeFailureReason(reason: string): string {\n return reason.replaceAll('_', ' ').replaceAll('-', ' ');\n}\n\nexport function CarriedOverStepPanel() {\n return (\n <EmptyState\n className=\"min-h-120 rounded-8 border border-border-neutral-base bg-background-components-base px-16 py-20\"\n icon=\"componentLine\"\n title=\"Carried over from a previous attempt\"\n description=\"Not executed in this run.\"\n variant=\"compact\"\n />\n );\n}\n\nexport function toSelectedAttemptError(\n step: Step,\n error: Record<string, unknown> | null,\n): StepError | null {\n if (error === null) return null;\n\n const rawReason = error.reason;\n const parsedReason =\n typeof rawReason === 'string' &&\n STEP_ERROR_REASONS.has(rawReason as StepError['reason'] & string)\n ? (rawReason as NonNullable<StepError['reason']>)\n : undefined;\n const rawAgentConfigIssue = error.agentConfigIssue ?? error.agent_config_issue;\n const agentConfigIssue =\n typeof rawAgentConfigIssue === 'string' &&\n AGENT_CONFIG_ISSUES.has(rawAgentConfigIssue as NonNullable<StepError['agentConfigIssue']>)\n ? (rawAgentConfigIssue as NonNullable<StepError['agentConfigIssue']>)\n : undefined;\n const exitCode = error.exitCode ?? error.exit_code;\n const resolvedReason = parsedReason\n ? parsedReason\n : agentConfigIssue\n ? 'agent_config_invalid'\n : undefined;\n\n if (resolvedReason === undefined) return null;\n\n return {\n message: typeof error.message === 'string' ? error.message : '',\n exitCode: exitCode === null || typeof exitCode === 'number' ? exitCode : null,\n signal: typeof error.signal === 'string' ? error.signal : undefined,\n reason: resolvedReason,\n agentConfigIssue,\n category: step.type === 'setup' || step.type === 'checkout' ? 'setup' : 'user',\n };\n}\n\nexport function isAgentConfigFailure(step: Step, error: StepError | null): boolean {\n return step.type === 'agent' && error?.reason === 'agent_config_invalid';\n}\n\nexport function jobSucceededSummary(job: Job, execution: JobExecution): string | undefined {\n if (job.carriedOver || execution.status !== 'succeeded') return undefined;\n const stepCount = execution.steps.filter((step) => step.status === 'succeeded').length;\n if (stepCount === 0) return undefined;\n const duration = execution.displayDuration;\n return `${stepCount} step${stepCount === 1 ? '' : 's'} succeeded${duration ? ` in ${formatJobExecutionTime(duration)}` : ''}`;\n}\n"],"names":["EmptyState","AGENT_CONFIG_ISSUES","deriveJobDisplayStatus","deriveJobExecutionDisplayStatus","STEP_ERROR_REASONS","formatJobExecutionTime","emptyStateForJob","job","jobExecution","carriedOver","title","description","status","displayStatus","mode","preStepFailureDescription","statusReason","sequence","undefined","emptyStateForMissingExecution","listenerStatus","skippedJobDescription","reason","humanizeFailureReason","replaceAll","CarriedOverStepPanel","className","icon","variant","toSelectedAttemptError","step","error","rawReason","parsedReason","has","rawAgentConfigIssue","agentConfigIssue","agent_config_issue","exitCode","exit_code","resolvedReason","message","signal","category","type","isAgentConfigFailure","jobSucceededSummary","execution","stepCount","steps","filter","length","duration","displayDuration"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"job-execution-switcher.d.ts","sourceRoot":"","sources":["../../../src/components/job-detail/job-execution-switcher.tsx"],"names":[],"mappings":"AAaA,OAAO,EAAkC,KAAK,GAAG,EAAoB,MAAM,uBAAuB,CAAC;AAGnG,MAAM,WAAW,yBAAyB;IACxC,GAAG,EAAE,GAAG,CAAC;IACT,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,4BAA4B,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/D,OAAO,CAAC,EAAE,SAAS,GAAG,OAAO,GAAG,SAAS,CAAC;IAC1C,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC;AAED,wBAAgB,oBAAoB,CAAC,EACnC,GAAG,EACH,oBAAoB,EACpB,4BAA4B,EAC5B,OAAmB,EACnB,SAAS,GACV,EAAE,yBAAyB,sCA+F3B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/job-detail/job-execution-switcher.tsx"],"sourcesContent":["import {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuTrigger,\n} from '@shipfox/react-ui/dropdown-menu';\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {Text} from '@shipfox/react-ui/typography';\nimport {cn} from '@shipfox/react-ui/utils';\nimport {useMemo} from 'react';\nimport {getWorkflowStatusVisual} from '#components/workflow-status/status-visuals.js';\nimport {WorkflowStatusIcon} from '#components/workflow-status/workflow-status-icon.js';\nimport {deriveJobExecutionDisplayStatus, type Job, type JobExecution} from '#core/workflow-run.js';\nimport {formatJobExecutionTime, JobExecutionTimeText} from './job-execution-time-text.js';\n\nexport interface JobExecutionSwitcherProps {\n job: Job;\n selectedJobExecution: string | null;\n onSelectedJobExecutionChange: (jobExecutionId: string) => void;\n variant?: 'compact' | 'title' | undefined;\n className?: string | undefined;\n}\n\nexport function JobExecutionSwitcher({\n job,\n selectedJobExecution,\n onSelectedJobExecutionChange,\n variant = 'compact',\n className,\n}: JobExecutionSwitcherProps) {\n const executions = useMemo(\n () => [...job.jobExecutions].sort((left, right) => right.sequence - left.sequence),\n [job.jobExecutions],\n );\n const selected =\n executions.find((jobExecution) => jobExecution.id === selectedJobExecution) ??\n executions[0] ??\n null;\n\n if (selected === null) return null;\n\n if (!job.executionCountVisible) {\n return (\n <div\n className={cn(\n 'flex min-w-0 items-center gap-6 text-sm leading-20 text-foreground-neutral-subtle',\n className,\n )}\n >\n <ExecutionSummary execution={selected} />\n </div>\n );\n }\n\n return (\n <DropdownMenu>\n <DropdownMenuTrigger\n className={cn(\n 'inline-flex min-w-0 max-w-full items-center rounded-6 text-left transition-colors focus-visible:shadow-border-interactive-with-active focus-visible:outline-none',\n variant === 'title'\n ? '-mx-6 -my-4 gap-8 px-6 py-4 hover:bg-background-components-hover'\n : 'min-h-28 gap-6 px-8 py-4 text-sm leading-20 text-foreground-neutral-subtle hover:bg-background-components-hover',\n className,\n )}\n aria-label={`Switch job execution, currently execution ${selected.sequence}: ${selected.displayName}`}\n >\n {variant === 'title' ? (\n <TitleExecutionSummary execution={selected} />\n ) : (\n <ExecutionSummary execution={selected} />\n )}\n <Icon name=\"arrowDownSLine\" className=\"size-14 shrink-0 text-foreground-neutral-muted\" />\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" size=\"lg\" className=\"max-h-[320px] overflow-y-auto\">\n <DropdownMenuLabel>\n {executions.length} execution{executions.length === 1 ? '' : 's'}\n </DropdownMenuLabel>\n {executions.map((jobExecution) => {\n const isSelected = jobExecution.id === selected.id;\n\n return (\n <DropdownMenuItem\n key={jobExecution.id}\n asChild\n onSelect={() => onSelectedJobExecutionChange(jobExecution.id)}\n >\n <button\n type=\"button\"\n aria-current={isSelected ? 'true' : undefined}\n aria-label={executionAccessibleLabel(jobExecution)}\n className=\"w-full text-left\"\n >\n <WorkflowStatusIcon\n status={deriveJobExecutionDisplayStatus(jobExecution)}\n size={14}\n tooltip={false}\n />\n <span className=\"font-code text-xs leading-20 text-foreground-neutral-base tabular-nums\">\n #{jobExecution.sequence}\n </span>\n <span className=\"min-w-0 truncate text-xs leading-20 text-foreground-neutral-base\">\n {jobExecution.displayName}\n </span>\n {!isSelected && jobExecution.statusReason ? (\n <span className=\"min-w-0 flex-1 truncate text-xs leading-20 text-foreground-neutral-muted\">\n {jobExecution.statusReason}\n </span>\n ) : (\n <span className=\"min-w-0 flex-1\" />\n )}\n {isSelected ? (\n <Icon name=\"check\" className=\"size-14 shrink-0 text-foreground-neutral-base\" />\n ) : null}\n <JobExecutionDuration\n execution={jobExecution}\n className=\"ml-auto shrink-0 font-code text-xs leading-20 text-foreground-neutral-muted tabular-nums\"\n />\n </button>\n </DropdownMenuItem>\n );\n })}\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n\nfunction executionAccessibleLabel(execution: JobExecution): string {\n const status = getWorkflowStatusVisual(deriveJobExecutionDisplayStatus(execution));\n return [\n `Execution #${execution.sequence}: ${execution.displayName}`,\n status.label,\n execution.statusReason ?? undefined,\n execution.displayDuration\n ? `duration ${formatJobExecutionTime(execution.displayDuration)}`\n : undefined,\n ]\n .filter((part): part is string => Boolean(part))\n .join(', ');\n}\n\nfunction TitleExecutionSummary({execution}: {execution: JobExecution}) {\n return (\n <span className=\"flex min-w-0 items-center gap-8\">\n <Text as=\"span\" size=\"sm\" bold className=\"shrink-0 text-foreground-neutral-base\">\n #{execution.sequence}\n </Text>\n <Text as=\"span\" size=\"sm\" bold className=\"min-w-0 truncate text-foreground-neutral-base\">\n {execution.displayName}\n </Text>\n </span>\n );\n}\n\nfunction ExecutionSummary({execution}: {execution: JobExecution}) {\n const displayStatus = deriveJobExecutionDisplayStatus(execution);\n\n return (\n <span className=\"flex min-w-0 items-center gap-6\">\n <WorkflowStatusIcon status={displayStatus} size={14} tooltip={false} />\n <span className=\"shrink-0 font-code text-xs leading-20 text-foreground-neutral-base tabular-nums\">\n Execution #{execution.sequence}\n </span>\n {execution.statusReason ? (\n <Text as=\"span\" size=\"xs\" className=\"min-w-0 truncate text-foreground-neutral-muted\">\n · {execution.statusReason}\n </Text>\n ) : null}\n <JobExecutionDuration\n execution={execution}\n className=\"shrink-0 font-code text-xs leading-20 text-foreground-neutral-muted tabular-nums\"\n />\n </span>\n );\n}\n\nfunction JobExecutionDuration({\n execution,\n className,\n}: {\n execution: JobExecution;\n className?: string | undefined;\n}) {\n const duration = execution.displayDuration;\n if (!duration) return null;\n\n return (\n <span className={className}>\n <JobExecutionTimeText time={duration} />\n </span>\n );\n}\n"],"names":["DropdownMenu","DropdownMenuContent","DropdownMenuItem","DropdownMenuLabel","DropdownMenuTrigger","Icon","Text","cn","useMemo","getWorkflowStatusVisual","WorkflowStatusIcon","deriveJobExecutionDisplayStatus","formatJobExecutionTime","JobExecutionTimeText","JobExecutionSwitcher","job","selectedJobExecution","onSelectedJobExecutionChange","variant","className","executions","jobExecutions","sort","left","right","sequence","selected","find","jobExecution","id","executionCountVisible","div","ExecutionSummary","execution","aria-label","displayName","TitleExecutionSummary","name","align","size","length","map","isSelected","asChild","onSelect","button","type","aria-current","undefined","executionAccessibleLabel","status","tooltip","span","statusReason","JobExecutionDuration","label","displayDuration","filter","part","Boolean","join","as","bold","displayStatus","duration","time"],"mappings":";AAAA,SACEA,YAAY,EACZC,mBAAmB,EACnBC,gBAAgB,EAChBC,iBAAiB,EACjBC,mBAAmB,QACd,kCAAkC;AACzC,SAAQC,IAAI,QAAO,yBAAyB;AAC5C,SAAQC,IAAI,QAAO,+BAA+B;AAClD,SAAQC,EAAE,QAAO,0BAA0B;AAC3C,SAAQC,OAAO,QAAO,QAAQ;AAC9B,SAAQC,uBAAuB,QAAO,gDAAgD;AACtF,SAAQC,kBAAkB,QAAO,sDAAsD;AACvF,SAAQC,+BAA+B,QAAoC,wBAAwB;AACnG,SAAQC,sBAAsB,EAAEC,oBAAoB,QAAO,+BAA+B;AAU1F,OAAO,SAASC,qBAAqB,EACnCC,GAAG,EACHC,oBAAoB,EACpBC,4BAA4B,EAC5BC,UAAU,SAAS,EACnBC,SAAS,EACiB;IAC1B,MAAMC,aAAaZ,QACjB,IAAM;eAAIO,IAAIM,aAAa;SAAC,CAACC,IAAI,CAAC,CAACC,MAAMC,QAAUA,MAAMC,QAAQ,GAAGF,KAAKE,QAAQ,GACjF;QAACV,IAAIM,aAAa;KAAC;IAErB,MAAMK,WACJN,WAAWO,IAAI,CAAC,CAACC,eAAiBA,aAAaC,EAAE,KAAKb,yBACtDI,UAAU,CAAC,EAAE,IACb;IAEF,IAAIM,aAAa,MAAM,OAAO;IAE9B,IAAI,CAACX,IAAIe,qBAAqB,EAAE;QAC9B,qBACE,KAACC;YACCZ,WAAWZ,GACT,qFACAY;sBAGF,cAAA,KAACa;gBAAiBC,WAAWP;;;IAGnC;IAEA,qBACE,MAAC1B;;0BACC,MAACI;gBACCe,WAAWZ,GACT,oKACAW,YAAY,UACR,qEACA,mHACJC;gBAEFe,cAAY,CAAC,0CAA0C,EAAER,SAASD,QAAQ,CAAC,EAAE,EAAEC,SAASS,WAAW,EAAE;;oBAEpGjB,YAAY,wBACX,KAACkB;wBAAsBH,WAAWP;uCAElC,KAACM;wBAAiBC,WAAWP;;kCAE/B,KAACrB;wBAAKgC,MAAK;wBAAiBlB,WAAU;;;;0BAExC,MAAClB;gBAAoBqC,OAAM;gBAAQC,MAAK;gBAAKpB,WAAU;;kCACrD,MAAChB;;4BACEiB,WAAWoB,MAAM;4BAAC;4BAAWpB,WAAWoB,MAAM,KAAK,IAAI,KAAK;;;oBAE9DpB,WAAWqB,GAAG,CAAC,CAACb;wBACf,MAAMc,aAAad,aAAaC,EAAE,KAAKH,SAASG,EAAE;wBAElD,qBACE,KAAC3B;4BAECyC,OAAO;4BACPC,UAAU,IAAM3B,6BAA6BW,aAAaC,EAAE;sCAE5D,cAAA,MAACgB;gCACCC,MAAK;gCACLC,gBAAcL,aAAa,SAASM;gCACpCd,cAAYe,yBAAyBrB;gCACrCT,WAAU;;kDAEV,KAACT;wCACCwC,QAAQvC,gCAAgCiB;wCACxCW,MAAM;wCACNY,SAAS;;kDAEX,MAACC;wCAAKjC,WAAU;;4CAAyE;4CACrFS,aAAaH,QAAQ;;;kDAEzB,KAAC2B;wCAAKjC,WAAU;kDACbS,aAAaO,WAAW;;oCAE1B,CAACO,cAAcd,aAAayB,YAAY,iBACvC,KAACD;wCAAKjC,WAAU;kDACbS,aAAayB,YAAY;uDAG5B,KAACD;wCAAKjC,WAAU;;oCAEjBuB,2BACC,KAACrC;wCAAKgC,MAAK;wCAAQlB,WAAU;yCAC3B;kDACJ,KAACmC;wCACCrB,WAAWL;wCACXT,WAAU;;;;2BAjCTS,aAAaC,EAAE;oBAsC1B;;;;;AAIR;AAEA,SAASoB,yBAAyBhB,SAAuB;IACvD,MAAMiB,SAASzC,wBAAwBE,gCAAgCsB;IACvE,OAAO;QACL,CAAC,WAAW,EAAEA,UAAUR,QAAQ,CAAC,EAAE,EAAEQ,UAAUE,WAAW,EAAE;QAC5De,OAAOK,KAAK;QACZtB,UAAUoB,YAAY,IAAIL;QAC1Bf,UAAUuB,eAAe,GACrB,CAAC,SAAS,EAAE5C,uBAAuBqB,UAAUuB,eAAe,GAAG,GAC/DR;KACL,CACES,MAAM,CAAC,CAACC,OAAyBC,QAAQD,OACzCE,IAAI,CAAC;AACV;AAEA,SAASxB,sBAAsB,EAACH,SAAS,EAA4B;IACnE,qBACE,MAACmB;QAAKjC,WAAU;;0BACd,MAACb;gBAAKuD,IAAG;gBAAOtB,MAAK;gBAAKuB,IAAI;gBAAC3C,WAAU;;oBAAwC;oBAC7Ec,UAAUR,QAAQ;;;0BAEtB,KAACnB;gBAAKuD,IAAG;gBAAOtB,MAAK;gBAAKuB,IAAI;gBAAC3C,WAAU;0BACtCc,UAAUE,WAAW;;;;AAI9B;AAEA,SAASH,iBAAiB,EAACC,SAAS,EAA4B;IAC9D,MAAM8B,gBAAgBpD,gCAAgCsB;IAEtD,qBACE,MAACmB;QAAKjC,WAAU;;0BACd,KAACT;gBAAmBwC,QAAQa;gBAAexB,MAAM;gBAAIY,SAAS;;0BAC9D,MAACC;gBAAKjC,WAAU;;oBAAkF;oBACpFc,UAAUR,QAAQ;;;YAE/BQ,UAAUoB,YAAY,iBACrB,MAAC/C;gBAAKuD,IAAG;gBAAOtB,MAAK;gBAAKpB,WAAU;;oBAAiD;oBAChFc,UAAUoB,YAAY;;iBAEzB;0BACJ,KAACC;gBACCrB,WAAWA;gBACXd,WAAU;;;;AAIlB;AAEA,SAASmC,qBAAqB,EAC5BrB,SAAS,EACTd,SAAS,EAIV;IACC,MAAM6C,WAAW/B,UAAUuB,eAAe;IAC1C,IAAI,CAACQ,UAAU,OAAO;IAEtB,qBACE,KAACZ;QAAKjC,WAAWA;kBACf,cAAA,KAACN;YAAqBoD,MAAMD;;;AAGlC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"job-execution-time-text.d.ts","sourceRoot":"","sources":["../../../src/components/job-detail/job-execution-time-text.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,uBAAuB,CAAC;AAE5D,wBAAgB,oBAAoB,CAAC,EAAC,IAAI,EAAC,EAAE;IAAC,IAAI,EAAE,gBAAgB,CAAA;CAAC,UAIpE;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,gBAAgB,GAAG,MAAM,CAMrE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/job-detail/job-execution-time-text.tsx"],"sourcesContent":["import {useTimeTick} from '@shipfox/react-ui/time-ticker';\nimport {formatDuration} from '@shipfox/react-ui/utils';\nimport type {JobExecutionTime} from '#core/workflow-run.js';\n\nexport function JobExecutionTimeText({time}: {time: JobExecutionTime}) {\n useTimeTick();\n\n return formatJobExecutionTime(time);\n}\n\nexport function formatJobExecutionTime(time: JobExecutionTime): string {\n if (time.state === 'live') {\n return formatDuration(Date.now() - Date.parse(time.fromIso));\n }\n\n return formatElapsedDuration(time.elapsed);\n}\n\nfunction formatElapsedDuration(duration: {\n days?: number | undefined;\n hours?: number | undefined;\n minutes?: number | undefined;\n seconds?: number | undefined;\n}): string {\n const days = duration.days ?? 0;\n const hours = duration.hours ?? 0;\n const minutes = duration.minutes ?? 0;\n const seconds = duration.seconds ?? 0;\n\n if (days > 0) return hours > 0 ? `${days}d ${hours}h` : `${days}d`;\n if (hours > 0) return minutes > 0 ? `${hours}h ${minutes}m` : `${hours}h`;\n if (minutes > 0) return seconds > 0 ? `${minutes}m ${seconds}s` : `${minutes}m`;\n return `${seconds}s`;\n}\n"],"names":["useTimeTick","formatDuration","JobExecutionTimeText","time","formatJobExecutionTime","state","Date","now","parse","fromIso","formatElapsedDuration","elapsed","duration","days","hours","minutes","seconds"],"mappings":"AAAA,SAAQA,WAAW,QAAO,gCAAgC;AAC1D,SAAQC,cAAc,QAAO,0BAA0B;AAGvD,OAAO,SAASC,qBAAqB,EAACC,IAAI,EAA2B;IACnEH;IAEA,OAAOI,uBAAuBD;AAChC;AAEA,OAAO,SAASC,uBAAuBD,IAAsB;IAC3D,IAAIA,KAAKE,KAAK,KAAK,QAAQ;QACzB,OAAOJ,eAAeK,KAAKC,GAAG,KAAKD,KAAKE,KAAK,CAACL,KAAKM,OAAO;IAC5D;IAEA,OAAOC,sBAAsBP,KAAKQ,OAAO;AAC3C;AAEA,SAASD,sBAAsBE,QAK9B;IACC,MAAMC,OAAOD,SAASC,IAAI,IAAI;IAC9B,MAAMC,QAAQF,SAASE,KAAK,IAAI;IAChC,MAAMC,UAAUH,SAASG,OAAO,IAAI;IACpC,MAAMC,UAAUJ,SAASI,OAAO,IAAI;IAEpC,IAAIH,OAAO,GAAG,OAAOC,QAAQ,IAAI,GAAGD,KAAK,EAAE,EAAEC,MAAM,CAAC,CAAC,GAAG,GAAGD,KAAK,CAAC,CAAC;IAClE,IAAIC,QAAQ,GAAG,OAAOC,UAAU,IAAI,GAAGD,MAAM,EAAE,EAAEC,QAAQ,CAAC,CAAC,GAAG,GAAGD,MAAM,CAAC,CAAC;IACzE,IAAIC,UAAU,GAAG,OAAOC,UAAU,IAAI,GAAGD,QAAQ,EAAE,EAAEC,QAAQ,CAAC,CAAC,GAAG,GAAGD,QAAQ,CAAC,CAAC;IAC/E,OAAO,GAAGC,QAAQ,CAAC,CAAC;AACtB"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type Job, type JobExecution, type Step, type StepAttempt } from '#core/workflow-run.js';
|
|
2
|
+
import type { WorkflowJobSearch } from '#routes/inputs.js';
|
|
3
|
+
export interface ResolvedWorkflowJobSelection {
|
|
4
|
+
jobExecution: JobExecution | undefined;
|
|
5
|
+
step: Step | undefined;
|
|
6
|
+
attempt: StepAttempt | undefined;
|
|
7
|
+
selectedAttemptId: string | null;
|
|
8
|
+
}
|
|
9
|
+
export interface WorkflowJobLandingSelection {
|
|
10
|
+
stepId: string;
|
|
11
|
+
attemptId: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Resolves job-scoped URL state. A valid step is authoritative over an execution id because a
|
|
15
|
+
* step carries its owning execution. A step from another job is ignored and falls back to the
|
|
16
|
+
* job's default execution rather than following a mangled URL into a different job.
|
|
17
|
+
*/
|
|
18
|
+
export declare function resolveWorkflowJobSelection({ job, selection, }: {
|
|
19
|
+
job: Job;
|
|
20
|
+
selection: WorkflowJobSearch;
|
|
21
|
+
}): ResolvedWorkflowJobSelection;
|
|
22
|
+
/** Picks the running step, then the first failed step, for the first useful page state. */
|
|
23
|
+
export declare function workflowJobLandingSelection(jobExecution: JobExecution | undefined): WorkflowJobLandingSelection | undefined;
|
|
24
|
+
//# sourceMappingURL=job-selection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"job-selection.d.ts","sourceRoot":"","sources":["../../../src/components/job-detail/job-selection.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,GAAG,EACR,KAAK,YAAY,EAGjB,KAAK,IAAI,EACT,KAAK,WAAW,EACjB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,mBAAmB,CAAC;AAEzD,MAAM,WAAW,4BAA4B;IAC3C,YAAY,EAAE,YAAY,GAAG,SAAS,CAAC;IACvC,IAAI,EAAE,IAAI,GAAG,SAAS,CAAC;IACvB,OAAO,EAAE,WAAW,GAAG,SAAS,CAAC;IACjC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAED,MAAM,WAAW,2BAA2B;IAC1C,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CAAC,EAC1C,GAAG,EACH,SAAS,GACV,EAAE;IACD,GAAG,EAAE,GAAG,CAAC;IACT,SAAS,EAAE,iBAAiB,CAAC;CAC9B,GAAG,4BAA4B,CAuB/B;AAED,2FAA2F;AAC3F,wBAAgB,2BAA2B,CACzC,YAAY,EAAE,YAAY,GAAG,SAAS,GACrC,2BAA2B,GAAG,SAAS,CAazC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { compareStepAttempts, resolveJobExecution, resolveStepAttempt } from '#core/workflow-run.js';
|
|
2
|
+
/**
|
|
3
|
+
* Resolves job-scoped URL state. A valid step is authoritative over an execution id because a
|
|
4
|
+
* step carries its owning execution. A step from another job is ignored and falls back to the
|
|
5
|
+
* job's default execution rather than following a mangled URL into a different job.
|
|
6
|
+
*/ export function resolveWorkflowJobSelection({ job, selection }) {
|
|
7
|
+
const step = selection.stepId ? findStep(job, selection.stepId) : undefined;
|
|
8
|
+
if (step) {
|
|
9
|
+
const attempt = resolveStepAttempt(step, selection.stepAttemptId);
|
|
10
|
+
const jobExecution = job.jobExecutions.find((candidate)=>candidate.id === step.jobExecutionId);
|
|
11
|
+
return {
|
|
12
|
+
jobExecution: jobExecution ?? resolveJobExecution(job, undefined),
|
|
13
|
+
step,
|
|
14
|
+
attempt,
|
|
15
|
+
selectedAttemptId: attempt?.id ?? null
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
jobExecution: selection.stepId ? resolveJobExecution(job, undefined) : resolveJobExecution(job, selection.jobExecutionId),
|
|
20
|
+
step: undefined,
|
|
21
|
+
attempt: undefined,
|
|
22
|
+
selectedAttemptId: null
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/** Picks the running step, then the first failed step, for the first useful page state. */ export function workflowJobLandingSelection(jobExecution) {
|
|
26
|
+
if (!jobExecution) return undefined;
|
|
27
|
+
const entries = orderedStepAttempts(jobExecution);
|
|
28
|
+
const running = [
|
|
29
|
+
...entries
|
|
30
|
+
].reverse().find((entry)=>entry.attempt.status === 'running');
|
|
31
|
+
const failedStep = [
|
|
32
|
+
...jobExecution.steps
|
|
33
|
+
].sort(compareSteps).find((step)=>step.status === 'failed');
|
|
34
|
+
const failedAttempt = failedStep ? resolveStepAttempt(failedStep, undefined) : undefined;
|
|
35
|
+
const failed = failedStep && failedAttempt ? {
|
|
36
|
+
step: failedStep,
|
|
37
|
+
attempt: failedAttempt
|
|
38
|
+
} : undefined;
|
|
39
|
+
const selected = running ?? failed;
|
|
40
|
+
return selected ? {
|
|
41
|
+
stepId: selected.step.id,
|
|
42
|
+
attemptId: selected.attempt.id
|
|
43
|
+
} : undefined;
|
|
44
|
+
}
|
|
45
|
+
function findStep(job, stepId) {
|
|
46
|
+
for (const execution of job.jobExecutions){
|
|
47
|
+
const step = execution.steps.find((candidate)=>candidate.id === stepId);
|
|
48
|
+
if (step) return step;
|
|
49
|
+
}
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
function orderedStepAttempts(jobExecution) {
|
|
53
|
+
return [
|
|
54
|
+
...jobExecution.steps
|
|
55
|
+
].sort((left, right)=>left.position - right.position || left.id.localeCompare(right.id)).flatMap((step)=>[
|
|
56
|
+
...step.attempts
|
|
57
|
+
].sort(compareStepAttempts).map((attempt)=>({
|
|
58
|
+
step,
|
|
59
|
+
attempt
|
|
60
|
+
})));
|
|
61
|
+
}
|
|
62
|
+
function compareSteps(left, right) {
|
|
63
|
+
return left.position - right.position || left.id.localeCompare(right.id);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
//# sourceMappingURL=job-selection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/job-detail/job-selection.ts"],"sourcesContent":["import {\n compareStepAttempts,\n type Job,\n type JobExecution,\n resolveJobExecution,\n resolveStepAttempt,\n type Step,\n type StepAttempt,\n} from '#core/workflow-run.js';\nimport type {WorkflowJobSearch} from '#routes/inputs.js';\n\nexport interface ResolvedWorkflowJobSelection {\n jobExecution: JobExecution | undefined;\n step: Step | undefined;\n attempt: StepAttempt | undefined;\n selectedAttemptId: string | null;\n}\n\nexport interface WorkflowJobLandingSelection {\n stepId: string;\n attemptId: string;\n}\n\n/**\n * Resolves job-scoped URL state. A valid step is authoritative over an execution id because a\n * step carries its owning execution. A step from another job is ignored and falls back to the\n * job's default execution rather than following a mangled URL into a different job.\n */\nexport function resolveWorkflowJobSelection({\n job,\n selection,\n}: {\n job: Job;\n selection: WorkflowJobSearch;\n}): ResolvedWorkflowJobSelection {\n const step = selection.stepId ? findStep(job, selection.stepId) : undefined;\n if (step) {\n const attempt = resolveStepAttempt(step, selection.stepAttemptId);\n const jobExecution = job.jobExecutions.find(\n (candidate) => candidate.id === step.jobExecutionId,\n );\n return {\n jobExecution: jobExecution ?? resolveJobExecution(job, undefined),\n step,\n attempt,\n selectedAttemptId: attempt?.id ?? null,\n };\n }\n\n return {\n jobExecution: selection.stepId\n ? resolveJobExecution(job, undefined)\n : resolveJobExecution(job, selection.jobExecutionId),\n step: undefined,\n attempt: undefined,\n selectedAttemptId: null,\n };\n}\n\n/** Picks the running step, then the first failed step, for the first useful page state. */\nexport function workflowJobLandingSelection(\n jobExecution: JobExecution | undefined,\n): WorkflowJobLandingSelection | undefined {\n if (!jobExecution) return undefined;\n\n const entries = orderedStepAttempts(jobExecution);\n const running = [...entries].reverse().find((entry) => entry.attempt.status === 'running');\n const failedStep = [...jobExecution.steps]\n .sort(compareSteps)\n .find((step) => step.status === 'failed');\n const failedAttempt = failedStep ? resolveStepAttempt(failedStep, undefined) : undefined;\n const failed =\n failedStep && failedAttempt ? {step: failedStep, attempt: failedAttempt} : undefined;\n const selected = running ?? failed;\n return selected ? {stepId: selected.step.id, attemptId: selected.attempt.id} : undefined;\n}\n\nfunction findStep(job: Job, stepId: string): Step | undefined {\n for (const execution of job.jobExecutions) {\n const step = execution.steps.find((candidate) => candidate.id === stepId);\n if (step) return step;\n }\n return undefined;\n}\n\nfunction orderedStepAttempts(jobExecution: JobExecution) {\n return [...jobExecution.steps]\n .sort((left, right) => left.position - right.position || left.id.localeCompare(right.id))\n .flatMap((step) =>\n [...step.attempts].sort(compareStepAttempts).map((attempt) => ({step, attempt})),\n );\n}\n\nfunction compareSteps(left: Step, right: Step): number {\n return left.position - right.position || left.id.localeCompare(right.id);\n}\n"],"names":["compareStepAttempts","resolveJobExecution","resolveStepAttempt","resolveWorkflowJobSelection","job","selection","step","stepId","findStep","undefined","attempt","stepAttemptId","jobExecution","jobExecutions","find","candidate","id","jobExecutionId","selectedAttemptId","workflowJobLandingSelection","entries","orderedStepAttempts","running","reverse","entry","status","failedStep","steps","sort","compareSteps","failedAttempt","failed","selected","attemptId","execution","left","right","position","localeCompare","flatMap","attempts","map"],"mappings":"AAAA,SACEA,mBAAmB,EAGnBC,mBAAmB,EACnBC,kBAAkB,QAGb,wBAAwB;AAe/B;;;;CAIC,GACD,OAAO,SAASC,4BAA4B,EAC1CC,GAAG,EACHC,SAAS,EAIV;IACC,MAAMC,OAAOD,UAAUE,MAAM,GAAGC,SAASJ,KAAKC,UAAUE,MAAM,IAAIE;IAClE,IAAIH,MAAM;QACR,MAAMI,UAAUR,mBAAmBI,MAAMD,UAAUM,aAAa;QAChE,MAAMC,eAAeR,IAAIS,aAAa,CAACC,IAAI,CACzC,CAACC,YAAcA,UAAUC,EAAE,KAAKV,KAAKW,cAAc;QAErD,OAAO;YACLL,cAAcA,gBAAgBX,oBAAoBG,KAAKK;YACvDH;YACAI;YACAQ,mBAAmBR,SAASM,MAAM;QACpC;IACF;IAEA,OAAO;QACLJ,cAAcP,UAAUE,MAAM,GAC1BN,oBAAoBG,KAAKK,aACzBR,oBAAoBG,KAAKC,UAAUY,cAAc;QACrDX,MAAMG;QACNC,SAASD;QACTS,mBAAmB;IACrB;AACF;AAEA,yFAAyF,GACzF,OAAO,SAASC,4BACdP,YAAsC;IAEtC,IAAI,CAACA,cAAc,OAAOH;IAE1B,MAAMW,UAAUC,oBAAoBT;IACpC,MAAMU,UAAU;WAAIF;KAAQ,CAACG,OAAO,GAAGT,IAAI,CAAC,CAACU,QAAUA,MAAMd,OAAO,CAACe,MAAM,KAAK;IAChF,MAAMC,aAAa;WAAId,aAAae,KAAK;KAAC,CACvCC,IAAI,CAACC,cACLf,IAAI,CAAC,CAACR,OAASA,KAAKmB,MAAM,KAAK;IAClC,MAAMK,gBAAgBJ,aAAaxB,mBAAmBwB,YAAYjB,aAAaA;IAC/E,MAAMsB,SACJL,cAAcI,gBAAgB;QAACxB,MAAMoB;QAAYhB,SAASoB;IAAa,IAAIrB;IAC7E,MAAMuB,WAAWV,WAAWS;IAC5B,OAAOC,WAAW;QAACzB,QAAQyB,SAAS1B,IAAI,CAACU,EAAE;QAAEiB,WAAWD,SAAStB,OAAO,CAACM,EAAE;IAAA,IAAIP;AACjF;AAEA,SAASD,SAASJ,GAAQ,EAAEG,MAAc;IACxC,KAAK,MAAM2B,aAAa9B,IAAIS,aAAa,CAAE;QACzC,MAAMP,OAAO4B,UAAUP,KAAK,CAACb,IAAI,CAAC,CAACC,YAAcA,UAAUC,EAAE,KAAKT;QAClE,IAAID,MAAM,OAAOA;IACnB;IACA,OAAOG;AACT;AAEA,SAASY,oBAAoBT,YAA0B;IACrD,OAAO;WAAIA,aAAae,KAAK;KAAC,CAC3BC,IAAI,CAAC,CAACO,MAAMC,QAAUD,KAAKE,QAAQ,GAAGD,MAAMC,QAAQ,IAAIF,KAAKnB,EAAE,CAACsB,aAAa,CAACF,MAAMpB,EAAE,GACtFuB,OAAO,CAAC,CAACjC,OACR;eAAIA,KAAKkC,QAAQ;SAAC,CAACZ,IAAI,CAAC5B,qBAAqByC,GAAG,CAAC,CAAC/B,UAAa,CAAA;gBAACJ;gBAAMI;YAAO,CAAA;AAEnF;AAEA,SAASmB,aAAaM,IAAU,EAAEC,KAAW;IAC3C,OAAOD,KAAKE,QAAQ,GAAGD,MAAMC,QAAQ,IAAIF,KAAKnB,EAAE,CAACsB,aAAa,CAACF,MAAMpB,EAAE;AACzE"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type RefObject } from 'react';
|
|
2
|
+
export interface StepAttemptLogPanelProps {
|
|
3
|
+
stepId: string;
|
|
4
|
+
attempt: number;
|
|
5
|
+
attemptStatus: string;
|
|
6
|
+
/** The start time used by the page's quiet waiting-for-output state. */
|
|
7
|
+
attemptStartedAt?: string | undefined;
|
|
8
|
+
/** The page scroll column used by the uncapped job-detail log surface. */
|
|
9
|
+
pageScrollRef: RefObject<HTMLElement | null>;
|
|
10
|
+
surfaceClassName?: string | undefined;
|
|
11
|
+
initialErrorRetryCount?: number | undefined;
|
|
12
|
+
initialErrorRetryDelayMs?: number | undefined;
|
|
13
|
+
}
|
|
14
|
+
export declare function StepAttemptLogPanel({ stepId, attempt, attemptStatus, attemptStartedAt, pageScrollRef, surfaceClassName, initialErrorRetryCount, initialErrorRetryDelayMs, }: StepAttemptLogPanelProps): import("react").JSX.Element;
|
|
15
|
+
//# sourceMappingURL=step-attempt-log-panel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"step-attempt-log-panel.d.ts","sourceRoot":"","sources":["../../../src/components/job-detail/step-attempt-log-panel.tsx"],"names":[],"mappings":"AASA,OAAO,EAAC,KAAK,SAAS,EAAoB,MAAM,OAAO,CAAC;AAQxD,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,wEAAwE;IACxE,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,0EAA0E;IAC1E,aAAa,EAAE,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAC7C,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,sBAAsB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5C,wBAAwB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC/C;AAED,wBAAgB,mBAAmB,CAAC,EAClC,MAAM,EACN,OAAO,EACP,aAAa,EACb,gBAAgB,EAChB,aAAa,EACb,gBAA2C,EAC3C,sBAAsD,EACtD,wBAA2D,GAC5D,EAAE,wBAAwB,+BAwF1B"}
|
|
@@ -4,12 +4,12 @@ import { Button } from '@shipfox/react-ui/button';
|
|
|
4
4
|
import { Callout } from '@shipfox/react-ui/callout';
|
|
5
5
|
import { Text } from '@shipfox/react-ui/typography';
|
|
6
6
|
import { useEffect, useRef } from 'react';
|
|
7
|
+
import { JobExecutionTimeText } from './job-execution-time-text.js';
|
|
7
8
|
const TAIL_FOLLOW_THRESHOLD_PX = 24;
|
|
8
9
|
const INITIAL_LOG_ERROR_RETRY_COUNT = 5;
|
|
9
10
|
const INITIAL_LOG_ERROR_RETRY_DELAY_MS = 1_500;
|
|
10
|
-
const
|
|
11
|
-
export function StepAttemptLogPanel({ stepId, attempt, attemptStatus, initialErrorRetryCount = INITIAL_LOG_ERROR_RETRY_COUNT, initialErrorRetryDelayMs = INITIAL_LOG_ERROR_RETRY_DELAY_MS }) {
|
|
12
|
-
const panelRef = useRef(null);
|
|
11
|
+
const defaultLogSurfaceClasses = 'rounded-8';
|
|
12
|
+
export function StepAttemptLogPanel({ stepId, attempt, attemptStatus, attemptStartedAt, pageScrollRef, surfaceClassName = defaultLogSurfaceClasses, initialErrorRetryCount = INITIAL_LOG_ERROR_RETRY_COUNT, initialErrorRetryDelayMs = INITIAL_LOG_ERROR_RETRY_DELAY_MS }) {
|
|
13
13
|
const shouldFollowTailRef = useRef(true);
|
|
14
14
|
const missingStreamRetryCount = attemptStatus === 'running' ? undefined : initialErrorRetryCount;
|
|
15
15
|
const retryMissingStream = attemptStatus === 'running' || isTerminalAttemptStatus(attemptStatus);
|
|
@@ -26,12 +26,26 @@ export function StepAttemptLogPanel({ stepId, attempt, attemptStatus, initialErr
|
|
|
26
26
|
const missingActiveStream = retryMissingStream && query.data === undefined && isMissingStepLogStreamError(query.error);
|
|
27
27
|
const initialError = query.isError && query.data === undefined && !missingActiveStream;
|
|
28
28
|
const staleError = query.isError && query.data !== undefined;
|
|
29
|
+
useEffect(()=>{
|
|
30
|
+
const scrollElement = pageScrollRef.current;
|
|
31
|
+
if (!scrollElement) return undefined;
|
|
32
|
+
const onScroll = ()=>{
|
|
33
|
+
shouldFollowTailRef.current = isNearPageBottom(scrollElement);
|
|
34
|
+
};
|
|
35
|
+
scrollElement.addEventListener('scroll', onScroll, {
|
|
36
|
+
passive: true
|
|
37
|
+
});
|
|
38
|
+
onScroll();
|
|
39
|
+
return ()=>scrollElement.removeEventListener('scroll', onScroll);
|
|
40
|
+
}, [
|
|
41
|
+
pageScrollRef
|
|
42
|
+
]);
|
|
29
43
|
useEffect(()=>{
|
|
30
44
|
if (recordCount === 0) return undefined;
|
|
31
45
|
if (anchorToFailure) return undefined;
|
|
32
46
|
if (!shouldFollowTailRef.current) return undefined;
|
|
33
47
|
const frame = scheduleAnimationFrame(()=>{
|
|
34
|
-
const scrollElement =
|
|
48
|
+
const scrollElement = pageScrollRef.current;
|
|
35
49
|
if (!scrollElement) return;
|
|
36
50
|
scrollElement.scrollTop = scrollElement.scrollHeight;
|
|
37
51
|
});
|
|
@@ -40,21 +54,25 @@ export function StepAttemptLogPanel({ stepId, attempt, attemptStatus, initialErr
|
|
|
40
54
|
};
|
|
41
55
|
}, [
|
|
42
56
|
anchorToFailure,
|
|
57
|
+
pageScrollRef,
|
|
43
58
|
recordCount
|
|
44
59
|
]);
|
|
45
|
-
function handleLogScroll(event) {
|
|
46
|
-
const element = event.currentTarget;
|
|
47
|
-
const distanceFromBottom = element.scrollHeight - element.scrollTop - element.clientHeight;
|
|
48
|
-
shouldFollowTailRef.current = distanceFromBottom <= TAIL_FOLLOW_THRESHOLD_PX;
|
|
49
|
-
}
|
|
50
60
|
if (query.isPending) {
|
|
51
61
|
return /*#__PURE__*/ _jsx(StepLogsLoadingSurface, {
|
|
52
|
-
label: "Loading logs"
|
|
62
|
+
label: "Loading logs",
|
|
63
|
+
className: surfaceClassName
|
|
53
64
|
});
|
|
54
65
|
}
|
|
55
66
|
if (missingActiveStream) {
|
|
67
|
+
if (attemptStatus === 'running' && attemptStartedAt) {
|
|
68
|
+
return /*#__PURE__*/ _jsx(StepLogsWaitingSurface, {
|
|
69
|
+
startedAt: attemptStartedAt,
|
|
70
|
+
className: surfaceClassName
|
|
71
|
+
});
|
|
72
|
+
}
|
|
56
73
|
return /*#__PURE__*/ _jsx(StepLogsLoadingSurface, {
|
|
57
|
-
label: "Waiting for logs"
|
|
74
|
+
label: "Waiting for logs",
|
|
75
|
+
className: surfaceClassName
|
|
58
76
|
});
|
|
59
77
|
}
|
|
60
78
|
if (initialError) {
|
|
@@ -64,7 +82,6 @@ export function StepAttemptLogPanel({ stepId, attempt, attemptStatus, initialErr
|
|
|
64
82
|
});
|
|
65
83
|
}
|
|
66
84
|
return /*#__PURE__*/ _jsxs("div", {
|
|
67
|
-
ref: panelRef,
|
|
68
85
|
className: "flex min-w-0 flex-col gap-8",
|
|
69
86
|
children: [
|
|
70
87
|
staleError ? /*#__PURE__*/ _jsx(Callout, {
|
|
@@ -93,18 +110,42 @@ export function StepAttemptLogPanel({ stepId, attempt, attemptStatus, initialErr
|
|
|
93
110
|
records: records,
|
|
94
111
|
emptyState: query.data?.complete ? 'complete' : 'pending',
|
|
95
112
|
anchorToFailure: anchorToFailure,
|
|
96
|
-
className:
|
|
97
|
-
onScroll: handleLogScroll
|
|
113
|
+
className: surfaceClassName
|
|
98
114
|
})
|
|
99
115
|
]
|
|
100
116
|
});
|
|
101
117
|
}
|
|
102
|
-
function StepLogsLoadingSurface({ label }) {
|
|
118
|
+
function StepLogsLoadingSurface({ label, className }) {
|
|
103
119
|
return /*#__PURE__*/ _jsx("div", {
|
|
104
120
|
role: "status",
|
|
105
121
|
"aria-label": label,
|
|
106
122
|
children: /*#__PURE__*/ _jsx(LogViewSkeleton, {
|
|
107
|
-
className:
|
|
123
|
+
className: className
|
|
124
|
+
})
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
function StepLogsWaitingSurface({ startedAt, className }) {
|
|
128
|
+
return /*#__PURE__*/ _jsx("div", {
|
|
129
|
+
role: "status",
|
|
130
|
+
"aria-label": "Waiting for logs",
|
|
131
|
+
className: className,
|
|
132
|
+
children: /*#__PURE__*/ _jsxs(Text, {
|
|
133
|
+
size: "xs",
|
|
134
|
+
className: "px-10 py-12 text-foreground-neutral-muted",
|
|
135
|
+
children: [
|
|
136
|
+
"Waiting for output ·",
|
|
137
|
+
' ',
|
|
138
|
+
/*#__PURE__*/ _jsx("span", {
|
|
139
|
+
className: "font-code tabular-nums",
|
|
140
|
+
"aria-hidden": "true",
|
|
141
|
+
children: /*#__PURE__*/ _jsx(JobExecutionTimeText, {
|
|
142
|
+
time: {
|
|
143
|
+
state: 'live',
|
|
144
|
+
fromIso: startedAt
|
|
145
|
+
}
|
|
146
|
+
})
|
|
147
|
+
})
|
|
148
|
+
]
|
|
108
149
|
})
|
|
109
150
|
});
|
|
110
151
|
}
|
|
@@ -121,6 +162,10 @@ function cancelScheduledFrame(frame) {
|
|
|
121
162
|
}
|
|
122
163
|
window.clearTimeout(frame);
|
|
123
164
|
}
|
|
165
|
+
function isNearPageBottom(element) {
|
|
166
|
+
const distanceFromBottom = element.scrollHeight - element.scrollTop - element.clientHeight;
|
|
167
|
+
return distanceFromBottom <= TAIL_FOLLOW_THRESHOLD_PX;
|
|
168
|
+
}
|
|
124
169
|
function isTerminalAttemptStatus(status) {
|
|
125
170
|
return status === 'succeeded' || status === 'failed' || status === 'cancelled';
|
|
126
171
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/job-detail/step-attempt-log-panel.tsx"],"sourcesContent":["import {\n isMissingStepLogStreamError,\n LogView,\n LogViewSkeleton,\n useStepAttemptLogsQuery,\n} from '@shipfox/client-logs';\nimport {Button} from '@shipfox/react-ui/button';\nimport {Callout} from '@shipfox/react-ui/callout';\nimport {Text} from '@shipfox/react-ui/typography';\nimport {type RefObject, useEffect, useRef} from 'react';\nimport {JobExecutionTimeText} from './job-execution-time-text.js';\n\nconst TAIL_FOLLOW_THRESHOLD_PX = 24;\nconst INITIAL_LOG_ERROR_RETRY_COUNT = 5;\nconst INITIAL_LOG_ERROR_RETRY_DELAY_MS = 1_500;\nconst defaultLogSurfaceClasses = 'rounded-8';\n\nexport interface StepAttemptLogPanelProps {\n stepId: string;\n attempt: number;\n attemptStatus: string;\n /** The start time used by the page's quiet waiting-for-output state. */\n attemptStartedAt?: string | undefined;\n /** The page scroll column used by the uncapped job-detail log surface. */\n pageScrollRef: RefObject<HTMLElement | null>;\n surfaceClassName?: string | undefined;\n initialErrorRetryCount?: number | undefined;\n initialErrorRetryDelayMs?: number | undefined;\n}\n\nexport function StepAttemptLogPanel({\n stepId,\n attempt,\n attemptStatus,\n attemptStartedAt,\n pageScrollRef,\n surfaceClassName = defaultLogSurfaceClasses,\n initialErrorRetryCount = INITIAL_LOG_ERROR_RETRY_COUNT,\n initialErrorRetryDelayMs = INITIAL_LOG_ERROR_RETRY_DELAY_MS,\n}: StepAttemptLogPanelProps) {\n const shouldFollowTailRef = useRef(true);\n const missingStreamRetryCount = attemptStatus === 'running' ? undefined : initialErrorRetryCount;\n const retryMissingStream = attemptStatus === 'running' || isTerminalAttemptStatus(attemptStatus);\n const query = useStepAttemptLogsQuery(stepId, attempt, {\n retryMissingStream,\n missingStreamRetryCount,\n missingStreamRetryDelayMs: initialErrorRetryDelayMs,\n initialErrorRetryCount,\n initialErrorRetryDelayMs,\n });\n const records = query.data?.records ?? [];\n const recordCount = records.length;\n const anchorToFailure = attemptStatus === 'failed';\n const missingActiveStream =\n retryMissingStream && query.data === undefined && isMissingStepLogStreamError(query.error);\n const initialError = query.isError && query.data === undefined && !missingActiveStream;\n const staleError = query.isError && query.data !== undefined;\n\n useEffect(() => {\n const scrollElement = pageScrollRef.current;\n if (!scrollElement) return undefined;\n\n const onScroll = () => {\n shouldFollowTailRef.current = isNearPageBottom(scrollElement);\n };\n scrollElement.addEventListener('scroll', onScroll, {passive: true});\n onScroll();\n return () => scrollElement.removeEventListener('scroll', onScroll);\n }, [pageScrollRef]);\n\n useEffect(() => {\n if (recordCount === 0) return undefined;\n if (anchorToFailure) return undefined;\n if (!shouldFollowTailRef.current) return undefined;\n\n const frame = scheduleAnimationFrame(() => {\n const scrollElement = pageScrollRef.current;\n if (!scrollElement) return;\n scrollElement.scrollTop = scrollElement.scrollHeight;\n });\n\n return () => {\n cancelScheduledFrame(frame);\n };\n }, [anchorToFailure, pageScrollRef, recordCount]);\n\n if (query.isPending) {\n return <StepLogsLoadingSurface label=\"Loading logs\" className={surfaceClassName} />;\n }\n\n if (missingActiveStream) {\n if (attemptStatus === 'running' && attemptStartedAt) {\n return <StepLogsWaitingSurface startedAt={attemptStartedAt} className={surfaceClassName} />;\n }\n return <StepLogsLoadingSurface label=\"Waiting for logs\" className={surfaceClassName} />;\n }\n\n if (initialError) {\n return <StepLogsError retrying={query.isFetching} onRetry={() => void query.refetch()} />;\n }\n\n return (\n <div className=\"flex min-w-0 flex-col gap-8\">\n {staleError ? (\n <Callout role=\"alert\" type=\"warning\" className=\"px-10 py-8\">\n <div className=\"flex min-w-0 flex-1 items-center justify-between gap-8\">\n <Text size=\"xs\">Could not refresh logs.</Text>\n <Button\n type=\"button\"\n size=\"2xs\"\n variant=\"secondary\"\n isLoading={query.isFetching}\n onClick={() => void query.refetch()}\n >\n Retry\n </Button>\n </div>\n </Callout>\n ) : null}\n <LogView\n records={records}\n emptyState={query.data?.complete ? 'complete' : 'pending'}\n anchorToFailure={anchorToFailure}\n className={surfaceClassName}\n />\n </div>\n );\n}\n\nfunction StepLogsLoadingSurface({label, className}: {label: string; className: string}) {\n return (\n <div role=\"status\" aria-label={label}>\n <LogViewSkeleton className={className} />\n </div>\n );\n}\n\nfunction StepLogsWaitingSurface({startedAt, className}: {startedAt: string; className: string}) {\n return (\n <div role=\"status\" aria-label=\"Waiting for logs\" className={className}>\n <Text size=\"xs\" className=\"px-10 py-12 text-foreground-neutral-muted\">\n Waiting for output ·{' '}\n <span className=\"font-code tabular-nums\" aria-hidden=\"true\">\n <JobExecutionTimeText time={{state: 'live', fromIso: startedAt}} />\n </span>\n </Text>\n </div>\n );\n}\n\nfunction scheduleAnimationFrame(callback: FrameRequestCallback): number {\n if (typeof globalThis.requestAnimationFrame === 'function') {\n return globalThis.requestAnimationFrame(callback);\n }\n return window.setTimeout(() => callback(Date.now()), 0);\n}\n\nfunction cancelScheduledFrame(frame: number) {\n if (typeof globalThis.cancelAnimationFrame === 'function') {\n globalThis.cancelAnimationFrame(frame);\n return;\n }\n window.clearTimeout(frame);\n}\n\nfunction isNearPageBottom(element: HTMLElement): boolean {\n const distanceFromBottom = element.scrollHeight - element.scrollTop - element.clientHeight;\n return distanceFromBottom <= TAIL_FOLLOW_THRESHOLD_PX;\n}\n\nfunction isTerminalAttemptStatus(status: string): boolean {\n return status === 'succeeded' || status === 'failed' || status === 'cancelled';\n}\n\nfunction StepLogsError({retrying, onRetry}: {retrying: boolean; onRetry: () => void}) {\n return (\n <Callout role=\"alert\" type=\"error\" className=\"px-10 py-8\">\n <div className=\"flex min-w-0 flex-1 items-center justify-between gap-8\">\n <Text size=\"xs\">Could not load logs.</Text>\n <Button type=\"button\" size=\"2xs\" variant=\"secondary\" isLoading={retrying} onClick={onRetry}>\n Retry\n </Button>\n </div>\n </Callout>\n );\n}\n"],"names":["isMissingStepLogStreamError","LogView","LogViewSkeleton","useStepAttemptLogsQuery","Button","Callout","Text","useEffect","useRef","JobExecutionTimeText","TAIL_FOLLOW_THRESHOLD_PX","INITIAL_LOG_ERROR_RETRY_COUNT","INITIAL_LOG_ERROR_RETRY_DELAY_MS","defaultLogSurfaceClasses","StepAttemptLogPanel","stepId","attempt","attemptStatus","attemptStartedAt","pageScrollRef","surfaceClassName","initialErrorRetryCount","initialErrorRetryDelayMs","shouldFollowTailRef","missingStreamRetryCount","undefined","retryMissingStream","isTerminalAttemptStatus","query","missingStreamRetryDelayMs","records","data","recordCount","length","anchorToFailure","missingActiveStream","error","initialError","isError","staleError","scrollElement","current","onScroll","isNearPageBottom","addEventListener","passive","removeEventListener","frame","scheduleAnimationFrame","scrollTop","scrollHeight","cancelScheduledFrame","isPending","StepLogsLoadingSurface","label","className","StepLogsWaitingSurface","startedAt","StepLogsError","retrying","isFetching","onRetry","refetch","div","role","type","size","variant","isLoading","onClick","emptyState","complete","aria-label","span","aria-hidden","time","state","fromIso","callback","globalThis","requestAnimationFrame","window","setTimeout","Date","now","cancelAnimationFrame","clearTimeout","element","distanceFromBottom","clientHeight","status"],"mappings":"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"}
|
|
@@ -54,7 +54,7 @@ export function JobGraphContent({ model, trigger, selectedJobId, onSelectJob })
|
|
|
54
54
|
intervalMs: 1000,
|
|
55
55
|
reducedMotionIntervalMs: 10_000,
|
|
56
56
|
children: /*#__PURE__*/ _jsx("div", {
|
|
57
|
-
className: "min-h-0 overflow-auto bg-
|
|
57
|
+
className: "min-h-0 overflow-auto bg-transparent",
|
|
58
58
|
children: /*#__PURE__*/ _jsxs("div", {
|
|
59
59
|
className: "relative",
|
|
60
60
|
style: {
|