@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
|
@@ -1,13 +1,28 @@
|
|
|
1
|
+
import {MetadataSeparator} from '@shipfox/client-ui';
|
|
2
|
+
import {Icon} from '@shipfox/react-ui/icon';
|
|
1
3
|
import {Text} from '@shipfox/react-ui/typography';
|
|
4
|
+
import {cn} from '@shipfox/react-ui/utils';
|
|
2
5
|
import {Link} from '@tanstack/react-router';
|
|
3
|
-
import type {RunAnnotationSummary} from '#core/
|
|
6
|
+
import type {RunAnnotationSummary} from '#core/run-annotation.js';
|
|
4
7
|
import {
|
|
5
|
-
WORKFLOW_RUN_ANNOTATION_SEVERITIES,
|
|
6
8
|
type WorkflowRunAnnotationSeverity,
|
|
7
9
|
type WorkflowRunsSearch,
|
|
8
10
|
workflowRunSearchParams,
|
|
9
11
|
} from '#routes/inputs.js';
|
|
10
|
-
import {
|
|
12
|
+
import {SEVERITY_ICON, SEVERITY_ICON_TONE} from './severity-visuals.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The severities this line names.
|
|
16
|
+
*
|
|
17
|
+
* The line exists so a failing run cannot look clean while the explanation sits behind an
|
|
18
|
+
* inactive section, which makes its subject "what needs attention", not "what happened".
|
|
19
|
+
* `info` and `success` are already inside the total, and repeating them gives "nothing is
|
|
20
|
+
* wrong" the same weight and the same accent as "something failed".
|
|
21
|
+
*/
|
|
22
|
+
const ATTENTION_SEVERITIES = [
|
|
23
|
+
'error',
|
|
24
|
+
'warning',
|
|
25
|
+
] as const satisfies readonly WorkflowRunAnnotationSeverity[];
|
|
11
26
|
|
|
12
27
|
export interface RunAnnotationSummaryLineProps {
|
|
13
28
|
summary?: RunAnnotationSummary | undefined;
|
|
@@ -24,22 +39,49 @@ export function RunAnnotationSummaryLine({
|
|
|
24
39
|
workflowRunId,
|
|
25
40
|
search = {},
|
|
26
41
|
}: RunAnnotationSummaryLineProps) {
|
|
27
|
-
|
|
42
|
+
// A run with no annotations says so in its empty state; a "0 annotations" line beside it is
|
|
43
|
+
// the same fact twice.
|
|
44
|
+
if (!summary || summary.total === 0) return null;
|
|
28
45
|
|
|
29
|
-
const visibleSeverities =
|
|
30
|
-
|
|
31
|
-
);
|
|
46
|
+
const visibleSeverities = ATTENTION_SEVERITIES.filter((severity) => summary[severity] > 0);
|
|
47
|
+
|
|
48
|
+
const totalLabel = countLabel(summary.total, 'annotation', summary.truncated);
|
|
32
49
|
|
|
33
50
|
return (
|
|
34
|
-
<div className="flex flex-wrap items-center gap-x-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
51
|
+
<div className="flex flex-wrap items-center gap-x-inline gap-y-tight text-foreground-neutral-subtle">
|
|
52
|
+
{search.severity && workspaceSlug && projectSlug && workflowRunId ? (
|
|
53
|
+
// The severity filter has no control of its own, so while one is active the total is
|
|
54
|
+
// the way back out. Unfiltered it stays plain text: a link to where you already are
|
|
55
|
+
// is noise.
|
|
56
|
+
<Link
|
|
57
|
+
to="/w/$workspaceSlug/p/$projectSlug/runs/$workflowRunId"
|
|
58
|
+
params={{workspaceSlug, projectSlug, workflowRunId}}
|
|
59
|
+
search={
|
|
60
|
+
workflowRunSearchParams(
|
|
61
|
+
{...withoutSeverity(search), tab: 'annotations'},
|
|
62
|
+
search,
|
|
63
|
+
) as never
|
|
64
|
+
}
|
|
65
|
+
aria-label={`Show all ${totalLabel}`}
|
|
66
|
+
className="shrink-0 rounded-4 outline-none focus-visible:shadow-border-interactive-with-active"
|
|
67
|
+
>
|
|
68
|
+
{/* Underlined rather than accented: the escape hatch has to read as a link without
|
|
69
|
+
becoming the loudest thing on a line whose job is to be scannable. */}
|
|
70
|
+
<Text as="span" size="xs" className="text-foreground-neutral-subtle underline">
|
|
71
|
+
{totalLabel}
|
|
72
|
+
</Text>
|
|
73
|
+
</Link>
|
|
74
|
+
) : (
|
|
75
|
+
<Text as="span" size="xs" className="shrink-0 text-foreground-neutral-subtle">
|
|
76
|
+
{totalLabel}
|
|
77
|
+
</Text>
|
|
78
|
+
)}
|
|
38
79
|
{visibleSeverities.map((severity) => (
|
|
39
|
-
<span key={severity} className="inline-flex shrink-0 items-center gap-
|
|
80
|
+
<span key={severity} className="inline-flex shrink-0 items-center gap-inline">
|
|
40
81
|
<MetadataSeparator />
|
|
41
82
|
<SeverityLink
|
|
42
83
|
count={summary[severity]}
|
|
84
|
+
truncated={summary.truncated}
|
|
43
85
|
severity={severity}
|
|
44
86
|
workspaceSlug={workspaceSlug}
|
|
45
87
|
projectSlug={projectSlug}
|
|
@@ -54,6 +96,7 @@ export function RunAnnotationSummaryLine({
|
|
|
54
96
|
|
|
55
97
|
function SeverityLink({
|
|
56
98
|
count,
|
|
99
|
+
truncated,
|
|
57
100
|
severity,
|
|
58
101
|
workspaceSlug,
|
|
59
102
|
projectSlug,
|
|
@@ -61,19 +104,33 @@ function SeverityLink({
|
|
|
61
104
|
search,
|
|
62
105
|
}: {
|
|
63
106
|
count: number;
|
|
107
|
+
truncated: boolean;
|
|
64
108
|
severity: WorkflowRunAnnotationSeverity;
|
|
65
109
|
workspaceSlug?: string | undefined;
|
|
66
110
|
projectSlug?: string | undefined;
|
|
67
111
|
workflowRunId?: string | undefined;
|
|
68
112
|
search: WorkflowRunsSearch;
|
|
69
113
|
}) {
|
|
70
|
-
const label =
|
|
114
|
+
const label = countLabel(count, severity, truncated);
|
|
115
|
+
const content = (
|
|
116
|
+
<>
|
|
117
|
+
<Icon
|
|
118
|
+
name={SEVERITY_ICON[severity]}
|
|
119
|
+
size={12}
|
|
120
|
+
aria-hidden="true"
|
|
121
|
+
className={cn('shrink-0', SEVERITY_ICON_TONE[severity])}
|
|
122
|
+
/>
|
|
123
|
+
<Text as="span" size="xs" className="text-foreground-neutral-subtle">
|
|
124
|
+
{label}
|
|
125
|
+
</Text>
|
|
126
|
+
</>
|
|
127
|
+
);
|
|
128
|
+
|
|
71
129
|
if (!workspaceSlug || !projectSlug || !workflowRunId) {
|
|
72
|
-
return <span className="
|
|
130
|
+
return <span className="inline-flex items-center gap-tight">{content}</span>;
|
|
73
131
|
}
|
|
74
132
|
|
|
75
|
-
const searchWithoutAnnotation =
|
|
76
|
-
delete searchWithoutAnnotation.annotation;
|
|
133
|
+
const searchWithoutAnnotation = withoutAnnotation(search);
|
|
77
134
|
|
|
78
135
|
return (
|
|
79
136
|
<Link
|
|
@@ -85,13 +142,31 @@ function SeverityLink({
|
|
|
85
142
|
search,
|
|
86
143
|
) as never
|
|
87
144
|
}
|
|
88
|
-
className="
|
|
145
|
+
className="inline-flex items-center gap-tight rounded-4 outline-none hover:underline focus-visible:shadow-border-interactive-with-active"
|
|
89
146
|
>
|
|
90
|
-
{
|
|
147
|
+
{content}
|
|
91
148
|
</Link>
|
|
92
149
|
);
|
|
93
150
|
}
|
|
94
151
|
|
|
95
|
-
|
|
96
|
-
|
|
152
|
+
/** A selected annotation is stale the moment the list it was chosen from is refiltered. */
|
|
153
|
+
function withoutAnnotation(search: WorkflowRunsSearch): WorkflowRunsSearch {
|
|
154
|
+
const next = {...search};
|
|
155
|
+
delete next.annotation;
|
|
156
|
+
return next;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function withoutSeverity(search: WorkflowRunsSearch): WorkflowRunsSearch {
|
|
160
|
+
const next = withoutAnnotation(search);
|
|
161
|
+
delete next.severity;
|
|
162
|
+
return next;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* A truncated read renders `500+ annotations`: the counts are a lower bound once the page budget
|
|
167
|
+
* is hit, and presenting them as exact would understate a run the reader is trying to trust.
|
|
168
|
+
*/
|
|
169
|
+
function countLabel(count: number, word: string, truncated: boolean): string {
|
|
170
|
+
const plural = count === 1 && !truncated ? word : `${word}s`;
|
|
171
|
+
return `${count}${truncated ? '+' : ''} ${plural}`;
|
|
97
172
|
}
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import type {Meta, StoryObj} from '@storybook/react';
|
|
2
|
+
import type {ReactNode} from 'react';
|
|
3
|
+
import {
|
|
4
|
+
buildRunAnnotationList,
|
|
5
|
+
type RunAnnotationRecord,
|
|
6
|
+
type RunAnnotationStyle,
|
|
7
|
+
summarizeRunAnnotations,
|
|
8
|
+
} from '#core/run-annotation.js';
|
|
9
|
+
import type {Job} from '#core/workflow-run.js';
|
|
10
|
+
import {
|
|
11
|
+
workflowJob,
|
|
12
|
+
workflowJobExecutionDto,
|
|
13
|
+
workflowStepAttemptDto,
|
|
14
|
+
workflowStepDto,
|
|
15
|
+
} from '#test/fixtures/workflow-run.js';
|
|
16
|
+
import {RunAnnotationCountChip} from './run-annotation-count-chip.js';
|
|
17
|
+
import {RunAnnotationList, type RunAnnotationListQuery} from './run-annotation-list.js';
|
|
18
|
+
import {RunAnnotationSummaryLine} from './run-annotation-summary-line.js';
|
|
19
|
+
|
|
20
|
+
const WORKSPACE_SLUG = 'acme';
|
|
21
|
+
const PROJECT_SLUG = 'platform';
|
|
22
|
+
const RUN_ID = '11111111-1111-4111-8111-111111111111';
|
|
23
|
+
const BUILD_JOB_ID = '44444444-4444-4444-8444-00000000000b';
|
|
24
|
+
const TEST_JOB_ID = '44444444-4444-4444-8444-00000000000c';
|
|
25
|
+
const BUILD_EXECUTION_ID = '77777777-7777-4777-8777-00000000000b';
|
|
26
|
+
const TEST_EXECUTION_ID = '77777777-7777-4777-8777-00000000000c';
|
|
27
|
+
const BUILD_STEP_ID = '55555555-5555-4555-8555-00000000000b';
|
|
28
|
+
const TEST_STEP_ID = '55555555-5555-4555-8555-00000000000c';
|
|
29
|
+
const BUILD_ATTEMPT_ID = '66666666-6666-4666-8666-00000000000b';
|
|
30
|
+
const TEST_ATTEMPT_ID = '66666666-6666-4666-8666-00000000000c';
|
|
31
|
+
|
|
32
|
+
const LONG_BODY = [
|
|
33
|
+
'### Failing specs',
|
|
34
|
+
'',
|
|
35
|
+
...Array.from(
|
|
36
|
+
{length: 40},
|
|
37
|
+
(_unused, index) => `- \`packages/web/src/route-${index}.test.ts\` expected 200, received 503`,
|
|
38
|
+
),
|
|
39
|
+
].join('\n');
|
|
40
|
+
|
|
41
|
+
const meta = {
|
|
42
|
+
title: 'Workflows/RunAnnotations',
|
|
43
|
+
parameters: {
|
|
44
|
+
layout: 'fullscreen',
|
|
45
|
+
},
|
|
46
|
+
} satisfies Meta;
|
|
47
|
+
|
|
48
|
+
export default meta;
|
|
49
|
+
type Story = StoryObj<typeof meta>;
|
|
50
|
+
|
|
51
|
+
export const Playground: Story = {
|
|
52
|
+
render: () => {
|
|
53
|
+
const annotations = [
|
|
54
|
+
annotation({
|
|
55
|
+
id: 'a1',
|
|
56
|
+
context: 'smoke check',
|
|
57
|
+
style: 'error',
|
|
58
|
+
sequence: 3,
|
|
59
|
+
body: 'Task nine failed the smoke check against `https://preview.example.com`.\n\n```sh\ncurl -sSf https://preview.example.com/healthz\n```',
|
|
60
|
+
}),
|
|
61
|
+
annotation({
|
|
62
|
+
id: 'a2',
|
|
63
|
+
context: 'flaky tests',
|
|
64
|
+
style: 'warning',
|
|
65
|
+
sequence: 1,
|
|
66
|
+
jobId: TEST_JOB_ID,
|
|
67
|
+
jobExecutionId: TEST_EXECUTION_ID,
|
|
68
|
+
originStepId: TEST_STEP_ID,
|
|
69
|
+
body: '2 specs retried before passing.\n\n| Spec | Retries |\n| --- | --- |\n| `checkout.spec.ts` | 1 |\n| `billing.spec.ts` | 2 |',
|
|
70
|
+
}),
|
|
71
|
+
annotation({
|
|
72
|
+
id: 'a3',
|
|
73
|
+
context: 'deploy',
|
|
74
|
+
style: 'success',
|
|
75
|
+
sequence: 2,
|
|
76
|
+
body: 'Deployed **v42** to staging: [preview](https://preview.example.com)',
|
|
77
|
+
}),
|
|
78
|
+
annotation({
|
|
79
|
+
id: 'a4',
|
|
80
|
+
context: 'coverage',
|
|
81
|
+
style: 'info',
|
|
82
|
+
sequence: 4,
|
|
83
|
+
body: 'Line coverage held at 87.4%.',
|
|
84
|
+
}),
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
return <AnnotationsFrame annotations={annotations} />;
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export const BoundedBody: Story = {
|
|
92
|
+
// The only story in this package that earns a second Argos mode. The clamp's fade is the one
|
|
93
|
+
// gradient in the client, and it blends a surface token that flips with the theme, so a
|
|
94
|
+
// dark-only capture would never catch it breaking on a light card.
|
|
95
|
+
parameters: {
|
|
96
|
+
argos: {modes: {dark: {theme: 'dark'}, light: {theme: 'light'}}},
|
|
97
|
+
},
|
|
98
|
+
render: () => (
|
|
99
|
+
<AnnotationsFrame
|
|
100
|
+
annotations={[
|
|
101
|
+
annotation({id: 'long', context: 'test results', style: 'error', body: LONG_BODY}),
|
|
102
|
+
]}
|
|
103
|
+
/>
|
|
104
|
+
),
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export const DataStates: Story = {
|
|
108
|
+
render: () => {
|
|
109
|
+
const loaded = [annotation({id: 'a1', context: 'deploy', style: 'success'})];
|
|
110
|
+
return (
|
|
111
|
+
<div className="grid max-w-[1120px] gap-16 p-16 md:grid-cols-2">
|
|
112
|
+
<StateExample label="Loading">
|
|
113
|
+
<AnnotationsList annotations={[]} query={storyQuery({isPending: true})} />
|
|
114
|
+
</StateExample>
|
|
115
|
+
<StateExample label="Empty">
|
|
116
|
+
<AnnotationsList annotations={[]} />
|
|
117
|
+
</StateExample>
|
|
118
|
+
<StateExample label="Error">
|
|
119
|
+
<AnnotationsList
|
|
120
|
+
annotations={[]}
|
|
121
|
+
query={storyQuery({isError: true, data: undefined, error: new Error('Storybook')})}
|
|
122
|
+
/>
|
|
123
|
+
</StateExample>
|
|
124
|
+
<StateExample label="Stale">
|
|
125
|
+
<AnnotationsList
|
|
126
|
+
annotations={loaded}
|
|
127
|
+
query={storyQuery({isError: true, error: new Error('Storybook')})}
|
|
128
|
+
/>
|
|
129
|
+
</StateExample>
|
|
130
|
+
<StateExample label="Filtered miss">
|
|
131
|
+
<AnnotationsList annotations={[]} filtered filteredJobName="build" />
|
|
132
|
+
</StateExample>
|
|
133
|
+
<StateExample label="Truncated counts">
|
|
134
|
+
<RunAnnotationSummaryLine
|
|
135
|
+
summary={{...summarizeRunAnnotations(loaded), total: 500, error: 12, truncated: true}}
|
|
136
|
+
workspaceSlug={WORKSPACE_SLUG}
|
|
137
|
+
projectSlug={PROJECT_SLUG}
|
|
138
|
+
workflowRunId={RUN_ID}
|
|
139
|
+
/>
|
|
140
|
+
</StateExample>
|
|
141
|
+
</div>
|
|
142
|
+
);
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
export const CountChips: Story = {
|
|
147
|
+
render: () => (
|
|
148
|
+
<div className="flex max-w-[1120px] flex-wrap items-center gap-12 p-16">
|
|
149
|
+
{(['error', 'warning', 'info', 'success'] as const).map((style) => (
|
|
150
|
+
<RunAnnotationCountChip
|
|
151
|
+
key={style}
|
|
152
|
+
summary={summarizeRunAnnotations([annotation({id: style, context: style, style})])}
|
|
153
|
+
workspaceSlug={WORKSPACE_SLUG}
|
|
154
|
+
projectSlug={PROJECT_SLUG}
|
|
155
|
+
workflowRunId={RUN_ID}
|
|
156
|
+
jobId={BUILD_JOB_ID}
|
|
157
|
+
/>
|
|
158
|
+
))}
|
|
159
|
+
<RunAnnotationCountChip
|
|
160
|
+
summary={{total: 500, error: 12, warning: 3, info: 0, success: 0, truncated: true}}
|
|
161
|
+
workspaceSlug={WORKSPACE_SLUG}
|
|
162
|
+
projectSlug={PROJECT_SLUG}
|
|
163
|
+
workflowRunId={RUN_ID}
|
|
164
|
+
/>
|
|
165
|
+
</div>
|
|
166
|
+
),
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
function AnnotationsFrame({annotations}: {annotations: RunAnnotationRecord[]}) {
|
|
170
|
+
return (
|
|
171
|
+
<div className="min-h-screen bg-background-neutral-base py-16">
|
|
172
|
+
<div className="mx-auto flex w-full max-w-[1120px] flex-col gap-16 px-24">
|
|
173
|
+
<RunAnnotationSummaryLine
|
|
174
|
+
summary={summarizeRunAnnotations(annotations)}
|
|
175
|
+
workspaceSlug={WORKSPACE_SLUG}
|
|
176
|
+
projectSlug={PROJECT_SLUG}
|
|
177
|
+
workflowRunId={RUN_ID}
|
|
178
|
+
/>
|
|
179
|
+
<AnnotationsList annotations={annotations} />
|
|
180
|
+
</div>
|
|
181
|
+
</div>
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function AnnotationsList({
|
|
186
|
+
annotations,
|
|
187
|
+
query = storyQuery(),
|
|
188
|
+
filtered = false,
|
|
189
|
+
filteredJobName,
|
|
190
|
+
}: {
|
|
191
|
+
annotations: RunAnnotationRecord[];
|
|
192
|
+
query?: RunAnnotationListQuery;
|
|
193
|
+
filtered?: boolean;
|
|
194
|
+
filteredJobName?: string;
|
|
195
|
+
}) {
|
|
196
|
+
return (
|
|
197
|
+
<RunAnnotationList
|
|
198
|
+
query={query}
|
|
199
|
+
entries={query.data === undefined ? undefined : buildRunAnnotationList({annotations, jobs})}
|
|
200
|
+
workspaceSlug={WORKSPACE_SLUG}
|
|
201
|
+
projectSlug={PROJECT_SLUG}
|
|
202
|
+
workflowRunId={RUN_ID}
|
|
203
|
+
runAttempt={1}
|
|
204
|
+
filtered={filtered}
|
|
205
|
+
filteredJobName={filteredJobName}
|
|
206
|
+
onClearFilters={() => undefined}
|
|
207
|
+
/>
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function StateExample({label, children}: {label: string; children: ReactNode}) {
|
|
212
|
+
return (
|
|
213
|
+
<div className="flex flex-col gap-8">
|
|
214
|
+
<span className="text-xs font-medium text-foreground-neutral-muted">{label}</span>
|
|
215
|
+
{children}
|
|
216
|
+
</div>
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function storyQuery(overrides: Partial<RunAnnotationListQuery> = {}): RunAnnotationListQuery {
|
|
221
|
+
return {
|
|
222
|
+
isPending: false,
|
|
223
|
+
isError: false,
|
|
224
|
+
isFetching: false,
|
|
225
|
+
data: {pages: []},
|
|
226
|
+
error: null,
|
|
227
|
+
refetch: () => undefined,
|
|
228
|
+
hasNextPage: false,
|
|
229
|
+
isFetchingNextPage: false,
|
|
230
|
+
fetchNextPage: () => undefined,
|
|
231
|
+
...overrides,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function annotation(
|
|
236
|
+
overrides: Partial<RunAnnotationRecord> & {id: string; style: RunAnnotationStyle},
|
|
237
|
+
): RunAnnotationRecord {
|
|
238
|
+
return {
|
|
239
|
+
jobId: BUILD_JOB_ID,
|
|
240
|
+
jobExecutionId: BUILD_EXECUTION_ID,
|
|
241
|
+
originStepId: BUILD_STEP_ID,
|
|
242
|
+
originStepAttempt: 1,
|
|
243
|
+
context: 'default',
|
|
244
|
+
sequence: 1,
|
|
245
|
+
body: 'Body',
|
|
246
|
+
...overrides,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const jobs: Job[] = [
|
|
251
|
+
workflowJob({
|
|
252
|
+
id: BUILD_JOB_ID,
|
|
253
|
+
key: 'build',
|
|
254
|
+
name: 'build',
|
|
255
|
+
position: 0,
|
|
256
|
+
job_executions: [
|
|
257
|
+
workflowJobExecutionDto({
|
|
258
|
+
id: BUILD_EXECUTION_ID,
|
|
259
|
+
job_id: BUILD_JOB_ID,
|
|
260
|
+
steps: [
|
|
261
|
+
workflowStepDto({
|
|
262
|
+
id: BUILD_STEP_ID,
|
|
263
|
+
name: 'run smoke checks',
|
|
264
|
+
attempts: [
|
|
265
|
+
workflowStepAttemptDto({id: BUILD_ATTEMPT_ID, step_id: BUILD_STEP_ID, attempt: 1}),
|
|
266
|
+
],
|
|
267
|
+
}),
|
|
268
|
+
],
|
|
269
|
+
}),
|
|
270
|
+
],
|
|
271
|
+
}),
|
|
272
|
+
workflowJob({
|
|
273
|
+
id: TEST_JOB_ID,
|
|
274
|
+
key: 'test',
|
|
275
|
+
name: 'test',
|
|
276
|
+
position: 1,
|
|
277
|
+
job_executions: [
|
|
278
|
+
workflowJobExecutionDto({
|
|
279
|
+
id: TEST_EXECUTION_ID,
|
|
280
|
+
job_id: TEST_JOB_ID,
|
|
281
|
+
steps: [
|
|
282
|
+
workflowStepDto({
|
|
283
|
+
id: TEST_STEP_ID,
|
|
284
|
+
name: 'vitest',
|
|
285
|
+
attempts: [
|
|
286
|
+
workflowStepAttemptDto({id: TEST_ATTEMPT_ID, step_id: TEST_STEP_ID, attempt: 1}),
|
|
287
|
+
],
|
|
288
|
+
}),
|
|
289
|
+
],
|
|
290
|
+
}),
|
|
291
|
+
],
|
|
292
|
+
}),
|
|
293
|
+
];
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type {IconName} from '@shipfox/react-ui/icon';
|
|
2
|
+
import type {RunAnnotationSeverity} from '#core/run-annotation.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The glyph vocabulary for annotation severity, shared by every count surface.
|
|
6
|
+
*
|
|
7
|
+
* Deliberately the same marks the `Callout` uses on the cards themselves, so the glyph beside
|
|
8
|
+
* `1 error` in the summary is the glyph on the error card below it.
|
|
9
|
+
*/
|
|
10
|
+
export const SEVERITY_ICON: Record<RunAnnotationSeverity, IconName> = {
|
|
11
|
+
error: 'closeCircleFill',
|
|
12
|
+
warning: 'errorWarningFill',
|
|
13
|
+
info: 'info',
|
|
14
|
+
success: 'checkboxCircleFill',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Severity tone lives in the glyph, never in the label.
|
|
19
|
+
*
|
|
20
|
+
* Coloring the text would make severity compete with the brand accent, which this system
|
|
21
|
+
* reserves for links, focus, and "you are here". Shape plus tone in a 12px mark distinguishes
|
|
22
|
+
* an error from a warning without either of them shouting.
|
|
23
|
+
*/
|
|
24
|
+
export const SEVERITY_ICON_TONE: Record<RunAnnotationSeverity, string> = {
|
|
25
|
+
error: 'text-tag-error-icon',
|
|
26
|
+
warning: 'text-tag-warning-icon',
|
|
27
|
+
info: 'text-tag-blue-icon',
|
|
28
|
+
success: 'text-tag-success-icon',
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/** Hairline tone for a bordered count chip, matching the glyph it sits beside. */
|
|
32
|
+
export const SEVERITY_CHIP_TONE: Record<RunAnnotationSeverity, string> = {
|
|
33
|
+
error: 'border-tag-error-border text-tag-error-icon',
|
|
34
|
+
warning: 'border-tag-warning-border text-tag-warning-icon',
|
|
35
|
+
info: 'border-tag-blue-border text-tag-blue-icon',
|
|
36
|
+
success: 'border-tag-success-border text-tag-success-icon',
|
|
37
|
+
};
|
|
@@ -27,6 +27,50 @@ describe('RunWorkspaceNav', () => {
|
|
|
27
27
|
vi.useRealTimers();
|
|
28
28
|
});
|
|
29
29
|
|
|
30
|
+
test('announces the annotation count with its unit and marks a truncated read', async () => {
|
|
31
|
+
const run = workflowRunDetail({
|
|
32
|
+
id: RUN_ID,
|
|
33
|
+
jobs: [workflowJobDto({id: CURRENT_JOB_ID, name: 'build', position: 0})],
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
renderWithRouter(
|
|
37
|
+
<RunWorkspaceNav
|
|
38
|
+
workspaceSlug="acme"
|
|
39
|
+
projectSlug="project"
|
|
40
|
+
run={run}
|
|
41
|
+
activeSection="summary"
|
|
42
|
+
annotationSummary={{
|
|
43
|
+
total: 500,
|
|
44
|
+
error: 12,
|
|
45
|
+
warning: 3,
|
|
46
|
+
info: 0,
|
|
47
|
+
success: 0,
|
|
48
|
+
truncated: true,
|
|
49
|
+
}}
|
|
50
|
+
/>,
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
expect(await screen.findByRole('link', {name: 'Annotations, 500 or more'})).toBeVisible();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('omits the annotation count until the read resolves', async () => {
|
|
57
|
+
const run = workflowRunDetail({
|
|
58
|
+
id: RUN_ID,
|
|
59
|
+
jobs: [workflowJobDto({id: CURRENT_JOB_ID, name: 'build', position: 0})],
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
renderWithRouter(
|
|
63
|
+
<RunWorkspaceNav
|
|
64
|
+
workspaceSlug="acme"
|
|
65
|
+
projectSlug="project"
|
|
66
|
+
run={run}
|
|
67
|
+
activeSection="summary"
|
|
68
|
+
/>,
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
expect(await screen.findByRole('link', {name: 'Annotations'})).toBeVisible();
|
|
72
|
+
});
|
|
73
|
+
|
|
30
74
|
test('shows the complete run hierarchy and marks the current job', async () => {
|
|
31
75
|
const run = workflowRunDetail({
|
|
32
76
|
id: RUN_ID,
|
|
@@ -6,13 +6,13 @@ import {cn} from '@shipfox/react-ui/utils';
|
|
|
6
6
|
import {Link} from '@tanstack/react-router';
|
|
7
7
|
import {useEffect, useMemo, useRef, useState} from 'react';
|
|
8
8
|
import {WorkflowStatusIcon} from '#components/workflow-status/workflow-status-icon.js';
|
|
9
|
+
import type {RunAnnotationSummary} from '#core/run-annotation.js';
|
|
9
10
|
import {
|
|
10
11
|
defaultJobExecution,
|
|
11
12
|
deriveJobDisplayStatus,
|
|
12
13
|
type Job,
|
|
13
14
|
type WorkflowRunDetail,
|
|
14
15
|
} from '#core/workflow-run.js';
|
|
15
|
-
import type {RunAnnotationSummary} from '#core/workflow-run-tabs.js';
|
|
16
16
|
import {
|
|
17
17
|
type WorkflowJobSearch,
|
|
18
18
|
type WorkflowRunTab,
|
|
@@ -58,7 +58,7 @@ export function RunWorkspaceNav({
|
|
|
58
58
|
<CollapsibleTrigger asChild>
|
|
59
59
|
<button
|
|
60
60
|
type="button"
|
|
61
|
-
className="flex min-h-44 w-full items-center justify-between gap-
|
|
61
|
+
className="flex min-h-44 w-full items-center justify-between gap-cluster px-row text-left outline-none focus-visible:shadow-border-interactive-with-active min-[768px]:hidden"
|
|
62
62
|
aria-label="Toggle run navigation"
|
|
63
63
|
>
|
|
64
64
|
<span className="min-w-0">
|
|
@@ -82,7 +82,7 @@ export function RunWorkspaceNav({
|
|
|
82
82
|
</CollapsibleTrigger>
|
|
83
83
|
<div
|
|
84
84
|
className={cn(
|
|
85
|
-
'max-h-[50vh] overflow-y-auto p-
|
|
85
|
+
'max-h-[50vh] overflow-y-auto p-tight min-[768px]:block min-[768px]:max-h-none min-[768px]:p-[12px]',
|
|
86
86
|
!mobileOpen && 'hidden',
|
|
87
87
|
)}
|
|
88
88
|
>
|
|
@@ -136,7 +136,7 @@ function RunWorkspaceNavContent({
|
|
|
136
136
|
}, [currentJobId, mobileOpen]);
|
|
137
137
|
|
|
138
138
|
return (
|
|
139
|
-
<nav aria-label="Run workspace" className="flex min-w-0 flex-col gap-
|
|
139
|
+
<nav aria-label="Run workspace" className="flex min-w-0 flex-col gap-group">
|
|
140
140
|
<ul>
|
|
141
141
|
<li>
|
|
142
142
|
<RunSectionLink
|
|
@@ -152,7 +152,7 @@ function RunWorkspaceNavContent({
|
|
|
152
152
|
</ul>
|
|
153
153
|
|
|
154
154
|
<section aria-labelledby="run-workspace-jobs-heading" className="min-w-0">
|
|
155
|
-
<div className="flex items-center justify-between gap-
|
|
155
|
+
<div className="flex items-center justify-between gap-inline px-tight pb-[6px]">
|
|
156
156
|
<Text
|
|
157
157
|
as="h2"
|
|
158
158
|
id="run-workspace-jobs-heading"
|
|
@@ -182,7 +182,7 @@ function RunWorkspaceNavContent({
|
|
|
182
182
|
onClick={onNavigate}
|
|
183
183
|
aria-current={current ? 'page' : undefined}
|
|
184
184
|
className={cn(
|
|
185
|
-
'flex min-h-32 min-w-0 items-center gap-
|
|
185
|
+
'flex min-h-32 min-w-0 items-center gap-inline rounded-4 px-tight text-left outline-none transition-colors hover:bg-background-neutral-hover focus-visible:shadow-border-interactive-with-active @max-[767px]:min-h-44 [@media(pointer:coarse)]:min-h-44',
|
|
186
186
|
current && 'bg-background-neutral-hover',
|
|
187
187
|
)}
|
|
188
188
|
>
|
|
@@ -210,7 +210,7 @@ function RunWorkspaceNavContent({
|
|
|
210
210
|
id="run-workspace-details-heading"
|
|
211
211
|
size="xs"
|
|
212
212
|
bold
|
|
213
|
-
className="px-
|
|
213
|
+
className="px-tight pb-[6px] text-foreground-neutral-muted"
|
|
214
214
|
>
|
|
215
215
|
Run details
|
|
216
216
|
</Text>
|
|
@@ -224,6 +224,7 @@ function RunWorkspaceNavContent({
|
|
|
224
224
|
section="annotations"
|
|
225
225
|
current={!currentJobId && activeSection === 'annotations'}
|
|
226
226
|
count={annotationSummary?.total}
|
|
227
|
+
countTruncated={annotationSummary?.truncated}
|
|
227
228
|
onNavigate={onNavigate}
|
|
228
229
|
/>
|
|
229
230
|
</li>
|
|
@@ -252,6 +253,7 @@ function RunSectionLink({
|
|
|
252
253
|
section,
|
|
253
254
|
current,
|
|
254
255
|
count,
|
|
256
|
+
countTruncated = false,
|
|
255
257
|
onNavigate,
|
|
256
258
|
}: {
|
|
257
259
|
workspaceSlug: string;
|
|
@@ -261,6 +263,8 @@ function RunSectionLink({
|
|
|
261
263
|
section: RunWorkspaceSection;
|
|
262
264
|
current: boolean;
|
|
263
265
|
count?: number | undefined;
|
|
266
|
+
/** The read hit its page budget, so the count is a lower bound and renders as `N+`. */
|
|
267
|
+
countTruncated?: boolean | undefined;
|
|
264
268
|
onNavigate?: (() => void) | undefined;
|
|
265
269
|
}) {
|
|
266
270
|
return (
|
|
@@ -275,15 +279,24 @@ function RunSectionLink({
|
|
|
275
279
|
}
|
|
276
280
|
onClick={onNavigate}
|
|
277
281
|
aria-current={current ? 'page' : undefined}
|
|
282
|
+
aria-label={
|
|
283
|
+
count
|
|
284
|
+
? `${sectionLabel(section)}, ${countTruncated ? `${count} or more` : count}`
|
|
285
|
+
: undefined
|
|
286
|
+
}
|
|
278
287
|
className={cn(
|
|
279
|
-
'flex min-h-32 items-center gap-
|
|
288
|
+
'flex min-h-32 items-center gap-inline rounded-4 px-tight text-xs font-medium text-foreground-neutral-base outline-none transition-colors hover:bg-background-neutral-hover focus-visible:shadow-border-interactive-with-active @max-[767px]:min-h-44 [@media(pointer:coarse)]:min-h-44',
|
|
280
289
|
current && 'bg-background-neutral-hover',
|
|
281
290
|
)}
|
|
282
291
|
>
|
|
283
292
|
{sectionLabel(section)}
|
|
284
293
|
{count ? (
|
|
285
|
-
<span
|
|
294
|
+
<span
|
|
295
|
+
aria-hidden="true"
|
|
296
|
+
className="ml-auto font-code text-xs text-foreground-neutral-muted tabular-nums"
|
|
297
|
+
>
|
|
286
298
|
{count}
|
|
299
|
+
{countTruncated ? '+' : ''}
|
|
287
300
|
</span>
|
|
288
301
|
) : null}
|
|
289
302
|
</Link>
|