@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
|
@@ -2,37 +2,39 @@ import {ApiError} from '@shipfox/client-api';
|
|
|
2
2
|
import {QueryLoadError} from '@shipfox/client-ui';
|
|
3
3
|
import {EmptyState} from '@shipfox/react-ui/empty-state';
|
|
4
4
|
import {RelativeTimeProvider} from '@shipfox/react-ui/relative-time';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
Select,
|
|
7
|
+
SelectContent,
|
|
8
|
+
SelectItem,
|
|
9
|
+
SelectTrigger,
|
|
10
|
+
SelectValue,
|
|
11
|
+
} from '@shipfox/react-ui/select';
|
|
6
12
|
import {toast} from '@shipfox/react-ui/toast';
|
|
13
|
+
import {Text} from '@shipfox/react-ui/typography';
|
|
7
14
|
import {useNavigate} from '@tanstack/react-router';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
type WorkflowRunRerunMode,
|
|
14
|
-
} from '#core/workflow-run.js';
|
|
15
|
-
import type {RunAnnotationSummary} from '#core/workflow-run-tabs.js';
|
|
16
|
-
import {
|
|
17
|
-
type WorkflowRunSelectionInput,
|
|
18
|
-
withoutWorkflowRunSelectionSearch,
|
|
19
|
-
} from '#core/workflow-run-url-state.js';
|
|
15
|
+
import {type ReactNode, useEffect, useMemo} from 'react';
|
|
16
|
+
import {buildRunAnnotationList, type RunAnnotationSummary} from '#core/run-annotation.js';
|
|
17
|
+
import {isWorkflowRunTerminal, type Job, type WorkflowRunRerunMode} from '#core/workflow-run.js';
|
|
18
|
+
import {withoutWorkflowRunSelectionSearch} from '#core/workflow-run-url-state.js';
|
|
19
|
+
import {useRunAnnotationsQuery} from '#hooks/api/run-annotations.js';
|
|
20
20
|
import {
|
|
21
21
|
useCancelWorkflowRunMutation,
|
|
22
22
|
useRerunWorkflowRunMutation,
|
|
23
23
|
useWorkflowRunAttemptQuery,
|
|
24
24
|
} from '#hooks/api/workflow-runs.js';
|
|
25
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
type WorkflowJobSearch,
|
|
27
|
+
type WorkflowRunsSearch,
|
|
28
|
+
type WorkflowRunTab,
|
|
29
|
+
workflowJobSearchParams,
|
|
30
|
+
workflowRunSearchParams,
|
|
31
|
+
} from '#routes/inputs.js';
|
|
26
32
|
import {JobGraph} from '../job-graph/index.js';
|
|
27
33
|
import type {JobGraphSelectionSource} from '../job-graph/types.js';
|
|
28
34
|
import {WorkflowRunSummary} from '../workflow-run-summary/index.js';
|
|
29
|
-
import {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
RunJobsList,
|
|
33
|
-
RunTabStrip,
|
|
34
|
-
} from '../workflow-run-tabs/index.js';
|
|
35
|
-
import {WorkflowSourceContent, WorkflowSourcePanel} from '../workflow-source-panel/index.js';
|
|
35
|
+
import {RunAnnotationList, RunAnnotationSummaryLine} from '../workflow-run-tabs/index.js';
|
|
36
|
+
import {WorkflowSourceContent} from '../workflow-source-panel/index.js';
|
|
37
|
+
import {RunWorkspaceNav} from './run-workspace-nav.js';
|
|
36
38
|
import {resolveWorkflowRunSelection} from './workflow-run-selection.js';
|
|
37
39
|
import {
|
|
38
40
|
WorkflowRunContentSkeleton,
|
|
@@ -41,40 +43,58 @@ import {
|
|
|
41
43
|
WorkflowRunStaleError,
|
|
42
44
|
} from './workflow-run-states.js';
|
|
43
45
|
|
|
44
|
-
|
|
45
|
-
stepId: string;
|
|
46
|
-
location: StepSourceLocation;
|
|
47
|
-
}
|
|
46
|
+
type RunWorkspaceSection = Exclude<WorkflowRunTab, 'jobs'>;
|
|
48
47
|
|
|
49
48
|
export interface WorkflowRunViewProps {
|
|
50
49
|
projectId: string;
|
|
51
50
|
workspaceSlug?: string | undefined;
|
|
52
51
|
projectSlug?: string | undefined;
|
|
53
52
|
workflowRunId?: string | undefined;
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
runAttempt?: number | undefined;
|
|
54
|
+
selection?: WorkflowRunsSearch | undefined;
|
|
56
55
|
tab?: WorkflowRunTab | undefined;
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
activeJobId?: string | undefined;
|
|
57
|
+
jobSearch?: WorkflowJobSearch | undefined;
|
|
58
|
+
jobContent?: ReactNode | undefined;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
/**
|
|
62
|
-
*
|
|
63
|
-
*
|
|
62
|
+
* The persistent run workspace. Run-level sections and dedicated job routes share the same
|
|
63
|
+
* header and job index, while the all-jobs Summary always opens on the dependency graph.
|
|
64
64
|
*/
|
|
65
65
|
export function WorkflowRunView({
|
|
66
66
|
projectId,
|
|
67
67
|
workspaceSlug,
|
|
68
68
|
projectSlug,
|
|
69
69
|
workflowRunId,
|
|
70
|
+
runAttempt,
|
|
70
71
|
selection,
|
|
71
|
-
onSelectionChange,
|
|
72
72
|
tab,
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
activeJobId,
|
|
74
|
+
jobSearch,
|
|
75
|
+
jobContent,
|
|
75
76
|
}: WorkflowRunViewProps) {
|
|
76
|
-
const runQuery = useWorkflowRunAttemptQuery({
|
|
77
|
+
const runQuery = useWorkflowRunAttemptQuery({
|
|
78
|
+
workflowRunId,
|
|
79
|
+
runAttempt: selection?.runAttempt ?? runAttempt,
|
|
80
|
+
});
|
|
77
81
|
const rerunMutation = useRerunWorkflowRunMutation(projectId);
|
|
82
|
+
// Annotations are read on their own cadence rather than hydrated into the run, so the two
|
|
83
|
+
// modules stay decoupled. The key is shared, so the job page's count chip costs no extra fetch.
|
|
84
|
+
//
|
|
85
|
+
// Polling is scoped to the surface that renders bodies. The read has no counts-only mode, so
|
|
86
|
+
// every poll transfers whole Markdown bodies; doing that every four seconds behind Summary,
|
|
87
|
+
// Source, and the job log spends megabytes to keep one rail number warm. Off that surface the
|
|
88
|
+
// counts still refresh on navigation and on window focus.
|
|
89
|
+
const annotationsQuery = useRunAnnotationsQuery({
|
|
90
|
+
workflowRunId,
|
|
91
|
+
runAttempt: runQuery.data?.runAttempt.attempt,
|
|
92
|
+
live:
|
|
93
|
+
runWorkspaceSection(tab) === 'annotations' &&
|
|
94
|
+
!activeJobId &&
|
|
95
|
+
Boolean(runQuery.data) &&
|
|
96
|
+
!isWorkflowRunTerminal(runQuery.data?.runAttempt.status ?? 'pending'),
|
|
97
|
+
});
|
|
78
98
|
|
|
79
99
|
return (
|
|
80
100
|
<RelativeTimeProvider>
|
|
@@ -83,12 +103,14 @@ export function WorkflowRunView({
|
|
|
83
103
|
workspaceSlug={workspaceSlug}
|
|
84
104
|
projectSlug={projectSlug}
|
|
85
105
|
query={runQuery}
|
|
106
|
+
annotations={annotationsQuery}
|
|
86
107
|
rerunMutation={rerunMutation}
|
|
108
|
+
runAttempt={runAttempt}
|
|
87
109
|
selection={selection}
|
|
88
|
-
onSelectionChange={onSelectionChange}
|
|
89
110
|
tab={tab}
|
|
90
|
-
|
|
91
|
-
|
|
111
|
+
activeJobId={activeJobId}
|
|
112
|
+
jobSearch={jobSearch}
|
|
113
|
+
jobContent={jobContent}
|
|
92
114
|
/>
|
|
93
115
|
</div>
|
|
94
116
|
</RelativeTimeProvider>
|
|
@@ -99,108 +121,82 @@ function RunViewContent({
|
|
|
99
121
|
workspaceSlug,
|
|
100
122
|
projectSlug,
|
|
101
123
|
query,
|
|
124
|
+
annotations,
|
|
102
125
|
rerunMutation,
|
|
126
|
+
runAttempt,
|
|
103
127
|
selection,
|
|
104
|
-
onSelectionChange,
|
|
105
128
|
tab,
|
|
106
|
-
|
|
107
|
-
|
|
129
|
+
activeJobId,
|
|
130
|
+
jobSearch,
|
|
131
|
+
jobContent,
|
|
108
132
|
}: {
|
|
109
133
|
workspaceSlug: string | undefined;
|
|
110
134
|
projectSlug: string | undefined;
|
|
111
135
|
query: ReturnType<typeof useWorkflowRunAttemptQuery>;
|
|
136
|
+
annotations: ReturnType<typeof useRunAnnotationsQuery>;
|
|
112
137
|
rerunMutation: ReturnType<typeof useRerunWorkflowRunMutation>;
|
|
113
|
-
|
|
114
|
-
|
|
138
|
+
runAttempt: number | undefined;
|
|
139
|
+
selection: WorkflowRunsSearch | undefined;
|
|
115
140
|
tab: WorkflowRunTab | undefined;
|
|
116
|
-
|
|
117
|
-
|
|
141
|
+
activeJobId: string | undefined;
|
|
142
|
+
jobSearch: WorkflowJobSearch | undefined;
|
|
143
|
+
jobContent: ReactNode | undefined;
|
|
118
144
|
}) {
|
|
119
145
|
const navigate = useNavigate();
|
|
120
|
-
const [selectedJobId, setSelectedJobId] = useState<string | undefined>();
|
|
121
|
-
const [selectedJobExecutionId, setSelectedJobExecutionId] = useState<string | undefined>();
|
|
122
|
-
const [localTab, setLocalTab] = useState<WorkflowRunTab>(tab ?? 'summary');
|
|
123
|
-
const [sourcePanelOpen, setSourcePanelOpen] = useState(false);
|
|
124
|
-
const [sourceFocus, setSourceFocus] = useState<WorkflowSourceFocus | null>(null);
|
|
125
|
-
const sourcePanelId = useId();
|
|
126
|
-
// The step Source button that last opened the panel, so Escape / Close returns focus to it.
|
|
127
|
-
const lastSourceTriggerRef = useRef<HTMLButtonElement | null>(null);
|
|
128
|
-
const selectionControlled = selection !== undefined;
|
|
129
|
-
const tabControlled = onTabChange !== undefined;
|
|
130
|
-
const activeTab = tabControlled ? (tab ?? 'summary') : localTab;
|
|
131
|
-
const sourceAvailable =
|
|
132
|
-
query.data?.sourceSnapshot !== null && query.data?.sourceSnapshot !== undefined;
|
|
133
|
-
const cancelMutation = useCancelWorkflowRunMutation(query.data);
|
|
134
|
-
|
|
135
|
-
useEffect(() => {
|
|
136
|
-
if (!sourceAvailable) {
|
|
137
|
-
setSourcePanelOpen(false);
|
|
138
|
-
setSourceFocus(null);
|
|
139
|
-
}
|
|
140
|
-
}, [sourceAvailable]);
|
|
141
|
-
|
|
142
|
-
// If a refetch drops the focused step or its location, close the panel so it never points at
|
|
143
|
-
// an unmounted Source button.
|
|
144
|
-
useEffect(() => {
|
|
145
|
-
if (!sourceFocus) return;
|
|
146
|
-
const stillLocated = query.data?.jobs.some((job) =>
|
|
147
|
-
job.jobExecutions.some((execution) =>
|
|
148
|
-
execution.steps.some((step) => step.id === sourceFocus.stepId && step.sourceLocation),
|
|
149
|
-
),
|
|
150
|
-
);
|
|
151
|
-
if (!stillLocated) {
|
|
152
|
-
setSourceFocus(null);
|
|
153
|
-
setSourcePanelOpen(false);
|
|
154
|
-
}
|
|
155
|
-
}, [sourceFocus, query.data]);
|
|
156
|
-
|
|
157
146
|
const runData = query.data;
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
run: runData,
|
|
162
|
-
selection: selection as WorkflowRunSelectionInput,
|
|
163
|
-
})
|
|
164
|
-
: undefined;
|
|
165
|
-
const hasExplicitJobSelection = Boolean(
|
|
166
|
-
selection?.jobId || selection?.jobExecutionId || selection?.stepId || selection?.stepAttemptId,
|
|
167
|
-
);
|
|
168
|
-
const selectedJob = selectionControlled
|
|
169
|
-
? hasExplicitJobSelection
|
|
170
|
-
? resolvedSelection?.job
|
|
171
|
-
: undefined
|
|
172
|
-
: (runData?.jobs.find((job) => job.id === selectedJobId) ?? runData?.jobs.at(0));
|
|
173
|
-
const selectedJobExecution = selectionControlled
|
|
174
|
-
? resolvedSelection?.jobExecution
|
|
175
|
-
: selectedJob
|
|
176
|
-
? resolveJobExecution(selectedJob, selectedJobExecutionId)
|
|
177
|
-
: undefined;
|
|
178
|
-
const selectedAttemptId = selectionControlled
|
|
179
|
-
? (resolvedSelection?.selectedAttemptId ?? null)
|
|
180
|
-
: undefined;
|
|
181
|
-
// Explicit per-step focus wins; fall back to the URL-selected step so deep links still
|
|
182
|
-
// pre-highlight source in the Source tab.
|
|
183
|
-
const highlightedLineRange =
|
|
184
|
-
sourceFocus?.location ?? resolvedSelection?.step?.sourceLocation ?? null;
|
|
147
|
+
const annotationSummary = annotations.summary;
|
|
148
|
+
const activeSection = runWorkspaceSection(tab);
|
|
149
|
+
const cancelMutation = useCancelWorkflowRunMutation(runData);
|
|
185
150
|
const sourceSnapshot = runData?.sourceSnapshot ?? null;
|
|
151
|
+
const resolvedSelection =
|
|
152
|
+
runData && selection ? resolveWorkflowRunSelection({run: runData, selection}) : undefined;
|
|
153
|
+
const highlightedLineRange = resolvedSelection?.step?.sourceLocation ?? null;
|
|
186
154
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
if (
|
|
195
|
-
|
|
196
|
-
|
|
155
|
+
useEffect(() => {
|
|
156
|
+
const hasLegacyJobSelection = Boolean(
|
|
157
|
+
selection?.jobId ||
|
|
158
|
+
selection?.jobExecutionId ||
|
|
159
|
+
selection?.stepId ||
|
|
160
|
+
selection?.stepAttemptId,
|
|
161
|
+
);
|
|
162
|
+
if (
|
|
163
|
+
activeJobId ||
|
|
164
|
+
activeSection !== 'summary' ||
|
|
165
|
+
!hasLegacyJobSelection ||
|
|
166
|
+
!resolvedSelection?.job ||
|
|
167
|
+
!runData ||
|
|
168
|
+
!workspaceSlug ||
|
|
169
|
+
!projectSlug
|
|
170
|
+
) {
|
|
171
|
+
return;
|
|
197
172
|
}
|
|
198
|
-
}
|
|
199
173
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
174
|
+
void navigate({
|
|
175
|
+
to: '/w/$workspaceSlug/p/$projectSlug/runs/$workflowRunId/jobs/$jobId',
|
|
176
|
+
params: {
|
|
177
|
+
workspaceSlug,
|
|
178
|
+
projectSlug,
|
|
179
|
+
workflowRunId: runData.id,
|
|
180
|
+
jobId: resolvedSelection.job.id,
|
|
181
|
+
},
|
|
182
|
+
search: workflowJobSearchParams({
|
|
183
|
+
jobExecutionId: selection?.jobExecutionId,
|
|
184
|
+
stepId: selection?.stepId,
|
|
185
|
+
stepAttemptId: selection?.stepAttemptId,
|
|
186
|
+
runAttempt: selection?.runAttempt,
|
|
187
|
+
}) as never,
|
|
188
|
+
replace: true,
|
|
189
|
+
});
|
|
190
|
+
}, [
|
|
191
|
+
activeJobId,
|
|
192
|
+
activeSection,
|
|
193
|
+
navigate,
|
|
194
|
+
projectSlug,
|
|
195
|
+
resolvedSelection?.job,
|
|
196
|
+
runData,
|
|
197
|
+
selection,
|
|
198
|
+
workspaceSlug,
|
|
199
|
+
]);
|
|
204
200
|
|
|
205
201
|
async function rerun(mode: WorkflowRunRerunMode) {
|
|
206
202
|
if (!runData || !workspaceSlug || !projectSlug) {
|
|
@@ -212,11 +208,7 @@ function RunViewContent({
|
|
|
212
208
|
toast.success('Re-run started');
|
|
213
209
|
await navigate({
|
|
214
210
|
to: '/w/$workspaceSlug/p/$projectSlug/runs/$workflowRunId',
|
|
215
|
-
params: {
|
|
216
|
-
workspaceSlug,
|
|
217
|
-
projectSlug,
|
|
218
|
-
workflowRunId: run.id,
|
|
219
|
-
},
|
|
211
|
+
params: {workspaceSlug, projectSlug, workflowRunId: run.id},
|
|
220
212
|
search: ((previous: Record<string, unknown>) =>
|
|
221
213
|
withoutWorkflowRunSelectionSearch(previous)) as never,
|
|
222
214
|
});
|
|
@@ -225,91 +217,60 @@ function RunViewContent({
|
|
|
225
217
|
}
|
|
226
218
|
}
|
|
227
219
|
|
|
228
|
-
function
|
|
229
|
-
if (source
|
|
230
|
-
if (!selectionControlled) {
|
|
231
|
-
setSelectedJobId(jobId);
|
|
232
|
-
setSelectedJobExecutionId(undefined);
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
235
|
-
// Keyboard roving focus is an interaction within the Summary graph. Keep that panel mounted
|
|
236
|
-
// and let JobGraphView own the visual selection so focus is not moved to a different tab.
|
|
237
|
-
if (source === 'keyboard') return;
|
|
238
|
-
|
|
239
|
-
onSelectionChange?.(
|
|
240
|
-
jobId ? {jobId, runAttempt: selection?.runAttempt} : {runAttempt: selection?.runAttempt},
|
|
241
|
-
);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
function selectJobExecution(jobExecutionId: string | undefined) {
|
|
245
|
-
if (!selectionControlled) {
|
|
246
|
-
setSelectedJobExecutionId(jobExecutionId);
|
|
247
|
-
return;
|
|
248
|
-
}
|
|
249
|
-
if (!selectedJob) return;
|
|
220
|
+
function selectGraphJob(jobId: string | undefined, source: JobGraphSelectionSource = 'pointer') {
|
|
221
|
+
if (source !== 'pointer' || !jobId || !runData || !workspaceSlug || !projectSlug) return;
|
|
250
222
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
223
|
+
void navigate({
|
|
224
|
+
to: '/w/$workspaceSlug/p/$projectSlug/runs/$workflowRunId/jobs/$jobId',
|
|
225
|
+
params: {workspaceSlug, projectSlug, workflowRunId: runData.id, jobId},
|
|
226
|
+
search: workflowJobSearchParams({
|
|
227
|
+
runAttempt: selection?.runAttempt ?? runAttempt ?? runData.runAttempt.attempt,
|
|
228
|
+
}) as never,
|
|
255
229
|
});
|
|
256
230
|
}
|
|
257
231
|
|
|
258
|
-
function
|
|
259
|
-
if (!
|
|
260
|
-
|
|
261
|
-
if (
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
});
|
|
267
|
-
return;
|
|
268
|
-
}
|
|
232
|
+
function selectAnnotationJob(jobId: string | undefined) {
|
|
233
|
+
if (!runData || !workspaceSlug || !projectSlug) return;
|
|
234
|
+
const nextSearch: WorkflowRunsSearch = {...selection, tab: 'annotations'};
|
|
235
|
+
if (jobId) nextSearch.jobId = jobId;
|
|
236
|
+
else delete nextSearch.jobId;
|
|
237
|
+
delete nextSearch.jobExecutionId;
|
|
238
|
+
delete nextSearch.stepId;
|
|
239
|
+
delete nextSearch.stepAttemptId;
|
|
269
240
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
jobId: selectedJob.id,
|
|
275
|
-
jobExecutionId: selectedJobExecution.id,
|
|
276
|
-
stepId: match.stepId,
|
|
277
|
-
stepAttemptId: match.attemptId,
|
|
278
|
-
runAttempt: selection?.runAttempt,
|
|
241
|
+
void navigate({
|
|
242
|
+
to: '/w/$workspaceSlug/p/$projectSlug/runs/$workflowRunId',
|
|
243
|
+
params: {workspaceSlug, projectSlug, workflowRunId: runData.id},
|
|
244
|
+
search: workflowRunSearchParams(nextSearch, nextSearch) as never,
|
|
279
245
|
});
|
|
280
246
|
}
|
|
281
247
|
|
|
282
|
-
function
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
248
|
+
function clearAnnotationFilters() {
|
|
249
|
+
if (!runData || !workspaceSlug || !projectSlug) return;
|
|
250
|
+
const nextSearch: WorkflowRunsSearch = {...selection, tab: 'annotations'};
|
|
251
|
+
delete nextSearch.jobId;
|
|
252
|
+
delete nextSearch.jobExecutionId;
|
|
253
|
+
delete nextSearch.stepId;
|
|
254
|
+
delete nextSearch.stepAttemptId;
|
|
255
|
+
delete nextSearch.severity;
|
|
256
|
+
delete nextSearch.annotation;
|
|
291
257
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
if (trigger?.isConnected) trigger.focus();
|
|
299
|
-
setSourceFocus(null);
|
|
300
|
-
}, 0);
|
|
258
|
+
void navigate({
|
|
259
|
+
to: '/w/$workspaceSlug/p/$projectSlug/runs/$workflowRunId',
|
|
260
|
+
params: {workspaceSlug, projectSlug, workflowRunId: runData.id},
|
|
261
|
+
search: workflowRunSearchParams(nextSearch, nextSearch) as never,
|
|
262
|
+
replace: true,
|
|
263
|
+
});
|
|
301
264
|
}
|
|
302
265
|
|
|
303
266
|
function cancelRun() {
|
|
304
267
|
cancelMutation.mutate(undefined, {
|
|
305
|
-
onError: (error) =>
|
|
306
|
-
toast.error(cancelErrorMessage(error));
|
|
307
|
-
},
|
|
268
|
+
onError: (error) => toast.error(cancelErrorMessage(error)),
|
|
308
269
|
});
|
|
309
270
|
}
|
|
310
271
|
|
|
311
272
|
const fatalError = query.isError && runData === undefined;
|
|
312
|
-
const
|
|
273
|
+
const loadingOrError = fatalError ? (
|
|
313
274
|
query.error instanceof ApiError && query.error.status === 404 ? (
|
|
314
275
|
<WorkflowRunNotFound />
|
|
315
276
|
) : (
|
|
@@ -320,146 +281,312 @@ function RunViewContent({
|
|
|
320
281
|
) : null;
|
|
321
282
|
|
|
322
283
|
return (
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
284
|
+
<div className="flex min-h-0 min-w-0 flex-1 flex-col">
|
|
285
|
+
{runData ? (
|
|
286
|
+
<WorkflowRunSummary
|
|
287
|
+
workspaceSlug={workspaceSlug}
|
|
288
|
+
projectSlug={projectSlug}
|
|
289
|
+
run={runData}
|
|
290
|
+
cancelling={cancelMutation.isPending}
|
|
291
|
+
onCancel={cancelRun}
|
|
292
|
+
rerunPending={rerunMutation.isPending}
|
|
293
|
+
onRerun={(mode) => void rerun(mode)}
|
|
294
|
+
latestAttempt={runData.latestAttempt}
|
|
295
|
+
/>
|
|
296
|
+
) : (
|
|
297
|
+
<WorkflowRunSkeleton />
|
|
298
|
+
)}
|
|
299
|
+
{runData && query.isError ? <WorkflowRunStaleError query={query} /> : null}
|
|
300
|
+
|
|
301
|
+
<div
|
|
302
|
+
data-run-workspace-layout
|
|
303
|
+
className="flex min-h-0 min-w-0 flex-1 flex-col border-t border-border-neutral-base min-[768px]:flex-row"
|
|
328
304
|
>
|
|
329
|
-
{runData ? (
|
|
330
|
-
<
|
|
305
|
+
{runData && workspaceSlug && projectSlug ? (
|
|
306
|
+
<RunWorkspaceNav
|
|
331
307
|
workspaceSlug={workspaceSlug}
|
|
332
308
|
projectSlug={projectSlug}
|
|
333
309
|
run={runData}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
latestAttempt={runData.latestAttempt}
|
|
310
|
+
activeSection={activeSection}
|
|
311
|
+
currentJobId={activeJobId}
|
|
312
|
+
jobSearch={jobSearch}
|
|
313
|
+
annotationSummary={annotationSummary}
|
|
339
314
|
/>
|
|
340
315
|
) : (
|
|
341
|
-
<
|
|
316
|
+
<RunWorkspaceNavSkeleton />
|
|
342
317
|
)}
|
|
343
|
-
|
|
344
|
-
<
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
>
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
</TabsContents>
|
|
439
|
-
</Tabs>
|
|
440
|
-
<WorkflowSourcePanel
|
|
441
|
-
id={sourcePanelId}
|
|
442
|
-
source={sourceSnapshot}
|
|
443
|
-
open={sourcePanelOpen && sourceAvailable}
|
|
444
|
-
onClose={closeSourcePanel}
|
|
445
|
-
highlightedLineRange={highlightedLineRange}
|
|
446
|
-
scrollHighlightedIntoView
|
|
318
|
+
|
|
319
|
+
<div
|
|
320
|
+
data-run-workspace-content
|
|
321
|
+
className="flex min-h-0 min-w-0 flex-1 flex-col bg-background-neutral-base"
|
|
322
|
+
>
|
|
323
|
+
{runData && jobContent ? (
|
|
324
|
+
jobContent
|
|
325
|
+
) : runData ? (
|
|
326
|
+
<RunSectionContent
|
|
327
|
+
section={activeSection}
|
|
328
|
+
run={runData}
|
|
329
|
+
annotations={annotations}
|
|
330
|
+
annotationSummary={annotationSummary}
|
|
331
|
+
workspaceSlug={workspaceSlug}
|
|
332
|
+
projectSlug={projectSlug}
|
|
333
|
+
selection={selection}
|
|
334
|
+
selectedJobId={selection?.jobId}
|
|
335
|
+
onSelectGraphJob={selectGraphJob}
|
|
336
|
+
onSelectAnnotationJob={selectAnnotationJob}
|
|
337
|
+
onClearAnnotationFilters={clearAnnotationFilters}
|
|
338
|
+
sourceSnapshot={sourceSnapshot}
|
|
339
|
+
highlightedLineRange={highlightedLineRange}
|
|
340
|
+
/>
|
|
341
|
+
) : (
|
|
342
|
+
<div className="min-h-0 flex-1 overflow-auto p-24">{loadingOrError}</div>
|
|
343
|
+
)}
|
|
344
|
+
</div>
|
|
345
|
+
</div>
|
|
346
|
+
</div>
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function RunSectionContent({
|
|
351
|
+
section,
|
|
352
|
+
run,
|
|
353
|
+
annotations,
|
|
354
|
+
annotationSummary,
|
|
355
|
+
workspaceSlug,
|
|
356
|
+
projectSlug,
|
|
357
|
+
selection,
|
|
358
|
+
selectedJobId,
|
|
359
|
+
onSelectGraphJob,
|
|
360
|
+
onSelectAnnotationJob,
|
|
361
|
+
onClearAnnotationFilters,
|
|
362
|
+
sourceSnapshot,
|
|
363
|
+
highlightedLineRange,
|
|
364
|
+
}: {
|
|
365
|
+
section: RunWorkspaceSection;
|
|
366
|
+
run: NonNullable<ReturnType<typeof useWorkflowRunAttemptQuery>['data']>;
|
|
367
|
+
annotations: ReturnType<typeof useRunAnnotationsQuery>;
|
|
368
|
+
annotationSummary: RunAnnotationSummary | undefined;
|
|
369
|
+
workspaceSlug: string | undefined;
|
|
370
|
+
projectSlug: string | undefined;
|
|
371
|
+
selection: WorkflowRunsSearch | undefined;
|
|
372
|
+
selectedJobId: string | undefined;
|
|
373
|
+
onSelectGraphJob: (jobId: string | undefined, source?: JobGraphSelectionSource) => void;
|
|
374
|
+
onSelectAnnotationJob: (jobId: string | undefined) => void;
|
|
375
|
+
onClearAnnotationFilters: () => void;
|
|
376
|
+
sourceSnapshot: NonNullable<
|
|
377
|
+
ReturnType<typeof useWorkflowRunAttemptQuery>['data']
|
|
378
|
+
>['sourceSnapshot'];
|
|
379
|
+
highlightedLineRange: NonNullable<
|
|
380
|
+
ReturnType<typeof useWorkflowRunAttemptQuery>['data']
|
|
381
|
+
>['jobs'][number]['jobExecutions'][number]['steps'][number]['sourceLocation'];
|
|
382
|
+
}) {
|
|
383
|
+
if (section === 'summary') {
|
|
384
|
+
return (
|
|
385
|
+
<section aria-label="All jobs summary" className="min-h-0 flex-1 overflow-auto pb-24 pt-16">
|
|
386
|
+
<div className="mx-auto flex w-full max-w-[1120px] flex-col gap-16 px-24">
|
|
387
|
+
<Text as="h2" className="sr-only">
|
|
388
|
+
All jobs summary
|
|
389
|
+
</Text>
|
|
390
|
+
<JobGraph
|
|
391
|
+
run={run}
|
|
392
|
+
selectedJobId={selectedJobId}
|
|
393
|
+
onSelectedJobChange={onSelectGraphJob}
|
|
394
|
+
className="min-h-160 overflow-hidden"
|
|
395
|
+
/>
|
|
396
|
+
</div>
|
|
397
|
+
</section>
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (section === 'annotations') {
|
|
402
|
+
return (
|
|
403
|
+
<RunAnnotationsSection
|
|
404
|
+
run={run}
|
|
405
|
+
annotations={annotations}
|
|
406
|
+
annotationSummary={annotationSummary}
|
|
407
|
+
workspaceSlug={workspaceSlug}
|
|
408
|
+
projectSlug={projectSlug}
|
|
409
|
+
selection={selection}
|
|
410
|
+
selectedJobId={selectedJobId}
|
|
411
|
+
onSelectAnnotationJob={onSelectAnnotationJob}
|
|
412
|
+
onClearAnnotationFilters={onClearAnnotationFilters}
|
|
447
413
|
/>
|
|
448
|
-
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
return (
|
|
418
|
+
<section aria-label="Workflow source" className="min-h-0 flex-1 overflow-auto pb-24 pt-16">
|
|
419
|
+
<div className="mx-auto flex min-h-full w-full max-w-[1120px] flex-col px-24">
|
|
420
|
+
<Text as="h2" className="sr-only">
|
|
421
|
+
Workflow source
|
|
422
|
+
</Text>
|
|
423
|
+
{sourceSnapshot ? (
|
|
424
|
+
<WorkflowSourceContent
|
|
425
|
+
source={sourceSnapshot}
|
|
426
|
+
highlightedLineRange={highlightedLineRange}
|
|
427
|
+
scrollHighlightedIntoView
|
|
428
|
+
/>
|
|
429
|
+
) : (
|
|
430
|
+
<EmptyState
|
|
431
|
+
className="min-h-160 w-full"
|
|
432
|
+
icon="fileDamageLine"
|
|
433
|
+
title="Source snapshot unavailable"
|
|
434
|
+
description={
|
|
435
|
+
run.isTemporary
|
|
436
|
+
? 'Temporary runs do not capture workflow source.'
|
|
437
|
+
: 'This run was created before workflow source snapshots were captured.'
|
|
438
|
+
}
|
|
439
|
+
/>
|
|
440
|
+
)}
|
|
441
|
+
</div>
|
|
442
|
+
</section>
|
|
449
443
|
);
|
|
450
444
|
}
|
|
451
445
|
|
|
446
|
+
function RunAnnotationsSection({
|
|
447
|
+
run,
|
|
448
|
+
annotations,
|
|
449
|
+
annotationSummary,
|
|
450
|
+
workspaceSlug,
|
|
451
|
+
projectSlug,
|
|
452
|
+
selection,
|
|
453
|
+
selectedJobId,
|
|
454
|
+
onSelectAnnotationJob,
|
|
455
|
+
onClearAnnotationFilters,
|
|
456
|
+
}: {
|
|
457
|
+
run: NonNullable<ReturnType<typeof useWorkflowRunAttemptQuery>['data']>;
|
|
458
|
+
annotations: ReturnType<typeof useRunAnnotationsQuery>;
|
|
459
|
+
annotationSummary: RunAnnotationSummary | undefined;
|
|
460
|
+
workspaceSlug: string | undefined;
|
|
461
|
+
projectSlug: string | undefined;
|
|
462
|
+
selection: WorkflowRunsSearch | undefined;
|
|
463
|
+
selectedJobId: string | undefined;
|
|
464
|
+
onSelectAnnotationJob: (jobId: string | undefined) => void;
|
|
465
|
+
onClearAnnotationFilters: () => void;
|
|
466
|
+
}) {
|
|
467
|
+
const selectedJob = run.jobs.find((job) => job.id === selectedJobId);
|
|
468
|
+
const severity = selection?.severity;
|
|
469
|
+
const records = annotations.annotations;
|
|
470
|
+
|
|
471
|
+
const entries = useMemo(
|
|
472
|
+
() =>
|
|
473
|
+
records
|
|
474
|
+
? buildRunAnnotationList({
|
|
475
|
+
annotations: records,
|
|
476
|
+
jobs: run.jobs,
|
|
477
|
+
severity,
|
|
478
|
+
jobId: selectedJob?.id,
|
|
479
|
+
})
|
|
480
|
+
: undefined,
|
|
481
|
+
[records, run.jobs, selectedJob?.id, severity],
|
|
482
|
+
);
|
|
483
|
+
|
|
484
|
+
return (
|
|
485
|
+
<section aria-label="Run annotations" className="min-h-0 flex-1 overflow-auto pb-24 pt-16">
|
|
486
|
+
<div className="mx-auto flex w-full max-w-[1120px] flex-col gap-16 px-24">
|
|
487
|
+
<Text as="h2" className="sr-only">
|
|
488
|
+
Annotations
|
|
489
|
+
</Text>
|
|
490
|
+
<div className="flex flex-wrap items-center justify-between gap-8">
|
|
491
|
+
<RunAnnotationSummaryLine
|
|
492
|
+
summary={annotationSummary}
|
|
493
|
+
workspaceSlug={workspaceSlug}
|
|
494
|
+
projectSlug={projectSlug}
|
|
495
|
+
workflowRunId={run.id}
|
|
496
|
+
search={selection}
|
|
497
|
+
/>
|
|
498
|
+
<AnnotationJobFilter
|
|
499
|
+
jobs={run.jobs}
|
|
500
|
+
selectedJobId={selectedJob?.id}
|
|
501
|
+
onSelect={onSelectAnnotationJob}
|
|
502
|
+
/>
|
|
503
|
+
</div>
|
|
504
|
+
<RunAnnotationList
|
|
505
|
+
// Remounting on a filter or run-attempt change resets the render window, so the next
|
|
506
|
+
// list never inherits a "show more" position from different data.
|
|
507
|
+
key={`${run.id}:${run.runAttempt.attempt}:${severity ?? 'all'}:${selectedJob?.id ?? 'all'}`}
|
|
508
|
+
query={annotations.query}
|
|
509
|
+
entries={entries}
|
|
510
|
+
workspaceSlug={workspaceSlug}
|
|
511
|
+
projectSlug={projectSlug}
|
|
512
|
+
workflowRunId={run.id}
|
|
513
|
+
runAttempt={run.runAttempt.attempt}
|
|
514
|
+
// A run with no annotations at all offers no filter to clear, whatever the URL says.
|
|
515
|
+
filtered={Boolean((severity || selectedJob) && (annotationSummary?.total ?? 0) > 0)}
|
|
516
|
+
filteredJobName={selectedJob?.displayName}
|
|
517
|
+
filteredSeverity={severity}
|
|
518
|
+
onClearFilters={onClearAnnotationFilters}
|
|
519
|
+
selectedAnnotationId={selection?.annotation}
|
|
520
|
+
/>
|
|
521
|
+
</div>
|
|
522
|
+
</section>
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
const ALL_ANNOTATION_JOBS = 'all-jobs';
|
|
527
|
+
|
|
528
|
+
function AnnotationJobFilter({
|
|
529
|
+
jobs,
|
|
530
|
+
selectedJobId,
|
|
531
|
+
onSelect,
|
|
532
|
+
}: {
|
|
533
|
+
jobs: Job[];
|
|
534
|
+
selectedJobId: string | undefined;
|
|
535
|
+
onSelect: (jobId: string | undefined) => void;
|
|
536
|
+
}) {
|
|
537
|
+
const sortedJobs = [...jobs].sort(
|
|
538
|
+
(left, right) => left.position - right.position || left.id.localeCompare(right.id),
|
|
539
|
+
);
|
|
540
|
+
|
|
541
|
+
return (
|
|
542
|
+
<Select
|
|
543
|
+
value={selectedJobId ?? ALL_ANNOTATION_JOBS}
|
|
544
|
+
onValueChange={(value) => onSelect(value === ALL_ANNOTATION_JOBS ? undefined : value)}
|
|
545
|
+
>
|
|
546
|
+
<SelectTrigger
|
|
547
|
+
size="small"
|
|
548
|
+
aria-label="Filter annotations by job"
|
|
549
|
+
className="w-full min-[480px]:w-240"
|
|
550
|
+
>
|
|
551
|
+
<SelectValue />
|
|
552
|
+
</SelectTrigger>
|
|
553
|
+
<SelectContent align="end">
|
|
554
|
+
<SelectItem value={ALL_ANNOTATION_JOBS}>All jobs</SelectItem>
|
|
555
|
+
{sortedJobs.map((job) => (
|
|
556
|
+
<SelectItem key={job.id} value={job.id}>
|
|
557
|
+
{job.displayName}
|
|
558
|
+
</SelectItem>
|
|
559
|
+
))}
|
|
560
|
+
</SelectContent>
|
|
561
|
+
</Select>
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
function RunWorkspaceNavSkeleton() {
|
|
566
|
+
return (
|
|
567
|
+
<aside
|
|
568
|
+
aria-label="Loading run navigation"
|
|
569
|
+
className="hidden w-240 shrink-0 border-r border-border-neutral-base bg-background-neutral-background p-12 min-[768px]:block"
|
|
570
|
+
>
|
|
571
|
+
<div className="flex flex-col gap-16">
|
|
572
|
+
<div className="h-32 rounded-4 bg-background-components-subtle" />
|
|
573
|
+
<div className="flex flex-col gap-6">
|
|
574
|
+
<div className="h-16 w-64 rounded-4 bg-background-components-subtle" />
|
|
575
|
+
<div className="h-32 rounded-4 bg-background-components-subtle" />
|
|
576
|
+
<div className="h-32 rounded-4 bg-background-components-subtle" />
|
|
577
|
+
</div>
|
|
578
|
+
</div>
|
|
579
|
+
</aside>
|
|
580
|
+
);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
function runWorkspaceSection(tab: WorkflowRunTab | undefined): RunWorkspaceSection {
|
|
584
|
+
return tab === 'annotations' || tab === 'source' ? tab : 'summary';
|
|
585
|
+
}
|
|
586
|
+
|
|
452
587
|
function cancelErrorMessage(error: unknown): string {
|
|
453
588
|
if (error instanceof ApiError && error.code === 'run-already-finished') {
|
|
454
589
|
return 'This workflow run has already finished.';
|
|
455
590
|
}
|
|
456
591
|
return 'Could not cancel workflow run.';
|
|
457
592
|
}
|
|
458
|
-
|
|
459
|
-
function findAttemptSelection(jobExecution: JobExecution, attemptId: string) {
|
|
460
|
-
for (const step of jobExecution.steps) {
|
|
461
|
-
const attempt = step.attempts.find((candidate) => candidate.id === attemptId);
|
|
462
|
-
if (attempt) return {stepId: step.id, attemptId: attempt.id};
|
|
463
|
-
}
|
|
464
|
-
return undefined;
|
|
465
|
-
}
|