@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
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import type {Meta, StoryObj} from '@storybook/react';
|
|
2
|
+
import {QueryClient, QueryClientProvider} from '@tanstack/react-query';
|
|
3
|
+
import {useState} from 'react';
|
|
4
|
+
import {
|
|
5
|
+
workflowJob,
|
|
6
|
+
workflowJobExecutionDto,
|
|
7
|
+
workflowStepAttemptDto,
|
|
8
|
+
workflowStepDto,
|
|
9
|
+
} from '#test/fixtures/workflow-run.js';
|
|
10
|
+
import {buildStepListModel, type StepListEntryModel} from '../step-list/step-list-model.js';
|
|
11
|
+
import {StepInspectorSheet} from './step-troubleshooting.js';
|
|
12
|
+
|
|
13
|
+
const meta = {
|
|
14
|
+
title: 'Workflows/StepInspector',
|
|
15
|
+
parameters: {
|
|
16
|
+
layout: 'fullscreen',
|
|
17
|
+
},
|
|
18
|
+
} satisfies Meta;
|
|
19
|
+
|
|
20
|
+
export default meta;
|
|
21
|
+
type Story = StoryObj<typeof meta>;
|
|
22
|
+
|
|
23
|
+
export const FailedStep: Story = {
|
|
24
|
+
render: () => <FailedStepStory />,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
function FailedStepStory() {
|
|
28
|
+
const [queryClient] = useState(
|
|
29
|
+
() => new QueryClient({defaultOptions: {queries: {staleTime: Number.POSITIVE_INFINITY}}}),
|
|
30
|
+
);
|
|
31
|
+
const entry = failedStepEntry();
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<QueryClientProvider client={queryClient}>
|
|
35
|
+
<main className="min-h-screen bg-background-neutral-base p-16">
|
|
36
|
+
<StepInspectorSheet
|
|
37
|
+
entry={entry}
|
|
38
|
+
open
|
|
39
|
+
onOpenChange={() => undefined}
|
|
40
|
+
workspaceSlug="acme"
|
|
41
|
+
projectSlug="platform"
|
|
42
|
+
workflowRunId="11111111-1111-4111-8111-111111111111"
|
|
43
|
+
runAttempt={1}
|
|
44
|
+
jobId="44444444-4444-4444-8444-000000000001"
|
|
45
|
+
/>
|
|
46
|
+
</main>
|
|
47
|
+
</QueryClientProvider>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function failedStepEntry(): StepListEntryModel {
|
|
52
|
+
const jobId = '44444444-4444-4444-8444-000000000001';
|
|
53
|
+
const executionId = '77777777-7777-4777-8777-000000000001';
|
|
54
|
+
const stepId = '55555555-5555-4555-8555-000000000001';
|
|
55
|
+
const attemptId = '66666666-6666-4666-8666-000000000001';
|
|
56
|
+
const job = workflowJob({
|
|
57
|
+
id: jobId,
|
|
58
|
+
name: 'verification',
|
|
59
|
+
key: 'verification',
|
|
60
|
+
status: 'failed',
|
|
61
|
+
job_executions: [
|
|
62
|
+
workflowJobExecutionDto({
|
|
63
|
+
id: executionId,
|
|
64
|
+
job_id: jobId,
|
|
65
|
+
status: 'failed',
|
|
66
|
+
steps: [
|
|
67
|
+
workflowStepDto({
|
|
68
|
+
id: stepId,
|
|
69
|
+
job_execution_id: executionId,
|
|
70
|
+
name: 'Run verification suite',
|
|
71
|
+
key: 'run-verification',
|
|
72
|
+
status: 'failed',
|
|
73
|
+
status_reason: 'agent_invocation_failed',
|
|
74
|
+
config: {run: 'pnpm test --filter=@shipfox/client-workflows'},
|
|
75
|
+
evaluation_trace: [
|
|
76
|
+
{
|
|
77
|
+
expression: 'inputs["branch"]',
|
|
78
|
+
roots: ['inputs'],
|
|
79
|
+
fill_target: 'step-dispatch',
|
|
80
|
+
evaluated_at: '2026-06-21T12:04:00.000Z',
|
|
81
|
+
field: 'branch',
|
|
82
|
+
value: 'main',
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
error: {
|
|
86
|
+
message: 'The verification agent returned a non-zero exit code.',
|
|
87
|
+
reason: 'agent_invocation_failed',
|
|
88
|
+
category: 'user',
|
|
89
|
+
exit_code: 1,
|
|
90
|
+
},
|
|
91
|
+
attempts: [
|
|
92
|
+
workflowStepAttemptDto({
|
|
93
|
+
id: attemptId,
|
|
94
|
+
step_id: stepId,
|
|
95
|
+
status: 'failed',
|
|
96
|
+
exit_code: 1,
|
|
97
|
+
output: {summary: 'Tests failed before the report was uploaded.'},
|
|
98
|
+
outputs: {failed_tests: 3},
|
|
99
|
+
error: {
|
|
100
|
+
message: 'The verification agent returned a non-zero exit code.',
|
|
101
|
+
reason: 'agent_invocation_failed',
|
|
102
|
+
category: 'user',
|
|
103
|
+
exit_code: 1,
|
|
104
|
+
},
|
|
105
|
+
finished_at: '2026-06-21T12:04:00.000Z',
|
|
106
|
+
}),
|
|
107
|
+
],
|
|
108
|
+
}),
|
|
109
|
+
],
|
|
110
|
+
}),
|
|
111
|
+
],
|
|
112
|
+
});
|
|
113
|
+
const execution = job.jobExecutions[0];
|
|
114
|
+
if (!execution) throw new Error('Story fixture is missing a job execution.');
|
|
115
|
+
|
|
116
|
+
const entry = buildStepListModel({job, jobExecution: execution}).entries[0];
|
|
117
|
+
if (!entry) throw new Error('Story fixture is missing a step attempt.');
|
|
118
|
+
|
|
119
|
+
return entry;
|
|
120
|
+
}
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import {configureApiClient} from '@shipfox/client-api';
|
|
2
|
+
import {QueryClient, QueryClientProvider} from '@tanstack/react-query';
|
|
3
|
+
import {
|
|
4
|
+
createMemoryHistory,
|
|
5
|
+
createRootRoute,
|
|
6
|
+
createRoute,
|
|
7
|
+
createRouter,
|
|
8
|
+
Outlet,
|
|
9
|
+
RouterProvider,
|
|
10
|
+
} from '@tanstack/react-router';
|
|
11
|
+
import {act, render, screen} from '@testing-library/react';
|
|
12
|
+
import userEvent from '@testing-library/user-event';
|
|
13
|
+
import {useState} from 'react';
|
|
14
|
+
import {
|
|
15
|
+
workflowJob,
|
|
16
|
+
workflowJobExecutionDto,
|
|
17
|
+
workflowStepAttemptDto,
|
|
18
|
+
workflowStepDto,
|
|
19
|
+
} from '#test/fixtures/workflow-run.js';
|
|
20
|
+
import {buildStepListModel, type StepListEntryModel} from '../step-list/step-list-model.js';
|
|
21
|
+
import {StepInspectorSheet} from './step-troubleshooting.js';
|
|
22
|
+
|
|
23
|
+
const STEP_ID = '55555555-5555-4555-8555-555555555555';
|
|
24
|
+
const ATTEMPT_ID = '66666666-6666-4666-8666-666666666666';
|
|
25
|
+
const EXECUTION_ID = '77777777-7777-4777-8777-777777777777';
|
|
26
|
+
const INSPECTOR_TRIGGER_NAME = 'Open inspector';
|
|
27
|
+
|
|
28
|
+
describe('StepInspectorSheet', () => {
|
|
29
|
+
afterEach(() => {
|
|
30
|
+
configureApiClient({baseUrl: '', fetchImpl: undefined});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('shows a loading state only after the inspector is opened', async () => {
|
|
34
|
+
const user = userEvent.setup();
|
|
35
|
+
configureApiClient({
|
|
36
|
+
fetchImpl: vi.fn(() => new Promise<Response>(() => undefined)),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
await renderPanel();
|
|
40
|
+
|
|
41
|
+
expect(screen.queryByRole('status', {name: 'Loading troubleshooting details'})).toBeNull();
|
|
42
|
+
await user.click(screen.getByRole('button', {name: INSPECTOR_TRIGGER_NAME}));
|
|
43
|
+
expect(
|
|
44
|
+
await screen.findByRole('status', {name: 'Loading troubleshooting details'}),
|
|
45
|
+
).toBeInTheDocument();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('keeps failure detail out of the default log path', async () => {
|
|
49
|
+
const user = userEvent.setup();
|
|
50
|
+
configureApiClient({
|
|
51
|
+
fetchImpl: vi.fn(() => new Promise<Response>(() => undefined)),
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
await renderPanel();
|
|
55
|
+
|
|
56
|
+
expect(screen.queryByRole('alert')).toBeNull();
|
|
57
|
+
await user.click(screen.getByRole('button', {name: INSPECTOR_TRIGGER_NAME}));
|
|
58
|
+
expect(await screen.findByRole('alert')).toBeInTheDocument();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('shows the evaluation count only after the lazy detail response arrives', async () => {
|
|
62
|
+
const user = userEvent.setup();
|
|
63
|
+
configureApiClient({
|
|
64
|
+
fetchImpl: vi.fn(async () =>
|
|
65
|
+
jsonResponse({
|
|
66
|
+
step_id: STEP_ID,
|
|
67
|
+
attempt: 1,
|
|
68
|
+
authored_config: {run: 'pnpm test'},
|
|
69
|
+
config: {run: 'pnpm test --filter=client'},
|
|
70
|
+
evaluation_trace: [
|
|
71
|
+
{
|
|
72
|
+
expression: 'inputs.message',
|
|
73
|
+
roots: ['inputs.message'],
|
|
74
|
+
fill_target: 'run',
|
|
75
|
+
evaluated_at: '2026-08-05T12:00:00.000Z',
|
|
76
|
+
field: 'run',
|
|
77
|
+
value: 'hello',
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
}),
|
|
81
|
+
),
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
await renderPanel();
|
|
85
|
+
|
|
86
|
+
expect(screen.queryByText('Evaluation')).toBeNull();
|
|
87
|
+
await user.click(screen.getByRole('button', {name: INSPECTOR_TRIGGER_NAME}));
|
|
88
|
+
expect((await screen.findAllByText('Evaluation')).length).toBeGreaterThan(0);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('shows an actionable error and retries the detail request', async () => {
|
|
92
|
+
const user = userEvent.setup();
|
|
93
|
+
const fetchImpl = vi
|
|
94
|
+
.fn()
|
|
95
|
+
.mockResolvedValueOnce(jsonResponse({code: 'server-error'}, {status: 500}))
|
|
96
|
+
.mockResolvedValueOnce(
|
|
97
|
+
jsonResponse({
|
|
98
|
+
step_id: STEP_ID,
|
|
99
|
+
attempt: 1,
|
|
100
|
+
authored_config: {run: 'pnpm test'},
|
|
101
|
+
config: {run: 'pnpm test --filter=client'},
|
|
102
|
+
evaluation_trace: null,
|
|
103
|
+
}),
|
|
104
|
+
);
|
|
105
|
+
configureApiClient({fetchImpl});
|
|
106
|
+
|
|
107
|
+
await renderPanel();
|
|
108
|
+
await user.click(screen.getByRole('button', {name: INSPECTOR_TRIGGER_NAME}));
|
|
109
|
+
|
|
110
|
+
expect(await screen.findByText('Details unavailable')).toBeInTheDocument();
|
|
111
|
+
await user.click(screen.getByRole('button', {name: 'Retry'}));
|
|
112
|
+
expect(await screen.findByRole('region', {name: 'Inputs'})).toBeInTheDocument();
|
|
113
|
+
expect(screen.getByText('Resolved configuration')).toBeInTheDocument();
|
|
114
|
+
expect(fetchImpl).toHaveBeenCalledTimes(2);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('keeps the annotation link available when the detail request fails', async () => {
|
|
118
|
+
const user = userEvent.setup();
|
|
119
|
+
configureApiClient({
|
|
120
|
+
fetchImpl: vi.fn(() => Promise.resolve(jsonResponse({code: 'server-error'}, {status: 500}))),
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
await renderPanel({annotationCount: 2});
|
|
124
|
+
await user.click(screen.getByRole('button', {name: INSPECTOR_TRIGGER_NAME}));
|
|
125
|
+
|
|
126
|
+
expect(await screen.findByText('Details unavailable')).toBeInTheDocument();
|
|
127
|
+
expect(screen.getByRole('link', {name: 'View 2 annotations'})).toBeInTheDocument();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('does not replace an annotation link with an empty inspector state', async () => {
|
|
131
|
+
const user = userEvent.setup();
|
|
132
|
+
configureApiClient({
|
|
133
|
+
fetchImpl: vi.fn(async () =>
|
|
134
|
+
jsonResponse({
|
|
135
|
+
step_id: STEP_ID,
|
|
136
|
+
attempt: 1,
|
|
137
|
+
authored_config: null,
|
|
138
|
+
config: null,
|
|
139
|
+
evaluation_trace: null,
|
|
140
|
+
}),
|
|
141
|
+
),
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
await renderPanel({annotationCount: 2, entry: emptyStepEntry()});
|
|
145
|
+
await user.click(screen.getByRole('button', {name: INSPECTOR_TRIGGER_NAME}));
|
|
146
|
+
|
|
147
|
+
expect(await screen.findByRole('link', {name: 'View 2 annotations'})).toBeInTheDocument();
|
|
148
|
+
expect(screen.queryByText('No additional troubleshooting details were recorded.')).toBeNull();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('does not show current step configuration or evaluation for a historical attempt', async () => {
|
|
152
|
+
const user = userEvent.setup();
|
|
153
|
+
configureApiClient({
|
|
154
|
+
fetchImpl: vi.fn(async () =>
|
|
155
|
+
jsonResponse({
|
|
156
|
+
step_id: STEP_ID,
|
|
157
|
+
attempt: 1,
|
|
158
|
+
authored_config: null,
|
|
159
|
+
config: null,
|
|
160
|
+
evaluation_trace: null,
|
|
161
|
+
}),
|
|
162
|
+
),
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
await renderPanel();
|
|
166
|
+
await user.click(screen.getByRole('button', {name: INSPECTOR_TRIGGER_NAME}));
|
|
167
|
+
expect(await screen.findByRole('region', {name: 'Outputs'})).toBeInTheDocument();
|
|
168
|
+
|
|
169
|
+
expect(screen.queryByText('Authored configuration')).toBeNull();
|
|
170
|
+
expect(screen.queryByText('Resolved configuration')).toBeNull();
|
|
171
|
+
expect(screen.queryByText('Evaluation')).toBeNull();
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
async function renderPanel({
|
|
176
|
+
annotationCount,
|
|
177
|
+
entry,
|
|
178
|
+
}: {
|
|
179
|
+
annotationCount?: number;
|
|
180
|
+
entry?: StepListEntryModel;
|
|
181
|
+
} = {}) {
|
|
182
|
+
const queryClient = new QueryClient({defaultOptions: {queries: {retry: false}}});
|
|
183
|
+
const rootRoute = createRootRoute({component: Outlet});
|
|
184
|
+
const panelRoute = createRoute({
|
|
185
|
+
getParentRoute: () => rootRoute,
|
|
186
|
+
path: '/w/$workspaceSlug/p/$projectSlug/runs/$workflowRunId',
|
|
187
|
+
component: () => (
|
|
188
|
+
<QueryClientProvider client={queryClient}>
|
|
189
|
+
<PanelHarness annotationCount={annotationCount} entry={entry} />
|
|
190
|
+
</QueryClientProvider>
|
|
191
|
+
),
|
|
192
|
+
});
|
|
193
|
+
const router = createRouter({
|
|
194
|
+
history: createMemoryHistory({initialEntries: ['/w/acme/p/platform/runs/run-1']}),
|
|
195
|
+
routeTree: rootRoute.addChildren([panelRoute]),
|
|
196
|
+
});
|
|
197
|
+
await router.load();
|
|
198
|
+
let result: ReturnType<typeof render> | undefined;
|
|
199
|
+
await act(() => {
|
|
200
|
+
result = render(<RouterProvider router={router} />);
|
|
201
|
+
});
|
|
202
|
+
if (!result) throw new Error('Step inspector did not render.');
|
|
203
|
+
return result;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function PanelHarness({
|
|
207
|
+
annotationCount,
|
|
208
|
+
entry: providedEntry,
|
|
209
|
+
}: {
|
|
210
|
+
annotationCount?: number | undefined;
|
|
211
|
+
entry?: StepListEntryModel | undefined;
|
|
212
|
+
}) {
|
|
213
|
+
const [inspectorOpen, setInspectorOpen] = useState(false);
|
|
214
|
+
const entry = providedEntry ?? stepEntry();
|
|
215
|
+
return (
|
|
216
|
+
<>
|
|
217
|
+
<button type="button" onClick={() => setInspectorOpen(true)}>
|
|
218
|
+
Open inspector
|
|
219
|
+
</button>
|
|
220
|
+
<StepInspectorSheet
|
|
221
|
+
entry={entry}
|
|
222
|
+
open={inspectorOpen}
|
|
223
|
+
onOpenChange={setInspectorOpen}
|
|
224
|
+
workspaceSlug="acme"
|
|
225
|
+
projectSlug="platform"
|
|
226
|
+
workflowRunId="11111111-1111-4111-8111-111111111111"
|
|
227
|
+
runAttempt={1}
|
|
228
|
+
jobId="44444444-4444-4444-8444-444444444444"
|
|
229
|
+
annotationCount={annotationCount}
|
|
230
|
+
/>
|
|
231
|
+
</>
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function stepEntry(): StepListEntryModel {
|
|
236
|
+
const jobId = '44444444-4444-4444-8444-444444444444';
|
|
237
|
+
const job = workflowJob({
|
|
238
|
+
id: jobId,
|
|
239
|
+
name: 'verification',
|
|
240
|
+
key: 'verification',
|
|
241
|
+
status: 'failed',
|
|
242
|
+
job_executions: [
|
|
243
|
+
workflowJobExecutionDto({
|
|
244
|
+
id: EXECUTION_ID,
|
|
245
|
+
job_id: jobId,
|
|
246
|
+
status: 'failed',
|
|
247
|
+
steps: [
|
|
248
|
+
workflowStepDto({
|
|
249
|
+
id: STEP_ID,
|
|
250
|
+
job_execution_id: EXECUTION_ID,
|
|
251
|
+
name: 'Run verification',
|
|
252
|
+
status: 'failed',
|
|
253
|
+
config: {run: 'pnpm test'},
|
|
254
|
+
evaluation_trace: [
|
|
255
|
+
{
|
|
256
|
+
expression: 'inputs.message',
|
|
257
|
+
roots: ['inputs.message'],
|
|
258
|
+
fill_target: 'run',
|
|
259
|
+
evaluated_at: '2026-08-05T12:00:00.000Z',
|
|
260
|
+
field: 'run',
|
|
261
|
+
value: 'current attempt value',
|
|
262
|
+
},
|
|
263
|
+
],
|
|
264
|
+
attempts: [
|
|
265
|
+
workflowStepAttemptDto({
|
|
266
|
+
id: ATTEMPT_ID,
|
|
267
|
+
step_id: STEP_ID,
|
|
268
|
+
status: 'failed',
|
|
269
|
+
output: {result: 'failed'},
|
|
270
|
+
finished_at: '2026-08-05T12:01:00.000Z',
|
|
271
|
+
}),
|
|
272
|
+
],
|
|
273
|
+
}),
|
|
274
|
+
],
|
|
275
|
+
}),
|
|
276
|
+
],
|
|
277
|
+
});
|
|
278
|
+
const execution = job.jobExecutions[0];
|
|
279
|
+
if (!execution) throw new Error('Test fixture is missing an execution.');
|
|
280
|
+
const entry = buildStepListModel({job, jobExecution: execution}).entries[0];
|
|
281
|
+
if (!entry) throw new Error('Test fixture is missing a step attempt.');
|
|
282
|
+
|
|
283
|
+
return entry;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function emptyStepEntry(): StepListEntryModel {
|
|
287
|
+
const jobId = '44444444-4444-4444-8444-444444444444';
|
|
288
|
+
const job = workflowJob({
|
|
289
|
+
id: jobId,
|
|
290
|
+
name: 'verification',
|
|
291
|
+
key: 'verification',
|
|
292
|
+
status: 'succeeded',
|
|
293
|
+
job_executions: [
|
|
294
|
+
workflowJobExecutionDto({
|
|
295
|
+
id: EXECUTION_ID,
|
|
296
|
+
job_id: jobId,
|
|
297
|
+
status: 'succeeded',
|
|
298
|
+
steps: [
|
|
299
|
+
workflowStepDto({
|
|
300
|
+
id: STEP_ID,
|
|
301
|
+
job_execution_id: EXECUTION_ID,
|
|
302
|
+
name: 'Run verification',
|
|
303
|
+
status: 'succeeded',
|
|
304
|
+
config: {},
|
|
305
|
+
attempts: [
|
|
306
|
+
workflowStepAttemptDto({
|
|
307
|
+
id: ATTEMPT_ID,
|
|
308
|
+
step_id: STEP_ID,
|
|
309
|
+
status: 'succeeded',
|
|
310
|
+
output: null,
|
|
311
|
+
outputs: null,
|
|
312
|
+
response: null,
|
|
313
|
+
error: null,
|
|
314
|
+
exit_code: 0,
|
|
315
|
+
finished_at: '2026-08-05T12:01:00.000Z',
|
|
316
|
+
}),
|
|
317
|
+
],
|
|
318
|
+
}),
|
|
319
|
+
],
|
|
320
|
+
}),
|
|
321
|
+
],
|
|
322
|
+
});
|
|
323
|
+
const execution = job.jobExecutions[0];
|
|
324
|
+
if (!execution) throw new Error('Test fixture is missing an execution.');
|
|
325
|
+
const entry = buildStepListModel({job, jobExecution: execution}).entries[0];
|
|
326
|
+
if (!entry) throw new Error('Test fixture is missing a step attempt.');
|
|
327
|
+
|
|
328
|
+
return entry;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function jsonResponse(body: unknown, init: ResponseInit = {}): Response {
|
|
332
|
+
return new Response(JSON.stringify(body), {
|
|
333
|
+
status: 200,
|
|
334
|
+
headers: {'content-type': 'application/json'},
|
|
335
|
+
...init,
|
|
336
|
+
});
|
|
337
|
+
}
|