@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
|
@@ -44,6 +44,12 @@ export interface RunAnnotationSummary {
|
|
|
44
44
|
success: number;
|
|
45
45
|
/** True when the read hit its page budget, so every count is a lower bound. */
|
|
46
46
|
truncated: boolean;
|
|
47
|
+
/** Optional per-step counts used by the step inspector without loading annotation bodies. */
|
|
48
|
+
stepCounts?: readonly {
|
|
49
|
+
stepId: string;
|
|
50
|
+
attempt: number;
|
|
51
|
+
total: number;
|
|
52
|
+
}[];
|
|
47
53
|
}
|
|
48
54
|
|
|
49
55
|
/** Everything the annotations list needs to route back to the step that emitted an annotation. */
|
package/src/core/workflow-run.ts
CHANGED
|
@@ -46,6 +46,10 @@ export {
|
|
|
46
46
|
STEP_ERROR_REASONS,
|
|
47
47
|
} from './entities/step.js';
|
|
48
48
|
export type {
|
|
49
|
+
EvaluationTraceEntry,
|
|
50
|
+
EvaluationTraceLimitEntry,
|
|
51
|
+
EvaluationTraceValueEntry,
|
|
52
|
+
StepAttemptDetail,
|
|
49
53
|
StepAttemptDisplayDuration,
|
|
50
54
|
StepGateResult,
|
|
51
55
|
} from './entities/step-attempt.js';
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import {
|
|
2
|
+
workflowRunAnnotationSummaryQueryOptions,
|
|
3
|
+
workflowRunAnnotationsQueryOptions,
|
|
4
|
+
} from './annotations.js';
|
|
5
|
+
|
|
6
|
+
const WORKFLOW_RUN_ID = '11111111-1111-4111-8111-111111111111';
|
|
7
|
+
|
|
8
|
+
describe('annotation query polling', () => {
|
|
9
|
+
it('polls enabled list and summary queries by default', () => {
|
|
10
|
+
const summary = workflowRunAnnotationSummaryQueryOptions(WORKFLOW_RUN_ID, 1);
|
|
11
|
+
const list = workflowRunAnnotationsQueryOptions(WORKFLOW_RUN_ID, 1);
|
|
12
|
+
|
|
13
|
+
expect(summary.refetchInterval).toBe(5_000);
|
|
14
|
+
expect(refetchIntervalForPages(list, 1)).toBe(5_000);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('polls when enabled explicitly for both query builders', () => {
|
|
18
|
+
const summary = workflowRunAnnotationSummaryQueryOptions(WORKFLOW_RUN_ID, 1, undefined, {
|
|
19
|
+
polling: true,
|
|
20
|
+
});
|
|
21
|
+
const list = workflowRunAnnotationsQueryOptions(WORKFLOW_RUN_ID, 1, undefined, {
|
|
22
|
+
polling: true,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
expect(summary.refetchInterval).toBe(5_000);
|
|
26
|
+
expect(refetchIntervalForPages(list, 1)).toBe(5_000);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('does not poll disabled query builders', () => {
|
|
30
|
+
const summary = workflowRunAnnotationSummaryQueryOptions(WORKFLOW_RUN_ID, 1, undefined, {
|
|
31
|
+
enabled: false,
|
|
32
|
+
});
|
|
33
|
+
const list = workflowRunAnnotationsQueryOptions(WORKFLOW_RUN_ID, 1, undefined, {
|
|
34
|
+
enabled: false,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
expect(summary.refetchInterval).toBe(false);
|
|
38
|
+
expect(list.refetchInterval).toBe(false);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('stops summary polling when the owning run is terminal', () => {
|
|
42
|
+
const options = workflowRunAnnotationSummaryQueryOptions(WORKFLOW_RUN_ID, 1, undefined, {
|
|
43
|
+
polling: false,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
expect(options.refetchInterval).toBe(false);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('stops annotation list polling when the owning run is terminal', () => {
|
|
50
|
+
const options = workflowRunAnnotationsQueryOptions(WORKFLOW_RUN_ID, 1, undefined, {
|
|
51
|
+
polling: false,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
expect(options.refetchInterval).toBe(false);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('stops annotation list polling after the first page has been loaded', () => {
|
|
58
|
+
const options = workflowRunAnnotationsQueryOptions(WORKFLOW_RUN_ID, 1);
|
|
59
|
+
|
|
60
|
+
expect(refetchIntervalForPages(options, 2)).toBe(false);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('keeps execution-scoped summaries on a distinct reusable query key', () => {
|
|
64
|
+
const runSummary = workflowRunAnnotationSummaryQueryOptions(WORKFLOW_RUN_ID, 1);
|
|
65
|
+
const executionSummary = workflowRunAnnotationSummaryQueryOptions(
|
|
66
|
+
WORKFLOW_RUN_ID,
|
|
67
|
+
1,
|
|
68
|
+
'22222222-2222-4222-8222-222222222222',
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
expect(runSummary.queryKey).not.toEqual(executionSummary.queryKey);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
function refetchIntervalForPages(
|
|
76
|
+
options: ReturnType<typeof workflowRunAnnotationsQueryOptions>,
|
|
77
|
+
pageCount: number,
|
|
78
|
+
) {
|
|
79
|
+
if (typeof options.refetchInterval !== 'function') return options.refetchInterval;
|
|
80
|
+
|
|
81
|
+
return options.refetchInterval({
|
|
82
|
+
state: {data: {pages: Array.from({length: pageCount}, () => ({}))}},
|
|
83
|
+
} as never);
|
|
84
|
+
}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type AnnotationStyleDto,
|
|
3
|
+
annotationSummaryResponseSchema,
|
|
4
|
+
readAnnotationsResponseSchema,
|
|
5
|
+
} from '@shipfox/annotations-dto';
|
|
6
|
+
import {checkedApiRequest} from '@shipfox/client-api';
|
|
7
|
+
import {
|
|
8
|
+
infiniteQueryOptions,
|
|
9
|
+
queryOptions,
|
|
10
|
+
useInfiniteQuery,
|
|
11
|
+
useQuery,
|
|
12
|
+
} from '@tanstack/react-query';
|
|
13
|
+
import type {RunAnnotationSummary} from '#core/run-annotation.js';
|
|
14
|
+
|
|
15
|
+
const ANNOTATIONS_PAGE_SIZE = 100;
|
|
16
|
+
const ANNOTATIONS_REFRESH_INTERVAL_MS = 5_000;
|
|
17
|
+
|
|
18
|
+
export interface WorkflowRunAnnotation {
|
|
19
|
+
id: string;
|
|
20
|
+
jobId: string;
|
|
21
|
+
jobExecutionId: string;
|
|
22
|
+
originStepId: string;
|
|
23
|
+
originStepAttempt: number;
|
|
24
|
+
context: string;
|
|
25
|
+
style: AnnotationStyleDto;
|
|
26
|
+
sequence: number;
|
|
27
|
+
body: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface WorkflowRunAnnotationsPage {
|
|
31
|
+
annotations: WorkflowRunAnnotation[];
|
|
32
|
+
hasMore: boolean;
|
|
33
|
+
nextCursor: string | null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const workflowRunAnnotationsQueryKeys = {
|
|
37
|
+
all: ['workflow-run-annotations'] as const,
|
|
38
|
+
list: (workflowRunId: string, attempt: number, jobExecutionId?: string) =>
|
|
39
|
+
[
|
|
40
|
+
...workflowRunAnnotationsQueryKeys.all,
|
|
41
|
+
'list',
|
|
42
|
+
workflowRunId,
|
|
43
|
+
attempt,
|
|
44
|
+
jobExecutionId ?? null,
|
|
45
|
+
] as const,
|
|
46
|
+
summary: (workflowRunId: string, attempt: number, jobExecutionId?: string) =>
|
|
47
|
+
[
|
|
48
|
+
...workflowRunAnnotationsQueryKeys.all,
|
|
49
|
+
'summary',
|
|
50
|
+
workflowRunId,
|
|
51
|
+
attempt,
|
|
52
|
+
jobExecutionId ?? null,
|
|
53
|
+
] as const,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
function annotationParams({
|
|
57
|
+
workflowRunId,
|
|
58
|
+
attempt,
|
|
59
|
+
jobExecutionId,
|
|
60
|
+
cursor,
|
|
61
|
+
}: {
|
|
62
|
+
workflowRunId: string;
|
|
63
|
+
attempt: number;
|
|
64
|
+
jobExecutionId?: string | undefined;
|
|
65
|
+
cursor?: string | null | undefined;
|
|
66
|
+
}): URLSearchParams {
|
|
67
|
+
const params = new URLSearchParams({
|
|
68
|
+
workflow_run_id: workflowRunId,
|
|
69
|
+
attempt: String(attempt),
|
|
70
|
+
limit: String(ANNOTATIONS_PAGE_SIZE),
|
|
71
|
+
});
|
|
72
|
+
if (jobExecutionId) params.set('job_execution_id', jobExecutionId);
|
|
73
|
+
if (cursor) params.set('cursor', cursor);
|
|
74
|
+
return params;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function getWorkflowRunAnnotationsPage({
|
|
78
|
+
workflowRunId,
|
|
79
|
+
attempt,
|
|
80
|
+
jobExecutionId,
|
|
81
|
+
cursor,
|
|
82
|
+
signal,
|
|
83
|
+
}: {
|
|
84
|
+
workflowRunId: string;
|
|
85
|
+
attempt: number;
|
|
86
|
+
jobExecutionId?: string | undefined;
|
|
87
|
+
cursor?: string | null | undefined;
|
|
88
|
+
signal?: AbortSignal;
|
|
89
|
+
}): Promise<WorkflowRunAnnotationsPage> {
|
|
90
|
+
const response = await checkedApiRequest(
|
|
91
|
+
readAnnotationsResponseSchema,
|
|
92
|
+
`/annotations?${annotationParams({workflowRunId, attempt, jobExecutionId, cursor}).toString()}`,
|
|
93
|
+
{signal},
|
|
94
|
+
);
|
|
95
|
+
return {
|
|
96
|
+
annotations: response.annotations.map(toWorkflowRunAnnotation),
|
|
97
|
+
hasMore: response.has_more,
|
|
98
|
+
nextCursor: response.next_cursor,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function getWorkflowRunAnnotationSummary({
|
|
103
|
+
workflowRunId,
|
|
104
|
+
attempt,
|
|
105
|
+
jobExecutionId,
|
|
106
|
+
signal,
|
|
107
|
+
}: {
|
|
108
|
+
workflowRunId: string;
|
|
109
|
+
attempt: number;
|
|
110
|
+
jobExecutionId?: string | undefined;
|
|
111
|
+
signal?: AbortSignal;
|
|
112
|
+
}): Promise<RunAnnotationSummary> {
|
|
113
|
+
const params = annotationParams({workflowRunId, attempt, jobExecutionId});
|
|
114
|
+
params.delete('limit');
|
|
115
|
+
const response = await checkedApiRequest(
|
|
116
|
+
annotationSummaryResponseSchema,
|
|
117
|
+
`/annotations/summary?${params.toString()}`,
|
|
118
|
+
{signal},
|
|
119
|
+
);
|
|
120
|
+
return {
|
|
121
|
+
total: response.total,
|
|
122
|
+
error: response.error,
|
|
123
|
+
warning: response.warning,
|
|
124
|
+
info: response.info,
|
|
125
|
+
success: response.success,
|
|
126
|
+
truncated: false,
|
|
127
|
+
stepCounts: response.step_counts.map((step) => ({
|
|
128
|
+
stepId: step.origin_step_id,
|
|
129
|
+
attempt: step.origin_step_attempt,
|
|
130
|
+
total: step.total,
|
|
131
|
+
})),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function workflowRunAnnotationsQueryOptions(
|
|
136
|
+
workflowRunId: string | undefined,
|
|
137
|
+
attempt: number | undefined,
|
|
138
|
+
jobExecutionId?: string | undefined,
|
|
139
|
+
options?: {enabled?: boolean | undefined; polling?: boolean | undefined},
|
|
140
|
+
) {
|
|
141
|
+
const enabled = Boolean(workflowRunId) && attempt !== undefined && (options?.enabled ?? true);
|
|
142
|
+
const polling = options?.polling ?? true;
|
|
143
|
+
return infiniteQueryOptions({
|
|
144
|
+
queryKey:
|
|
145
|
+
workflowRunId && attempt !== undefined
|
|
146
|
+
? workflowRunAnnotationsQueryKeys.list(workflowRunId, attempt, jobExecutionId)
|
|
147
|
+
: ([...workflowRunAnnotationsQueryKeys.all, 'list'] as const),
|
|
148
|
+
enabled,
|
|
149
|
+
initialPageParam: null as string | null,
|
|
150
|
+
queryFn: ({pageParam, signal}) =>
|
|
151
|
+
getWorkflowRunAnnotationsPage({
|
|
152
|
+
workflowRunId: workflowRunId ?? '',
|
|
153
|
+
attempt: attempt ?? 0,
|
|
154
|
+
...(jobExecutionId ? {jobExecutionId} : {}),
|
|
155
|
+
cursor: pageParam,
|
|
156
|
+
signal,
|
|
157
|
+
}),
|
|
158
|
+
getNextPageParam: (lastPage) => lastPage.nextCursor,
|
|
159
|
+
staleTime: 2_000,
|
|
160
|
+
refetchInterval:
|
|
161
|
+
enabled && polling
|
|
162
|
+
? (query) => {
|
|
163
|
+
const pages = query.state.data?.pages;
|
|
164
|
+
return pages && pages.length > 1 ? false : ANNOTATIONS_REFRESH_INTERVAL_MS;
|
|
165
|
+
}
|
|
166
|
+
: false,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function useWorkflowRunAnnotationsQuery(
|
|
171
|
+
workflowRunId: string | undefined,
|
|
172
|
+
attempt: number | undefined,
|
|
173
|
+
jobExecutionId?: string | undefined,
|
|
174
|
+
options?: {enabled?: boolean | undefined; polling?: boolean | undefined},
|
|
175
|
+
) {
|
|
176
|
+
return useInfiniteQuery(
|
|
177
|
+
workflowRunAnnotationsQueryOptions(workflowRunId, attempt, jobExecutionId, options),
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function workflowRunAnnotationSummaryQueryOptions(
|
|
182
|
+
workflowRunId: string | undefined,
|
|
183
|
+
attempt: number | undefined,
|
|
184
|
+
jobExecutionId?: string | undefined,
|
|
185
|
+
options?: {enabled?: boolean | undefined; polling?: boolean | undefined},
|
|
186
|
+
) {
|
|
187
|
+
const enabled = Boolean(workflowRunId) && attempt !== undefined && (options?.enabled ?? true);
|
|
188
|
+
const polling = options?.polling ?? true;
|
|
189
|
+
return queryOptions({
|
|
190
|
+
queryKey:
|
|
191
|
+
workflowRunId && attempt !== undefined
|
|
192
|
+
? workflowRunAnnotationsQueryKeys.summary(workflowRunId, attempt, jobExecutionId)
|
|
193
|
+
: ([...workflowRunAnnotationsQueryKeys.all, 'summary'] as const),
|
|
194
|
+
enabled,
|
|
195
|
+
queryFn: ({signal}) =>
|
|
196
|
+
getWorkflowRunAnnotationSummary({
|
|
197
|
+
workflowRunId: workflowRunId ?? '',
|
|
198
|
+
attempt: attempt ?? 0,
|
|
199
|
+
...(jobExecutionId ? {jobExecutionId} : {}),
|
|
200
|
+
signal,
|
|
201
|
+
}),
|
|
202
|
+
staleTime: 2_000,
|
|
203
|
+
refetchInterval: enabled && polling ? ANNOTATIONS_REFRESH_INTERVAL_MS : false,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export function useWorkflowRunAnnotationSummaryQuery(
|
|
208
|
+
workflowRunId: string | undefined,
|
|
209
|
+
attempt: number | undefined,
|
|
210
|
+
jobExecutionId?: string | undefined,
|
|
211
|
+
options?: {enabled?: boolean | undefined; polling?: boolean | undefined},
|
|
212
|
+
) {
|
|
213
|
+
return useQuery(
|
|
214
|
+
workflowRunAnnotationSummaryQueryOptions(workflowRunId, attempt, jobExecutionId, options),
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function flattenWorkflowRunAnnotations(
|
|
219
|
+
data: {pages: readonly WorkflowRunAnnotationsPage[]} | undefined,
|
|
220
|
+
): WorkflowRunAnnotation[] {
|
|
221
|
+
return data?.pages.flatMap((page) => page.annotations) ?? [];
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function toWorkflowRunAnnotation(
|
|
225
|
+
annotation: Awaited<
|
|
226
|
+
ReturnType<typeof readAnnotationsResponseSchema.parse>
|
|
227
|
+
>['annotations'][number],
|
|
228
|
+
): WorkflowRunAnnotation {
|
|
229
|
+
return {
|
|
230
|
+
id: annotation.id,
|
|
231
|
+
jobId: annotation.job_id,
|
|
232
|
+
jobExecutionId: annotation.job_execution_id,
|
|
233
|
+
originStepId: annotation.origin_step_id,
|
|
234
|
+
originStepAttempt: annotation.origin_step_attempt,
|
|
235
|
+
context: annotation.context,
|
|
236
|
+
style: annotation.style,
|
|
237
|
+
sequence: annotation.sequence,
|
|
238
|
+
body: annotation.body,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import {stepAttemptDetailResponseSchema} from '@shipfox/api-workflows-dto';
|
|
2
|
+
import {checkedApiRequest} from '@shipfox/client-api';
|
|
3
|
+
import {queryOptions, type UseQueryOptions, useQuery} from '@tanstack/react-query';
|
|
4
|
+
import type {StepAttemptDetail} from '#core/workflow-run.js';
|
|
5
|
+
import {toStepAttemptDetail} from './workflow-run-mapper.js';
|
|
6
|
+
|
|
7
|
+
export const stepAttemptDetailQueryKeys = {
|
|
8
|
+
all: ['workflow-step-attempt-details'] as const,
|
|
9
|
+
detail: (stepId: string, attempt: number) =>
|
|
10
|
+
[...stepAttemptDetailQueryKeys.all, stepId, attempt] as const,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
type StepAttemptDetailQueryKey =
|
|
14
|
+
| ReturnType<typeof stepAttemptDetailQueryKeys.detail>
|
|
15
|
+
| readonly ['workflow-step-attempt-details'];
|
|
16
|
+
|
|
17
|
+
type StepAttemptDetailQueryOptions = UseQueryOptions<
|
|
18
|
+
StepAttemptDetail,
|
|
19
|
+
Error,
|
|
20
|
+
StepAttemptDetail,
|
|
21
|
+
StepAttemptDetailQueryKey
|
|
22
|
+
>;
|
|
23
|
+
|
|
24
|
+
async function getStepAttemptDetail({
|
|
25
|
+
stepId,
|
|
26
|
+
attempt,
|
|
27
|
+
signal,
|
|
28
|
+
}: {
|
|
29
|
+
stepId: string;
|
|
30
|
+
attempt: number;
|
|
31
|
+
signal?: AbortSignal;
|
|
32
|
+
}): Promise<StepAttemptDetail> {
|
|
33
|
+
const response = await checkedApiRequest(
|
|
34
|
+
stepAttemptDetailResponseSchema,
|
|
35
|
+
`/workflows/runs/steps/${stepId}/attempts/${attempt}`,
|
|
36
|
+
{signal},
|
|
37
|
+
);
|
|
38
|
+
return toStepAttemptDetail(response);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function stepAttemptDetailQueryOptions(
|
|
42
|
+
stepId: string | undefined,
|
|
43
|
+
attempt: number | undefined,
|
|
44
|
+
): StepAttemptDetailQueryOptions {
|
|
45
|
+
return queryOptions({
|
|
46
|
+
queryKey:
|
|
47
|
+
stepId && attempt !== undefined
|
|
48
|
+
? stepAttemptDetailQueryKeys.detail(stepId, attempt)
|
|
49
|
+
: ([...stepAttemptDetailQueryKeys.all] as const),
|
|
50
|
+
enabled: Boolean(stepId) && attempt !== undefined,
|
|
51
|
+
queryFn: ({signal}) =>
|
|
52
|
+
getStepAttemptDetail({stepId: stepId ?? '', attempt: attempt ?? 0, signal}),
|
|
53
|
+
staleTime: Infinity,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function useStepAttemptDetailQuery(
|
|
58
|
+
stepId: string | undefined,
|
|
59
|
+
attempt: number | undefined,
|
|
60
|
+
options?: {enabled?: boolean | undefined},
|
|
61
|
+
) {
|
|
62
|
+
return useQuery({
|
|
63
|
+
...stepAttemptDetailQueryOptions(stepId, attempt),
|
|
64
|
+
...options,
|
|
65
|
+
enabled: Boolean(stepId) && attempt !== undefined && (options?.enabled ?? true),
|
|
66
|
+
});
|
|
67
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
EvaluationTraceDto,
|
|
2
3
|
JobListeningDto,
|
|
4
|
+
StepAttemptDetailResponseDto,
|
|
3
5
|
StepAttemptDto,
|
|
4
6
|
StepGateResultDto,
|
|
5
7
|
WorkflowExecutionEventDto,
|
|
@@ -13,6 +15,7 @@ import type {
|
|
|
13
15
|
WorkflowRunStepDetailDto,
|
|
14
16
|
} from '@shipfox/api-workflows-dto';
|
|
15
17
|
import {
|
|
18
|
+
type EvaluationTraceEntry,
|
|
16
19
|
Job,
|
|
17
20
|
JobExecution,
|
|
18
21
|
type JobListening,
|
|
@@ -139,6 +142,10 @@ export function toJob(dto: WorkflowRunJobDetailDto): Job {
|
|
|
139
142
|
status: dto.status,
|
|
140
143
|
statusReason: dto.status_reason,
|
|
141
144
|
carriedOver: dto.carried_over,
|
|
145
|
+
outputs: dto.outputs ?? null,
|
|
146
|
+
success: dto.success ?? null,
|
|
147
|
+
runner: dto.runner ?? null,
|
|
148
|
+
evaluationTrace: toEvaluationTrace(dto.evaluation_trace),
|
|
142
149
|
listening: dto.listening ? toJobListening(dto.listening) : null,
|
|
143
150
|
listenerStatus: dto.listener_status,
|
|
144
151
|
resolutionReason: dto.resolution_reason,
|
|
@@ -158,11 +165,15 @@ export function toJobExecution(dto: WorkflowRunJobExecutionDetailDto): JobExecut
|
|
|
158
165
|
name: dto.name,
|
|
159
166
|
status: dto.status,
|
|
160
167
|
statusReason: dto.status_reason,
|
|
168
|
+
statusReasonMessage: dto.status_reason_message ?? null,
|
|
169
|
+
runner: dto.runner ?? null,
|
|
170
|
+
outputs: dto.outputs ?? null,
|
|
161
171
|
triggerEvents: (dto.trigger_events ?? []).map(toWorkflowExecutionEvent),
|
|
162
172
|
queuedAt: dto.queued_at ?? null,
|
|
163
173
|
startedAt: dto.started_at ?? null,
|
|
164
174
|
finishedAt: dto.finished_at ?? null,
|
|
165
175
|
timedOutAt: dto.timed_out_at ?? null,
|
|
176
|
+
evaluationTrace: toEvaluationTrace(dto.evaluation_trace),
|
|
166
177
|
createdAt: dto.created_at,
|
|
167
178
|
updatedAt: dto.updated_at,
|
|
168
179
|
steps: dto.steps.map(toStep),
|
|
@@ -179,8 +190,10 @@ export function toStep(dto: WorkflowRunStepDetailDto): Step {
|
|
|
179
190
|
? {startLine: dto.source_location.start_line, endLine: dto.source_location.end_line}
|
|
180
191
|
: null,
|
|
181
192
|
status: dto.status,
|
|
193
|
+
statusReason: dto.status_reason,
|
|
182
194
|
type: dto.type,
|
|
183
195
|
config: dto.config,
|
|
196
|
+
evaluationTrace: toEvaluationTrace(dto.evaluation_trace),
|
|
184
197
|
agentConfig: toAgentStepConfig(dto),
|
|
185
198
|
error: dto.error
|
|
186
199
|
? {
|
|
@@ -210,6 +223,8 @@ export function toStepAttempt(dto: StepAttemptDto, jobExecutionId: string): Step
|
|
|
210
223
|
status: dto.status,
|
|
211
224
|
exitCode: dto.exit_code ?? null,
|
|
212
225
|
output: dto.output ?? null,
|
|
226
|
+
outputs: dto.outputs ?? dto.output ?? null,
|
|
227
|
+
response: dto.response ?? null,
|
|
213
228
|
error: dto.error ?? null,
|
|
214
229
|
gateResult: toStepGateResult(dto.gate_result),
|
|
215
230
|
restartFeedback: dto.restart_feedback ?? null,
|
|
@@ -218,6 +233,16 @@ export function toStepAttempt(dto: StepAttemptDto, jobExecutionId: string): Step
|
|
|
218
233
|
});
|
|
219
234
|
}
|
|
220
235
|
|
|
236
|
+
export function toStepAttemptDetail(dto: StepAttemptDetailResponseDto) {
|
|
237
|
+
return {
|
|
238
|
+
stepId: dto.step_id,
|
|
239
|
+
attempt: dto.attempt,
|
|
240
|
+
authoredConfig: dto.authored_config,
|
|
241
|
+
config: dto.config,
|
|
242
|
+
evaluationTrace: toEvaluationTrace(dto.evaluation_trace),
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
221
246
|
function toJobListening(dto: JobListeningDto): JobListening {
|
|
222
247
|
return {
|
|
223
248
|
on: dto.on,
|
|
@@ -259,6 +284,28 @@ function toStepGateResult(dto: StepGateResultDto): StepGateResult {
|
|
|
259
284
|
return dto;
|
|
260
285
|
}
|
|
261
286
|
|
|
287
|
+
function toEvaluationTrace(trace: EvaluationTraceDto | null): EvaluationTraceEntry[] | null {
|
|
288
|
+
return (
|
|
289
|
+
trace?.map((entry) =>
|
|
290
|
+
'dropped' in entry
|
|
291
|
+
? entry
|
|
292
|
+
: {
|
|
293
|
+
expression: entry.expression,
|
|
294
|
+
roots: entry.roots,
|
|
295
|
+
fillTarget: entry.fill_target,
|
|
296
|
+
evaluatedAt: entry.evaluated_at,
|
|
297
|
+
field: entry.field,
|
|
298
|
+
...(entry.value === undefined ? {} : {value: entry.value}),
|
|
299
|
+
...(entry.truncated === undefined ? {} : {truncated: entry.truncated}),
|
|
300
|
+
...(entry.expr_truncated === undefined ? {} : {exprTruncated: entry.expr_truncated}),
|
|
301
|
+
...(entry.reference === undefined ? {} : {reference: entry.reference}),
|
|
302
|
+
...(entry.degraded === undefined ? {} : {degraded: entry.degraded}),
|
|
303
|
+
...(entry.env_key === undefined ? {} : {envKey: entry.env_key}),
|
|
304
|
+
},
|
|
305
|
+
) ?? null
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
|
|
262
309
|
function toAgentStepConfig(dto: WorkflowRunStepDetailDto): Step['agentConfig'] {
|
|
263
310
|
if (dto.type !== 'agent') return null;
|
|
264
311
|
return {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type {StepAttemptDetailResponseDto} from '@shipfox/api-workflows-dto';
|
|
1
2
|
import {configureApiClient} from '@shipfox/client-api';
|
|
2
3
|
import {type InfiniteData, QueryClient, QueryClientProvider} from '@tanstack/react-query';
|
|
3
4
|
import {act, cleanup, renderHook, waitFor} from '@testing-library/react';
|
|
@@ -11,7 +12,8 @@ import {
|
|
|
11
12
|
workflowRunDto,
|
|
12
13
|
workflowRunListResponseDto,
|
|
13
14
|
} from '#test/fixtures/workflow-run.js';
|
|
14
|
-
import {
|
|
15
|
+
import {useStepAttemptDetailQuery} from './step-attempt-detail.js';
|
|
16
|
+
import {toStepAttemptDetail, toWorkflowRun, toWorkflowRunListPage} from './workflow-run-mapper.js';
|
|
15
17
|
import {
|
|
16
18
|
fireManualWorkflow,
|
|
17
19
|
useCancelWorkflowRunMutation,
|
|
@@ -133,6 +135,64 @@ describe('workflow run API hooks', () => {
|
|
|
133
135
|
expect(cached).toHaveProperty('triggerSource', 'manual');
|
|
134
136
|
});
|
|
135
137
|
|
|
138
|
+
test('maps lazy step attempt details to authored and resolved troubleshooting data', () => {
|
|
139
|
+
const dto: StepAttemptDetailResponseDto = {
|
|
140
|
+
step_id: '88888888-8888-4888-8888-888888888888',
|
|
141
|
+
attempt: 2,
|
|
142
|
+
authored_config: {run: 'echo $' + '{{ inputs.message }}'},
|
|
143
|
+
config: {run: 'echo hello'},
|
|
144
|
+
evaluation_trace: [
|
|
145
|
+
{
|
|
146
|
+
expression: 'inputs.message',
|
|
147
|
+
roots: ['inputs.message'],
|
|
148
|
+
fill_target: 'run',
|
|
149
|
+
evaluated_at: '2026-08-05T12:00:00.000Z',
|
|
150
|
+
field: 'run',
|
|
151
|
+
value: 'hello',
|
|
152
|
+
degraded: false,
|
|
153
|
+
},
|
|
154
|
+
],
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
expect(toStepAttemptDetail(dto)).toEqual({
|
|
158
|
+
stepId: dto.step_id,
|
|
159
|
+
attempt: 2,
|
|
160
|
+
authoredConfig: dto.authored_config,
|
|
161
|
+
config: dto.config,
|
|
162
|
+
evaluationTrace: [
|
|
163
|
+
{
|
|
164
|
+
expression: 'inputs.message',
|
|
165
|
+
roots: ['inputs.message'],
|
|
166
|
+
fillTarget: 'run',
|
|
167
|
+
evaluatedAt: '2026-08-05T12:00:00.000Z',
|
|
168
|
+
field: 'run',
|
|
169
|
+
value: 'hello',
|
|
170
|
+
degraded: false,
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test('does not fetch step attempt details while the inspector is closed', async () => {
|
|
177
|
+
const fetchImpl = vi.fn(async () =>
|
|
178
|
+
jsonResponse({
|
|
179
|
+
step_id: '88888888-8888-4888-8888-888888888888',
|
|
180
|
+
attempt: 1,
|
|
181
|
+
authored_config: null,
|
|
182
|
+
config: {},
|
|
183
|
+
evaluation_trace: null,
|
|
184
|
+
}),
|
|
185
|
+
);
|
|
186
|
+
configureApiClient({baseUrl: 'https://api.example.test', fetchImpl});
|
|
187
|
+
|
|
188
|
+
renderWithQueryClient(() =>
|
|
189
|
+
useStepAttemptDetailQuery('88888888-8888-4888-8888-888888888888', 1, {enabled: false}),
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
await Promise.resolve();
|
|
193
|
+
expect(fetchImpl).not.toHaveBeenCalled();
|
|
194
|
+
});
|
|
195
|
+
|
|
136
196
|
test('rejects malformed detail responses before they reach the query cache', async () => {
|
|
137
197
|
const fetchImpl = vi.fn(async () => jsonResponse({id: RUN_ID, trigger_source: 'manual'}));
|
|
138
198
|
configureApiClient({baseUrl: 'https://api.example.test', fetchImpl});
|