@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
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shipfox/client-workflows",
|
|
3
3
|
"license": "MIT",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "18.0.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/ShipfoxHQ/shipfox.git",
|
|
@@ -27,16 +27,16 @@
|
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"@swc/helpers": "^0.5.17",
|
|
29
29
|
"date-fns": "^4.1.0",
|
|
30
|
-
"@shipfox/annotations-dto": "12.
|
|
31
|
-
"@shipfox/api-definitions-dto": "12.
|
|
32
|
-
"@shipfox/api-workflows-dto": "12.
|
|
33
|
-
"@shipfox/client-logs": "16.0.0",
|
|
30
|
+
"@shipfox/annotations-dto": "12.3.0",
|
|
31
|
+
"@shipfox/api-definitions-dto": "12.3.0",
|
|
32
|
+
"@shipfox/api-workflows-dto": "12.5.0",
|
|
34
33
|
"@shipfox/client-api": "6.0.1",
|
|
35
|
-
"@shipfox/client-
|
|
36
|
-
"@shipfox/
|
|
37
|
-
"@shipfox/client-
|
|
38
|
-
"@shipfox/client-triggers": "
|
|
39
|
-
"@shipfox/client-
|
|
34
|
+
"@shipfox/client-logs": "17.0.0",
|
|
35
|
+
"@shipfox/client-projects": "17.0.0",
|
|
36
|
+
"@shipfox/client-shell": "17.0.0",
|
|
37
|
+
"@shipfox/client-triggers": "17.0.0",
|
|
38
|
+
"@shipfox/client-ui": "17.0.0",
|
|
39
|
+
"@shipfox/react-ui": "1.2.0"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
42
|
"@tanstack/react-query": "^5.101.0",
|
|
@@ -112,7 +112,7 @@ export function Identifier({
|
|
|
112
112
|
onClick={() => {
|
|
113
113
|
void handleCopy();
|
|
114
114
|
}}
|
|
115
|
-
className="inline-flex h-20 min-w-0 shrink-0 items-center gap-
|
|
115
|
+
className="inline-flex h-20 min-w-0 shrink-0 items-center gap-tight rounded-4 px-[var(--pad-menu-surface)] text-foreground-neutral-muted outline-none transition-colors hover:bg-background-components-hover hover:text-foreground-neutral-base focus-visible:shadow-border-interactive-with-active"
|
|
116
116
|
>
|
|
117
117
|
<Icon name="copy" aria-hidden="true" className="size-12 shrink-0" />
|
|
118
118
|
<Code as="span" variant="label" className="truncate">
|
|
@@ -167,7 +167,7 @@ function CopyFeedbackPortal({
|
|
|
167
167
|
: `translate(-50%, ${COPY_FEEDBACK_OFFSET_PX}px)`,
|
|
168
168
|
}}
|
|
169
169
|
className={cn(
|
|
170
|
-
'pointer-events-none fixed z-50 whitespace-nowrap rounded-8 bg-background-components-base px-
|
|
170
|
+
'pointer-events-none fixed z-50 whitespace-nowrap rounded-8 bg-background-components-base px-tight py-[4px] text-xs font-display font-medium leading-20 shadow-tooltip',
|
|
171
171
|
kind === 'failed' ? 'text-foreground-highlight-error' : 'text-foreground-neutral-base',
|
|
172
172
|
)}
|
|
173
173
|
>
|
|
@@ -21,7 +21,11 @@ export function AgentConfigFailureCallout({
|
|
|
21
21
|
const copy = agentConfigFailureCopy(config, error);
|
|
22
22
|
|
|
23
23
|
return (
|
|
24
|
-
<Alert
|
|
24
|
+
<Alert
|
|
25
|
+
variant="warning"
|
|
26
|
+
animated={false}
|
|
27
|
+
className="rounded-none border-x-0 border-t-0 border-b border-tag-warning-border bg-transparent px-0 py-row"
|
|
28
|
+
>
|
|
25
29
|
<AlertContent>
|
|
26
30
|
<AlertTitle>{copy.title}</AlertTitle>
|
|
27
31
|
<AlertDescription>{copy.description}</AlertDescription>
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import {render, screen, within} from '@testing-library/react';
|
|
2
|
+
import userEvent from '@testing-library/user-event';
|
|
3
|
+
import {workflowJob, workflowJobExecutionDto} from '#test/fixtures/workflow-run.js';
|
|
4
|
+
import {JobContextPanel} from './job-context-panel.js';
|
|
5
|
+
|
|
6
|
+
describe('JobContextPanel', () => {
|
|
7
|
+
test('opens job details in a labelled sheet from the info action', async () => {
|
|
8
|
+
const user = userEvent.setup();
|
|
9
|
+
const job = workflowJob({
|
|
10
|
+
name: 'build',
|
|
11
|
+
runner: ['runner-linux-x64'],
|
|
12
|
+
job_executions: [
|
|
13
|
+
workflowJobExecutionDto({
|
|
14
|
+
status: 'succeeded',
|
|
15
|
+
outputs: {failed_tests: 4},
|
|
16
|
+
}),
|
|
17
|
+
],
|
|
18
|
+
});
|
|
19
|
+
const execution = job.jobExecutions[0];
|
|
20
|
+
if (!execution) throw new Error('Test fixture is missing a job execution.');
|
|
21
|
+
|
|
22
|
+
render(<JobContextPanel job={job} execution={execution} />);
|
|
23
|
+
|
|
24
|
+
expect(screen.queryByRole('dialog', {name: 'build'})).not.toBeInTheDocument();
|
|
25
|
+
await user.click(screen.getByRole('button', {name: 'Inspect job details'}));
|
|
26
|
+
|
|
27
|
+
const dialog = await screen.findByRole('dialog', {name: 'build'});
|
|
28
|
+
expect(within(dialog).getByText('Runner')).toBeInTheDocument();
|
|
29
|
+
expect(within(dialog).getAllByText('Outputs')).not.toHaveLength(0);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('separates execution-name traces from job condition traces', async () => {
|
|
33
|
+
const user = userEvent.setup();
|
|
34
|
+
const job = workflowJob({
|
|
35
|
+
name: 'build',
|
|
36
|
+
evaluation_trace: [
|
|
37
|
+
{
|
|
38
|
+
expression: 'inputs.environment == "production"',
|
|
39
|
+
roots: ['inputs.environment'],
|
|
40
|
+
fill_target: 'job-activation',
|
|
41
|
+
evaluated_at: '2026-08-05T12:00:00.000Z',
|
|
42
|
+
field: 'job.if',
|
|
43
|
+
value: 'true',
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
job_executions: [
|
|
47
|
+
workflowJobExecutionDto({
|
|
48
|
+
status: 'succeeded',
|
|
49
|
+
evaluation_trace: [
|
|
50
|
+
{
|
|
51
|
+
expression: 'inputs.environment',
|
|
52
|
+
roots: ['inputs.environment'],
|
|
53
|
+
fill_target: 'execution-creation',
|
|
54
|
+
evaluated_at: '2026-08-05T12:00:00.000Z',
|
|
55
|
+
field: 'job.execution_name',
|
|
56
|
+
value: 'production',
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
}),
|
|
60
|
+
],
|
|
61
|
+
});
|
|
62
|
+
const execution = job.jobExecutions[0];
|
|
63
|
+
if (!execution) throw new Error('Test fixture is missing a job execution.');
|
|
64
|
+
|
|
65
|
+
render(<JobContextPanel job={job} execution={execution} />);
|
|
66
|
+
await user.click(screen.getByRole('button', {name: 'Inspect job details'}));
|
|
67
|
+
|
|
68
|
+
const dialog = await screen.findByRole('dialog', {name: 'build'});
|
|
69
|
+
expect(within(dialog).getByText('Execution name evaluation (1)')).toBeInTheDocument();
|
|
70
|
+
expect(within(dialog).getByText('Condition evaluation (1)')).toBeInTheDocument();
|
|
71
|
+
});
|
|
72
|
+
});
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import {Icon} from '@shipfox/react-ui/icon';
|
|
2
|
+
import {
|
|
3
|
+
Sheet,
|
|
4
|
+
SheetBody,
|
|
5
|
+
SheetContent,
|
|
6
|
+
SheetDescription,
|
|
7
|
+
SheetHeader,
|
|
8
|
+
SheetTitle,
|
|
9
|
+
} from '@shipfox/react-ui/sheet';
|
|
10
|
+
import {Tooltip, TooltipContent, TooltipTrigger} from '@shipfox/react-ui/tooltip';
|
|
11
|
+
import {Code, Text} from '@shipfox/react-ui/typography';
|
|
12
|
+
import {useState} from 'react';
|
|
13
|
+
import type {EvaluationTraceEntry, Job, JobExecution} from '#core/workflow-run.js';
|
|
14
|
+
import {formatJobExecutionTime} from './job-execution-time-text.js';
|
|
15
|
+
import {JsonCode} from './json-code.js';
|
|
16
|
+
import {EvaluationTrace} from './step-troubleshooting.js';
|
|
17
|
+
|
|
18
|
+
export function JobContextPanel({job, execution}: {job: Job; execution: JobExecution}) {
|
|
19
|
+
const runner = execution.runner?.length ? execution.runner : job.runner;
|
|
20
|
+
const outputs = execution.outputs ?? job.outputs;
|
|
21
|
+
const trace = [...(job.evaluationTrace ?? []), ...(execution.evaluationTrace ?? [])];
|
|
22
|
+
const statusReason = execution.statusReason ?? job.statusReason;
|
|
23
|
+
const hasTiming = Boolean(execution.queueTime || execution.runTime);
|
|
24
|
+
const hasContext = Boolean(
|
|
25
|
+
runner?.length ||
|
|
26
|
+
outputs ||
|
|
27
|
+
execution.triggerEvents.length ||
|
|
28
|
+
job.success ||
|
|
29
|
+
statusReason ||
|
|
30
|
+
execution.statusReasonMessage ||
|
|
31
|
+
trace?.length ||
|
|
32
|
+
hasTiming,
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
if (!hasContext) return null;
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<JobContextSheet
|
|
39
|
+
job={job}
|
|
40
|
+
execution={execution}
|
|
41
|
+
runner={runner}
|
|
42
|
+
outputs={outputs}
|
|
43
|
+
trace={trace}
|
|
44
|
+
statusReason={statusReason}
|
|
45
|
+
/>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function JobContextSheet({
|
|
50
|
+
job,
|
|
51
|
+
execution,
|
|
52
|
+
runner,
|
|
53
|
+
outputs,
|
|
54
|
+
trace,
|
|
55
|
+
statusReason,
|
|
56
|
+
}: {
|
|
57
|
+
job: Job;
|
|
58
|
+
execution: JobExecution;
|
|
59
|
+
runner: string[] | null;
|
|
60
|
+
outputs: Record<string, unknown> | null;
|
|
61
|
+
trace: EvaluationTraceEntry[];
|
|
62
|
+
statusReason: JobExecution['statusReason'];
|
|
63
|
+
}) {
|
|
64
|
+
const [open, setOpen] = useState(false);
|
|
65
|
+
const {conditionTrace, executionNameTrace} = splitJobEvaluationTrace(trace);
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<Sheet open={open} onOpenChange={setOpen}>
|
|
69
|
+
<Tooltip>
|
|
70
|
+
<TooltipTrigger asChild>
|
|
71
|
+
<button
|
|
72
|
+
type="button"
|
|
73
|
+
aria-label="Inspect job details"
|
|
74
|
+
onClick={() => setOpen(true)}
|
|
75
|
+
className="flex size-28 shrink-0 items-center justify-center rounded-4 bg-transparent text-foreground-neutral-muted outline-none transition-colors hover:bg-transparent hover:text-foreground-neutral-base active:bg-transparent focus-visible:shadow-button-neutral-focus"
|
|
76
|
+
>
|
|
77
|
+
<Icon name="informationLine" size={14} aria-hidden="true" />
|
|
78
|
+
</button>
|
|
79
|
+
</TooltipTrigger>
|
|
80
|
+
<TooltipContent>Inspect job details</TooltipContent>
|
|
81
|
+
</Tooltip>
|
|
82
|
+
<SheetContent side="right" className="w-full sm:max-w-[560px]">
|
|
83
|
+
<SheetHeader>
|
|
84
|
+
<SheetTitle>{job.displayName}</SheetTitle>
|
|
85
|
+
<SheetDescription>
|
|
86
|
+
Execution #{execution.sequence} · {execution.displayName}
|
|
87
|
+
</SheetDescription>
|
|
88
|
+
</SheetHeader>
|
|
89
|
+
<SheetBody className="gap-section">
|
|
90
|
+
<div className="grid w-full min-w-0 gap-group min-[640px]:grid-cols-2">
|
|
91
|
+
{runner?.length ? <ContextList label="Runner" values={runner} mono /> : null}
|
|
92
|
+
{execution.queueTime || execution.runTime ? (
|
|
93
|
+
<div className="flex min-w-0 flex-col gap-tight">
|
|
94
|
+
<Text size="xs" bold className="text-foreground-neutral-base">
|
|
95
|
+
Timing
|
|
96
|
+
</Text>
|
|
97
|
+
<div className="flex flex-wrap gap-inline text-xs text-foreground-neutral-muted">
|
|
98
|
+
{execution.queueTime ? (
|
|
99
|
+
<span>Queue {formatJobExecutionTime(execution.queueTime)}</span>
|
|
100
|
+
) : null}
|
|
101
|
+
{execution.runTime ? (
|
|
102
|
+
<span>Run {formatJobExecutionTime(execution.runTime)}</span>
|
|
103
|
+
) : null}
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
) : null}
|
|
107
|
+
{outputs ? (
|
|
108
|
+
<JsonCode
|
|
109
|
+
title="Outputs"
|
|
110
|
+
value={outputs}
|
|
111
|
+
emptyMessage="No outputs declared; the `outputs:` mapping is empty."
|
|
112
|
+
/>
|
|
113
|
+
) : null}
|
|
114
|
+
{job.success ? <ContextValue label="Condition" value={job.success} mono /> : null}
|
|
115
|
+
{statusReason ? (
|
|
116
|
+
<ContextValue label="Status reason" value={humanize(statusReason)} />
|
|
117
|
+
) : null}
|
|
118
|
+
{execution.statusReasonMessage ? (
|
|
119
|
+
<ContextValue label="Failure details" value={execution.statusReasonMessage} />
|
|
120
|
+
) : null}
|
|
121
|
+
{execution.triggerEvents.length ? (
|
|
122
|
+
<JsonCode
|
|
123
|
+
title={`Trigger events (${execution.triggerEvents.length})`}
|
|
124
|
+
value={execution.triggerEvents}
|
|
125
|
+
/>
|
|
126
|
+
) : null}
|
|
127
|
+
{executionNameTrace.length ? (
|
|
128
|
+
<EvaluationTraceSection
|
|
129
|
+
title={`Execution name evaluation (${executionNameTrace.length})`}
|
|
130
|
+
trace={executionNameTrace}
|
|
131
|
+
/>
|
|
132
|
+
) : null}
|
|
133
|
+
{conditionTrace.length ? (
|
|
134
|
+
<EvaluationTraceSection
|
|
135
|
+
title={`Condition evaluation (${conditionTrace.length})`}
|
|
136
|
+
trace={conditionTrace}
|
|
137
|
+
/>
|
|
138
|
+
) : null}
|
|
139
|
+
</div>
|
|
140
|
+
</SheetBody>
|
|
141
|
+
</SheetContent>
|
|
142
|
+
</Sheet>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function EvaluationTraceSection({
|
|
147
|
+
title,
|
|
148
|
+
trace,
|
|
149
|
+
}: {
|
|
150
|
+
title: string;
|
|
151
|
+
trace: readonly EvaluationTraceEntry[];
|
|
152
|
+
}) {
|
|
153
|
+
return (
|
|
154
|
+
<div className="flex min-w-0 flex-col gap-tight">
|
|
155
|
+
<Text size="xs" bold className="text-foreground-neutral-base">
|
|
156
|
+
{title}
|
|
157
|
+
</Text>
|
|
158
|
+
<EvaluationTrace trace={trace} />
|
|
159
|
+
</div>
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function ContextValue({
|
|
164
|
+
label,
|
|
165
|
+
value,
|
|
166
|
+
mono = false,
|
|
167
|
+
}: {
|
|
168
|
+
label: string;
|
|
169
|
+
value: string;
|
|
170
|
+
mono?: boolean;
|
|
171
|
+
}) {
|
|
172
|
+
return (
|
|
173
|
+
<div className="flex min-w-0 flex-col gap-tight">
|
|
174
|
+
<Text size="xs" bold className="text-foreground-neutral-base">
|
|
175
|
+
{label}
|
|
176
|
+
</Text>
|
|
177
|
+
<Text
|
|
178
|
+
size="xs"
|
|
179
|
+
className={
|
|
180
|
+
mono
|
|
181
|
+
? 'break-words font-code text-foreground-neutral-muted'
|
|
182
|
+
: 'text-foreground-neutral-muted'
|
|
183
|
+
}
|
|
184
|
+
>
|
|
185
|
+
{value}
|
|
186
|
+
</Text>
|
|
187
|
+
</div>
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function ContextList({
|
|
192
|
+
label,
|
|
193
|
+
values,
|
|
194
|
+
mono = false,
|
|
195
|
+
}: {
|
|
196
|
+
label: string;
|
|
197
|
+
values: string[];
|
|
198
|
+
mono?: boolean;
|
|
199
|
+
}) {
|
|
200
|
+
return (
|
|
201
|
+
<div className="flex min-w-0 flex-col gap-tight">
|
|
202
|
+
<Text size="xs" bold className="text-foreground-neutral-base">
|
|
203
|
+
{label}
|
|
204
|
+
</Text>
|
|
205
|
+
<div className="flex min-w-0 flex-wrap gap-tight">
|
|
206
|
+
{values.map((value) => (
|
|
207
|
+
<Code
|
|
208
|
+
key={value}
|
|
209
|
+
as="span"
|
|
210
|
+
variant="label"
|
|
211
|
+
className={mono ? 'text-foreground-neutral-muted' : 'text-foreground-neutral-base'}
|
|
212
|
+
>
|
|
213
|
+
{value}
|
|
214
|
+
</Code>
|
|
215
|
+
))}
|
|
216
|
+
</div>
|
|
217
|
+
</div>
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function humanize(value: string): string {
|
|
222
|
+
return value.replaceAll('_', ' ').replace(/\b\w/g, (letter) => letter.toUpperCase());
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function splitJobEvaluationTrace(trace: readonly EvaluationTraceEntry[]): {
|
|
226
|
+
conditionTrace: EvaluationTraceEntry[];
|
|
227
|
+
executionNameTrace: EvaluationTraceEntry[];
|
|
228
|
+
} {
|
|
229
|
+
const conditionTrace: EvaluationTraceEntry[] = [];
|
|
230
|
+
const executionNameTrace: EvaluationTraceEntry[] = [];
|
|
231
|
+
|
|
232
|
+
for (const entry of trace) {
|
|
233
|
+
if (!('dropped' in entry) && entry.field === 'job.execution_name') {
|
|
234
|
+
executionNameTrace.push(entry);
|
|
235
|
+
} else {
|
|
236
|
+
conditionTrace.push(entry);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return {conditionTrace, executionNameTrace};
|
|
241
|
+
}
|
|
@@ -2,6 +2,7 @@ import {Icon} from '@shipfox/react-ui/icon';
|
|
|
2
2
|
import {Tooltip, TooltipContent, TooltipTrigger} from '@shipfox/react-ui/tooltip';
|
|
3
3
|
import {Code, Text} from '@shipfox/react-ui/typography';
|
|
4
4
|
import {formatTimestamp} from '@shipfox/react-ui/utils';
|
|
5
|
+
import type {ReactNode} from 'react';
|
|
5
6
|
import {getWorkflowStatusVisual} from '#components/workflow-status/status-visuals.js';
|
|
6
7
|
import {WorkflowStatusIcon} from '#components/workflow-status/workflow-status-icon.js';
|
|
7
8
|
import type {RunAnnotationSummary} from '#core/run-annotation.js';
|
|
@@ -26,6 +27,7 @@ export interface JobDetailHeaderProps {
|
|
|
26
27
|
runAttempt?: number | undefined;
|
|
27
28
|
/** Counts for this job only. Renders a link into the run's Annotations section, never a body. */
|
|
28
29
|
annotationSummary?: RunAnnotationSummary | undefined;
|
|
30
|
+
jobContext?: ReactNode;
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
export function JobDetailHeader({
|
|
@@ -37,58 +39,62 @@ export function JobDetailHeader({
|
|
|
37
39
|
workflowRunId,
|
|
38
40
|
runAttempt,
|
|
39
41
|
annotationSummary,
|
|
42
|
+
jobContext,
|
|
40
43
|
}: JobDetailHeaderProps) {
|
|
41
44
|
const selectedStatus = selectedExecutionStatus(job, selectedJobExecution);
|
|
42
45
|
const jobStatus = getWorkflowStatusVisual(selectedStatus);
|
|
43
46
|
|
|
44
47
|
return (
|
|
45
|
-
<header className="border-b border-border-neutral-base px-
|
|
46
|
-
<div className="flex min-w-0
|
|
47
|
-
<div className="flex min-w-0
|
|
48
|
-
<
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
<Code
|
|
55
|
-
as="h2"
|
|
56
|
-
variant="paragraph"
|
|
57
|
-
bold
|
|
58
|
-
tabIndex={-1}
|
|
59
|
-
className="min-w-0 truncate text-lg leading-24 text-foreground-neutral-base outline-none"
|
|
60
|
-
data-job-heading
|
|
61
|
-
>
|
|
62
|
-
{job.displayName}
|
|
63
|
-
</Code>
|
|
64
|
-
</div>
|
|
65
|
-
|
|
66
|
-
{selectedJobExecution || annotationSummary?.total ? (
|
|
67
|
-
<div className="flex min-w-0 flex-wrap items-center gap-10 text-foreground-neutral-muted">
|
|
68
|
-
{selectedJobExecution && job.executionCountVisible ? (
|
|
69
|
-
<JobExecutionSwitcher
|
|
70
|
-
job={job}
|
|
71
|
-
selectedJobExecution={selectedJobExecution.id}
|
|
72
|
-
onSelectedJobExecutionChange={onSelectedJobExecutionChange}
|
|
73
|
-
variant="title"
|
|
74
|
-
/>
|
|
75
|
-
) : null}
|
|
76
|
-
{selectedJobExecution ? (
|
|
77
|
-
<>
|
|
78
|
-
<JobDurationMeta execution={selectedJobExecution} kind="queue" />
|
|
79
|
-
<JobDurationMeta execution={selectedJobExecution} kind="run" />
|
|
80
|
-
</>
|
|
81
|
-
) : null}
|
|
82
|
-
<RunAnnotationCountChip
|
|
83
|
-
summary={annotationSummary}
|
|
84
|
-
workspaceSlug={workspaceSlug}
|
|
85
|
-
projectSlug={projectSlug}
|
|
86
|
-
workflowRunId={workflowRunId}
|
|
87
|
-
runAttempt={runAttempt}
|
|
88
|
-
jobId={job.id}
|
|
48
|
+
<header className="border-b border-border-neutral-base px-row py-row">
|
|
49
|
+
<div className="flex min-w-0 items-start justify-between gap-cluster">
|
|
50
|
+
<div className="flex min-w-0 flex-col gap-inline">
|
|
51
|
+
<div className="flex min-w-0 items-center gap-inline">
|
|
52
|
+
<WorkflowStatusIcon
|
|
53
|
+
status={selectedStatus}
|
|
54
|
+
size={14}
|
|
55
|
+
tooltip
|
|
56
|
+
ariaLabel={`Job status: ${jobStatus.label}`}
|
|
89
57
|
/>
|
|
58
|
+
<Code
|
|
59
|
+
as="h1"
|
|
60
|
+
variant="paragraph"
|
|
61
|
+
bold
|
|
62
|
+
tabIndex={-1}
|
|
63
|
+
className="min-w-0 truncate text-lg leading-24 text-foreground-neutral-base outline-none"
|
|
64
|
+
data-job-heading
|
|
65
|
+
>
|
|
66
|
+
{job.displayName}
|
|
67
|
+
</Code>
|
|
90
68
|
</div>
|
|
91
|
-
|
|
69
|
+
|
|
70
|
+
{selectedJobExecution || annotationSummary?.total ? (
|
|
71
|
+
<div className="flex min-w-0 flex-wrap items-center gap-inline text-foreground-neutral-muted">
|
|
72
|
+
{selectedJobExecution && job.executionCountVisible ? (
|
|
73
|
+
<JobExecutionSwitcher
|
|
74
|
+
job={job}
|
|
75
|
+
selectedJobExecution={selectedJobExecution.id}
|
|
76
|
+
onSelectedJobExecutionChange={onSelectedJobExecutionChange}
|
|
77
|
+
variant="title"
|
|
78
|
+
/>
|
|
79
|
+
) : null}
|
|
80
|
+
{selectedJobExecution ? (
|
|
81
|
+
<>
|
|
82
|
+
<JobDurationMeta execution={selectedJobExecution} kind="queue" />
|
|
83
|
+
<JobDurationMeta execution={selectedJobExecution} kind="run" />
|
|
84
|
+
</>
|
|
85
|
+
) : null}
|
|
86
|
+
<RunAnnotationCountChip
|
|
87
|
+
summary={annotationSummary}
|
|
88
|
+
workspaceSlug={workspaceSlug}
|
|
89
|
+
projectSlug={projectSlug}
|
|
90
|
+
workflowRunId={workflowRunId}
|
|
91
|
+
runAttempt={runAttempt}
|
|
92
|
+
jobId={job.id}
|
|
93
|
+
/>
|
|
94
|
+
</div>
|
|
95
|
+
) : null}
|
|
96
|
+
</div>
|
|
97
|
+
{jobContext}
|
|
92
98
|
</div>
|
|
93
99
|
</header>
|
|
94
100
|
);
|
|
@@ -116,7 +122,7 @@ function JobDurationMeta({execution, kind}: {execution: JobExecution; kind: 'que
|
|
|
116
122
|
return (
|
|
117
123
|
<Tooltip>
|
|
118
124
|
<TooltipTrigger asChild>
|
|
119
|
-
<span className="inline-flex items-center gap-
|
|
125
|
+
<span className="inline-flex items-center gap-tight whitespace-nowrap font-code text-xs leading-20 tabular-nums">
|
|
120
126
|
<Icon
|
|
121
127
|
name={kind === 'queue' ? 'hourglassLine' : 'timerLine'}
|
|
122
128
|
size={12}
|