@shipfox/client-workflows 12.0.2 → 13.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.tsx +3 -3
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +56 -0
- package/dist/components/job-graph/job-duration-format.d.ts +2 -2
- package/dist/components/job-graph/job-duration-format.d.ts.map +1 -1
- package/dist/components/job-graph/job-duration-format.js +4 -2
- package/dist/components/job-graph/job-duration-format.js.map +1 -1
- package/dist/components/job-graph/job-node.d.ts +1 -1
- package/dist/components/job-graph/job-node.d.ts.map +1 -1
- package/dist/components/job-graph/job-node.js +5 -4
- package/dist/components/job-graph/job-node.js.map +1 -1
- package/dist/components/step-list/step-list.d.ts +2 -2
- package/dist/components/step-list/step-list.d.ts.map +1 -1
- package/dist/components/step-list/step-list.js.map +1 -1
- package/dist/components/workflow-run-list/job-status-strip.d.ts +21 -0
- package/dist/components/workflow-run-list/job-status-strip.d.ts.map +1 -0
- package/dist/components/workflow-run-list/job-status-strip.js +185 -0
- package/dist/components/workflow-run-list/job-status-strip.js.map +1 -0
- package/dist/components/workflow-run-list/run-display.d.ts +27 -3
- package/dist/components/workflow-run-list/run-display.d.ts.map +1 -1
- package/dist/components/workflow-run-list/run-display.js +78 -7
- package/dist/components/workflow-run-list/run-display.js.map +1 -1
- package/dist/components/workflow-run-list/types.d.ts +23 -24
- package/dist/components/workflow-run-list/types.d.ts.map +1 -1
- package/dist/components/workflow-run-list/types.js.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-filter-menu.d.ts +22 -0
- package/dist/components/workflow-run-list/workflow-run-filter-menu.d.ts.map +1 -0
- package/dist/components/workflow-run-list/workflow-run-filter-menu.js +68 -0
- package/dist/components/workflow-run-list/workflow-run-filter-menu.js.map +1 -0
- package/dist/components/workflow-run-list/workflow-run-filters.d.ts +18 -0
- package/dist/components/workflow-run-list/workflow-run-filters.d.ts.map +1 -0
- package/dist/components/workflow-run-list/workflow-run-filters.js +236 -0
- package/dist/components/workflow-run-list/workflow-run-filters.js.map +1 -0
- package/dist/components/workflow-run-list/workflow-run-list-content.d.ts +8 -4
- package/dist/components/workflow-run-list/workflow-run-list-content.d.ts.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-list-content.js +40 -12
- package/dist/components/workflow-run-list/workflow-run-list-content.js.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-list-states.d.ts +29 -2
- package/dist/components/workflow-run-list/workflow-run-list-states.d.ts.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-list-states.js +103 -44
- package/dist/components/workflow-run-list/workflow-run-list-states.js.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-list-view.d.ts +1 -1
- package/dist/components/workflow-run-list/workflow-run-list-view.d.ts.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-list-view.js +60 -43
- package/dist/components/workflow-run-list/workflow-run-list-view.js.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-list.d.ts +1 -1
- package/dist/components/workflow-run-list/workflow-run-list.d.ts.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-list.js +20 -8
- package/dist/components/workflow-run-list/workflow-run-list.js.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-row.d.ts +19 -9
- package/dist/components/workflow-run-list/workflow-run-row.d.ts.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-row.js +179 -112
- package/dist/components/workflow-run-list/workflow-run-row.js.map +1 -1
- package/dist/components/workflow-run-number-label.d.ts +15 -0
- package/dist/components/workflow-run-number-label.d.ts.map +1 -0
- package/dist/components/workflow-run-number-label.js +41 -0
- package/dist/components/workflow-run-number-label.js.map +1 -0
- package/dist/components/workflow-run-summary/workflow-run-attempt-switcher.d.ts +3 -3
- package/dist/components/workflow-run-summary/workflow-run-attempt-switcher.d.ts.map +1 -1
- package/dist/components/workflow-run-summary/workflow-run-attempt-switcher.js +8 -7
- 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 +3 -3
- package/dist/components/workflow-run-summary/workflow-run-summary.d.ts.map +1 -1
- package/dist/components/workflow-run-summary/workflow-run-summary.js +16 -7
- package/dist/components/workflow-run-summary/workflow-run-summary.js.map +1 -1
- package/dist/components/workflow-run-view/agent-config-failure-callout.d.ts +2 -2
- package/dist/components/workflow-run-view/agent-config-failure-callout.d.ts.map +1 -1
- package/dist/components/workflow-run-view/agent-config-failure-callout.js +4 -4
- package/dist/components/workflow-run-view/agent-config-failure-callout.js.map +1 -1
- package/dist/components/workflow-run-view/job-card.d.ts +2 -2
- package/dist/components/workflow-run-view/job-card.d.ts.map +1 -1
- package/dist/components/workflow-run-view/job-card.js +51 -32
- package/dist/components/workflow-run-view/job-card.js.map +1 -1
- package/dist/components/workflow-run-view/job-execution-switcher.d.ts +1 -1
- package/dist/components/workflow-run-view/job-execution-switcher.d.ts.map +1 -1
- package/dist/components/workflow-run-view/job-execution-switcher.js +20 -8
- package/dist/components/workflow-run-view/job-execution-switcher.js.map +1 -1
- package/dist/components/workflow-run-view/workflow-run-view.d.ts +3 -2
- package/dist/components/workflow-run-view/workflow-run-view.d.ts.map +1 -1
- package/dist/components/workflow-run-view/workflow-run-view.js +15 -11
- package/dist/components/workflow-run-view/workflow-run-view.js.map +1 -1
- package/dist/components/workflow-status/status-visuals.d.ts +4 -4
- package/dist/components/workflow-status/status-visuals.d.ts.map +1 -1
- package/dist/components/workflow-status/status-visuals.js +7 -0
- package/dist/components/workflow-status/status-visuals.js.map +1 -1
- package/dist/components/workflow-status/workflow-status-icon.d.ts +3 -4
- package/dist/components/workflow-status/workflow-status-icon.d.ts.map +1 -1
- package/dist/components/workflow-status/workflow-status-icon.js +6 -4
- package/dist/components/workflow-status/workflow-status-icon.js.map +1 -1
- package/dist/core/entities/job-execution.d.ts +10 -0
- package/dist/core/entities/job-execution.d.ts.map +1 -1
- package/dist/core/entities/job-execution.js +11 -0
- package/dist/core/entities/job-execution.js.map +1 -1
- package/dist/core/entities/job.d.ts +4 -2
- package/dist/core/entities/job.d.ts.map +1 -1
- package/dist/core/entities/job.js +8 -1
- package/dist/core/entities/job.js.map +1 -1
- package/dist/core/entities/workflow-run.d.ts +52 -4
- package/dist/core/entities/workflow-run.d.ts.map +1 -1
- package/dist/core/entities/workflow-run.js +22 -3
- package/dist/core/entities/workflow-run.js.map +1 -1
- package/dist/core/workflow-run.d.ts +6 -6
- package/dist/core/workflow-run.d.ts.map +1 -1
- package/dist/core/workflow-run.js +3 -3
- package/dist/core/workflow-run.js.map +1 -1
- package/dist/feature.d.ts +7 -7
- package/dist/feature.js +5 -5
- package/dist/feature.js.map +1 -1
- package/dist/hooks/api/workflow-run-mapper.d.ts +4 -3
- package/dist/hooks/api/workflow-run-mapper.d.ts.map +1 -1
- package/dist/hooks/api/workflow-run-mapper.js +31 -3
- package/dist/hooks/api/workflow-run-mapper.js.map +1 -1
- package/dist/hooks/api/workflow-runs.d.ts +4 -4
- package/dist/hooks/api/workflow-runs.d.ts.map +1 -1
- package/dist/hooks/api/workflow-runs.js +12 -4
- package/dist/hooks/api/workflow-runs.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/pages/project-workflows-page.js +46 -1
- package/dist/pages/project-workflows-page.js.map +1 -1
- package/dist/pages/workflow-run-detail-page.d.ts +11 -0
- package/dist/pages/workflow-run-detail-page.d.ts.map +1 -0
- package/dist/pages/workflow-run-detail-page.js +30 -0
- package/dist/pages/workflow-run-detail-page.js.map +1 -0
- package/dist/pages/workflow-run-list-page.d.ts +10 -0
- package/dist/pages/workflow-run-list-page.d.ts.map +1 -0
- package/dist/pages/workflow-run-list-page.js +43 -0
- package/dist/pages/workflow-run-list-page.js.map +1 -0
- package/dist/routes/inputs.d.ts +61 -5
- package/dist/routes/inputs.d.ts.map +1 -1
- package/dist/routes/inputs.js +150 -13
- package/dist/routes/inputs.js.map +1 -1
- package/dist/routes/project-route.d.ts +6 -0
- package/dist/routes/project-route.d.ts.map +1 -0
- package/dist/routes/project-route.js +25 -0
- package/dist/routes/project-route.js.map +1 -0
- package/dist/routes/run-detail.d.ts.map +1 -1
- package/dist/routes/run-detail.js +12 -7
- package/dist/routes/run-detail.js.map +1 -1
- package/dist/routes/runs.d.ts.map +1 -1
- package/dist/routes/runs.js +11 -6
- package/dist/routes/runs.js.map +1 -1
- package/dist/routes/workflows.d.ts.map +1 -1
- package/dist/routes/workflows.js +6 -5
- package/dist/routes/workflows.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +9 -9
- package/src/components/job-graph/job-duration-format.ts +11 -2
- package/src/components/job-graph/job-graph-view.test.tsx +19 -8
- package/src/components/job-graph/job-graph.stories.tsx +23 -19
- package/src/components/job-graph/job-node.stories.tsx +10 -5
- package/src/components/job-graph/job-node.test.tsx +55 -10
- package/src/components/job-graph/job-node.tsx +16 -9
- package/src/components/step-list/step-list.stories.tsx +2 -3
- package/src/components/step-list/step-list.tsx +3 -2
- package/src/components/workflow-run-list/job-status-strip.tsx +189 -0
- package/src/components/workflow-run-list/run-display.test.ts +169 -30
- package/src/components/workflow-run-list/run-display.ts +116 -9
- package/src/components/workflow-run-list/types.ts +21 -17
- package/src/components/workflow-run-list/workflow-run-filter-menu.tsx +100 -0
- package/src/components/workflow-run-list/workflow-run-filters.tsx +243 -0
- package/src/components/workflow-run-list/workflow-run-list-content.tsx +44 -16
- package/src/components/workflow-run-list/workflow-run-list-states.tsx +128 -27
- package/src/components/workflow-run-list/workflow-run-list-view.test.tsx +441 -104
- package/src/components/workflow-run-list/workflow-run-list-view.tsx +73 -43
- package/src/components/workflow-run-list/workflow-run-list.stories.tsx +140 -20
- package/src/components/workflow-run-list/workflow-run-list.tsx +20 -10
- package/src/components/workflow-run-list/workflow-run-row.tsx +173 -106
- package/src/components/workflow-run-number-label.test.tsx +45 -0
- package/src/components/workflow-run-number-label.tsx +47 -0
- package/src/components/workflow-run-summary/workflow-run-attempt-switcher.test.tsx +7 -8
- package/src/components/workflow-run-summary/workflow-run-attempt-switcher.tsx +13 -12
- package/src/components/workflow-run-summary/workflow-run-summary.stories.tsx +6 -8
- package/src/components/workflow-run-summary/workflow-run-summary.test.tsx +27 -2
- package/src/components/workflow-run-summary/workflow-run-summary.tsx +19 -9
- package/src/components/workflow-run-view/agent-config-failure-callout.stories.tsx +2 -3
- package/src/components/workflow-run-view/agent-config-failure-callout.tsx +4 -4
- package/src/components/workflow-run-view/job-card.stories.tsx +11 -10
- package/src/components/workflow-run-view/job-card.test.tsx +115 -0
- package/src/components/workflow-run-view/job-card.tsx +74 -35
- package/src/components/workflow-run-view/job-execution-switcher.tsx +31 -9
- package/src/components/workflow-run-view/workflow-run-view.test.tsx +88 -30
- package/src/components/workflow-run-view/workflow-run-view.tsx +24 -14
- package/src/components/workflow-status/status-visuals.test.ts +9 -0
- package/src/components/workflow-status/status-visuals.ts +6 -4
- package/src/components/workflow-status/workflow-status-icon.stories.tsx +10 -2
- package/src/components/workflow-status/workflow-status-icon.test.tsx +18 -0
- package/src/components/workflow-status/workflow-status-icon.tsx +17 -24
- package/src/core/entities/job-execution.ts +22 -0
- package/src/core/entities/job-status.test.ts +104 -0
- package/src/core/entities/job.ts +18 -3
- package/src/core/entities/workflow-run.ts +76 -7
- package/src/core/workflow-run.test.ts +24 -9
- package/src/core/workflow-run.ts +17 -2
- package/src/feature.ts +5 -5
- package/src/hooks/api/workflow-run-mapper.ts +30 -3
- package/src/hooks/api/workflow-runs.test.tsx +2 -0
- package/src/hooks/api/workflow-runs.ts +12 -7
- package/src/index.ts +7 -2
- package/src/page-harness-budget.test.ts +1 -1
- package/src/pages/project-workflows-page.test.tsx +59 -5
- package/src/pages/project-workflows-page.tsx +34 -1
- package/src/pages/workflow-run-detail-page.tsx +45 -0
- package/src/pages/workflow-run-list-page.tsx +65 -0
- package/src/pages/{workflow-run-page.test.tsx → workflow-run-pages.test.tsx} +211 -47
- package/src/routes/inputs.test.ts +193 -12
- package/src/routes/inputs.ts +170 -11
- package/src/routes/project-route.tsx +25 -0
- package/src/routes/run-detail.tsx +15 -8
- package/src/routes/runs.tsx +14 -7
- package/src/routes/workflows.tsx +5 -4
- package/test/fixtures/workflow-run.ts +76 -7
- package/test/pages.tsx +18 -8
- package/test/render.tsx +8 -2
- package/tsconfig.build.tsbuildinfo +1 -1
- package/dist/components/workflow-run-list/workflow-run-list-header.d.ts +0 -10
- package/dist/components/workflow-run-list/workflow-run-list-header.d.ts.map +0 -1
- package/dist/components/workflow-run-list/workflow-run-list-header.js +0 -55
- package/dist/components/workflow-run-list/workflow-run-list-header.js.map +0 -1
- package/dist/pages/workflow-run-first-time-use.d.ts +0 -6
- package/dist/pages/workflow-run-first-time-use.d.ts.map +0 -1
- package/dist/pages/workflow-run-first-time-use.js +0 -17
- package/dist/pages/workflow-run-first-time-use.js.map +0 -1
- package/dist/pages/workflow-run-page.d.ts +0 -10
- package/dist/pages/workflow-run-page.d.ts.map +0 -1
- package/dist/pages/workflow-run-page.js +0 -90
- package/dist/pages/workflow-run-page.js.map +0 -1
- package/src/components/workflow-run-list/workflow-run-list-header.tsx +0 -53
- package/src/pages/workflow-run-first-time-use.tsx +0 -17
- package/src/pages/workflow-run-page.tsx +0 -94
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import {Tooltip, TooltipContent, TooltipTrigger} from '@shipfox/react-ui/tooltip';
|
|
2
|
+
import {Code, Text} from '@shipfox/react-ui/typography';
|
|
3
|
+
import {cn} from '@shipfox/react-ui/utils';
|
|
4
|
+
import {getWorkflowStatusVisual} from '#components/workflow-status/status-visuals.js';
|
|
5
|
+
import {WorkflowStatusIcon} from '#components/workflow-status/workflow-status-icon.js';
|
|
6
|
+
import type {JobStatus, WorkflowRunJobSummary, WorkflowRunJobs} from '#core/workflow-run.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* How many glyphs fit the strip's column before it starts costing the run name width.
|
|
10
|
+
*
|
|
11
|
+
* Sized so the widest case still fits the row's 160px strip column: seven 12px glyphs with
|
|
12
|
+
* 3px gaps is 102px, and the overflow indicator adds a glyph plus a count of at most five
|
|
13
|
+
* monospace characters, about 56px. Overshooting does not wrap, it paints over the duration
|
|
14
|
+
* column. This sits under the API's preview bound, so resizing the strip is a change here.
|
|
15
|
+
*/
|
|
16
|
+
const MAX_VISIBLE_JOBS = 7;
|
|
17
|
+
const GLYPH_SIZE = 12;
|
|
18
|
+
const MAX_TOOLTIP_JOB_NAMES = 6;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Above this the overflow count is abbreviated.
|
|
22
|
+
*
|
|
23
|
+
* Nothing caps a workflow's job count, so an exact count is unbounded in width and would
|
|
24
|
+
* eventually push the strip over its column. Abbreviating keeps the label at most five
|
|
25
|
+
* characters for any magnitude, and the exact figure stays in the tooltip and the strip's
|
|
26
|
+
* accessible name, which is where a precise number is actually read.
|
|
27
|
+
*/
|
|
28
|
+
const MAX_EXACT_OVERFLOW_COUNT = 999;
|
|
29
|
+
const COMPACT_COUNT_FORMAT = new Intl.NumberFormat('en', {
|
|
30
|
+
notation: 'compact',
|
|
31
|
+
maximumFractionDigits: 1,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Worst-first, so the overflow glyph reports the most alarming thing it is standing in for.
|
|
35
|
+
// A strip that hides a failure behind eight green discs would be worse than no strip at all.
|
|
36
|
+
const STATUS_SEVERITY: Record<JobStatus, number> = {
|
|
37
|
+
failed: 5,
|
|
38
|
+
running: 4,
|
|
39
|
+
pending: 3,
|
|
40
|
+
cancelled: 2,
|
|
41
|
+
skipped: 1,
|
|
42
|
+
succeeded: 0,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const NOTABLE_STATUSES: readonly JobStatus[] = ['failed', 'running'];
|
|
46
|
+
|
|
47
|
+
export interface JobStatusStripProps {
|
|
48
|
+
jobs: WorkflowRunJobs;
|
|
49
|
+
className?: string | undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* A run's jobs as a row of status glyphs, in graph order.
|
|
54
|
+
*
|
|
55
|
+
* This is the reason the list is worth its width: it answers "where did this run fail"
|
|
56
|
+
* without opening the run. Glyphs are silent to assistive tech and carry no tooltip of their
|
|
57
|
+
* own; the strip speaks once, as a summary, so a 40-job run does not read out 40 labels.
|
|
58
|
+
*
|
|
59
|
+
* What it draws comes from the preview; what it says comes from the counts, which cover every
|
|
60
|
+
* job including those the server never sent. That split is what lets the overflow glyph
|
|
61
|
+
* report a failure sitting past the preview instead of quietly dropping it.
|
|
62
|
+
*/
|
|
63
|
+
export function JobStatusStrip({jobs, className}: JobStatusStripProps) {
|
|
64
|
+
if (jobs.total === 0) return null;
|
|
65
|
+
|
|
66
|
+
const visible = jobs.preview.slice(0, MAX_VISIBLE_JOBS);
|
|
67
|
+
const hiddenCount = jobs.total - visible.length;
|
|
68
|
+
const overflowStatus = worstHiddenStatus(jobs, visible);
|
|
69
|
+
const summary = jobStatusSummary(jobs);
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<Tooltip>
|
|
73
|
+
<TooltipTrigger asChild>
|
|
74
|
+
<span
|
|
75
|
+
role="img"
|
|
76
|
+
aria-label={summary}
|
|
77
|
+
className={cn('inline-flex items-center gap-3', className)}
|
|
78
|
+
>
|
|
79
|
+
<span aria-hidden="true" className="inline-flex items-center gap-3">
|
|
80
|
+
{visible.map((job) => (
|
|
81
|
+
<WorkflowStatusIcon
|
|
82
|
+
key={job.id}
|
|
83
|
+
status={job.status}
|
|
84
|
+
size={GLYPH_SIZE}
|
|
85
|
+
// One ripple per running job, on every row, would turn a calm list into a
|
|
86
|
+
// field of pulses. The run's own glyph already carries the live edge.
|
|
87
|
+
ripple={false}
|
|
88
|
+
tooltip={false}
|
|
89
|
+
/>
|
|
90
|
+
))}
|
|
91
|
+
{hiddenCount > 0 && overflowStatus ? (
|
|
92
|
+
<span className="inline-flex items-center gap-2">
|
|
93
|
+
<WorkflowStatusIcon
|
|
94
|
+
status={overflowStatus}
|
|
95
|
+
size={GLYPH_SIZE}
|
|
96
|
+
ripple={false}
|
|
97
|
+
tooltip={false}
|
|
98
|
+
/>
|
|
99
|
+
<Code
|
|
100
|
+
as="span"
|
|
101
|
+
variant="label"
|
|
102
|
+
className="tabular-nums text-foreground-neutral-muted"
|
|
103
|
+
>
|
|
104
|
+
+{overflowCountLabel(hiddenCount)}
|
|
105
|
+
</Code>
|
|
106
|
+
</span>
|
|
107
|
+
) : null}
|
|
108
|
+
</span>
|
|
109
|
+
</span>
|
|
110
|
+
</TooltipTrigger>
|
|
111
|
+
<TooltipContent>
|
|
112
|
+
<JobStatusStripTooltip jobs={jobs} summary={summary} />
|
|
113
|
+
</TooltipContent>
|
|
114
|
+
</Tooltip>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function JobStatusStripTooltip({jobs, summary}: {jobs: WorkflowRunJobs; summary: string}) {
|
|
119
|
+
// Naming the jobs that need attention is the whole point of hovering; succeeded jobs are
|
|
120
|
+
// left to the counts so the tooltip stays a glance rather than a list. Only previewed jobs
|
|
121
|
+
// can be named, but the count of those left over is read off the totals, so a failure past
|
|
122
|
+
// the preview is still accounted for rather than silently missing.
|
|
123
|
+
const named = jobs.preview
|
|
124
|
+
.filter((job) => NOTABLE_STATUSES.includes(job.status))
|
|
125
|
+
.slice(0, MAX_TOOLTIP_JOB_NAMES);
|
|
126
|
+
const notableTotal = NOTABLE_STATUSES.reduce((total, status) => total + countOf(jobs, status), 0);
|
|
127
|
+
const remaining = notableTotal - named.length;
|
|
128
|
+
|
|
129
|
+
return (
|
|
130
|
+
<span className="block max-w-[280px]">
|
|
131
|
+
<Text as="span" size="xs" className="block">
|
|
132
|
+
{summary}
|
|
133
|
+
</Text>
|
|
134
|
+
{named.map((job) => (
|
|
135
|
+
<Code as="span" variant="label" key={job.id} className="mt-2 block truncate">
|
|
136
|
+
{getWorkflowStatusVisual(job.status).label.toLowerCase()} · {job.name ?? job.key}
|
|
137
|
+
</Code>
|
|
138
|
+
))}
|
|
139
|
+
{remaining > 0 ? (
|
|
140
|
+
<Text as="span" size="xs" className="mt-2 block text-foreground-neutral-muted">
|
|
141
|
+
and {remaining} more
|
|
142
|
+
</Text>
|
|
143
|
+
) : null}
|
|
144
|
+
</span>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** "12 jobs: 9 succeeded, 2 failed, 1 running" — a count is not an accessible name on its own. */
|
|
149
|
+
export function jobStatusSummary(jobs: WorkflowRunJobs): string {
|
|
150
|
+
const breakdown = [...jobs.statusCounts]
|
|
151
|
+
.sort((left, right) => STATUS_SEVERITY[right.status] - STATUS_SEVERITY[left.status])
|
|
152
|
+
.map(({status, count}) => `${count} ${getWorkflowStatusVisual(status).label.toLowerCase()}`)
|
|
153
|
+
.join(', ');
|
|
154
|
+
|
|
155
|
+
return `${jobs.total} ${jobs.total === 1 ? 'job' : 'jobs'}: ${breakdown}`;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* The worst status among the jobs the strip is not drawing, found by subtracting what it
|
|
160
|
+
* draws from the run's totals rather than by inspecting jobs it was never sent.
|
|
161
|
+
*/
|
|
162
|
+
function worstHiddenStatus(
|
|
163
|
+
jobs: WorkflowRunJobs,
|
|
164
|
+
visible: readonly WorkflowRunJobSummary[],
|
|
165
|
+
): JobStatus | null {
|
|
166
|
+
const hidden = new Map(jobs.statusCounts.map(({status, count}) => [status, count]));
|
|
167
|
+
for (const job of visible) {
|
|
168
|
+
const remaining = (hidden.get(job.status) ?? 0) - 1;
|
|
169
|
+
if (remaining > 0) hidden.set(job.status, remaining);
|
|
170
|
+
else hidden.delete(job.status);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
let worst: JobStatus | null = null;
|
|
174
|
+
for (const [status, count] of hidden) {
|
|
175
|
+
if (count <= 0) continue;
|
|
176
|
+
if (worst === null || STATUS_SEVERITY[status] > STATUS_SEVERITY[worst]) worst = status;
|
|
177
|
+
}
|
|
178
|
+
return worst;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function overflowCountLabel(hiddenCount: number): string {
|
|
182
|
+
return hiddenCount <= MAX_EXACT_OVERFLOW_COUNT
|
|
183
|
+
? String(hiddenCount)
|
|
184
|
+
: COMPACT_COUNT_FORMAT.format(hiddenCount);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function countOf(jobs: WorkflowRunJobs, status: JobStatus): number {
|
|
188
|
+
return jobs.statusCounts.find((entry) => entry.status === status)?.count ?? 0;
|
|
189
|
+
}
|
|
@@ -1,53 +1,192 @@
|
|
|
1
1
|
import {workflowRunListItem} from '#test/fixtures/workflow-run.js';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
runCalendarDate,
|
|
4
|
+
runMatchesFilters,
|
|
5
|
+
runMatchesSearch,
|
|
6
|
+
runMatchesStatusFilter,
|
|
7
|
+
workflowRunFacets,
|
|
8
|
+
} from './run-display.js';
|
|
3
9
|
|
|
4
10
|
describe('runMatchesSearch', () => {
|
|
5
11
|
test('matches every run on a blank query', () => {
|
|
6
|
-
|
|
12
|
+
expect(runMatchesSearch(workflowRunListItem(), ' ')).toBe(true);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test('matches the run name case-insensitively', () => {
|
|
16
|
+
expect(runMatchesSearch(workflowRunListItem({name: 'Deploy Web'}), 'deploy')).toBe(true);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('matches the run number, which the search box promises', () => {
|
|
20
|
+
expect(runMatchesSearch(workflowRunListItem({number: 5184}), '5184')).toBe(true);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('matches the run id, so a pasted identifier finds its row', () => {
|
|
24
|
+
expect(runMatchesSearch(workflowRunListItem({id: 'ABCD1234-X'}), 'abcd1234-x')).toBe(true);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('matches the workflow name even when the run has a different display name', () => {
|
|
28
|
+
const run = workflowRunListItem({name: 'Deploy to production', workflow_name: 'CI'});
|
|
29
|
+
|
|
30
|
+
expect(runMatchesSearch(run, 'CI')).toBe(true);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('matches the trigger label', () => {
|
|
34
|
+
const run = workflowRunListItem({trigger_source: 'github_acme', trigger_event: 'push'});
|
|
7
35
|
|
|
8
|
-
expect(
|
|
36
|
+
expect(runMatchesSearch(run, 'github_acme · push')).toBe(true);
|
|
9
37
|
});
|
|
10
38
|
|
|
11
|
-
test('matches
|
|
39
|
+
test('matches the branch, so a ref is findable without opening its filter', () => {
|
|
12
40
|
const run = workflowRunListItem({
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
41
|
+
trigger_reference: {
|
|
42
|
+
repository: 'acme/api',
|
|
43
|
+
ref: 'refs/heads/release/v2',
|
|
44
|
+
commit: 'abcdef1234567890',
|
|
45
|
+
actor: null,
|
|
46
|
+
},
|
|
19
47
|
});
|
|
20
48
|
|
|
21
|
-
expect(runMatchesSearch(run, '
|
|
22
|
-
expect(runMatchesSearch(run, 'deploy production')).toBe(true);
|
|
23
|
-
expect(runMatchesSearch(run, 'RUNNING')).toBe(true);
|
|
24
|
-
expect(runMatchesSearch(run, 'github_acme · push')).toBe(true);
|
|
49
|
+
expect(runMatchesSearch(run, 'release/v2')).toBe(true);
|
|
25
50
|
});
|
|
26
51
|
|
|
27
|
-
test('
|
|
28
|
-
const
|
|
52
|
+
test('matches the short commit, which is the form the row shows', () => {
|
|
53
|
+
const run = workflowRunListItem({
|
|
54
|
+
trigger_reference: {
|
|
55
|
+
repository: 'acme/api',
|
|
56
|
+
ref: 'refs/heads/main',
|
|
57
|
+
commit: 'abcdef1234567890',
|
|
58
|
+
actor: null,
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
expect(runMatchesSearch(run, 'abcdef1')).toBe(true);
|
|
63
|
+
});
|
|
29
64
|
|
|
30
|
-
|
|
65
|
+
test('reports no match for an unrelated query', () => {
|
|
66
|
+
expect(runMatchesSearch(workflowRunListItem(), 'no-such-run')).toBe(false);
|
|
31
67
|
});
|
|
32
68
|
});
|
|
33
69
|
|
|
34
70
|
describe('runMatchesStatusFilter', () => {
|
|
35
|
-
test('
|
|
36
|
-
expect(runMatchesStatusFilter('succeeded',
|
|
37
|
-
expect(runMatchesStatusFilter('
|
|
71
|
+
test('accepts every status when nothing is selected', () => {
|
|
72
|
+
expect(runMatchesStatusFilter('succeeded', undefined)).toBe(true);
|
|
73
|
+
expect(runMatchesStatusFilter('succeeded', [])).toBe(true);
|
|
38
74
|
});
|
|
39
75
|
|
|
40
|
-
test('
|
|
41
|
-
expect(runMatchesStatusFilter('failed', 'failed')).toBe(true);
|
|
42
|
-
expect(runMatchesStatusFilter('
|
|
43
|
-
expect(runMatchesStatusFilter('succeeded', 'failed')).toBe(false);
|
|
76
|
+
test('accepts a run matching any one of several selected statuses', () => {
|
|
77
|
+
expect(runMatchesStatusFilter('failed', ['succeeded', 'failed'])).toBe(true);
|
|
78
|
+
expect(runMatchesStatusFilter('cancelled', ['succeeded', 'failed'])).toBe(false);
|
|
44
79
|
});
|
|
45
80
|
|
|
46
|
-
test('
|
|
47
|
-
expect(runMatchesStatusFilter('
|
|
48
|
-
expect(runMatchesStatusFilter('
|
|
49
|
-
expect(runMatchesStatusFilter('succeeded', 'running')).toBe(false);
|
|
50
|
-
expect(runMatchesStatusFilter('failed', 'running')).toBe(false);
|
|
51
|
-
expect(runMatchesStatusFilter('cancelled', 'running')).toBe(false);
|
|
81
|
+
test('treats running as in-progress so a freshly queued run is not hidden', () => {
|
|
82
|
+
expect(runMatchesStatusFilter('pending', ['running'])).toBe(true);
|
|
83
|
+
expect(runMatchesStatusFilter('running', ['running'])).toBe(true);
|
|
84
|
+
expect(runMatchesStatusFilter('succeeded', ['running'])).toBe(false);
|
|
52
85
|
});
|
|
53
86
|
});
|
|
87
|
+
|
|
88
|
+
describe('runMatchesFilters', () => {
|
|
89
|
+
const run = workflowRunListItem({
|
|
90
|
+
name: 'deploy-web',
|
|
91
|
+
status: 'failed',
|
|
92
|
+
trigger_event: 'push',
|
|
93
|
+
created_at: '2026-05-07T12:00:00.000Z',
|
|
94
|
+
trigger_reference: {
|
|
95
|
+
repository: 'acme/api',
|
|
96
|
+
ref: 'refs/heads/main',
|
|
97
|
+
commit: 'abcdef1234567890',
|
|
98
|
+
actor: 'octocat',
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('accepts a run matching every active dimension at once', () => {
|
|
103
|
+
expect(
|
|
104
|
+
runMatchesFilters(run, {
|
|
105
|
+
search: 'deploy',
|
|
106
|
+
status: ['failed'],
|
|
107
|
+
branch: ['main'],
|
|
108
|
+
actor: ['octocat'],
|
|
109
|
+
event: ['push'],
|
|
110
|
+
}),
|
|
111
|
+
).toBe(true);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test.each([
|
|
115
|
+
['status', {status: ['succeeded' as const]}],
|
|
116
|
+
['branch', {branch: ['release']}],
|
|
117
|
+
['actor', {actor: ['someone-else']}],
|
|
118
|
+
['event', {event: ['pull_request']}],
|
|
119
|
+
['search', {search: 'unrelated'}],
|
|
120
|
+
])('rejects a run failing the %s dimension alone', (_dimension, criteria) => {
|
|
121
|
+
expect(runMatchesFilters(run, criteria)).toBe(false);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test('rejects a run that has no value at all for an active dimension', () => {
|
|
125
|
+
const withoutReference = workflowRunListItem({trigger_reference: null});
|
|
126
|
+
|
|
127
|
+
expect(runMatchesFilters(withoutReference, {branch: ['main']})).toBe(false);
|
|
128
|
+
expect(runMatchesFilters(withoutReference, {actor: ['octocat']})).toBe(false);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test('treats both date bounds as inclusive', () => {
|
|
132
|
+
const date = runCalendarDate(run) as string;
|
|
133
|
+
|
|
134
|
+
expect(runMatchesFilters(run, {after: date, before: date})).toBe(true);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test('excludes a run outside either bound', () => {
|
|
138
|
+
const date = runCalendarDate(run) as string;
|
|
139
|
+
|
|
140
|
+
expect(runMatchesFilters(run, {after: shiftDay(date, 1)})).toBe(false);
|
|
141
|
+
expect(runMatchesFilters(run, {before: shiftDay(date, -1)})).toBe(false);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
describe('workflowRunFacets', () => {
|
|
146
|
+
test('collects sorted, deduplicated options from the loaded runs', () => {
|
|
147
|
+
const runs = [
|
|
148
|
+
workflowRunListItem({
|
|
149
|
+
id: '11111111-1111-4111-8111-000000000001',
|
|
150
|
+
trigger_event: 'push',
|
|
151
|
+
trigger_reference: {
|
|
152
|
+
repository: 'acme/api',
|
|
153
|
+
ref: 'refs/heads/main',
|
|
154
|
+
commit: 'aaaaaaa',
|
|
155
|
+
actor: 'octocat',
|
|
156
|
+
},
|
|
157
|
+
}),
|
|
158
|
+
workflowRunListItem({
|
|
159
|
+
id: '11111111-1111-4111-8111-000000000002',
|
|
160
|
+
trigger_event: 'pull_request',
|
|
161
|
+
trigger_reference: {
|
|
162
|
+
repository: 'acme/api',
|
|
163
|
+
ref: 'refs/heads/main',
|
|
164
|
+
commit: 'bbbbbbb',
|
|
165
|
+
actor: 'hubot',
|
|
166
|
+
},
|
|
167
|
+
}),
|
|
168
|
+
];
|
|
169
|
+
|
|
170
|
+
expect(workflowRunFacets(runs)).toEqual({
|
|
171
|
+
branch: ['main'],
|
|
172
|
+
actor: ['hubot', 'octocat'],
|
|
173
|
+
event: ['pull_request', 'push'],
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test('keeps a selected value no loaded run carries, so a shared link keeps its chip', () => {
|
|
178
|
+
const facets = workflowRunFacets([workflowRunListItem({trigger_reference: null})], {
|
|
179
|
+
branch: ['release/v2'],
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
expect(facets.branch).toEqual(['release/v2']);
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
function shiftDay(date: string, days: number): string {
|
|
187
|
+
const [year, month, day] = date.split('-').map(Number) as [number, number, number];
|
|
188
|
+
const shifted = new Date(year, month - 1, day + days);
|
|
189
|
+
const shiftedMonth = String(shifted.getMonth() + 1).padStart(2, '0');
|
|
190
|
+
const shiftedDay = String(shifted.getDate()).padStart(2, '0');
|
|
191
|
+
return `${shifted.getFullYear()}-${shiftedMonth}-${shiftedDay}`;
|
|
192
|
+
}
|
|
@@ -1,10 +1,39 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
type WorkflowRunListItem,
|
|
3
|
+
type WorkflowRunStatus,
|
|
4
|
+
workflowRunActor,
|
|
5
|
+
workflowRunBranchLabel,
|
|
6
|
+
workflowRunCommitLabel,
|
|
7
|
+
} from '#core/workflow-run.js';
|
|
8
|
+
import type {WorkflowRunListStatus, WorkflowRunsSearch} from '#routes/inputs.js';
|
|
9
|
+
|
|
10
|
+
export type WorkflowRunFilterCriteria = Pick<
|
|
11
|
+
WorkflowRunsSearch,
|
|
12
|
+
'search' | 'status' | 'branch' | 'actor' | 'event' | 'after' | 'before'
|
|
13
|
+
>;
|
|
14
|
+
|
|
15
|
+
/** The dimensions whose options are read off the loaded runs rather than a fixed enum. */
|
|
16
|
+
export type WorkflowRunFacetName = 'branch' | 'actor' | 'event';
|
|
17
|
+
|
|
18
|
+
export type WorkflowRunFacets = Record<WorkflowRunFacetName, string[]>;
|
|
3
19
|
|
|
4
20
|
export function runMatchesSearch(run: WorkflowRunListItem, query: string): boolean {
|
|
5
21
|
const needle = query.trim().toLowerCase();
|
|
6
22
|
if (needle === '') return true;
|
|
7
|
-
const haystack =
|
|
23
|
+
const haystack = [
|
|
24
|
+
run.id,
|
|
25
|
+
run.name,
|
|
26
|
+
run.workflowName,
|
|
27
|
+
run.status,
|
|
28
|
+
run.triggerLabel,
|
|
29
|
+
run.number?.toString(),
|
|
30
|
+
workflowRunBranchLabel(run),
|
|
31
|
+
workflowRunCommitLabel(run),
|
|
32
|
+
workflowRunActor(run),
|
|
33
|
+
]
|
|
34
|
+
.filter(Boolean)
|
|
35
|
+
.join(' ')
|
|
36
|
+
.toLowerCase();
|
|
8
37
|
return haystack.includes(needle);
|
|
9
38
|
}
|
|
10
39
|
|
|
@@ -12,11 +41,89 @@ const IN_PROGRESS_STATUSES: ReadonlySet<WorkflowRunStatus> = new Set(['pending',
|
|
|
12
41
|
|
|
13
42
|
export function runMatchesStatusFilter(
|
|
14
43
|
status: WorkflowRunStatus,
|
|
15
|
-
|
|
44
|
+
selected: readonly WorkflowRunListStatus[] | undefined,
|
|
45
|
+
): boolean {
|
|
46
|
+
if (!selected || selected.length === 0) return true;
|
|
47
|
+
return selected.some((filter) => {
|
|
48
|
+
// "Running" reads as in-progress: it covers freshly-queued `pending` runs (including the
|
|
49
|
+
// optimistic manual run inserted on fire) so they are not hidden the moment it is on.
|
|
50
|
+
if (filter === 'running') return IN_PROGRESS_STATUSES.has(status);
|
|
51
|
+
return status === filter;
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Applies every active filter to one run.
|
|
57
|
+
*
|
|
58
|
+
* Filtering runs over the pages already fetched, which is what the rail did before this page
|
|
59
|
+
* existed. Making the API honor the same parameters across full history is ENG-512's job; the
|
|
60
|
+
* predicates here are written against the same dimension names so that swap stays local.
|
|
61
|
+
*/
|
|
62
|
+
export function runMatchesFilters(
|
|
63
|
+
run: WorkflowRunListItem,
|
|
64
|
+
criteria: WorkflowRunFilterCriteria,
|
|
16
65
|
): boolean {
|
|
17
|
-
if (
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
if (
|
|
21
|
-
|
|
66
|
+
if (!runMatchesStatusFilter(run.status, criteria.status)) return false;
|
|
67
|
+
if (!matchesFacet(criteria.branch, workflowRunBranchLabel(run))) return false;
|
|
68
|
+
if (!matchesFacet(criteria.actor, workflowRunActor(run))) return false;
|
|
69
|
+
if (!matchesFacet(criteria.event, run.triggerEvent)) return false;
|
|
70
|
+
if (!matchesDateBounds(run, criteria)) return false;
|
|
71
|
+
return runMatchesSearch(run, criteria.search ?? '');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Filter options read off the loaded runs.
|
|
76
|
+
*
|
|
77
|
+
* Values the user already selected are folded in even when no loaded run carries them, so a
|
|
78
|
+
* shared link never renders as though its own filter were unset.
|
|
79
|
+
*/
|
|
80
|
+
export function workflowRunFacets(
|
|
81
|
+
runs: readonly WorkflowRunListItem[],
|
|
82
|
+
criteria: WorkflowRunFilterCriteria = {},
|
|
83
|
+
): WorkflowRunFacets {
|
|
84
|
+
return {
|
|
85
|
+
branch: facetValues(runs.map(workflowRunBranchLabel), criteria.branch),
|
|
86
|
+
actor: facetValues(runs.map(workflowRunActor), criteria.actor),
|
|
87
|
+
event: facetValues(
|
|
88
|
+
runs.map((run) => run.triggerEvent),
|
|
89
|
+
criteria.event,
|
|
90
|
+
),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* The run's local calendar date, which is both what the date filters bound and what the row's
|
|
96
|
+
* relative time is derived from, so a filtered result never contradicts what a row shows.
|
|
97
|
+
*/
|
|
98
|
+
export function runCalendarDate(run: Pick<WorkflowRunListItem, 'createdAt'>): string | null {
|
|
99
|
+
const date = new Date(run.createdAt);
|
|
100
|
+
if (Number.isNaN(date.getTime())) return null;
|
|
101
|
+
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
102
|
+
const day = String(date.getDate()).padStart(2, '0');
|
|
103
|
+
return `${date.getFullYear()}-${month}-${day}`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function matchesFacet(selected: readonly string[] | undefined, value: string | null): boolean {
|
|
107
|
+
if (!selected || selected.length === 0) return true;
|
|
108
|
+
return value !== null && selected.includes(value);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function matchesDateBounds(run: WorkflowRunListItem, criteria: WorkflowRunFilterCriteria): boolean {
|
|
112
|
+
if (!criteria.after && !criteria.before) return true;
|
|
113
|
+
const date = runCalendarDate(run);
|
|
114
|
+
if (date === null) return false;
|
|
115
|
+
// Both bounds are inclusive, and `YYYY-MM-DD` sorts correctly as text.
|
|
116
|
+
if (criteria.after && date < criteria.after) return false;
|
|
117
|
+
if (criteria.before && date > criteria.before) return false;
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function facetValues(
|
|
122
|
+
values: readonly (string | null)[],
|
|
123
|
+
selected: readonly string[] | undefined,
|
|
124
|
+
): string[] {
|
|
125
|
+
const present = values.filter((value): value is string => Boolean(value));
|
|
126
|
+
return [...new Set([...present, ...(selected ?? [])])].sort((left, right) =>
|
|
127
|
+
left.localeCompare(right),
|
|
128
|
+
);
|
|
22
129
|
}
|
|
@@ -1,28 +1,32 @@
|
|
|
1
1
|
import type {QueryLoadErrorQuery} from '@shipfox/client-ui';
|
|
2
2
|
import type {WorkflowRunListItem} from '#core/workflow-run.js';
|
|
3
|
+
import type {WorkflowRunFilterPatch, WorkflowRunsSearch} from '#routes/inputs.js';
|
|
3
4
|
|
|
4
|
-
export type
|
|
5
|
+
export type WorkflowRunListQuery = QueryLoadErrorQuery & {isPending: boolean};
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
selectedWorkflowRunId?: string | undefined;
|
|
7
|
+
interface WorkflowRunListCommonProps {
|
|
8
|
+
workspaceSlug?: string | undefined;
|
|
9
|
+
projectSlug?: string | undefined;
|
|
10
10
|
className?: string | undefined;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
/**
|
|
12
|
+
* The parsed URL search. Pair with `onFiltersChange` to keep it in the URL; omit the
|
|
13
|
+
* handler and the view holds filter state itself, which is what stories and isolated tests
|
|
14
|
+
* mount against.
|
|
15
|
+
*/
|
|
16
|
+
search?: WorkflowRunsSearch;
|
|
17
|
+
onFiltersChange?: (patch: WorkflowRunFilterPatch) => void;
|
|
18
|
+
onClearFilters?: () => void;
|
|
19
|
+
hasNextPage?: boolean;
|
|
20
|
+
isFetchingNextPage?: boolean;
|
|
21
|
+
isFetchNextPageError?: boolean;
|
|
22
|
+
onLoadMore?: () => void;
|
|
14
23
|
}
|
|
15
24
|
|
|
16
|
-
export
|
|
25
|
+
export interface WorkflowRunListProps extends WorkflowRunListCommonProps {
|
|
26
|
+
projectId: string;
|
|
27
|
+
}
|
|
17
28
|
|
|
18
|
-
export interface WorkflowRunListViewProps {
|
|
29
|
+
export interface WorkflowRunListViewProps extends WorkflowRunListCommonProps {
|
|
19
30
|
runs: WorkflowRunListItem[];
|
|
20
31
|
query: WorkflowRunListQuery;
|
|
21
|
-
workspaceId: string;
|
|
22
|
-
projectId: string;
|
|
23
|
-
selectedWorkflowRunId?: string | undefined;
|
|
24
|
-
className?: string | undefined;
|
|
25
|
-
search?: string;
|
|
26
|
-
statusFilter?: WorkflowRunListStatusFilter;
|
|
27
|
-
onFiltersChange?: (filters: {search?: string; status?: WorkflowRunListStatusFilter}) => void;
|
|
28
32
|
}
|