@shipfox/client-workflows 16.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/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-type.log +1 -0
- package/CHANGELOG.md +20 -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 +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 +80 -44
- 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 +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.js +7 -7
- 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 +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 +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 +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/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 +237 -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 +87 -45
- 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 +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 +7 -7
- 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 +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.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 +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 +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
|
@@ -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
|
+
}
|