@shipfox/client-workflows 15.0.0 → 17.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/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-type.log +1 -0
- package/CHANGELOG.md +41 -0
- package/dist/components/identifier/identifier.js +2 -2
- package/dist/components/identifier/identifier.js.map +1 -1
- package/dist/components/job-detail/agent-config-failure-callout.d.ts.map +1 -1
- package/dist/components/job-detail/agent-config-failure-callout.js +1 -1
- package/dist/components/job-detail/agent-config-failure-callout.js.map +1 -1
- package/dist/components/job-detail/job-context-panel.d.ts +6 -0
- package/dist/components/job-detail/job-context-panel.d.ts.map +1 -0
- package/dist/components/job-detail/job-context-panel.js +224 -0
- package/dist/components/job-detail/job-context-panel.js.map +1 -0
- package/dist/components/job-detail/job-detail-header.d.ts +10 -1
- package/dist/components/job-detail/job-detail-header.d.ts.map +1 -1
- package/dist/components/job-detail/job-detail-header.js +58 -39
- package/dist/components/job-detail/job-detail-header.js.map +1 -1
- package/dist/components/job-detail/job-detail-view.d.ts +1 -1
- package/dist/components/job-detail/job-detail-view.d.ts.map +1 -1
- package/dist/components/job-detail/job-detail-view.js +92 -41
- package/dist/components/job-detail/job-detail-view.js.map +1 -1
- package/dist/components/job-detail/job-empty-states.d.ts.map +1 -1
- package/dist/components/job-detail/job-empty-states.js +19 -10
- package/dist/components/job-detail/job-empty-states.js.map +1 -1
- package/dist/components/job-detail/job-execution-switcher.js +4 -4
- package/dist/components/job-detail/job-execution-switcher.js.map +1 -1
- package/dist/components/job-detail/json-code.d.ts +14 -0
- package/dist/components/job-detail/json-code.d.ts.map +1 -0
- package/dist/components/job-detail/json-code.js +93 -0
- package/dist/components/job-detail/json-code.js.map +1 -0
- package/dist/components/job-detail/step-attempt-log-panel.d.ts.map +1 -1
- package/dist/components/job-detail/step-attempt-log-panel.js +9 -6
- package/dist/components/job-detail/step-attempt-log-panel.js.map +1 -1
- package/dist/components/job-detail/step-troubleshooting.d.ts +18 -0
- package/dist/components/job-detail/step-troubleshooting.d.ts.map +1 -0
- package/dist/components/job-detail/step-troubleshooting.js +493 -0
- package/dist/components/job-detail/step-troubleshooting.js.map +1 -0
- package/dist/components/job-graph/job-graph-content.d.ts.map +1 -1
- package/dist/components/job-graph/job-graph-content.js +5 -4
- package/dist/components/job-graph/job-graph-content.js.map +1 -1
- package/dist/components/job-graph/job-node.js +3 -3
- package/dist/components/job-graph/job-node.js.map +1 -1
- package/dist/components/step-list/index.d.ts +1 -0
- package/dist/components/step-list/index.d.ts.map +1 -1
- package/dist/components/step-list/index.js.map +1 -1
- package/dist/components/step-list/step-list-model.d.ts.map +1 -1
- package/dist/components/step-list/step-list-model.js +5 -0
- package/dist/components/step-list/step-list-model.js.map +1 -1
- package/dist/components/step-list/step-list.d.ts +5 -1
- package/dist/components/step-list/step-list.d.ts.map +1 -1
- package/dist/components/step-list/step-list.js +117 -82
- package/dist/components/step-list/step-list.js.map +1 -1
- package/dist/components/workflow-run-duration-label.js +1 -1
- package/dist/components/workflow-run-duration-label.js.map +1 -1
- package/dist/components/workflow-run-list/job-status-strip.js +6 -6
- package/dist/components/workflow-run-list/job-status-strip.js.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-filter-menu.js +1 -1
- package/dist/components/workflow-run-list/workflow-run-filter-menu.js.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-filters.js +4 -4
- package/dist/components/workflow-run-list/workflow-run-filters.js.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-list-states.js +6 -6
- package/dist/components/workflow-run-list/workflow-run-list-states.js.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-list-view.js +1 -1
- package/dist/components/workflow-run-list/workflow-run-list-view.js.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-row.js +7 -7
- package/dist/components/workflow-run-list/workflow-run-row.js.map +1 -1
- package/dist/components/workflow-run-summary/workflow-run-attempt-switcher.js +1 -1
- package/dist/components/workflow-run-summary/workflow-run-attempt-switcher.js.map +1 -1
- package/dist/components/workflow-run-summary/workflow-run-summary.d.ts +0 -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 +9 -13
- package/dist/components/workflow-run-summary/workflow-run-summary.js.map +1 -1
- package/dist/components/workflow-run-tabs/index.d.ts +3 -0
- package/dist/components/workflow-run-tabs/index.d.ts.map +1 -1
- package/dist/components/workflow-run-tabs/index.js +3 -0
- 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 +39 -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 +211 -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 +90 -25
- package/dist/components/workflow-run-tabs/run-annotation-summary-line.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/run-workspace-nav.d.ts +1 -1
- package/dist/components/workflow-run-view/run-workspace-nav.d.ts.map +1 -1
- package/dist/components/workflow-run-view/run-workspace-nav.js +16 -10
- package/dist/components/workflow-run-view/run-workspace-nav.js.map +1 -1
- package/dist/components/workflow-run-view/workflow-run-states.js +6 -6
- package/dist/components/workflow-run-view/workflow-run-states.js.map +1 -1
- package/dist/components/workflow-run-view/workflow-run-view.d.ts +1 -3
- package/dist/components/workflow-run-view/workflow-run-view.d.ts.map +1 -1
- package/dist/components/workflow-run-view/workflow-run-view.js +217 -59
- package/dist/components/workflow-run-view/workflow-run-view.js.map +1 -1
- package/dist/core/entities/job-execution.d.ts +7 -0
- package/dist/core/entities/job-execution.d.ts.map +1 -1
- package/dist/core/entities/job-execution.js.map +1 -1
- package/dist/core/entities/job.d.ts +9 -0
- package/dist/core/entities/job.d.ts.map +1 -1
- package/dist/core/entities/job.js.map +1 -1
- package/dist/core/entities/step-attempt.d.ts +29 -0
- package/dist/core/entities/step-attempt.d.ts.map +1 -1
- package/dist/core/entities/step-attempt.js.map +1 -1
- package/dist/core/entities/step.d.ts +3 -1
- package/dist/core/entities/step.d.ts.map +1 -1
- package/dist/core/entities/step.js.map +1 -1
- package/dist/core/run-annotation.d.ts +79 -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 +1 -1
- package/dist/core/workflow-run.d.ts.map +1 -1
- package/dist/core/workflow-run.js.map +1 -1
- package/dist/hooks/api/annotations.d.ts +63 -0
- package/dist/hooks/api/annotations.d.ts.map +1 -0
- package/dist/hooks/api/annotations.js +145 -0
- package/dist/hooks/api/annotations.js.map +1 -0
- 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/step-attempt-detail.d.ts +14 -0
- package/dist/hooks/api/step-attempt-detail.d.ts.map +1 -0
- package/dist/hooks/api/step-attempt-detail.js +43 -0
- package/dist/hooks/api/step-attempt-detail.js.map +1 -0
- package/dist/hooks/api/workflow-run-mapper.d.ts +9 -2
- package/dist/hooks/api/workflow-run-mapper.d.ts.map +1 -1
- package/dist/hooks/api/workflow-run-mapper.js +47 -0
- package/dist/hooks/api/workflow-run-mapper.js.map +1 -1
- package/dist/pages/project-workflows-page.js +19 -19
- package/dist/pages/project-workflows-page.js.map +1 -1
- package/dist/pages/workflow-run-list-page.js +1 -1
- package/dist/pages/workflow-run-list-page.js.map +1 -1
- package/dist/routes/inputs.d.ts +7 -2
- package/dist/routes/inputs.d.ts.map +1 -1
- package/dist/routes/inputs.js +5 -6
- package/dist/routes/inputs.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +10 -9
- package/src/components/identifier/identifier.tsx +2 -2
- package/src/components/job-detail/agent-config-failure-callout.tsx +5 -1
- package/src/components/job-detail/job-context-panel.test.tsx +72 -0
- package/src/components/job-detail/job-context-panel.tsx +237 -0
- package/src/components/job-detail/job-detail-header.test.tsx +3 -0
- package/src/components/job-detail/job-detail-header.tsx +65 -34
- package/src/components/job-detail/job-detail-view.stories.tsx +555 -48
- package/src/components/job-detail/job-detail-view.tsx +101 -42
- package/src/components/job-detail/job-empty-states.tsx +23 -10
- package/src/components/job-detail/job-execution-switcher.tsx +5 -5
- package/src/components/job-detail/json-code.tsx +120 -0
- package/src/components/job-detail/step-attempt-log-panel.test.tsx +1 -0
- package/src/components/job-detail/step-attempt-log-panel.tsx +17 -6
- package/src/components/job-detail/step-troubleshooting.stories.tsx +120 -0
- package/src/components/job-detail/step-troubleshooting.test.tsx +337 -0
- package/src/components/job-detail/step-troubleshooting.tsx +537 -0
- package/src/components/job-graph/job-graph-content.tsx +4 -3
- package/src/components/job-graph/job-node.tsx +3 -3
- package/src/components/step-list/index.ts +1 -0
- package/src/components/step-list/step-list-model.ts +5 -0
- package/src/components/step-list/step-list.test.tsx +75 -2
- package/src/components/step-list/step-list.tsx +73 -20
- package/src/components/workflow-run-duration-label.tsx +1 -1
- package/src/components/workflow-run-list/job-status-strip.tsx +6 -6
- package/src/components/workflow-run-list/workflow-run-filter-menu.tsx +1 -1
- package/src/components/workflow-run-list/workflow-run-filters.tsx +4 -4
- package/src/components/workflow-run-list/workflow-run-list-states.tsx +6 -6
- package/src/components/workflow-run-list/workflow-run-list-view.tsx +1 -1
- package/src/components/workflow-run-list/workflow-run-row.tsx +7 -7
- package/src/components/workflow-run-summary/workflow-run-attempt-switcher.tsx +1 -1
- package/src/components/workflow-run-summary/workflow-run-summary.test.tsx +1 -1
- package/src/components/workflow-run-summary/workflow-run-summary.tsx +12 -12
- package/src/components/workflow-run-tabs/index.ts +7 -0
- 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 +273 -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 +95 -20
- 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/run-workspace-nav.test.tsx +44 -0
- package/src/components/workflow-run-view/run-workspace-nav.tsx +22 -9
- package/src/components/workflow-run-view/workflow-run-states.tsx +6 -6
- package/src/components/workflow-run-view/workflow-run-view.test.tsx +246 -20
- package/src/components/workflow-run-view/workflow-run-view.tsx +255 -37
- package/src/core/entities/job-execution.ts +7 -0
- package/src/core/entities/job.ts +9 -0
- package/src/core/entities/step-attempt.ts +33 -0
- package/src/core/entities/step.ts +3 -1
- package/src/core/run-annotation.test.ts +315 -0
- package/src/core/run-annotation.ts +244 -0
- package/src/core/workflow-run.ts +4 -0
- package/src/hooks/api/annotations.test.ts +84 -0
- package/src/hooks/api/annotations.ts +240 -0
- package/src/hooks/api/run-annotation-mapper.ts +30 -0
- package/src/hooks/api/run-annotations.ts +137 -0
- package/src/hooks/api/step-attempt-detail.ts +67 -0
- package/src/hooks/api/workflow-run-mapper.ts +46 -0
- package/src/hooks/api/workflow-runs.test.tsx +61 -1
- package/src/pages/project-workflows-page.tsx +19 -19
- package/src/pages/workflow-job-detail-page.test.tsx +118 -1
- package/src/pages/workflow-run-list-page.tsx +1 -1
- package/src/routes/inputs.ts +7 -2
- package/test/fixtures/workflow-run.ts +7 -0
- package/tsconfig.build.tsbuildinfo +1 -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/core/workflow-run-tabs.ts +0 -8
|
@@ -9,8 +9,12 @@ import {Skeleton} from '@shipfox/react-ui/skeleton';
|
|
|
9
9
|
import {TimeTickerProvider} from '@shipfox/react-ui/time-ticker';
|
|
10
10
|
import {Text} from '@shipfox/react-ui/typography';
|
|
11
11
|
import {Link} from '@tanstack/react-router';
|
|
12
|
-
import {type RefObject, useEffect, useRef} from 'react';
|
|
13
|
-
import type {
|
|
12
|
+
import {type RefObject, useEffect, useRef, useState} from 'react';
|
|
13
|
+
import type {RunAnnotationSummary} from '#core/run-annotation.js';
|
|
14
|
+
import {summarizeJobAnnotations} from '#core/run-annotation.js';
|
|
15
|
+
import {isWorkflowRunTerminal, type Job, type JobExecution} from '#core/workflow-run.js';
|
|
16
|
+
import {useWorkflowRunAnnotationSummaryQuery} from '#hooks/api/annotations.js';
|
|
17
|
+
import {useRunAnnotationsQuery} from '#hooks/api/run-annotations.js';
|
|
14
18
|
import type {useWorkflowRunAttemptQuery} from '#hooks/api/workflow-runs.js';
|
|
15
19
|
import {
|
|
16
20
|
type WorkflowJobSearch,
|
|
@@ -22,15 +26,13 @@ import {
|
|
|
22
26
|
WorkflowRunNotFound,
|
|
23
27
|
WorkflowRunStaleError,
|
|
24
28
|
} from '../workflow-run-view/workflow-run-states.js';
|
|
25
|
-
import {
|
|
29
|
+
import {JobContextPanel} from './job-context-panel.js';
|
|
26
30
|
import {JobDetailHeader} from './job-detail-header.js';
|
|
27
31
|
import {
|
|
28
32
|
CarriedOverStepPanel,
|
|
29
33
|
emptyStateForJob,
|
|
30
34
|
emptyStateForMissingExecution,
|
|
31
|
-
isAgentConfigFailure,
|
|
32
35
|
jobSucceededSummary,
|
|
33
|
-
toSelectedAttemptError,
|
|
34
36
|
} from './job-empty-states.js';
|
|
35
37
|
import {
|
|
36
38
|
resolveWorkflowJobSelection,
|
|
@@ -38,6 +40,9 @@ import {
|
|
|
38
40
|
workflowJobLandingSelection,
|
|
39
41
|
} from './job-selection.js';
|
|
40
42
|
import {StepAttemptLogPanel} from './step-attempt-log-panel.js';
|
|
43
|
+
import {StepInspectorSheet} from './step-troubleshooting.js';
|
|
44
|
+
|
|
45
|
+
type InspectorState = {key: string; attemptId: string | null};
|
|
41
46
|
|
|
42
47
|
export interface JobDetailViewProps {
|
|
43
48
|
workspaceSlug: string;
|
|
@@ -66,6 +71,37 @@ export function JobDetailView({
|
|
|
66
71
|
const pageScrollRef = useRef<HTMLDivElement>(null);
|
|
67
72
|
const landingSelectionRef = useRef<FrozenLandingSelection | undefined>(undefined);
|
|
68
73
|
const hasLoadedData = query.data !== undefined;
|
|
74
|
+
// Reuse the run workspace's bounded annotation read for the job header chip. The separate
|
|
75
|
+
// summary query below stays counts-only and is scoped to the inspector's selected execution.
|
|
76
|
+
const annotations = useRunAnnotationsQuery({
|
|
77
|
+
workflowRunId,
|
|
78
|
+
runAttempt: query.data?.runAttempt.attempt,
|
|
79
|
+
});
|
|
80
|
+
const jobAnnotationSummary = annotations.annotations
|
|
81
|
+
? summarizeJobAnnotations(annotations.annotations, jobId, {
|
|
82
|
+
truncated: annotations.summary?.truncated ?? false,
|
|
83
|
+
})
|
|
84
|
+
: undefined;
|
|
85
|
+
const inspectorResetKey = `${jobId}:${search.jobExecutionId ?? ''}`;
|
|
86
|
+
const [inspectorState, setInspectorState] = useState<InspectorState>(() => ({
|
|
87
|
+
key: inspectorResetKey,
|
|
88
|
+
attemptId: null,
|
|
89
|
+
}));
|
|
90
|
+
const inspectorOpenAttemptId =
|
|
91
|
+
inspectorState.key === inspectorResetKey ? inspectorState.attemptId : null;
|
|
92
|
+
const annotationJob = query.data?.jobs.find((candidate) => candidate.id === jobId);
|
|
93
|
+
const annotationExecutionId = annotationJob
|
|
94
|
+
? resolveWorkflowJobSelection({job: annotationJob, selection: search}).jobExecution?.id
|
|
95
|
+
: search.jobExecutionId;
|
|
96
|
+
const annotationPolling = query.data
|
|
97
|
+
? !isWorkflowRunTerminal(query.data.runAttempt.status)
|
|
98
|
+
: true;
|
|
99
|
+
const annotationSummaryQuery = useWorkflowRunAnnotationSummaryQuery(
|
|
100
|
+
query.data?.id,
|
|
101
|
+
query.data?.runAttempt.attempt,
|
|
102
|
+
annotationExecutionId,
|
|
103
|
+
{polling: annotationPolling},
|
|
104
|
+
);
|
|
69
105
|
|
|
70
106
|
useEffect(() => {
|
|
71
107
|
if (!jobId || !hasLoadedData) return;
|
|
@@ -173,6 +209,10 @@ export function JobDetailView({
|
|
|
173
209
|
});
|
|
174
210
|
}
|
|
175
211
|
|
|
212
|
+
function onInspectorOpenChange(attemptId: string | null) {
|
|
213
|
+
setInspectorState({key: inspectorResetKey, attemptId});
|
|
214
|
+
}
|
|
215
|
+
|
|
176
216
|
function retargetToRunningStep() {
|
|
177
217
|
if (!runningSelection || !selectedJobExecution) return;
|
|
178
218
|
onSelectionChange({
|
|
@@ -196,16 +236,23 @@ export function JobDetailView({
|
|
|
196
236
|
attempt={newerAttempt}
|
|
197
237
|
/>
|
|
198
238
|
) : null}
|
|
199
|
-
<div ref={pageScrollRef} className="@container min-h-0 flex-1 overflow-auto pb-
|
|
200
|
-
<section
|
|
201
|
-
aria-label={`${job.displayName} logs`}
|
|
202
|
-
className="mx-auto flex w-full max-w-[1120px] flex-col gap-8 px-24"
|
|
203
|
-
>
|
|
239
|
+
<div ref={pageScrollRef} className="@container min-h-0 flex-1 overflow-auto pb-panel">
|
|
240
|
+
<section aria-label={`${job.displayName} logs`} className="flex w-full flex-col">
|
|
204
241
|
<section className="min-w-0 overflow-hidden">
|
|
205
242
|
<JobDetailHeader
|
|
206
243
|
job={job}
|
|
207
244
|
selectedJobExecution={selectedJobExecution}
|
|
208
245
|
onSelectedJobExecutionChange={selectExecution}
|
|
246
|
+
workspaceSlug={workspaceSlug}
|
|
247
|
+
projectSlug={projectSlug}
|
|
248
|
+
workflowRunId={run.id}
|
|
249
|
+
runAttempt={run.runAttempt.attempt}
|
|
250
|
+
annotationSummary={jobAnnotationSummary}
|
|
251
|
+
jobContext={
|
|
252
|
+
selectedJobExecution ? (
|
|
253
|
+
<JobContextPanel job={job} execution={selectedJobExecution} />
|
|
254
|
+
) : undefined
|
|
255
|
+
}
|
|
209
256
|
/>
|
|
210
257
|
<Text as="h2" className="sr-only">
|
|
211
258
|
Logs
|
|
@@ -217,15 +264,30 @@ export function JobDetailView({
|
|
|
217
264
|
selectedAttemptId={selectedAttemptId}
|
|
218
265
|
defaultSelectedAttemptId={landingSelection?.attemptId}
|
|
219
266
|
onSelectedAttemptChange={selectAttempt}
|
|
267
|
+
inspectorOpenAttemptId={inspectorOpenAttemptId}
|
|
268
|
+
onInspectorOpenChange={onInspectorOpenChange}
|
|
220
269
|
autoSelectActiveAttempt
|
|
221
270
|
emptyState={emptyStateForJob(job, selectedJobExecution)}
|
|
222
271
|
showHeader={false}
|
|
223
|
-
className="rounded-none border-0"
|
|
272
|
+
className="rounded-none border-0 bg-transparent"
|
|
224
273
|
renderExpandedStep={(context) => (
|
|
225
|
-
<ExpandedStep
|
|
226
|
-
|
|
274
|
+
<ExpandedStep context={context} pageScrollRef={pageScrollRef} />
|
|
275
|
+
)}
|
|
276
|
+
renderInspector={(entry) => (
|
|
277
|
+
<StepInspectorSheet
|
|
278
|
+
entry={entry}
|
|
279
|
+
open
|
|
280
|
+
onOpenChange={(open) => onInspectorOpenChange(open ? entry.id : null)}
|
|
227
281
|
workspaceSlug={workspaceSlug}
|
|
228
|
-
|
|
282
|
+
projectSlug={projectSlug}
|
|
283
|
+
workflowRunId={run.id}
|
|
284
|
+
runAttempt={run.runAttempt.attempt}
|
|
285
|
+
jobId={job.id}
|
|
286
|
+
annotationCount={annotationCountForStep(
|
|
287
|
+
annotationSummaryQuery.data,
|
|
288
|
+
entry.step.id,
|
|
289
|
+
entry.attempt,
|
|
290
|
+
)}
|
|
229
291
|
/>
|
|
230
292
|
)}
|
|
231
293
|
/>
|
|
@@ -237,7 +299,7 @@ export function JobDetailView({
|
|
|
237
299
|
<div
|
|
238
300
|
role="status"
|
|
239
301
|
aria-live="polite"
|
|
240
|
-
className="flex min-w-0 items-center justify-between gap-
|
|
302
|
+
className="flex min-w-0 items-center justify-between gap-inline border-t border-border-neutral-base px-row py-row"
|
|
241
303
|
>
|
|
242
304
|
<Text size="xs" className="min-w-0 text-foreground-neutral-muted">
|
|
243
305
|
Run moved on to{' '}
|
|
@@ -248,7 +310,7 @@ export function JobDetailView({
|
|
|
248
310
|
</Text>
|
|
249
311
|
<button
|
|
250
312
|
type="button"
|
|
251
|
-
className="shrink-0 rounded-4 px-
|
|
313
|
+
className="shrink-0 rounded-4 px-tight py-[4px] text-xs font-medium text-foreground-highlight-interactive outline-none focus-visible:shadow-border-interactive-with-active"
|
|
252
314
|
onClick={retargetToRunningStep}
|
|
253
315
|
>
|
|
254
316
|
Jump to it
|
|
@@ -256,7 +318,7 @@ export function JobDetailView({
|
|
|
256
318
|
</div>
|
|
257
319
|
) : null}
|
|
258
320
|
{succeededSummary ? (
|
|
259
|
-
<Text size="xs" className="px-
|
|
321
|
+
<Text size="xs" className="px-row py-row text-foreground-neutral-muted">
|
|
260
322
|
{succeededSummary}
|
|
261
323
|
</Text>
|
|
262
324
|
) : null}
|
|
@@ -269,39 +331,27 @@ export function JobDetailView({
|
|
|
269
331
|
|
|
270
332
|
function ExpandedStep({
|
|
271
333
|
context,
|
|
272
|
-
workspaceSlug,
|
|
273
334
|
pageScrollRef,
|
|
274
335
|
}: {
|
|
275
336
|
context: StepExpandedContext;
|
|
276
|
-
workspaceSlug: string;
|
|
277
337
|
pageScrollRef: RefObject<HTMLDivElement | null>;
|
|
278
338
|
}) {
|
|
279
339
|
if (context.carriedOver) return <CarriedOverStepPanel />;
|
|
280
340
|
|
|
281
|
-
const selectedAttemptError =
|
|
282
|
-
toSelectedAttemptError(context.step, context.attemptError) ?? context.step.error;
|
|
283
|
-
|
|
284
341
|
return (
|
|
285
342
|
<section
|
|
286
343
|
role="region"
|
|
287
344
|
tabIndex={0}
|
|
288
345
|
aria-label={`${context.stepLabel} output, attempt ${context.attempt}`}
|
|
289
|
-
className="flex min-w-0 flex-col
|
|
346
|
+
className="flex min-w-0 flex-col border-t border-border-neutral-base bg-background-neutral-base outline-none focus-visible:shadow-border-interactive-with-active dark:bg-background-contrast-subtle"
|
|
290
347
|
>
|
|
291
|
-
{isAgentConfigFailure(context.step, selectedAttemptError) ? (
|
|
292
|
-
<AgentConfigFailureCallout
|
|
293
|
-
workspaceSlug={workspaceSlug}
|
|
294
|
-
config={context.step.agentConfig}
|
|
295
|
-
error={selectedAttemptError}
|
|
296
|
-
/>
|
|
297
|
-
) : null}
|
|
298
348
|
<StepAttemptLogPanel
|
|
299
349
|
stepId={context.stepId}
|
|
300
350
|
attempt={context.attempt}
|
|
301
351
|
attemptStatus={context.attemptStatus}
|
|
302
352
|
attemptStartedAt={context.attemptStartedAt}
|
|
303
353
|
pageScrollRef={pageScrollRef}
|
|
304
|
-
surfaceClassName="rounded-
|
|
354
|
+
surfaceClassName="rounded-none border-0 bg-transparent shadow-none"
|
|
305
355
|
/>
|
|
306
356
|
</section>
|
|
307
357
|
);
|
|
@@ -311,7 +361,7 @@ function EmptyStateForMissingExecution({job}: {job: Job}) {
|
|
|
311
361
|
const emptyState = emptyStateForMissingExecution(job);
|
|
312
362
|
return (
|
|
313
363
|
<EmptyState
|
|
314
|
-
className="min-h-120
|
|
364
|
+
className="min-h-120 p-panel"
|
|
315
365
|
icon="componentLine"
|
|
316
366
|
title={emptyState.title}
|
|
317
367
|
description={emptyState.description}
|
|
@@ -332,7 +382,7 @@ function JobNotFoundState({
|
|
|
332
382
|
search: WorkflowJobSearch;
|
|
333
383
|
}) {
|
|
334
384
|
return (
|
|
335
|
-
<div className="flex min-h-0 min-w-0 flex-1 flex-col items-center justify-center gap-
|
|
385
|
+
<div className="flex min-h-0 min-w-0 flex-1 flex-col items-center justify-center gap-cluster p-panel">
|
|
336
386
|
<EmptyState
|
|
337
387
|
icon="componentLine"
|
|
338
388
|
title="Job not found"
|
|
@@ -347,7 +397,7 @@ function JobNotFoundState({
|
|
|
347
397
|
{runAttempt: search.runAttempt},
|
|
348
398
|
) as never
|
|
349
399
|
}
|
|
350
|
-
className="inline-flex items-center gap-
|
|
400
|
+
className="inline-flex items-center gap-inline rounded-6 px-tight py-[6px] text-sm text-foreground-highlight-interactive outline-none focus-visible:shadow-border-interactive-with-active"
|
|
351
401
|
>
|
|
352
402
|
<Icon name="arrowLeftLine" size={14} aria-hidden="true" />
|
|
353
403
|
Back to run summary
|
|
@@ -370,8 +420,8 @@ function NewerAttemptNotice({
|
|
|
370
420
|
attempt: number;
|
|
371
421
|
}) {
|
|
372
422
|
return (
|
|
373
|
-
<div role="status" className="border-b border-border-neutral-base px-
|
|
374
|
-
<div className="
|
|
423
|
+
<div role="status" className="border-b border-border-neutral-base px-row py-row">
|
|
424
|
+
<div className="flex items-center justify-between gap-inline">
|
|
375
425
|
<Text size="xs" className="text-foreground-neutral-muted">
|
|
376
426
|
A newer run attempt is available.
|
|
377
427
|
</Text>
|
|
@@ -379,7 +429,7 @@ function NewerAttemptNotice({
|
|
|
379
429
|
to="/w/$workspaceSlug/p/$projectSlug/runs/$workflowRunId/jobs/$jobId"
|
|
380
430
|
params={{workspaceSlug, projectSlug, workflowRunId: runId, jobId}}
|
|
381
431
|
search={workflowJobSearchParams({runAttempt: attempt}) as never}
|
|
382
|
-
className="shrink-0 rounded-4 px-
|
|
432
|
+
className="shrink-0 rounded-4 px-tight py-[4px] text-xs font-medium text-foreground-highlight-interactive outline-none focus-visible:shadow-border-interactive-with-active"
|
|
383
433
|
>
|
|
384
434
|
View attempt #{attempt}
|
|
385
435
|
</Link>
|
|
@@ -414,21 +464,30 @@ function findAttempt(jobExecution: JobExecution, attemptId: string) {
|
|
|
414
464
|
return undefined;
|
|
415
465
|
}
|
|
416
466
|
|
|
467
|
+
function annotationCountForStep(
|
|
468
|
+
summary: RunAnnotationSummary | undefined,
|
|
469
|
+
stepId: string,
|
|
470
|
+
attempt: number,
|
|
471
|
+
): number | undefined {
|
|
472
|
+
return summary?.stepCounts?.find((entry) => entry.stepId === stepId && entry.attempt === attempt)
|
|
473
|
+
?.total;
|
|
474
|
+
}
|
|
475
|
+
|
|
417
476
|
function JobDetailSkeleton() {
|
|
418
477
|
return (
|
|
419
|
-
<div className="min-h-0 min-w-0 flex-1 overflow-auto
|
|
420
|
-
<div className="
|
|
421
|
-
<header className="flex items-center gap-
|
|
478
|
+
<div className="min-h-0 min-w-0 flex-1 overflow-auto pb-panel">
|
|
479
|
+
<div className="w-full">
|
|
480
|
+
<header className="flex items-center gap-cluster border-b border-border-neutral-base px-row py-row">
|
|
422
481
|
<Skeleton className="size-20 rounded-full" />
|
|
423
482
|
<Skeleton className="h-20 w-160 rounded-4" />
|
|
424
483
|
<Skeleton className="h-24 w-72 rounded-6" />
|
|
425
484
|
<Skeleton className="h-24 w-180 rounded-4" />
|
|
426
485
|
</header>
|
|
427
|
-
<div className="p-
|
|
486
|
+
<div className="p-panel-compact">
|
|
428
487
|
{JOB_DETAIL_SKELETON_ROWS.map((row) => (
|
|
429
488
|
<div
|
|
430
489
|
key={row}
|
|
431
|
-
className="flex min-h-44 items-center gap-
|
|
490
|
+
className="flex min-h-44 items-center gap-inline border-b border-border-neutral-base last:border-b-0"
|
|
432
491
|
>
|
|
433
492
|
<Skeleton className="size-14 rounded-full" />
|
|
434
493
|
<Skeleton className="h-16 w-180 rounded-4" />
|
|
@@ -25,11 +25,14 @@ export function emptyStateForJob(
|
|
|
25
25
|
job.mode === 'listening'
|
|
26
26
|
? deriveJobExecutionDisplayStatus(jobExecution)
|
|
27
27
|
: deriveJobDisplayStatus(job);
|
|
28
|
+
const runner = jobExecution.runner?.length ? jobExecution.runner : job.runner;
|
|
28
29
|
|
|
29
30
|
if (jobExecution.status === 'running' && displayStatus === 'pending') {
|
|
30
31
|
return {
|
|
31
|
-
title: 'Runner preparing job',
|
|
32
|
-
description:
|
|
32
|
+
title: runner?.length ? 'Runner preparing job' : 'Waiting for a runner',
|
|
33
|
+
description: runner?.length
|
|
34
|
+
? `Runner ${runner.join(', ')} is preparing the job. Steps will appear here when work begins.`
|
|
35
|
+
: 'No runner has claimed this job yet. Steps will appear here when a runner starts work.',
|
|
33
36
|
status: displayStatus,
|
|
34
37
|
};
|
|
35
38
|
}
|
|
@@ -61,7 +64,7 @@ export function emptyStateForJob(
|
|
|
61
64
|
if (displayStatus === 'failed') {
|
|
62
65
|
return {
|
|
63
66
|
title: 'Job failed before its first step started',
|
|
64
|
-
description: preStepFailureDescription(jobExecution.statusReason ?? job.statusReason),
|
|
67
|
+
description: preStepFailureDescription(jobExecution.statusReason ?? job.statusReason, runner),
|
|
65
68
|
status: displayStatus,
|
|
66
69
|
};
|
|
67
70
|
}
|
|
@@ -126,6 +129,14 @@ export function emptyStateForMissingExecution(job: Job): StepListEmptyState {
|
|
|
126
129
|
};
|
|
127
130
|
}
|
|
128
131
|
|
|
132
|
+
if (job.status === 'failed') {
|
|
133
|
+
return {
|
|
134
|
+
title: 'Job failed before an execution was created',
|
|
135
|
+
description: preStepFailureDescription(job.statusReason, job.runner),
|
|
136
|
+
status: 'failed',
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
129
140
|
return {
|
|
130
141
|
title: 'Execution details unavailable',
|
|
131
142
|
description: 'This job finished, but no job execution record is available.',
|
|
@@ -155,17 +166,19 @@ export function skippedJobDescription(reason: Job['statusReason']): string {
|
|
|
155
166
|
}
|
|
156
167
|
}
|
|
157
168
|
|
|
158
|
-
function preStepFailureDescription(reason: string | null): string {
|
|
169
|
+
function preStepFailureDescription(reason: string | null, runner: string[] | null = null): string {
|
|
170
|
+
const runnerCopy = runner?.length ? ` Required runner labels: ${runner.join(', ')}.` : '';
|
|
171
|
+
|
|
159
172
|
switch (reason) {
|
|
160
173
|
case 'runner_lost':
|
|
161
|
-
return
|
|
174
|
+
return `The runner stopped responding before work began.${runnerCopy} Check runner availability before re-running the workflow.`;
|
|
162
175
|
case 'timed_out':
|
|
163
|
-
return
|
|
176
|
+
return `The job timed out before work began.${runnerCopy} Check runner capacity and timeout configuration before re-running the workflow.`;
|
|
164
177
|
case 'user_cancelled':
|
|
165
178
|
case 'run_cancelled':
|
|
166
179
|
return 'The execution ended while the run was being cancelled.';
|
|
167
180
|
case 'step_failed':
|
|
168
|
-
return
|
|
181
|
+
return `The execution failed before step details were recorded.${runnerCopy} Review run annotations before re-running the workflow.`;
|
|
169
182
|
case 'condition_errored':
|
|
170
183
|
return 'The job condition could not be evaluated. Review run annotations before re-running the workflow.';
|
|
171
184
|
case 'dependency_not_completed':
|
|
@@ -176,9 +189,9 @@ function preStepFailureDescription(reason: string | null): string {
|
|
|
176
189
|
return 'The job condition did not match, so no work started.';
|
|
177
190
|
case 'unknown':
|
|
178
191
|
case null:
|
|
179
|
-
return
|
|
192
|
+
return `Shipfox did not record a failure reason.${runnerCopy} Re-run only after checking the runner and workflow configuration.`;
|
|
180
193
|
default:
|
|
181
|
-
return `The execution ended because ${humanizeFailureReason(reason)}
|
|
194
|
+
return `The execution ended because ${humanizeFailureReason(reason)}.${runnerCopy} Resolve that condition before re-running the workflow.`;
|
|
182
195
|
}
|
|
183
196
|
}
|
|
184
197
|
|
|
@@ -189,7 +202,7 @@ function humanizeFailureReason(reason: string): string {
|
|
|
189
202
|
export function CarriedOverStepPanel() {
|
|
190
203
|
return (
|
|
191
204
|
<EmptyState
|
|
192
|
-
className="min-h-120 rounded-8 border border-border-neutral-base bg-background-components-base
|
|
205
|
+
className="min-h-120 rounded-8 border border-border-neutral-base bg-background-components-base p-panel"
|
|
193
206
|
icon="componentLine"
|
|
194
207
|
title="Carried over from a previous attempt"
|
|
195
208
|
description="Not executed in this run."
|
|
@@ -44,7 +44,7 @@ export function JobExecutionSwitcher({
|
|
|
44
44
|
return (
|
|
45
45
|
<div
|
|
46
46
|
className={cn(
|
|
47
|
-
'flex min-w-0 items-center gap-
|
|
47
|
+
'flex min-w-0 items-center gap-inline text-sm leading-20 text-foreground-neutral-subtle',
|
|
48
48
|
className,
|
|
49
49
|
)}
|
|
50
50
|
>
|
|
@@ -59,8 +59,8 @@ export function JobExecutionSwitcher({
|
|
|
59
59
|
className={cn(
|
|
60
60
|
'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',
|
|
61
61
|
variant === 'title'
|
|
62
|
-
? '-mx-
|
|
63
|
-
: 'min-h-28 gap-
|
|
62
|
+
? '-mx-inline -my-[4px] gap-inline px-tight py-[4px] hover:bg-background-components-hover'
|
|
63
|
+
: 'min-h-28 gap-inline px-tight py-[4px] text-sm leading-20 text-foreground-neutral-subtle hover:bg-background-components-hover',
|
|
64
64
|
className,
|
|
65
65
|
)}
|
|
66
66
|
aria-label={`Switch job execution, currently execution ${selected.sequence}: ${selected.displayName}`}
|
|
@@ -141,7 +141,7 @@ function executionAccessibleLabel(execution: JobExecution): string {
|
|
|
141
141
|
|
|
142
142
|
function TitleExecutionSummary({execution}: {execution: JobExecution}) {
|
|
143
143
|
return (
|
|
144
|
-
<span className="flex min-w-0 items-center gap-
|
|
144
|
+
<span className="flex min-w-0 items-center gap-inline">
|
|
145
145
|
<Text as="span" size="sm" bold className="shrink-0 text-foreground-neutral-base">
|
|
146
146
|
#{execution.sequence}
|
|
147
147
|
</Text>
|
|
@@ -156,7 +156,7 @@ function ExecutionSummary({execution}: {execution: JobExecution}) {
|
|
|
156
156
|
const displayStatus = deriveJobExecutionDisplayStatus(execution);
|
|
157
157
|
|
|
158
158
|
return (
|
|
159
|
-
<span className="flex min-w-0 items-center gap-
|
|
159
|
+
<span className="flex min-w-0 items-center gap-inline">
|
|
160
160
|
<WorkflowStatusIcon status={displayStatus} size={14} tooltip={false} />
|
|
161
161
|
<span className="shrink-0 font-code text-xs leading-20 text-foreground-neutral-base tabular-nums">
|
|
162
162
|
Execution #{execution.sequence}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CodeBlock,
|
|
3
|
+
CodeBlockBody,
|
|
4
|
+
CodeBlockContent,
|
|
5
|
+
CodeBlockCopyButton,
|
|
6
|
+
CodeBlockFilename,
|
|
7
|
+
CodeBlockFiles,
|
|
8
|
+
CodeBlockHeader,
|
|
9
|
+
CodeBlockItem,
|
|
10
|
+
CodeTabs,
|
|
11
|
+
} from '@shipfox/react-ui/code-block';
|
|
12
|
+
import {Text} from '@shipfox/react-ui/typography';
|
|
13
|
+
import {useState} from 'react';
|
|
14
|
+
|
|
15
|
+
export interface JsonCodeEntry {
|
|
16
|
+
filename: string;
|
|
17
|
+
label?: string | undefined;
|
|
18
|
+
value: unknown;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function JsonCode({
|
|
22
|
+
title,
|
|
23
|
+
value,
|
|
24
|
+
emptyMessage,
|
|
25
|
+
}: {
|
|
26
|
+
title?: string | undefined;
|
|
27
|
+
value: unknown;
|
|
28
|
+
emptyMessage?: string | undefined;
|
|
29
|
+
}) {
|
|
30
|
+
const filename = title ?? 'value.json';
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<div className="flex min-w-0 flex-col gap-tight">
|
|
34
|
+
{title ? (
|
|
35
|
+
<Text size="xs" className="text-foreground-neutral-muted">
|
|
36
|
+
{title}
|
|
37
|
+
</Text>
|
|
38
|
+
) : null}
|
|
39
|
+
{emptyMessage && isEmptyObject(value) ? (
|
|
40
|
+
<Text size="xs" className="text-foreground-neutral-muted">
|
|
41
|
+
{emptyMessage}
|
|
42
|
+
</Text>
|
|
43
|
+
) : (
|
|
44
|
+
<JsonCodeBlock entries={[{filename, value}]} />
|
|
45
|
+
)}
|
|
46
|
+
</div>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function JsonCodeTabs({entries}: {entries: readonly JsonCodeEntry[]}) {
|
|
51
|
+
const entryKeys = entries.map((entry) => entry.label ?? entry.filename);
|
|
52
|
+
const [activeKey, setActiveKey] = useState(entryKeys[0] ?? '');
|
|
53
|
+
const activeValue = entryKeys.includes(activeKey) ? activeKey : (entryKeys[0] ?? '');
|
|
54
|
+
const codes = Object.fromEntries(
|
|
55
|
+
entries.map((entry) => [entry.label ?? entry.filename, stringifyJson(entry.value)]),
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<CodeTabs
|
|
60
|
+
value={activeValue}
|
|
61
|
+
onValueChange={setActiveKey}
|
|
62
|
+
codes={codes}
|
|
63
|
+
lang="json"
|
|
64
|
+
syntaxHighlighting={false}
|
|
65
|
+
className="h-auto min-h-0 rounded-8 bg-background-contrast-base shadow-button-neutral"
|
|
66
|
+
/>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function JsonCodeBlock({entries}: {entries: readonly JsonCodeEntry[]}) {
|
|
71
|
+
const data = entries.map((entry) => ({
|
|
72
|
+
filename: entry.filename,
|
|
73
|
+
language: 'json',
|
|
74
|
+
code: stringifyJson(entry.value),
|
|
75
|
+
}));
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<CodeBlock
|
|
79
|
+
data={data}
|
|
80
|
+
className="h-auto min-h-0 rounded-6 bg-background-contrast-base shadow-button-neutral"
|
|
81
|
+
>
|
|
82
|
+
<CodeBlockHeader className="bg-background-contrast-base p-tight">
|
|
83
|
+
<CodeBlockFiles>
|
|
84
|
+
{(item) => {
|
|
85
|
+
const entry = entries.find((candidate) => candidate.filename === item.filename);
|
|
86
|
+
return (
|
|
87
|
+
<CodeBlockFilename value={item.filename}>
|
|
88
|
+
{entry?.label ?? item.filename}
|
|
89
|
+
</CodeBlockFilename>
|
|
90
|
+
);
|
|
91
|
+
}}
|
|
92
|
+
</CodeBlockFiles>
|
|
93
|
+
<CodeBlockCopyButton />
|
|
94
|
+
</CodeBlockHeader>
|
|
95
|
+
<CodeBlockBody>
|
|
96
|
+
{(item) => (
|
|
97
|
+
<CodeBlockItem value={item.filename}>
|
|
98
|
+
<CodeBlockContent language="json" syntaxHighlighting={false}>
|
|
99
|
+
{item.code}
|
|
100
|
+
</CodeBlockContent>
|
|
101
|
+
</CodeBlockItem>
|
|
102
|
+
)}
|
|
103
|
+
</CodeBlockBody>
|
|
104
|
+
</CodeBlock>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function isEmptyObject(value: unknown): boolean {
|
|
109
|
+
return (
|
|
110
|
+
typeof value === 'object' &&
|
|
111
|
+
value !== null &&
|
|
112
|
+
!Array.isArray(value) &&
|
|
113
|
+
Object.keys(value).length === 0
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function stringifyJson(value: unknown): string {
|
|
118
|
+
const serialized = JSON.stringify(value, null, 2);
|
|
119
|
+
return serialized === undefined ? String(value) : serialized;
|
|
120
|
+
}
|
|
@@ -203,6 +203,7 @@ describe('StepAttemptLogPanel', () => {
|
|
|
203
203
|
renderPanel({attemptStatus: 'succeeded'});
|
|
204
204
|
|
|
205
205
|
expect(await screen.findByText('hello')).toBeInTheDocument();
|
|
206
|
+
expect(screen.getByRole('log')).toHaveAttribute('aria-live', 'off');
|
|
206
207
|
});
|
|
207
208
|
|
|
208
209
|
test('renders a terminal closed empty stream as no output', async () => {
|
|
@@ -100,10 +100,15 @@ export function StepAttemptLogPanel({
|
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
return (
|
|
103
|
-
<div className="flex min-w-0 flex-col gap-
|
|
103
|
+
<div className="flex min-w-0 flex-col gap-inline">
|
|
104
104
|
{staleError ? (
|
|
105
|
-
<Callout
|
|
106
|
-
|
|
105
|
+
<Callout
|
|
106
|
+
role="alert"
|
|
107
|
+
type="warning"
|
|
108
|
+
variant="secondary"
|
|
109
|
+
className="rounded-none border-b border-border-neutral-base px-0 py-row shadow-none"
|
|
110
|
+
>
|
|
111
|
+
<div className="flex min-w-0 flex-1 items-center justify-between gap-inline">
|
|
107
112
|
<Text size="xs">Could not refresh logs.</Text>
|
|
108
113
|
<Button
|
|
109
114
|
type="button"
|
|
@@ -121,6 +126,7 @@ export function StepAttemptLogPanel({
|
|
|
121
126
|
records={records}
|
|
122
127
|
emptyState={query.data?.complete ? 'complete' : 'pending'}
|
|
123
128
|
anchorToFailure={anchorToFailure}
|
|
129
|
+
ariaLive={attemptStatus === 'running' ? 'polite' : 'off'}
|
|
124
130
|
className={surfaceClassName}
|
|
125
131
|
/>
|
|
126
132
|
</div>
|
|
@@ -138,7 +144,7 @@ function StepLogsLoadingSurface({label, className}: {label: string; className: s
|
|
|
138
144
|
function StepLogsWaitingSurface({startedAt, className}: {startedAt: string; className: string}) {
|
|
139
145
|
return (
|
|
140
146
|
<div role="status" aria-label="Waiting for logs" className={className}>
|
|
141
|
-
<Text size="xs" className="px-
|
|
147
|
+
<Text size="xs" className="px-tight py-row text-foreground-neutral-muted">
|
|
142
148
|
Waiting for output ·{' '}
|
|
143
149
|
<span className="font-code tabular-nums" aria-hidden="true">
|
|
144
150
|
<JobExecutionTimeText time={{state: 'live', fromIso: startedAt}} />
|
|
@@ -174,8 +180,13 @@ function isTerminalAttemptStatus(status: string): boolean {
|
|
|
174
180
|
|
|
175
181
|
function StepLogsError({retrying, onRetry}: {retrying: boolean; onRetry: () => void}) {
|
|
176
182
|
return (
|
|
177
|
-
<Callout
|
|
178
|
-
|
|
183
|
+
<Callout
|
|
184
|
+
role="alert"
|
|
185
|
+
type="error"
|
|
186
|
+
variant="secondary"
|
|
187
|
+
className="rounded-none border-b border-border-neutral-base px-0 py-row shadow-none"
|
|
188
|
+
>
|
|
189
|
+
<div className="flex min-w-0 flex-1 items-center justify-between gap-inline">
|
|
179
190
|
<Text size="xs">Could not load logs.</Text>
|
|
180
191
|
<Button type="button" size="2xs" variant="secondary" isLoading={retrying} onClick={onRetry}>
|
|
181
192
|
Retry
|