@shipfox/client-workflows 16.0.0 → 18.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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +32 -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 +228 -0
- package/dist/components/job-detail/job-context-panel.js.map +1 -0
- package/dist/components/job-detail/job-detail-header.d.ts +3 -1
- package/dist/components/job-detail/job-detail-header.d.ts.map +1 -1
- package/dist/components/job-detail/job-detail-header.js +54 -48
- 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 +100 -57
- package/dist/components/job-detail/job-detail-view.js.map +1 -1
- package/dist/components/job-detail/job-empty-states.d.ts +4 -0
- package/dist/components/job-detail/job-empty-states.d.ts.map +1 -1
- package/dist/components/job-detail/job-empty-states.js +60 -11
- 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 +497 -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 +6 -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 +8 -13
- package/dist/components/workflow-run-summary/workflow-run-summary.js.map +1 -1
- package/dist/components/workflow-run-tabs/index.d.ts +1 -1
- package/dist/components/workflow-run-tabs/index.d.ts.map +1 -1
- package/dist/components/workflow-run-tabs/index.js.map +1 -1
- package/dist/components/workflow-run-tabs/run-annotation-count-chip.js +2 -2
- package/dist/components/workflow-run-tabs/run-annotation-count-chip.js.map +1 -1
- package/dist/components/workflow-run-tabs/run-annotation-list.d.ts +9 -2
- package/dist/components/workflow-run-tabs/run-annotation-list.d.ts.map +1 -1
- package/dist/components/workflow-run-tabs/run-annotation-list.js +28 -19
- package/dist/components/workflow-run-tabs/run-annotation-list.js.map +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 +5 -5
- package/dist/components/workflow-run-tabs/run-annotation-summary-line.js.map +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 +11 -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.map +1 -1
- package/dist/components/workflow-run-view/workflow-run-view.js +72 -12
- package/dist/components/workflow-run-view/workflow-run-view.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.map +1 -1
- package/dist/core/entities/job.d.ts +10 -1
- 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 +6 -0
- package/dist/core/run-annotation.d.ts.map +1 -1
- package/dist/core/run-annotation.js.map +1 -1
- 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/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 +48 -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/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +10 -10
- 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 +241 -0
- package/src/components/job-detail/job-detail-header.tsx +52 -46
- package/src/components/job-detail/job-detail-view.stories.tsx +555 -53
- package/src/components/job-detail/job-detail-view.tsx +105 -59
- package/src/components/job-detail/job-empty-states.test.ts +105 -1
- package/src/components/job-detail/job-empty-states.tsx +77 -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 +541 -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 +6 -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 +11 -11
- package/src/components/workflow-run-tabs/index.ts +5 -1
- package/src/components/workflow-run-tabs/run-annotation-count-chip.tsx +2 -2
- package/src/components/workflow-run-tabs/run-annotation-list.tsx +25 -10
- package/src/components/workflow-run-tabs/run-annotation-summary-line.tsx +5 -5
- package/src/components/workflow-run-view/run-workspace-nav.tsx +18 -11
- package/src/components/workflow-run-view/workflow-run-states.tsx +6 -6
- package/src/components/workflow-run-view/workflow-run-view.tsx +114 -14
- package/src/core/entities/job-execution.ts +9 -0
- package/src/core/entities/job.ts +12 -1
- package/src/core/entities/step-attempt.ts +33 -0
- package/src/core/entities/step.ts +3 -1
- package/src/core/run-annotation.ts +6 -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/step-attempt-detail.ts +67 -0
- package/src/hooks/api/workflow-run-mapper.ts +47 -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 +52 -1
- package/src/pages/workflow-run-list-page.tsx +1 -1
- package/test/fixtures/workflow-run.ts +7 -0
- package/tsconfig.build.tsbuildinfo +1 -1
|
@@ -9,9 +9,11 @@ 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';
|
|
12
|
+
import {type RefObject, useEffect, useRef, useState} from 'react';
|
|
13
|
+
import type {RunAnnotationSummary} from '#core/run-annotation.js';
|
|
13
14
|
import {summarizeJobAnnotations} from '#core/run-annotation.js';
|
|
14
|
-
import type
|
|
15
|
+
import {isWorkflowRunTerminal, type Job, type JobExecution} from '#core/workflow-run.js';
|
|
16
|
+
import {useWorkflowRunAnnotationSummaryQuery} from '#hooks/api/annotations.js';
|
|
15
17
|
import {useRunAnnotationsQuery} from '#hooks/api/run-annotations.js';
|
|
16
18
|
import type {useWorkflowRunAttemptQuery} from '#hooks/api/workflow-runs.js';
|
|
17
19
|
import {
|
|
@@ -24,15 +26,14 @@ import {
|
|
|
24
26
|
WorkflowRunNotFound,
|
|
25
27
|
WorkflowRunStaleError,
|
|
26
28
|
} from '../workflow-run-view/workflow-run-states.js';
|
|
27
|
-
import {
|
|
29
|
+
import {JobContextPanel} from './job-context-panel.js';
|
|
28
30
|
import {JobDetailHeader} from './job-detail-header.js';
|
|
29
31
|
import {
|
|
30
32
|
CarriedOverStepPanel,
|
|
31
33
|
emptyStateForJob,
|
|
32
34
|
emptyStateForMissingExecution,
|
|
33
|
-
isAgentConfigFailure,
|
|
34
35
|
jobSucceededSummary,
|
|
35
|
-
|
|
36
|
+
MaterializedOutputFailureNotice,
|
|
36
37
|
} from './job-empty-states.js';
|
|
37
38
|
import {
|
|
38
39
|
resolveWorkflowJobSelection,
|
|
@@ -40,6 +41,9 @@ import {
|
|
|
40
41
|
workflowJobLandingSelection,
|
|
41
42
|
} from './job-selection.js';
|
|
42
43
|
import {StepAttemptLogPanel} from './step-attempt-log-panel.js';
|
|
44
|
+
import {StepInspectorSheet} from './step-troubleshooting.js';
|
|
45
|
+
|
|
46
|
+
type InspectorState = {key: string; attemptId: string | null};
|
|
43
47
|
|
|
44
48
|
export interface JobDetailViewProps {
|
|
45
49
|
workspaceSlug: string;
|
|
@@ -68,7 +72,8 @@ export function JobDetailView({
|
|
|
68
72
|
const pageScrollRef = useRef<HTMLDivElement>(null);
|
|
69
73
|
const landingSelectionRef = useRef<FrozenLandingSelection | undefined>(undefined);
|
|
70
74
|
const hasLoadedData = query.data !== undefined;
|
|
71
|
-
//
|
|
75
|
+
// Reuse the run workspace's bounded annotation read for the job header chip. The separate
|
|
76
|
+
// summary query below stays counts-only and is scoped to the inspector's selected execution.
|
|
72
77
|
const annotations = useRunAnnotationsQuery({
|
|
73
78
|
workflowRunId,
|
|
74
79
|
runAttempt: query.data?.runAttempt.attempt,
|
|
@@ -78,6 +83,26 @@ export function JobDetailView({
|
|
|
78
83
|
truncated: annotations.summary?.truncated ?? false,
|
|
79
84
|
})
|
|
80
85
|
: undefined;
|
|
86
|
+
const inspectorResetKey = `${jobId}:${search.jobExecutionId ?? ''}`;
|
|
87
|
+
const [inspectorState, setInspectorState] = useState<InspectorState>(() => ({
|
|
88
|
+
key: inspectorResetKey,
|
|
89
|
+
attemptId: null,
|
|
90
|
+
}));
|
|
91
|
+
const inspectorOpenAttemptId =
|
|
92
|
+
inspectorState.key === inspectorResetKey ? inspectorState.attemptId : null;
|
|
93
|
+
const annotationJob = query.data?.jobs.find((candidate) => candidate.id === jobId);
|
|
94
|
+
const annotationExecutionId = annotationJob
|
|
95
|
+
? resolveWorkflowJobSelection({job: annotationJob, selection: search}).jobExecution?.id
|
|
96
|
+
: search.jobExecutionId;
|
|
97
|
+
const annotationPolling = query.data
|
|
98
|
+
? !isWorkflowRunTerminal(query.data.runAttempt.status)
|
|
99
|
+
: true;
|
|
100
|
+
const annotationSummaryQuery = useWorkflowRunAnnotationSummaryQuery(
|
|
101
|
+
query.data?.id,
|
|
102
|
+
query.data?.runAttempt.attempt,
|
|
103
|
+
annotationExecutionId,
|
|
104
|
+
{polling: annotationPolling},
|
|
105
|
+
);
|
|
81
106
|
|
|
82
107
|
useEffect(() => {
|
|
83
108
|
if (!jobId || !hasLoadedData) return;
|
|
@@ -185,6 +210,10 @@ export function JobDetailView({
|
|
|
185
210
|
});
|
|
186
211
|
}
|
|
187
212
|
|
|
213
|
+
function onInspectorOpenChange(attemptId: string | null) {
|
|
214
|
+
setInspectorState({key: inspectorResetKey, attemptId});
|
|
215
|
+
}
|
|
216
|
+
|
|
188
217
|
function retargetToRunningStep() {
|
|
189
218
|
if (!runningSelection || !selectedJobExecution) return;
|
|
190
219
|
onSelectionChange({
|
|
@@ -208,11 +237,8 @@ export function JobDetailView({
|
|
|
208
237
|
attempt={newerAttempt}
|
|
209
238
|
/>
|
|
210
239
|
) : null}
|
|
211
|
-
<div ref={pageScrollRef} className="@container min-h-0 flex-1 overflow-auto pb-
|
|
212
|
-
<section
|
|
213
|
-
aria-label={`${job.displayName} logs`}
|
|
214
|
-
className="mx-auto flex w-full max-w-[1120px] flex-col gap-8 px-24"
|
|
215
|
-
>
|
|
240
|
+
<div ref={pageScrollRef} className="@container min-h-0 flex-1 overflow-auto pb-panel">
|
|
241
|
+
<section aria-label={`${job.displayName} logs`} className="flex w-full flex-col">
|
|
216
242
|
<section className="min-w-0 overflow-hidden">
|
|
217
243
|
<JobDetailHeader
|
|
218
244
|
job={job}
|
|
@@ -220,32 +246,55 @@ export function JobDetailView({
|
|
|
220
246
|
onSelectedJobExecutionChange={selectExecution}
|
|
221
247
|
workspaceSlug={workspaceSlug}
|
|
222
248
|
projectSlug={projectSlug}
|
|
223
|
-
workflowRunId={
|
|
249
|
+
workflowRunId={run.id}
|
|
224
250
|
runAttempt={run.runAttempt.attempt}
|
|
225
251
|
annotationSummary={jobAnnotationSummary}
|
|
252
|
+
jobContext={
|
|
253
|
+
selectedJobExecution ? (
|
|
254
|
+
<JobContextPanel job={job} execution={selectedJobExecution} />
|
|
255
|
+
) : undefined
|
|
256
|
+
}
|
|
226
257
|
/>
|
|
227
|
-
<Text as="
|
|
258
|
+
<Text as="h2" className="sr-only">
|
|
228
259
|
Logs
|
|
229
260
|
</Text>
|
|
230
261
|
{selectedJobExecution ? (
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
262
|
+
<>
|
|
263
|
+
<MaterializedOutputFailureNotice jobExecution={selectedJobExecution} />
|
|
264
|
+
<StepList
|
|
265
|
+
job={job}
|
|
266
|
+
jobExecution={selectedJobExecution}
|
|
267
|
+
selectedAttemptId={selectedAttemptId}
|
|
268
|
+
defaultSelectedAttemptId={landingSelection?.attemptId}
|
|
269
|
+
onSelectedAttemptChange={selectAttempt}
|
|
270
|
+
inspectorOpenAttemptId={inspectorOpenAttemptId}
|
|
271
|
+
onInspectorOpenChange={onInspectorOpenChange}
|
|
272
|
+
autoSelectActiveAttempt
|
|
273
|
+
emptyState={emptyStateForJob(job, selectedJobExecution)}
|
|
274
|
+
showHeader={false}
|
|
275
|
+
className="rounded-none border-0 bg-transparent"
|
|
276
|
+
renderExpandedStep={(context) => (
|
|
277
|
+
<ExpandedStep context={context} pageScrollRef={pageScrollRef} />
|
|
278
|
+
)}
|
|
279
|
+
renderInspector={(entry) => (
|
|
280
|
+
<StepInspectorSheet
|
|
281
|
+
entry={entry}
|
|
282
|
+
open
|
|
283
|
+
onOpenChange={(open) => onInspectorOpenChange(open ? entry.id : null)}
|
|
284
|
+
workspaceSlug={workspaceSlug}
|
|
285
|
+
projectSlug={projectSlug}
|
|
286
|
+
workflowRunId={run.id}
|
|
287
|
+
runAttempt={run.runAttempt.attempt}
|
|
288
|
+
jobId={job.id}
|
|
289
|
+
annotationCount={annotationCountForStep(
|
|
290
|
+
annotationSummaryQuery.data,
|
|
291
|
+
entry.step.id,
|
|
292
|
+
entry.attempt,
|
|
293
|
+
)}
|
|
294
|
+
/>
|
|
295
|
+
)}
|
|
296
|
+
/>
|
|
297
|
+
</>
|
|
249
298
|
) : (
|
|
250
299
|
<EmptyStateForMissingExecution job={job} />
|
|
251
300
|
)}
|
|
@@ -254,7 +303,7 @@ export function JobDetailView({
|
|
|
254
303
|
<div
|
|
255
304
|
role="status"
|
|
256
305
|
aria-live="polite"
|
|
257
|
-
className="flex min-w-0 items-center justify-between gap-
|
|
306
|
+
className="flex min-w-0 items-center justify-between gap-inline border-t border-border-neutral-base px-row py-row"
|
|
258
307
|
>
|
|
259
308
|
<Text size="xs" className="min-w-0 text-foreground-neutral-muted">
|
|
260
309
|
Run moved on to{' '}
|
|
@@ -265,7 +314,7 @@ export function JobDetailView({
|
|
|
265
314
|
</Text>
|
|
266
315
|
<button
|
|
267
316
|
type="button"
|
|
268
|
-
className="shrink-0 rounded-4 px-
|
|
317
|
+
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"
|
|
269
318
|
onClick={retargetToRunningStep}
|
|
270
319
|
>
|
|
271
320
|
Jump to it
|
|
@@ -273,7 +322,7 @@ export function JobDetailView({
|
|
|
273
322
|
</div>
|
|
274
323
|
) : null}
|
|
275
324
|
{succeededSummary ? (
|
|
276
|
-
<Text size="xs" className="px-
|
|
325
|
+
<Text size="xs" className="px-row py-row text-foreground-neutral-muted">
|
|
277
326
|
{succeededSummary}
|
|
278
327
|
</Text>
|
|
279
328
|
) : null}
|
|
@@ -286,39 +335,27 @@ export function JobDetailView({
|
|
|
286
335
|
|
|
287
336
|
function ExpandedStep({
|
|
288
337
|
context,
|
|
289
|
-
workspaceSlug,
|
|
290
338
|
pageScrollRef,
|
|
291
339
|
}: {
|
|
292
340
|
context: StepExpandedContext;
|
|
293
|
-
workspaceSlug: string;
|
|
294
341
|
pageScrollRef: RefObject<HTMLDivElement | null>;
|
|
295
342
|
}) {
|
|
296
343
|
if (context.carriedOver) return <CarriedOverStepPanel />;
|
|
297
344
|
|
|
298
|
-
const selectedAttemptError =
|
|
299
|
-
toSelectedAttemptError(context.step, context.attemptError) ?? context.step.error;
|
|
300
|
-
|
|
301
345
|
return (
|
|
302
346
|
<section
|
|
303
347
|
role="region"
|
|
304
348
|
tabIndex={0}
|
|
305
349
|
aria-label={`${context.stepLabel} output, attempt ${context.attempt}`}
|
|
306
|
-
className="flex min-w-0 flex-col
|
|
350
|
+
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"
|
|
307
351
|
>
|
|
308
|
-
{isAgentConfigFailure(context.step, selectedAttemptError) ? (
|
|
309
|
-
<AgentConfigFailureCallout
|
|
310
|
-
workspaceSlug={workspaceSlug}
|
|
311
|
-
config={context.step.agentConfig}
|
|
312
|
-
error={selectedAttemptError}
|
|
313
|
-
/>
|
|
314
|
-
) : null}
|
|
315
352
|
<StepAttemptLogPanel
|
|
316
353
|
stepId={context.stepId}
|
|
317
354
|
attempt={context.attempt}
|
|
318
355
|
attemptStatus={context.attemptStatus}
|
|
319
356
|
attemptStartedAt={context.attemptStartedAt}
|
|
320
357
|
pageScrollRef={pageScrollRef}
|
|
321
|
-
surfaceClassName="rounded-
|
|
358
|
+
surfaceClassName="rounded-none border-0 bg-transparent shadow-none"
|
|
322
359
|
/>
|
|
323
360
|
</section>
|
|
324
361
|
);
|
|
@@ -328,7 +365,7 @@ function EmptyStateForMissingExecution({job}: {job: Job}) {
|
|
|
328
365
|
const emptyState = emptyStateForMissingExecution(job);
|
|
329
366
|
return (
|
|
330
367
|
<EmptyState
|
|
331
|
-
className="min-h-120
|
|
368
|
+
className="min-h-120 p-panel"
|
|
332
369
|
icon="componentLine"
|
|
333
370
|
title={emptyState.title}
|
|
334
371
|
description={emptyState.description}
|
|
@@ -349,7 +386,7 @@ function JobNotFoundState({
|
|
|
349
386
|
search: WorkflowJobSearch;
|
|
350
387
|
}) {
|
|
351
388
|
return (
|
|
352
|
-
<div className="flex min-h-0 min-w-0 flex-1 flex-col items-center justify-center gap-
|
|
389
|
+
<div className="flex min-h-0 min-w-0 flex-1 flex-col items-center justify-center gap-cluster p-panel">
|
|
353
390
|
<EmptyState
|
|
354
391
|
icon="componentLine"
|
|
355
392
|
title="Job not found"
|
|
@@ -364,7 +401,7 @@ function JobNotFoundState({
|
|
|
364
401
|
{runAttempt: search.runAttempt},
|
|
365
402
|
) as never
|
|
366
403
|
}
|
|
367
|
-
className="inline-flex items-center gap-
|
|
404
|
+
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"
|
|
368
405
|
>
|
|
369
406
|
<Icon name="arrowLeftLine" size={14} aria-hidden="true" />
|
|
370
407
|
Back to run summary
|
|
@@ -387,8 +424,8 @@ function NewerAttemptNotice({
|
|
|
387
424
|
attempt: number;
|
|
388
425
|
}) {
|
|
389
426
|
return (
|
|
390
|
-
<div role="status" className="border-b border-border-neutral-base px-
|
|
391
|
-
<div className="
|
|
427
|
+
<div role="status" className="border-b border-border-neutral-base px-row py-row">
|
|
428
|
+
<div className="flex items-center justify-between gap-inline">
|
|
392
429
|
<Text size="xs" className="text-foreground-neutral-muted">
|
|
393
430
|
A newer run attempt is available.
|
|
394
431
|
</Text>
|
|
@@ -396,7 +433,7 @@ function NewerAttemptNotice({
|
|
|
396
433
|
to="/w/$workspaceSlug/p/$projectSlug/runs/$workflowRunId/jobs/$jobId"
|
|
397
434
|
params={{workspaceSlug, projectSlug, workflowRunId: runId, jobId}}
|
|
398
435
|
search={workflowJobSearchParams({runAttempt: attempt}) as never}
|
|
399
|
-
className="shrink-0 rounded-4 px-
|
|
436
|
+
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"
|
|
400
437
|
>
|
|
401
438
|
View attempt #{attempt}
|
|
402
439
|
</Link>
|
|
@@ -431,21 +468,30 @@ function findAttempt(jobExecution: JobExecution, attemptId: string) {
|
|
|
431
468
|
return undefined;
|
|
432
469
|
}
|
|
433
470
|
|
|
471
|
+
function annotationCountForStep(
|
|
472
|
+
summary: RunAnnotationSummary | undefined,
|
|
473
|
+
stepId: string,
|
|
474
|
+
attempt: number,
|
|
475
|
+
): number | undefined {
|
|
476
|
+
return summary?.stepCounts?.find((entry) => entry.stepId === stepId && entry.attempt === attempt)
|
|
477
|
+
?.total;
|
|
478
|
+
}
|
|
479
|
+
|
|
434
480
|
function JobDetailSkeleton() {
|
|
435
481
|
return (
|
|
436
|
-
<div className="min-h-0 min-w-0 flex-1 overflow-auto
|
|
437
|
-
<div className="
|
|
438
|
-
<header className="flex items-center gap-
|
|
482
|
+
<div className="min-h-0 min-w-0 flex-1 overflow-auto pb-panel">
|
|
483
|
+
<div className="w-full">
|
|
484
|
+
<header className="flex items-center gap-cluster border-b border-border-neutral-base px-row py-row">
|
|
439
485
|
<Skeleton className="size-20 rounded-full" />
|
|
440
486
|
<Skeleton className="h-20 w-160 rounded-4" />
|
|
441
487
|
<Skeleton className="h-24 w-72 rounded-6" />
|
|
442
488
|
<Skeleton className="h-24 w-180 rounded-4" />
|
|
443
489
|
</header>
|
|
444
|
-
<div className="p-
|
|
490
|
+
<div className="p-panel-compact">
|
|
445
491
|
{JOB_DETAIL_SKELETON_ROWS.map((row) => (
|
|
446
492
|
<div
|
|
447
493
|
key={row}
|
|
448
|
-
className="flex min-h-44 items-center gap-
|
|
494
|
+
className="flex min-h-44 items-center gap-inline border-b border-border-neutral-base last:border-b-0"
|
|
449
495
|
>
|
|
450
496
|
<Skeleton className="size-14 rounded-full" />
|
|
451
497
|
<Skeleton className="h-16 w-180 rounded-4" />
|
|
@@ -3,7 +3,13 @@ import {
|
|
|
3
3
|
workflowJobExecutionDto,
|
|
4
4
|
workflowStepDto,
|
|
5
5
|
} from '#test/fixtures/workflow-run.js';
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
emptyStateForJob,
|
|
8
|
+
emptyStateForMissingExecution,
|
|
9
|
+
jobSucceededSummary,
|
|
10
|
+
outputFailureDescriptionForExecution,
|
|
11
|
+
skippedJobDescription,
|
|
12
|
+
} from './job-empty-states.js';
|
|
7
13
|
|
|
8
14
|
describe('jobSucceededSummary', () => {
|
|
9
15
|
test('counts only succeeded steps when skipped steps are present', () => {
|
|
@@ -25,3 +31,101 @@ describe('jobSucceededSummary', () => {
|
|
|
25
31
|
expect(jobSucceededSummary(job, execution)).toBe('1 step succeeded');
|
|
26
32
|
});
|
|
27
33
|
});
|
|
34
|
+
|
|
35
|
+
describe('skippedJobDescription', () => {
|
|
36
|
+
test('explains when materialized output exceeds the configured size limit', () => {
|
|
37
|
+
expect(skippedJobDescription('output_too_large')).toBe(
|
|
38
|
+
'The materialized job output exceeded its configured size limit.',
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe('materialized output failure descriptions', () => {
|
|
44
|
+
const fallback =
|
|
45
|
+
'A materialized job output could not be persisted: it exceeded a size or entry cap, contained a non-JSON-safe value, or referenced an unresolved value. Check the output mapping and values before re-running the workflow.';
|
|
46
|
+
|
|
47
|
+
test('uses the server-authored message for an execution with recorded steps', () => {
|
|
48
|
+
const job = workflowJob({
|
|
49
|
+
status: 'failed',
|
|
50
|
+
status_reason: 'output_invalid',
|
|
51
|
+
job_executions: [
|
|
52
|
+
workflowJobExecutionDto({
|
|
53
|
+
status: 'failed',
|
|
54
|
+
status_reason: 'output_invalid',
|
|
55
|
+
status_reason_message: 'Job output "payload" cannot be persisted as JSON: undefined.',
|
|
56
|
+
steps: [workflowStepDto({status: 'succeeded'})],
|
|
57
|
+
}),
|
|
58
|
+
],
|
|
59
|
+
});
|
|
60
|
+
const execution = job.jobExecutions[0];
|
|
61
|
+
if (!execution) throw new Error('Expected a job execution');
|
|
62
|
+
|
|
63
|
+
expect(outputFailureDescriptionForExecution(execution)).toBe(
|
|
64
|
+
'Job output "payload" cannot be persisted as JSON: undefined.',
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('keeps the generic fallback for old output-invalid rows without a message', () => {
|
|
69
|
+
const job = workflowJob({
|
|
70
|
+
status: 'failed',
|
|
71
|
+
status_reason: 'output_invalid',
|
|
72
|
+
job_executions: [
|
|
73
|
+
workflowJobExecutionDto({
|
|
74
|
+
status: 'failed',
|
|
75
|
+
status_reason: 'output_invalid',
|
|
76
|
+
steps: [workflowStepDto({status: 'succeeded'})],
|
|
77
|
+
}),
|
|
78
|
+
],
|
|
79
|
+
});
|
|
80
|
+
const execution = job.jobExecutions[0];
|
|
81
|
+
if (!execution) throw new Error('Expected a job execution');
|
|
82
|
+
|
|
83
|
+
expect(outputFailureDescriptionForExecution(execution)).toBe(fallback);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test('uses the message in the empty state when output materialization failed before steps', () => {
|
|
87
|
+
const job = workflowJob({
|
|
88
|
+
status: 'failed',
|
|
89
|
+
status_reason: 'output_invalid',
|
|
90
|
+
job_executions: [
|
|
91
|
+
workflowJobExecutionDto({
|
|
92
|
+
status: 'failed',
|
|
93
|
+
status_reason: 'output_invalid',
|
|
94
|
+
status_reason_message: 'Job outputs cannot define more than 10 entries (found 11)',
|
|
95
|
+
steps: [],
|
|
96
|
+
}),
|
|
97
|
+
],
|
|
98
|
+
});
|
|
99
|
+
const execution = job.jobExecutions[0];
|
|
100
|
+
if (!execution) throw new Error('Expected a job execution');
|
|
101
|
+
|
|
102
|
+
expect(emptyStateForJob(job, execution)).toMatchObject({
|
|
103
|
+
description: 'Job outputs cannot define more than 10 entries (found 11)',
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('keeps output failures scoped to the selected execution', () => {
|
|
108
|
+
const job = workflowJob({
|
|
109
|
+
status: 'failed',
|
|
110
|
+
status_reason: 'output_invalid',
|
|
111
|
+
job_executions: [
|
|
112
|
+
workflowJobExecutionDto({
|
|
113
|
+
status: 'succeeded',
|
|
114
|
+
status_reason: null,
|
|
115
|
+
steps: [workflowStepDto({status: 'succeeded'})],
|
|
116
|
+
}),
|
|
117
|
+
],
|
|
118
|
+
});
|
|
119
|
+
const execution = job.jobExecutions[0];
|
|
120
|
+
if (!execution) throw new Error('Expected a job execution');
|
|
121
|
+
|
|
122
|
+
expect(outputFailureDescriptionForExecution(execution)).toBeUndefined();
|
|
123
|
+
expect(
|
|
124
|
+
emptyStateForMissingExecution(
|
|
125
|
+
workflowJob({status: 'failed', status_reason: 'output_invalid'}),
|
|
126
|
+
),
|
|
127
|
+
).toMatchObject({
|
|
128
|
+
description: fallback,
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {Callout, CalloutContent, CalloutDescription, CalloutTitle} from '@shipfox/react-ui/callout';
|
|
1
2
|
import {EmptyState} from '@shipfox/react-ui/empty-state';
|
|
2
3
|
import type {Job, JobExecution, Step, StepError} from '#core/workflow-run.js';
|
|
3
4
|
import {
|
|
@@ -9,6 +10,44 @@ import {
|
|
|
9
10
|
import type {StepListEmptyState} from '../step-list/index.js';
|
|
10
11
|
import {formatJobExecutionTime} from './job-execution-time-text.js';
|
|
11
12
|
|
|
13
|
+
const MATERIALIZED_OUTPUT_FAILURE_DESCRIPTION =
|
|
14
|
+
'A materialized job output could not be persisted: it exceeded a size or entry cap, contained a non-JSON-safe value, or referenced an unresolved value. Check the output mapping and values before re-running the workflow.';
|
|
15
|
+
const OUTPUT_TOO_LARGE_FAILURE_DESCRIPTION =
|
|
16
|
+
'The materialized job output exceeded its configured size limit. Review the failure details before re-running the workflow.';
|
|
17
|
+
|
|
18
|
+
export function outputFailureDescriptionForExecution(
|
|
19
|
+
jobExecution: JobExecution,
|
|
20
|
+
): string | undefined {
|
|
21
|
+
if (jobExecution.steps.length === 0) return undefined;
|
|
22
|
+
|
|
23
|
+
if (jobExecution.statusReason === 'output_invalid') {
|
|
24
|
+
return jobExecution.statusReasonMessage || MATERIALIZED_OUTPUT_FAILURE_DESCRIPTION;
|
|
25
|
+
}
|
|
26
|
+
if (jobExecution.statusReason === 'output_too_large') {
|
|
27
|
+
return jobExecution.statusReasonMessage || OUTPUT_TOO_LARGE_FAILURE_DESCRIPTION;
|
|
28
|
+
}
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function MaterializedOutputFailureNotice({jobExecution}: {jobExecution: JobExecution}) {
|
|
33
|
+
const description = outputFailureDescriptionForExecution(jobExecution);
|
|
34
|
+
if (!description) return null;
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<Callout
|
|
38
|
+
role="alert"
|
|
39
|
+
type="error"
|
|
40
|
+
variant="secondary"
|
|
41
|
+
className="border-b border-border-neutral-base px-row py-row"
|
|
42
|
+
>
|
|
43
|
+
<CalloutContent>
|
|
44
|
+
<CalloutTitle>Job output could not be persisted</CalloutTitle>
|
|
45
|
+
<CalloutDescription>{description}</CalloutDescription>
|
|
46
|
+
</CalloutContent>
|
|
47
|
+
</Callout>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
12
51
|
export function emptyStateForJob(
|
|
13
52
|
job: Job,
|
|
14
53
|
jobExecution: JobExecution,
|
|
@@ -25,11 +64,14 @@ export function emptyStateForJob(
|
|
|
25
64
|
job.mode === 'listening'
|
|
26
65
|
? deriveJobExecutionDisplayStatus(jobExecution)
|
|
27
66
|
: deriveJobDisplayStatus(job);
|
|
67
|
+
const runner = jobExecution.runner?.length ? jobExecution.runner : job.runner;
|
|
28
68
|
|
|
29
69
|
if (jobExecution.status === 'running' && displayStatus === 'pending') {
|
|
30
70
|
return {
|
|
31
|
-
title: 'Runner preparing job',
|
|
32
|
-
description:
|
|
71
|
+
title: runner?.length ? 'Runner preparing job' : 'Waiting for a runner',
|
|
72
|
+
description: runner?.length
|
|
73
|
+
? `Runner ${runner.join(', ')} is preparing the job. Steps will appear here when work begins.`
|
|
74
|
+
: 'No runner has claimed this job yet. Steps will appear here when a runner starts work.',
|
|
33
75
|
status: displayStatus,
|
|
34
76
|
};
|
|
35
77
|
}
|
|
@@ -61,7 +103,11 @@ export function emptyStateForJob(
|
|
|
61
103
|
if (displayStatus === 'failed') {
|
|
62
104
|
return {
|
|
63
105
|
title: 'Job failed before its first step started',
|
|
64
|
-
description: preStepFailureDescription(
|
|
106
|
+
description: preStepFailureDescription(
|
|
107
|
+
jobExecution.statusReason ?? job.statusReason,
|
|
108
|
+
runner,
|
|
109
|
+
jobExecution.statusReasonMessage,
|
|
110
|
+
),
|
|
65
111
|
status: displayStatus,
|
|
66
112
|
};
|
|
67
113
|
}
|
|
@@ -126,6 +172,14 @@ export function emptyStateForMissingExecution(job: Job): StepListEmptyState {
|
|
|
126
172
|
};
|
|
127
173
|
}
|
|
128
174
|
|
|
175
|
+
if (job.status === 'failed') {
|
|
176
|
+
return {
|
|
177
|
+
title: 'Job failed before an execution was created',
|
|
178
|
+
description: preStepFailureDescription(job.statusReason, job.runner),
|
|
179
|
+
status: 'failed',
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
129
183
|
return {
|
|
130
184
|
title: 'Execution details unavailable',
|
|
131
185
|
description: 'This job finished, but no job execution record is available.',
|
|
@@ -148,24 +202,37 @@ export function skippedJobDescription(reason: Job['statusReason']): string {
|
|
|
148
202
|
case 'run_cancelled':
|
|
149
203
|
case 'timed_out':
|
|
150
204
|
case 'runner_lost':
|
|
205
|
+
case 'output_invalid':
|
|
151
206
|
case 'step_failed':
|
|
152
207
|
case 'unknown':
|
|
153
208
|
case null:
|
|
154
209
|
return 'This job did not start.';
|
|
210
|
+
case 'output_too_large':
|
|
211
|
+
return 'The materialized job output exceeded its configured size limit.';
|
|
155
212
|
}
|
|
156
213
|
}
|
|
157
214
|
|
|
158
|
-
function preStepFailureDescription(
|
|
215
|
+
function preStepFailureDescription(
|
|
216
|
+
reason: string | null,
|
|
217
|
+
runner: string[] | null = null,
|
|
218
|
+
statusReasonMessage: string | null = null,
|
|
219
|
+
): string {
|
|
220
|
+
const runnerCopy = runner?.length ? ` Required runner labels: ${runner.join(', ')}.` : '';
|
|
221
|
+
|
|
159
222
|
switch (reason) {
|
|
160
223
|
case 'runner_lost':
|
|
161
|
-
return
|
|
224
|
+
return `The runner stopped responding before work began.${runnerCopy} Check runner availability before re-running the workflow.`;
|
|
162
225
|
case 'timed_out':
|
|
163
|
-
return
|
|
226
|
+
return `The job timed out before work began.${runnerCopy} Check runner capacity and timeout configuration before re-running the workflow.`;
|
|
164
227
|
case 'user_cancelled':
|
|
165
228
|
case 'run_cancelled':
|
|
166
229
|
return 'The execution ended while the run was being cancelled.';
|
|
167
230
|
case 'step_failed':
|
|
168
|
-
return
|
|
231
|
+
return `The execution failed before step details were recorded.${runnerCopy} Review run annotations before re-running the workflow.`;
|
|
232
|
+
case 'output_too_large':
|
|
233
|
+
return statusReasonMessage || OUTPUT_TOO_LARGE_FAILURE_DESCRIPTION;
|
|
234
|
+
case 'output_invalid':
|
|
235
|
+
return statusReasonMessage || MATERIALIZED_OUTPUT_FAILURE_DESCRIPTION;
|
|
169
236
|
case 'condition_errored':
|
|
170
237
|
return 'The job condition could not be evaluated. Review run annotations before re-running the workflow.';
|
|
171
238
|
case 'dependency_not_completed':
|
|
@@ -176,9 +243,9 @@ function preStepFailureDescription(reason: string | null): string {
|
|
|
176
243
|
return 'The job condition did not match, so no work started.';
|
|
177
244
|
case 'unknown':
|
|
178
245
|
case null:
|
|
179
|
-
return
|
|
246
|
+
return `Shipfox did not record a failure reason.${runnerCopy} Re-run only after checking the runner and workflow configuration.`;
|
|
180
247
|
default:
|
|
181
|
-
return `The execution ended because ${humanizeFailureReason(reason)}
|
|
248
|
+
return `The execution ended because ${humanizeFailureReason(reason)}.${runnerCopy} Resolve that condition before re-running the workflow.`;
|
|
182
249
|
}
|
|
183
250
|
}
|
|
184
251
|
|
|
@@ -189,7 +256,7 @@ function humanizeFailureReason(reason: string): string {
|
|
|
189
256
|
export function CarriedOverStepPanel() {
|
|
190
257
|
return (
|
|
191
258
|
<EmptyState
|
|
192
|
-
className="min-h-120 rounded-8 border border-border-neutral-base bg-background-components-base
|
|
259
|
+
className="min-h-120 rounded-8 border border-border-neutral-base bg-background-components-base p-panel"
|
|
193
260
|
icon="componentLine"
|
|
194
261
|
title="Carried over from a previous attempt"
|
|
195
262
|
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}
|