@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.
Files changed (232) hide show
  1. package/.storybook/preview.tsx +3 -3
  2. package/.turbo/turbo-build.log +1 -1
  3. package/CHANGELOG.md +56 -0
  4. package/dist/components/job-graph/job-duration-format.d.ts +2 -2
  5. package/dist/components/job-graph/job-duration-format.d.ts.map +1 -1
  6. package/dist/components/job-graph/job-duration-format.js +4 -2
  7. package/dist/components/job-graph/job-duration-format.js.map +1 -1
  8. package/dist/components/job-graph/job-node.d.ts +1 -1
  9. package/dist/components/job-graph/job-node.d.ts.map +1 -1
  10. package/dist/components/job-graph/job-node.js +5 -4
  11. package/dist/components/job-graph/job-node.js.map +1 -1
  12. package/dist/components/step-list/step-list.d.ts +2 -2
  13. package/dist/components/step-list/step-list.d.ts.map +1 -1
  14. package/dist/components/step-list/step-list.js.map +1 -1
  15. package/dist/components/workflow-run-list/job-status-strip.d.ts +21 -0
  16. package/dist/components/workflow-run-list/job-status-strip.d.ts.map +1 -0
  17. package/dist/components/workflow-run-list/job-status-strip.js +185 -0
  18. package/dist/components/workflow-run-list/job-status-strip.js.map +1 -0
  19. package/dist/components/workflow-run-list/run-display.d.ts +27 -3
  20. package/dist/components/workflow-run-list/run-display.d.ts.map +1 -1
  21. package/dist/components/workflow-run-list/run-display.js +78 -7
  22. package/dist/components/workflow-run-list/run-display.js.map +1 -1
  23. package/dist/components/workflow-run-list/types.d.ts +23 -24
  24. package/dist/components/workflow-run-list/types.d.ts.map +1 -1
  25. package/dist/components/workflow-run-list/types.js.map +1 -1
  26. package/dist/components/workflow-run-list/workflow-run-filter-menu.d.ts +22 -0
  27. package/dist/components/workflow-run-list/workflow-run-filter-menu.d.ts.map +1 -0
  28. package/dist/components/workflow-run-list/workflow-run-filter-menu.js +68 -0
  29. package/dist/components/workflow-run-list/workflow-run-filter-menu.js.map +1 -0
  30. package/dist/components/workflow-run-list/workflow-run-filters.d.ts +18 -0
  31. package/dist/components/workflow-run-list/workflow-run-filters.d.ts.map +1 -0
  32. package/dist/components/workflow-run-list/workflow-run-filters.js +236 -0
  33. package/dist/components/workflow-run-list/workflow-run-filters.js.map +1 -0
  34. package/dist/components/workflow-run-list/workflow-run-list-content.d.ts +8 -4
  35. package/dist/components/workflow-run-list/workflow-run-list-content.d.ts.map +1 -1
  36. package/dist/components/workflow-run-list/workflow-run-list-content.js +40 -12
  37. package/dist/components/workflow-run-list/workflow-run-list-content.js.map +1 -1
  38. package/dist/components/workflow-run-list/workflow-run-list-states.d.ts +29 -2
  39. package/dist/components/workflow-run-list/workflow-run-list-states.d.ts.map +1 -1
  40. package/dist/components/workflow-run-list/workflow-run-list-states.js +103 -44
  41. package/dist/components/workflow-run-list/workflow-run-list-states.js.map +1 -1
  42. package/dist/components/workflow-run-list/workflow-run-list-view.d.ts +1 -1
  43. package/dist/components/workflow-run-list/workflow-run-list-view.d.ts.map +1 -1
  44. package/dist/components/workflow-run-list/workflow-run-list-view.js +60 -43
  45. package/dist/components/workflow-run-list/workflow-run-list-view.js.map +1 -1
  46. package/dist/components/workflow-run-list/workflow-run-list.d.ts +1 -1
  47. package/dist/components/workflow-run-list/workflow-run-list.d.ts.map +1 -1
  48. package/dist/components/workflow-run-list/workflow-run-list.js +20 -8
  49. package/dist/components/workflow-run-list/workflow-run-list.js.map +1 -1
  50. package/dist/components/workflow-run-list/workflow-run-row.d.ts +19 -9
  51. package/dist/components/workflow-run-list/workflow-run-row.d.ts.map +1 -1
  52. package/dist/components/workflow-run-list/workflow-run-row.js +179 -112
  53. package/dist/components/workflow-run-list/workflow-run-row.js.map +1 -1
  54. package/dist/components/workflow-run-number-label.d.ts +15 -0
  55. package/dist/components/workflow-run-number-label.d.ts.map +1 -0
  56. package/dist/components/workflow-run-number-label.js +41 -0
  57. package/dist/components/workflow-run-number-label.js.map +1 -0
  58. package/dist/components/workflow-run-summary/workflow-run-attempt-switcher.d.ts +3 -3
  59. package/dist/components/workflow-run-summary/workflow-run-attempt-switcher.d.ts.map +1 -1
  60. package/dist/components/workflow-run-summary/workflow-run-attempt-switcher.js +8 -7
  61. package/dist/components/workflow-run-summary/workflow-run-attempt-switcher.js.map +1 -1
  62. package/dist/components/workflow-run-summary/workflow-run-summary.d.ts +3 -3
  63. package/dist/components/workflow-run-summary/workflow-run-summary.d.ts.map +1 -1
  64. package/dist/components/workflow-run-summary/workflow-run-summary.js +16 -7
  65. package/dist/components/workflow-run-summary/workflow-run-summary.js.map +1 -1
  66. package/dist/components/workflow-run-view/agent-config-failure-callout.d.ts +2 -2
  67. package/dist/components/workflow-run-view/agent-config-failure-callout.d.ts.map +1 -1
  68. package/dist/components/workflow-run-view/agent-config-failure-callout.js +4 -4
  69. package/dist/components/workflow-run-view/agent-config-failure-callout.js.map +1 -1
  70. package/dist/components/workflow-run-view/job-card.d.ts +2 -2
  71. package/dist/components/workflow-run-view/job-card.d.ts.map +1 -1
  72. package/dist/components/workflow-run-view/job-card.js +51 -32
  73. package/dist/components/workflow-run-view/job-card.js.map +1 -1
  74. package/dist/components/workflow-run-view/job-execution-switcher.d.ts +1 -1
  75. package/dist/components/workflow-run-view/job-execution-switcher.d.ts.map +1 -1
  76. package/dist/components/workflow-run-view/job-execution-switcher.js +20 -8
  77. package/dist/components/workflow-run-view/job-execution-switcher.js.map +1 -1
  78. package/dist/components/workflow-run-view/workflow-run-view.d.ts +3 -2
  79. package/dist/components/workflow-run-view/workflow-run-view.d.ts.map +1 -1
  80. package/dist/components/workflow-run-view/workflow-run-view.js +15 -11
  81. package/dist/components/workflow-run-view/workflow-run-view.js.map +1 -1
  82. package/dist/components/workflow-status/status-visuals.d.ts +4 -4
  83. package/dist/components/workflow-status/status-visuals.d.ts.map +1 -1
  84. package/dist/components/workflow-status/status-visuals.js +7 -0
  85. package/dist/components/workflow-status/status-visuals.js.map +1 -1
  86. package/dist/components/workflow-status/workflow-status-icon.d.ts +3 -4
  87. package/dist/components/workflow-status/workflow-status-icon.d.ts.map +1 -1
  88. package/dist/components/workflow-status/workflow-status-icon.js +6 -4
  89. package/dist/components/workflow-status/workflow-status-icon.js.map +1 -1
  90. package/dist/core/entities/job-execution.d.ts +10 -0
  91. package/dist/core/entities/job-execution.d.ts.map +1 -1
  92. package/dist/core/entities/job-execution.js +11 -0
  93. package/dist/core/entities/job-execution.js.map +1 -1
  94. package/dist/core/entities/job.d.ts +4 -2
  95. package/dist/core/entities/job.d.ts.map +1 -1
  96. package/dist/core/entities/job.js +8 -1
  97. package/dist/core/entities/job.js.map +1 -1
  98. package/dist/core/entities/workflow-run.d.ts +52 -4
  99. package/dist/core/entities/workflow-run.d.ts.map +1 -1
  100. package/dist/core/entities/workflow-run.js +22 -3
  101. package/dist/core/entities/workflow-run.js.map +1 -1
  102. package/dist/core/workflow-run.d.ts +6 -6
  103. package/dist/core/workflow-run.d.ts.map +1 -1
  104. package/dist/core/workflow-run.js +3 -3
  105. package/dist/core/workflow-run.js.map +1 -1
  106. package/dist/feature.d.ts +7 -7
  107. package/dist/feature.js +5 -5
  108. package/dist/feature.js.map +1 -1
  109. package/dist/hooks/api/workflow-run-mapper.d.ts +4 -3
  110. package/dist/hooks/api/workflow-run-mapper.d.ts.map +1 -1
  111. package/dist/hooks/api/workflow-run-mapper.js +31 -3
  112. package/dist/hooks/api/workflow-run-mapper.js.map +1 -1
  113. package/dist/hooks/api/workflow-runs.d.ts +4 -4
  114. package/dist/hooks/api/workflow-runs.d.ts.map +1 -1
  115. package/dist/hooks/api/workflow-runs.js +12 -4
  116. package/dist/hooks/api/workflow-runs.js.map +1 -1
  117. package/dist/index.d.ts +4 -3
  118. package/dist/index.d.ts.map +1 -1
  119. package/dist/index.js +3 -2
  120. package/dist/index.js.map +1 -1
  121. package/dist/pages/project-workflows-page.js +46 -1
  122. package/dist/pages/project-workflows-page.js.map +1 -1
  123. package/dist/pages/workflow-run-detail-page.d.ts +11 -0
  124. package/dist/pages/workflow-run-detail-page.d.ts.map +1 -0
  125. package/dist/pages/workflow-run-detail-page.js +30 -0
  126. package/dist/pages/workflow-run-detail-page.js.map +1 -0
  127. package/dist/pages/workflow-run-list-page.d.ts +10 -0
  128. package/dist/pages/workflow-run-list-page.d.ts.map +1 -0
  129. package/dist/pages/workflow-run-list-page.js +43 -0
  130. package/dist/pages/workflow-run-list-page.js.map +1 -0
  131. package/dist/routes/inputs.d.ts +61 -5
  132. package/dist/routes/inputs.d.ts.map +1 -1
  133. package/dist/routes/inputs.js +150 -13
  134. package/dist/routes/inputs.js.map +1 -1
  135. package/dist/routes/project-route.d.ts +6 -0
  136. package/dist/routes/project-route.d.ts.map +1 -0
  137. package/dist/routes/project-route.js +25 -0
  138. package/dist/routes/project-route.js.map +1 -0
  139. package/dist/routes/run-detail.d.ts.map +1 -1
  140. package/dist/routes/run-detail.js +12 -7
  141. package/dist/routes/run-detail.js.map +1 -1
  142. package/dist/routes/runs.d.ts.map +1 -1
  143. package/dist/routes/runs.js +11 -6
  144. package/dist/routes/runs.js.map +1 -1
  145. package/dist/routes/workflows.d.ts.map +1 -1
  146. package/dist/routes/workflows.js +6 -5
  147. package/dist/routes/workflows.js.map +1 -1
  148. package/dist/tsconfig.test.tsbuildinfo +1 -1
  149. package/package.json +9 -9
  150. package/src/components/job-graph/job-duration-format.ts +11 -2
  151. package/src/components/job-graph/job-graph-view.test.tsx +19 -8
  152. package/src/components/job-graph/job-graph.stories.tsx +23 -19
  153. package/src/components/job-graph/job-node.stories.tsx +10 -5
  154. package/src/components/job-graph/job-node.test.tsx +55 -10
  155. package/src/components/job-graph/job-node.tsx +16 -9
  156. package/src/components/step-list/step-list.stories.tsx +2 -3
  157. package/src/components/step-list/step-list.tsx +3 -2
  158. package/src/components/workflow-run-list/job-status-strip.tsx +189 -0
  159. package/src/components/workflow-run-list/run-display.test.ts +169 -30
  160. package/src/components/workflow-run-list/run-display.ts +116 -9
  161. package/src/components/workflow-run-list/types.ts +21 -17
  162. package/src/components/workflow-run-list/workflow-run-filter-menu.tsx +100 -0
  163. package/src/components/workflow-run-list/workflow-run-filters.tsx +243 -0
  164. package/src/components/workflow-run-list/workflow-run-list-content.tsx +44 -16
  165. package/src/components/workflow-run-list/workflow-run-list-states.tsx +128 -27
  166. package/src/components/workflow-run-list/workflow-run-list-view.test.tsx +441 -104
  167. package/src/components/workflow-run-list/workflow-run-list-view.tsx +73 -43
  168. package/src/components/workflow-run-list/workflow-run-list.stories.tsx +140 -20
  169. package/src/components/workflow-run-list/workflow-run-list.tsx +20 -10
  170. package/src/components/workflow-run-list/workflow-run-row.tsx +173 -106
  171. package/src/components/workflow-run-number-label.test.tsx +45 -0
  172. package/src/components/workflow-run-number-label.tsx +47 -0
  173. package/src/components/workflow-run-summary/workflow-run-attempt-switcher.test.tsx +7 -8
  174. package/src/components/workflow-run-summary/workflow-run-attempt-switcher.tsx +13 -12
  175. package/src/components/workflow-run-summary/workflow-run-summary.stories.tsx +6 -8
  176. package/src/components/workflow-run-summary/workflow-run-summary.test.tsx +27 -2
  177. package/src/components/workflow-run-summary/workflow-run-summary.tsx +19 -9
  178. package/src/components/workflow-run-view/agent-config-failure-callout.stories.tsx +2 -3
  179. package/src/components/workflow-run-view/agent-config-failure-callout.tsx +4 -4
  180. package/src/components/workflow-run-view/job-card.stories.tsx +11 -10
  181. package/src/components/workflow-run-view/job-card.test.tsx +115 -0
  182. package/src/components/workflow-run-view/job-card.tsx +74 -35
  183. package/src/components/workflow-run-view/job-execution-switcher.tsx +31 -9
  184. package/src/components/workflow-run-view/workflow-run-view.test.tsx +88 -30
  185. package/src/components/workflow-run-view/workflow-run-view.tsx +24 -14
  186. package/src/components/workflow-status/status-visuals.test.ts +9 -0
  187. package/src/components/workflow-status/status-visuals.ts +6 -4
  188. package/src/components/workflow-status/workflow-status-icon.stories.tsx +10 -2
  189. package/src/components/workflow-status/workflow-status-icon.test.tsx +18 -0
  190. package/src/components/workflow-status/workflow-status-icon.tsx +17 -24
  191. package/src/core/entities/job-execution.ts +22 -0
  192. package/src/core/entities/job-status.test.ts +104 -0
  193. package/src/core/entities/job.ts +18 -3
  194. package/src/core/entities/workflow-run.ts +76 -7
  195. package/src/core/workflow-run.test.ts +24 -9
  196. package/src/core/workflow-run.ts +17 -2
  197. package/src/feature.ts +5 -5
  198. package/src/hooks/api/workflow-run-mapper.ts +30 -3
  199. package/src/hooks/api/workflow-runs.test.tsx +2 -0
  200. package/src/hooks/api/workflow-runs.ts +12 -7
  201. package/src/index.ts +7 -2
  202. package/src/page-harness-budget.test.ts +1 -1
  203. package/src/pages/project-workflows-page.test.tsx +59 -5
  204. package/src/pages/project-workflows-page.tsx +34 -1
  205. package/src/pages/workflow-run-detail-page.tsx +45 -0
  206. package/src/pages/workflow-run-list-page.tsx +65 -0
  207. package/src/pages/{workflow-run-page.test.tsx → workflow-run-pages.test.tsx} +211 -47
  208. package/src/routes/inputs.test.ts +193 -12
  209. package/src/routes/inputs.ts +170 -11
  210. package/src/routes/project-route.tsx +25 -0
  211. package/src/routes/run-detail.tsx +15 -8
  212. package/src/routes/runs.tsx +14 -7
  213. package/src/routes/workflows.tsx +5 -4
  214. package/test/fixtures/workflow-run.ts +76 -7
  215. package/test/pages.tsx +18 -8
  216. package/test/render.tsx +8 -2
  217. package/tsconfig.build.tsbuildinfo +1 -1
  218. package/dist/components/workflow-run-list/workflow-run-list-header.d.ts +0 -10
  219. package/dist/components/workflow-run-list/workflow-run-list-header.d.ts.map +0 -1
  220. package/dist/components/workflow-run-list/workflow-run-list-header.js +0 -55
  221. package/dist/components/workflow-run-list/workflow-run-list-header.js.map +0 -1
  222. package/dist/pages/workflow-run-first-time-use.d.ts +0 -6
  223. package/dist/pages/workflow-run-first-time-use.d.ts.map +0 -1
  224. package/dist/pages/workflow-run-first-time-use.js +0 -17
  225. package/dist/pages/workflow-run-first-time-use.js.map +0 -1
  226. package/dist/pages/workflow-run-page.d.ts +0 -10
  227. package/dist/pages/workflow-run-page.d.ts.map +0 -1
  228. package/dist/pages/workflow-run-page.js +0 -90
  229. package/dist/pages/workflow-run-page.js.map +0 -1
  230. package/src/components/workflow-run-list/workflow-run-list-header.tsx +0 -53
  231. package/src/pages/workflow-run-first-time-use.tsx +0 -17
  232. package/src/pages/workflow-run-page.tsx +0 -94
@@ -0,0 +1,185 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Tooltip, TooltipContent, TooltipTrigger } from '@shipfox/react-ui/tooltip';
3
+ import { Code, Text } from '@shipfox/react-ui/typography';
4
+ import { cn } from '@shipfox/react-ui/utils';
5
+ import { getWorkflowStatusVisual } from '#components/workflow-status/status-visuals.js';
6
+ import { WorkflowStatusIcon } from '#components/workflow-status/workflow-status-icon.js';
7
+ /**
8
+ * How many glyphs fit the strip's column before it starts costing the run name width.
9
+ *
10
+ * Sized so the widest case still fits the row's 160px strip column: seven 12px glyphs with
11
+ * 3px gaps is 102px, and the overflow indicator adds a glyph plus a count of at most five
12
+ * monospace characters, about 56px. Overshooting does not wrap, it paints over the duration
13
+ * column. This sits under the API's preview bound, so resizing the strip is a change here.
14
+ */ const MAX_VISIBLE_JOBS = 7;
15
+ const GLYPH_SIZE = 12;
16
+ const MAX_TOOLTIP_JOB_NAMES = 6;
17
+ /**
18
+ * Above this the overflow count is abbreviated.
19
+ *
20
+ * Nothing caps a workflow's job count, so an exact count is unbounded in width and would
21
+ * eventually push the strip over its column. Abbreviating keeps the label at most five
22
+ * characters for any magnitude, and the exact figure stays in the tooltip and the strip's
23
+ * accessible name, which is where a precise number is actually read.
24
+ */ const MAX_EXACT_OVERFLOW_COUNT = 999;
25
+ const COMPACT_COUNT_FORMAT = new Intl.NumberFormat('en', {
26
+ notation: 'compact',
27
+ maximumFractionDigits: 1
28
+ });
29
+ // Worst-first, so the overflow glyph reports the most alarming thing it is standing in for.
30
+ // A strip that hides a failure behind eight green discs would be worse than no strip at all.
31
+ const STATUS_SEVERITY = {
32
+ failed: 5,
33
+ running: 4,
34
+ pending: 3,
35
+ cancelled: 2,
36
+ skipped: 1,
37
+ succeeded: 0
38
+ };
39
+ const NOTABLE_STATUSES = [
40
+ 'failed',
41
+ 'running'
42
+ ];
43
+ /**
44
+ * A run's jobs as a row of status glyphs, in graph order.
45
+ *
46
+ * This is the reason the list is worth its width: it answers "where did this run fail"
47
+ * without opening the run. Glyphs are silent to assistive tech and carry no tooltip of their
48
+ * own; the strip speaks once, as a summary, so a 40-job run does not read out 40 labels.
49
+ *
50
+ * What it draws comes from the preview; what it says comes from the counts, which cover every
51
+ * job including those the server never sent. That split is what lets the overflow glyph
52
+ * report a failure sitting past the preview instead of quietly dropping it.
53
+ */ export function JobStatusStrip({ jobs, className }) {
54
+ if (jobs.total === 0) return null;
55
+ const visible = jobs.preview.slice(0, MAX_VISIBLE_JOBS);
56
+ const hiddenCount = jobs.total - visible.length;
57
+ const overflowStatus = worstHiddenStatus(jobs, visible);
58
+ const summary = jobStatusSummary(jobs);
59
+ return /*#__PURE__*/ _jsxs(Tooltip, {
60
+ children: [
61
+ /*#__PURE__*/ _jsx(TooltipTrigger, {
62
+ asChild: true,
63
+ children: /*#__PURE__*/ _jsx("span", {
64
+ role: "img",
65
+ "aria-label": summary,
66
+ className: cn('inline-flex items-center gap-3', className),
67
+ children: /*#__PURE__*/ _jsxs("span", {
68
+ "aria-hidden": "true",
69
+ className: "inline-flex items-center gap-3",
70
+ children: [
71
+ visible.map((job)=>/*#__PURE__*/ _jsx(WorkflowStatusIcon, {
72
+ status: job.status,
73
+ size: GLYPH_SIZE,
74
+ // One ripple per running job, on every row, would turn a calm list into a
75
+ // field of pulses. The run's own glyph already carries the live edge.
76
+ ripple: false,
77
+ tooltip: false
78
+ }, job.id)),
79
+ hiddenCount > 0 && overflowStatus ? /*#__PURE__*/ _jsxs("span", {
80
+ className: "inline-flex items-center gap-2",
81
+ children: [
82
+ /*#__PURE__*/ _jsx(WorkflowStatusIcon, {
83
+ status: overflowStatus,
84
+ size: GLYPH_SIZE,
85
+ ripple: false,
86
+ tooltip: false
87
+ }),
88
+ /*#__PURE__*/ _jsxs(Code, {
89
+ as: "span",
90
+ variant: "label",
91
+ className: "tabular-nums text-foreground-neutral-muted",
92
+ children: [
93
+ "+",
94
+ overflowCountLabel(hiddenCount)
95
+ ]
96
+ })
97
+ ]
98
+ }) : null
99
+ ]
100
+ })
101
+ })
102
+ }),
103
+ /*#__PURE__*/ _jsx(TooltipContent, {
104
+ children: /*#__PURE__*/ _jsx(JobStatusStripTooltip, {
105
+ jobs: jobs,
106
+ summary: summary
107
+ })
108
+ })
109
+ ]
110
+ });
111
+ }
112
+ function JobStatusStripTooltip({ jobs, summary }) {
113
+ // Naming the jobs that need attention is the whole point of hovering; succeeded jobs are
114
+ // left to the counts so the tooltip stays a glance rather than a list. Only previewed jobs
115
+ // can be named, but the count of those left over is read off the totals, so a failure past
116
+ // the preview is still accounted for rather than silently missing.
117
+ const named = jobs.preview.filter((job)=>NOTABLE_STATUSES.includes(job.status)).slice(0, MAX_TOOLTIP_JOB_NAMES);
118
+ const notableTotal = NOTABLE_STATUSES.reduce((total, status)=>total + countOf(jobs, status), 0);
119
+ const remaining = notableTotal - named.length;
120
+ return /*#__PURE__*/ _jsxs("span", {
121
+ className: "block max-w-[280px]",
122
+ children: [
123
+ /*#__PURE__*/ _jsx(Text, {
124
+ as: "span",
125
+ size: "xs",
126
+ className: "block",
127
+ children: summary
128
+ }),
129
+ named.map((job)=>/*#__PURE__*/ _jsxs(Code, {
130
+ as: "span",
131
+ variant: "label",
132
+ className: "mt-2 block truncate",
133
+ children: [
134
+ getWorkflowStatusVisual(job.status).label.toLowerCase(),
135
+ " · ",
136
+ job.name ?? job.key
137
+ ]
138
+ }, job.id)),
139
+ remaining > 0 ? /*#__PURE__*/ _jsxs(Text, {
140
+ as: "span",
141
+ size: "xs",
142
+ className: "mt-2 block text-foreground-neutral-muted",
143
+ children: [
144
+ "and ",
145
+ remaining,
146
+ " more"
147
+ ]
148
+ }) : null
149
+ ]
150
+ });
151
+ }
152
+ /** "12 jobs: 9 succeeded, 2 failed, 1 running" — a count is not an accessible name on its own. */ export function jobStatusSummary(jobs) {
153
+ const breakdown = [
154
+ ...jobs.statusCounts
155
+ ].sort((left, right)=>STATUS_SEVERITY[right.status] - STATUS_SEVERITY[left.status]).map(({ status, count })=>`${count} ${getWorkflowStatusVisual(status).label.toLowerCase()}`).join(', ');
156
+ return `${jobs.total} ${jobs.total === 1 ? 'job' : 'jobs'}: ${breakdown}`;
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
+ */ function worstHiddenStatus(jobs, visible) {
162
+ const hidden = new Map(jobs.statusCounts.map(({ status, count })=>[
163
+ status,
164
+ count
165
+ ]));
166
+ for (const job of visible){
167
+ const remaining = (hidden.get(job.status) ?? 0) - 1;
168
+ if (remaining > 0) hidden.set(job.status, remaining);
169
+ else hidden.delete(job.status);
170
+ }
171
+ let worst = null;
172
+ for (const [status, count] of hidden){
173
+ if (count <= 0) continue;
174
+ if (worst === null || STATUS_SEVERITY[status] > STATUS_SEVERITY[worst]) worst = status;
175
+ }
176
+ return worst;
177
+ }
178
+ export function overflowCountLabel(hiddenCount) {
179
+ return hiddenCount <= MAX_EXACT_OVERFLOW_COUNT ? String(hiddenCount) : COMPACT_COUNT_FORMAT.format(hiddenCount);
180
+ }
181
+ function countOf(jobs, status) {
182
+ return jobs.statusCounts.find((entry)=>entry.status === status)?.count ?? 0;
183
+ }
184
+
185
+ //# sourceMappingURL=job-status-strip.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/workflow-run-list/job-status-strip.tsx"],"sourcesContent":["import {Tooltip, TooltipContent, TooltipTrigger} from '@shipfox/react-ui/tooltip';\nimport {Code, Text} from '@shipfox/react-ui/typography';\nimport {cn} from '@shipfox/react-ui/utils';\nimport {getWorkflowStatusVisual} from '#components/workflow-status/status-visuals.js';\nimport {WorkflowStatusIcon} from '#components/workflow-status/workflow-status-icon.js';\nimport type {JobStatus, WorkflowRunJobSummary, WorkflowRunJobs} from '#core/workflow-run.js';\n\n/**\n * How many glyphs fit the strip's column before it starts costing the run name width.\n *\n * Sized so the widest case still fits the row's 160px strip column: seven 12px glyphs with\n * 3px gaps is 102px, and the overflow indicator adds a glyph plus a count of at most five\n * monospace characters, about 56px. Overshooting does not wrap, it paints over the duration\n * column. This sits under the API's preview bound, so resizing the strip is a change here.\n */\nconst MAX_VISIBLE_JOBS = 7;\nconst GLYPH_SIZE = 12;\nconst MAX_TOOLTIP_JOB_NAMES = 6;\n\n/**\n * Above this the overflow count is abbreviated.\n *\n * Nothing caps a workflow's job count, so an exact count is unbounded in width and would\n * eventually push the strip over its column. Abbreviating keeps the label at most five\n * characters for any magnitude, and the exact figure stays in the tooltip and the strip's\n * accessible name, which is where a precise number is actually read.\n */\nconst MAX_EXACT_OVERFLOW_COUNT = 999;\nconst COMPACT_COUNT_FORMAT = new Intl.NumberFormat('en', {\n notation: 'compact',\n maximumFractionDigits: 1,\n});\n\n// Worst-first, so the overflow glyph reports the most alarming thing it is standing in for.\n// A strip that hides a failure behind eight green discs would be worse than no strip at all.\nconst STATUS_SEVERITY: Record<JobStatus, number> = {\n failed: 5,\n running: 4,\n pending: 3,\n cancelled: 2,\n skipped: 1,\n succeeded: 0,\n};\n\nconst NOTABLE_STATUSES: readonly JobStatus[] = ['failed', 'running'];\n\nexport interface JobStatusStripProps {\n jobs: WorkflowRunJobs;\n className?: string | undefined;\n}\n\n/**\n * A run's jobs as a row of status glyphs, in graph order.\n *\n * This is the reason the list is worth its width: it answers \"where did this run fail\"\n * without opening the run. Glyphs are silent to assistive tech and carry no tooltip of their\n * own; the strip speaks once, as a summary, so a 40-job run does not read out 40 labels.\n *\n * What it draws comes from the preview; what it says comes from the counts, which cover every\n * job including those the server never sent. That split is what lets the overflow glyph\n * report a failure sitting past the preview instead of quietly dropping it.\n */\nexport function JobStatusStrip({jobs, className}: JobStatusStripProps) {\n if (jobs.total === 0) return null;\n\n const visible = jobs.preview.slice(0, MAX_VISIBLE_JOBS);\n const hiddenCount = jobs.total - visible.length;\n const overflowStatus = worstHiddenStatus(jobs, visible);\n const summary = jobStatusSummary(jobs);\n\n return (\n <Tooltip>\n <TooltipTrigger asChild>\n <span\n role=\"img\"\n aria-label={summary}\n className={cn('inline-flex items-center gap-3', className)}\n >\n <span aria-hidden=\"true\" className=\"inline-flex items-center gap-3\">\n {visible.map((job) => (\n <WorkflowStatusIcon\n key={job.id}\n status={job.status}\n size={GLYPH_SIZE}\n // One ripple per running job, on every row, would turn a calm list into a\n // field of pulses. The run's own glyph already carries the live edge.\n ripple={false}\n tooltip={false}\n />\n ))}\n {hiddenCount > 0 && overflowStatus ? (\n <span className=\"inline-flex items-center gap-2\">\n <WorkflowStatusIcon\n status={overflowStatus}\n size={GLYPH_SIZE}\n ripple={false}\n tooltip={false}\n />\n <Code\n as=\"span\"\n variant=\"label\"\n className=\"tabular-nums text-foreground-neutral-muted\"\n >\n +{overflowCountLabel(hiddenCount)}\n </Code>\n </span>\n ) : null}\n </span>\n </span>\n </TooltipTrigger>\n <TooltipContent>\n <JobStatusStripTooltip jobs={jobs} summary={summary} />\n </TooltipContent>\n </Tooltip>\n );\n}\n\nfunction JobStatusStripTooltip({jobs, summary}: {jobs: WorkflowRunJobs; summary: string}) {\n // Naming the jobs that need attention is the whole point of hovering; succeeded jobs are\n // left to the counts so the tooltip stays a glance rather than a list. Only previewed jobs\n // can be named, but the count of those left over is read off the totals, so a failure past\n // the preview is still accounted for rather than silently missing.\n const named = jobs.preview\n .filter((job) => NOTABLE_STATUSES.includes(job.status))\n .slice(0, MAX_TOOLTIP_JOB_NAMES);\n const notableTotal = NOTABLE_STATUSES.reduce((total, status) => total + countOf(jobs, status), 0);\n const remaining = notableTotal - named.length;\n\n return (\n <span className=\"block max-w-[280px]\">\n <Text as=\"span\" size=\"xs\" className=\"block\">\n {summary}\n </Text>\n {named.map((job) => (\n <Code as=\"span\" variant=\"label\" key={job.id} className=\"mt-2 block truncate\">\n {getWorkflowStatusVisual(job.status).label.toLowerCase()} · {job.name ?? job.key}\n </Code>\n ))}\n {remaining > 0 ? (\n <Text as=\"span\" size=\"xs\" className=\"mt-2 block text-foreground-neutral-muted\">\n and {remaining} more\n </Text>\n ) : null}\n </span>\n );\n}\n\n/** \"12 jobs: 9 succeeded, 2 failed, 1 running\" — a count is not an accessible name on its own. */\nexport function jobStatusSummary(jobs: WorkflowRunJobs): string {\n const breakdown = [...jobs.statusCounts]\n .sort((left, right) => STATUS_SEVERITY[right.status] - STATUS_SEVERITY[left.status])\n .map(({status, count}) => `${count} ${getWorkflowStatusVisual(status).label.toLowerCase()}`)\n .join(', ');\n\n return `${jobs.total} ${jobs.total === 1 ? 'job' : 'jobs'}: ${breakdown}`;\n}\n\n/**\n * The worst status among the jobs the strip is not drawing, found by subtracting what it\n * draws from the run's totals rather than by inspecting jobs it was never sent.\n */\nfunction worstHiddenStatus(\n jobs: WorkflowRunJobs,\n visible: readonly WorkflowRunJobSummary[],\n): JobStatus | null {\n const hidden = new Map(jobs.statusCounts.map(({status, count}) => [status, count]));\n for (const job of visible) {\n const remaining = (hidden.get(job.status) ?? 0) - 1;\n if (remaining > 0) hidden.set(job.status, remaining);\n else hidden.delete(job.status);\n }\n\n let worst: JobStatus | null = null;\n for (const [status, count] of hidden) {\n if (count <= 0) continue;\n if (worst === null || STATUS_SEVERITY[status] > STATUS_SEVERITY[worst]) worst = status;\n }\n return worst;\n}\n\nexport function overflowCountLabel(hiddenCount: number): string {\n return hiddenCount <= MAX_EXACT_OVERFLOW_COUNT\n ? String(hiddenCount)\n : COMPACT_COUNT_FORMAT.format(hiddenCount);\n}\n\nfunction countOf(jobs: WorkflowRunJobs, status: JobStatus): number {\n return jobs.statusCounts.find((entry) => entry.status === status)?.count ?? 0;\n}\n"],"names":["Tooltip","TooltipContent","TooltipTrigger","Code","Text","cn","getWorkflowStatusVisual","WorkflowStatusIcon","MAX_VISIBLE_JOBS","GLYPH_SIZE","MAX_TOOLTIP_JOB_NAMES","MAX_EXACT_OVERFLOW_COUNT","COMPACT_COUNT_FORMAT","Intl","NumberFormat","notation","maximumFractionDigits","STATUS_SEVERITY","failed","running","pending","cancelled","skipped","succeeded","NOTABLE_STATUSES","JobStatusStrip","jobs","className","total","visible","preview","slice","hiddenCount","length","overflowStatus","worstHiddenStatus","summary","jobStatusSummary","asChild","span","role","aria-label","aria-hidden","map","job","status","size","ripple","tooltip","id","as","variant","overflowCountLabel","JobStatusStripTooltip","named","filter","includes","notableTotal","reduce","countOf","remaining","label","toLowerCase","name","key","breakdown","statusCounts","sort","left","right","count","join","hidden","Map","get","set","delete","worst","String","format","find","entry"],"mappings":"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"}
@@ -1,5 +1,29 @@
1
- import type { WorkflowRunListItem, WorkflowRunStatus } from '#core/workflow-run.js';
2
- import type { WorkflowRunListStatusFilter } from './types.js';
1
+ import { type WorkflowRunListItem, type WorkflowRunStatus } from '#core/workflow-run.js';
2
+ import type { WorkflowRunListStatus, WorkflowRunsSearch } from '#routes/inputs.js';
3
+ export type WorkflowRunFilterCriteria = Pick<WorkflowRunsSearch, 'search' | 'status' | 'branch' | 'actor' | 'event' | 'after' | 'before'>;
4
+ /** The dimensions whose options are read off the loaded runs rather than a fixed enum. */
5
+ export type WorkflowRunFacetName = 'branch' | 'actor' | 'event';
6
+ export type WorkflowRunFacets = Record<WorkflowRunFacetName, string[]>;
3
7
  export declare function runMatchesSearch(run: WorkflowRunListItem, query: string): boolean;
4
- export declare function runMatchesStatusFilter(status: WorkflowRunStatus, filter: WorkflowRunListStatusFilter): boolean;
8
+ export declare function runMatchesStatusFilter(status: WorkflowRunStatus, selected: readonly WorkflowRunListStatus[] | undefined): boolean;
9
+ /**
10
+ * Applies every active filter to one run.
11
+ *
12
+ * Filtering runs over the pages already fetched, which is what the rail did before this page
13
+ * existed. Making the API honor the same parameters across full history is ENG-512's job; the
14
+ * predicates here are written against the same dimension names so that swap stays local.
15
+ */
16
+ export declare function runMatchesFilters(run: WorkflowRunListItem, criteria: WorkflowRunFilterCriteria): boolean;
17
+ /**
18
+ * Filter options read off the loaded runs.
19
+ *
20
+ * Values the user already selected are folded in even when no loaded run carries them, so a
21
+ * shared link never renders as though its own filter were unset.
22
+ */
23
+ export declare function workflowRunFacets(runs: readonly WorkflowRunListItem[], criteria?: WorkflowRunFilterCriteria): WorkflowRunFacets;
24
+ /**
25
+ * The run's local calendar date, which is both what the date filters bound and what the row's
26
+ * relative time is derived from, so a filtered result never contradicts what a row shows.
27
+ */
28
+ export declare function runCalendarDate(run: Pick<WorkflowRunListItem, 'createdAt'>): string | null;
5
29
  //# sourceMappingURL=run-display.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"run-display.d.ts","sourceRoot":"","sources":["../../../src/components/workflow-run-list/run-display.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,mBAAmB,EAAE,iBAAiB,EAAC,MAAM,uBAAuB,CAAC;AAClF,OAAO,KAAK,EAAC,2BAA2B,EAAC,MAAM,YAAY,CAAC;AAE5D,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,mBAAmB,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAKjF;AAID,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,iBAAiB,EACzB,MAAM,EAAE,2BAA2B,GAClC,OAAO,CAMT"}
1
+ {"version":3,"file":"run-display.d.ts","sourceRoot":"","sources":["../../../src/components/workflow-run-list/run-display.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EAIvB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAC,qBAAqB,EAAE,kBAAkB,EAAC,MAAM,mBAAmB,CAAC;AAEjF,MAAM,MAAM,yBAAyB,GAAG,IAAI,CAC1C,kBAAkB,EAClB,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,CACxE,CAAC;AAEF,0FAA0F;AAC1F,MAAM,MAAM,oBAAoB,GAAG,QAAQ,GAAG,OAAO,GAAG,OAAO,CAAC;AAEhE,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,oBAAoB,EAAE,MAAM,EAAE,CAAC,CAAC;AAEvE,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,mBAAmB,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAkBjF;AAID,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,iBAAiB,EACzB,QAAQ,EAAE,SAAS,qBAAqB,EAAE,GAAG,SAAS,GACrD,OAAO,CAQT;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,mBAAmB,EACxB,QAAQ,EAAE,yBAAyB,GAClC,OAAO,CAOT;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,SAAS,mBAAmB,EAAE,EACpC,QAAQ,GAAE,yBAA8B,GACvC,iBAAiB,CASnB;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,IAAI,CAAC,mBAAmB,EAAE,WAAW,CAAC,GAAG,MAAM,GAAG,IAAI,CAM1F"}
@@ -1,19 +1,90 @@
1
+ import { workflowRunActor, workflowRunBranchLabel, workflowRunCommitLabel } from '#core/workflow-run.js';
1
2
  export function runMatchesSearch(run, query) {
2
3
  const needle = query.trim().toLowerCase();
3
4
  if (needle === '') return true;
4
- const haystack = `${run.id} ${run.name} ${run.status} ${run.triggerLabel}`.toLowerCase();
5
+ const haystack = [
6
+ run.id,
7
+ run.name,
8
+ run.workflowName,
9
+ run.status,
10
+ run.triggerLabel,
11
+ run.number?.toString(),
12
+ workflowRunBranchLabel(run),
13
+ workflowRunCommitLabel(run),
14
+ workflowRunActor(run)
15
+ ].filter(Boolean).join(' ').toLowerCase();
5
16
  return haystack.includes(needle);
6
17
  }
7
18
  const IN_PROGRESS_STATUSES = new Set([
8
19
  'pending',
9
20
  'running'
10
21
  ]);
11
- export function runMatchesStatusFilter(status, filter) {
12
- if (filter === 'all') return true;
13
- // "Running" reads as in-progress: it covers freshly-queued `pending` runs (including the
14
- // optimistic manual run inserted on fire) so they are not hidden the moment the filter is on.
15
- if (filter === 'running') return IN_PROGRESS_STATUSES.has(status);
16
- return status === filter;
22
+ export function runMatchesStatusFilter(status, selected) {
23
+ if (!selected || selected.length === 0) return true;
24
+ return selected.some((filter)=>{
25
+ // "Running" reads as in-progress: it covers freshly-queued `pending` runs (including the
26
+ // optimistic manual run inserted on fire) so they are not hidden the moment it is on.
27
+ if (filter === 'running') return IN_PROGRESS_STATUSES.has(status);
28
+ return status === filter;
29
+ });
30
+ }
31
+ /**
32
+ * Applies every active filter to one run.
33
+ *
34
+ * Filtering runs over the pages already fetched, which is what the rail did before this page
35
+ * existed. Making the API honor the same parameters across full history is ENG-512's job; the
36
+ * predicates here are written against the same dimension names so that swap stays local.
37
+ */ export function runMatchesFilters(run, criteria) {
38
+ if (!runMatchesStatusFilter(run.status, criteria.status)) return false;
39
+ if (!matchesFacet(criteria.branch, workflowRunBranchLabel(run))) return false;
40
+ if (!matchesFacet(criteria.actor, workflowRunActor(run))) return false;
41
+ if (!matchesFacet(criteria.event, run.triggerEvent)) return false;
42
+ if (!matchesDateBounds(run, criteria)) return false;
43
+ return runMatchesSearch(run, criteria.search ?? '');
44
+ }
45
+ /**
46
+ * Filter options read off the loaded runs.
47
+ *
48
+ * Values the user already selected are folded in even when no loaded run carries them, so a
49
+ * shared link never renders as though its own filter were unset.
50
+ */ export function workflowRunFacets(runs, criteria = {}) {
51
+ return {
52
+ branch: facetValues(runs.map(workflowRunBranchLabel), criteria.branch),
53
+ actor: facetValues(runs.map(workflowRunActor), criteria.actor),
54
+ event: facetValues(runs.map((run)=>run.triggerEvent), criteria.event)
55
+ };
56
+ }
57
+ /**
58
+ * The run's local calendar date, which is both what the date filters bound and what the row's
59
+ * relative time is derived from, so a filtered result never contradicts what a row shows.
60
+ */ export function runCalendarDate(run) {
61
+ const date = new Date(run.createdAt);
62
+ if (Number.isNaN(date.getTime())) return null;
63
+ const month = String(date.getMonth() + 1).padStart(2, '0');
64
+ const day = String(date.getDate()).padStart(2, '0');
65
+ return `${date.getFullYear()}-${month}-${day}`;
66
+ }
67
+ function matchesFacet(selected, value) {
68
+ if (!selected || selected.length === 0) return true;
69
+ return value !== null && selected.includes(value);
70
+ }
71
+ function matchesDateBounds(run, criteria) {
72
+ if (!criteria.after && !criteria.before) return true;
73
+ const date = runCalendarDate(run);
74
+ if (date === null) return false;
75
+ // Both bounds are inclusive, and `YYYY-MM-DD` sorts correctly as text.
76
+ if (criteria.after && date < criteria.after) return false;
77
+ if (criteria.before && date > criteria.before) return false;
78
+ return true;
79
+ }
80
+ function facetValues(values, selected) {
81
+ const present = values.filter((value)=>Boolean(value));
82
+ return [
83
+ ...new Set([
84
+ ...present,
85
+ ...selected ?? []
86
+ ])
87
+ ].sort((left, right)=>left.localeCompare(right));
17
88
  }
18
89
 
19
90
  //# sourceMappingURL=run-display.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/workflow-run-list/run-display.ts"],"sourcesContent":["import type {WorkflowRunListItem, WorkflowRunStatus} from '#core/workflow-run.js';\nimport type {WorkflowRunListStatusFilter} from './types.js';\n\nexport function runMatchesSearch(run: WorkflowRunListItem, query: string): boolean {\n const needle = query.trim().toLowerCase();\n if (needle === '') return true;\n const haystack = `${run.id} ${run.name} ${run.status} ${run.triggerLabel}`.toLowerCase();\n return haystack.includes(needle);\n}\n\nconst IN_PROGRESS_STATUSES: ReadonlySet<WorkflowRunStatus> = new Set(['pending', 'running']);\n\nexport function runMatchesStatusFilter(\n status: WorkflowRunStatus,\n filter: WorkflowRunListStatusFilter,\n): boolean {\n if (filter === 'all') return true;\n // \"Running\" reads as in-progress: it covers freshly-queued `pending` runs (including the\n // optimistic manual run inserted on fire) so they are not hidden the moment the filter is on.\n if (filter === 'running') return IN_PROGRESS_STATUSES.has(status);\n return status === filter;\n}\n"],"names":["runMatchesSearch","run","query","needle","trim","toLowerCase","haystack","id","name","status","triggerLabel","includes","IN_PROGRESS_STATUSES","Set","runMatchesStatusFilter","filter","has"],"mappings":"AAGA,OAAO,SAASA,iBAAiBC,GAAwB,EAAEC,KAAa;IACtE,MAAMC,SAASD,MAAME,IAAI,GAAGC,WAAW;IACvC,IAAIF,WAAW,IAAI,OAAO;IAC1B,MAAMG,WAAW,GAAGL,IAAIM,EAAE,CAAC,CAAC,EAAEN,IAAIO,IAAI,CAAC,CAAC,EAAEP,IAAIQ,MAAM,CAAC,CAAC,EAAER,IAAIS,YAAY,EAAE,CAACL,WAAW;IACtF,OAAOC,SAASK,QAAQ,CAACR;AAC3B;AAEA,MAAMS,uBAAuD,IAAIC,IAAI;IAAC;IAAW;CAAU;AAE3F,OAAO,SAASC,uBACdL,MAAyB,EACzBM,MAAmC;IAEnC,IAAIA,WAAW,OAAO,OAAO;IAC7B,yFAAyF;IACzF,8FAA8F;IAC9F,IAAIA,WAAW,WAAW,OAAOH,qBAAqBI,GAAG,CAACP;IAC1D,OAAOA,WAAWM;AACpB"}
1
+ {"version":3,"sources":["../../../src/components/workflow-run-list/run-display.ts"],"sourcesContent":["import {\n type WorkflowRunListItem,\n type WorkflowRunStatus,\n workflowRunActor,\n workflowRunBranchLabel,\n workflowRunCommitLabel,\n} from '#core/workflow-run.js';\nimport type {WorkflowRunListStatus, WorkflowRunsSearch} from '#routes/inputs.js';\n\nexport type WorkflowRunFilterCriteria = Pick<\n WorkflowRunsSearch,\n 'search' | 'status' | 'branch' | 'actor' | 'event' | 'after' | 'before'\n>;\n\n/** The dimensions whose options are read off the loaded runs rather than a fixed enum. */\nexport type WorkflowRunFacetName = 'branch' | 'actor' | 'event';\n\nexport type WorkflowRunFacets = Record<WorkflowRunFacetName, string[]>;\n\nexport function runMatchesSearch(run: WorkflowRunListItem, query: string): boolean {\n const needle = query.trim().toLowerCase();\n if (needle === '') return true;\n const haystack = [\n run.id,\n run.name,\n run.workflowName,\n run.status,\n run.triggerLabel,\n run.number?.toString(),\n workflowRunBranchLabel(run),\n workflowRunCommitLabel(run),\n workflowRunActor(run),\n ]\n .filter(Boolean)\n .join(' ')\n .toLowerCase();\n return haystack.includes(needle);\n}\n\nconst IN_PROGRESS_STATUSES: ReadonlySet<WorkflowRunStatus> = new Set(['pending', 'running']);\n\nexport function runMatchesStatusFilter(\n status: WorkflowRunStatus,\n selected: readonly WorkflowRunListStatus[] | undefined,\n): boolean {\n if (!selected || selected.length === 0) return true;\n return selected.some((filter) => {\n // \"Running\" reads as in-progress: it covers freshly-queued `pending` runs (including the\n // optimistic manual run inserted on fire) so they are not hidden the moment it is on.\n if (filter === 'running') return IN_PROGRESS_STATUSES.has(status);\n return status === filter;\n });\n}\n\n/**\n * Applies every active filter to one run.\n *\n * Filtering runs over the pages already fetched, which is what the rail did before this page\n * existed. Making the API honor the same parameters across full history is ENG-512's job; the\n * predicates here are written against the same dimension names so that swap stays local.\n */\nexport function runMatchesFilters(\n run: WorkflowRunListItem,\n criteria: WorkflowRunFilterCriteria,\n): boolean {\n if (!runMatchesStatusFilter(run.status, criteria.status)) return false;\n if (!matchesFacet(criteria.branch, workflowRunBranchLabel(run))) return false;\n if (!matchesFacet(criteria.actor, workflowRunActor(run))) return false;\n if (!matchesFacet(criteria.event, run.triggerEvent)) return false;\n if (!matchesDateBounds(run, criteria)) return false;\n return runMatchesSearch(run, criteria.search ?? '');\n}\n\n/**\n * Filter options read off the loaded runs.\n *\n * Values the user already selected are folded in even when no loaded run carries them, so a\n * shared link never renders as though its own filter were unset.\n */\nexport function workflowRunFacets(\n runs: readonly WorkflowRunListItem[],\n criteria: WorkflowRunFilterCriteria = {},\n): WorkflowRunFacets {\n return {\n branch: facetValues(runs.map(workflowRunBranchLabel), criteria.branch),\n actor: facetValues(runs.map(workflowRunActor), criteria.actor),\n event: facetValues(\n runs.map((run) => run.triggerEvent),\n criteria.event,\n ),\n };\n}\n\n/**\n * The run's local calendar date, which is both what the date filters bound and what the row's\n * relative time is derived from, so a filtered result never contradicts what a row shows.\n */\nexport function runCalendarDate(run: Pick<WorkflowRunListItem, 'createdAt'>): string | null {\n const date = new Date(run.createdAt);\n if (Number.isNaN(date.getTime())) return null;\n const month = String(date.getMonth() + 1).padStart(2, '0');\n const day = String(date.getDate()).padStart(2, '0');\n return `${date.getFullYear()}-${month}-${day}`;\n}\n\nfunction matchesFacet(selected: readonly string[] | undefined, value: string | null): boolean {\n if (!selected || selected.length === 0) return true;\n return value !== null && selected.includes(value);\n}\n\nfunction matchesDateBounds(run: WorkflowRunListItem, criteria: WorkflowRunFilterCriteria): boolean {\n if (!criteria.after && !criteria.before) return true;\n const date = runCalendarDate(run);\n if (date === null) return false;\n // Both bounds are inclusive, and `YYYY-MM-DD` sorts correctly as text.\n if (criteria.after && date < criteria.after) return false;\n if (criteria.before && date > criteria.before) return false;\n return true;\n}\n\nfunction facetValues(\n values: readonly (string | null)[],\n selected: readonly string[] | undefined,\n): string[] {\n const present = values.filter((value): value is string => Boolean(value));\n return [...new Set([...present, ...(selected ?? [])])].sort((left, right) =>\n left.localeCompare(right),\n );\n}\n"],"names":["workflowRunActor","workflowRunBranchLabel","workflowRunCommitLabel","runMatchesSearch","run","query","needle","trim","toLowerCase","haystack","id","name","workflowName","status","triggerLabel","number","toString","filter","Boolean","join","includes","IN_PROGRESS_STATUSES","Set","runMatchesStatusFilter","selected","length","some","has","runMatchesFilters","criteria","matchesFacet","branch","actor","event","triggerEvent","matchesDateBounds","search","workflowRunFacets","runs","facetValues","map","runCalendarDate","date","Date","createdAt","Number","isNaN","getTime","month","String","getMonth","padStart","day","getDate","getFullYear","value","after","before","values","present","sort","left","right","localeCompare"],"mappings":"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"}
@@ -1,33 +1,32 @@
1
1
  import type { QueryLoadErrorQuery } from '@shipfox/client-ui';
2
2
  import type { WorkflowRunListItem } from '#core/workflow-run.js';
3
- export type WorkflowRunListStatusFilter = 'all' | 'failed' | 'running';
4
- export interface WorkflowRunListProps {
5
- workspaceId: string;
6
- projectId: string;
7
- selectedWorkflowRunId?: string | undefined;
8
- className?: string | undefined;
9
- search?: string;
10
- statusFilter?: WorkflowRunListStatusFilter;
11
- onFiltersChange?: (filters: {
12
- search?: string;
13
- status?: WorkflowRunListStatusFilter;
14
- }) => void;
15
- }
3
+ import type { WorkflowRunFilterPatch, WorkflowRunsSearch } from '#routes/inputs.js';
16
4
  export type WorkflowRunListQuery = QueryLoadErrorQuery & {
17
5
  isPending: boolean;
18
6
  };
19
- export interface WorkflowRunListViewProps {
7
+ interface WorkflowRunListCommonProps {
8
+ workspaceSlug?: string | undefined;
9
+ projectSlug?: string | undefined;
10
+ className?: string | undefined;
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;
23
+ }
24
+ export interface WorkflowRunListProps extends WorkflowRunListCommonProps {
25
+ projectId: string;
26
+ }
27
+ export interface WorkflowRunListViewProps extends WorkflowRunListCommonProps {
20
28
  runs: WorkflowRunListItem[];
21
29
  query: WorkflowRunListQuery;
22
- workspaceId: string;
23
- projectId: string;
24
- selectedWorkflowRunId?: string | undefined;
25
- className?: string | undefined;
26
- search?: string;
27
- statusFilter?: WorkflowRunListStatusFilter;
28
- onFiltersChange?: (filters: {
29
- search?: string;
30
- status?: WorkflowRunListStatusFilter;
31
- }) => void;
32
30
  }
31
+ export {};
33
32
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/workflow-run-list/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,oBAAoB,CAAC;AAC5D,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,uBAAuB,CAAC;AAE/D,MAAM,MAAM,2BAA2B,GAAG,KAAK,GAAG,QAAQ,GAAG,SAAS,CAAC;AAEvE,MAAM,WAAW,oBAAoB;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,qBAAqB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3C,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,2BAA2B,CAAC;IAC3C,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,2BAA2B,CAAA;KAAC,KAAK,IAAI,CAAC;CAC9F;AAED,MAAM,MAAM,oBAAoB,GAAG,mBAAmB,GAAG;IAAC,SAAS,EAAE,OAAO,CAAA;CAAC,CAAC;AAE9E,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,mBAAmB,EAAE,CAAC;IAC5B,KAAK,EAAE,oBAAoB,CAAC;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,qBAAqB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3C,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,2BAA2B,CAAC;IAC3C,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,2BAA2B,CAAA;KAAC,KAAK,IAAI,CAAC;CAC9F"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/workflow-run-list/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,oBAAoB,CAAC;AAC5D,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,uBAAuB,CAAC;AAC/D,OAAO,KAAK,EAAC,sBAAsB,EAAE,kBAAkB,EAAC,MAAM,mBAAmB,CAAC;AAElF,MAAM,MAAM,oBAAoB,GAAG,mBAAmB,GAAG;IAAC,SAAS,EAAE,OAAO,CAAA;CAAC,CAAC;AAE9E,UAAU,0BAA0B;IAClC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B;;;;OAIG;IACH,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,sBAAsB,KAAK,IAAI,CAAC;IAC1D,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;IAC5B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;CACzB;AAED,MAAM,WAAW,oBAAqB,SAAQ,0BAA0B;IACtE,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,wBAAyB,SAAQ,0BAA0B;IAC1E,IAAI,EAAE,mBAAmB,EAAE,CAAC;IAC5B,KAAK,EAAE,oBAAoB,CAAC;CAC7B"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/workflow-run-list/types.ts"],"sourcesContent":["import type {QueryLoadErrorQuery} from '@shipfox/client-ui';\nimport type {WorkflowRunListItem} from '#core/workflow-run.js';\n\nexport type WorkflowRunListStatusFilter = 'all' | 'failed' | 'running';\n\nexport interface WorkflowRunListProps {\n workspaceId: string;\n projectId: string;\n selectedWorkflowRunId?: string | undefined;\n className?: string | undefined;\n search?: string;\n statusFilter?: WorkflowRunListStatusFilter;\n onFiltersChange?: (filters: {search?: string; status?: WorkflowRunListStatusFilter}) => void;\n}\n\nexport type WorkflowRunListQuery = QueryLoadErrorQuery & {isPending: boolean};\n\nexport interface WorkflowRunListViewProps {\n runs: WorkflowRunListItem[];\n query: WorkflowRunListQuery;\n workspaceId: string;\n projectId: string;\n selectedWorkflowRunId?: string | undefined;\n className?: string | undefined;\n search?: string;\n statusFilter?: WorkflowRunListStatusFilter;\n onFiltersChange?: (filters: {search?: string; status?: WorkflowRunListStatusFilter}) => void;\n}\n"],"names":[],"mappings":"AAiBA,WAUC"}
1
+ {"version":3,"sources":["../../../src/components/workflow-run-list/types.ts"],"sourcesContent":["import type {QueryLoadErrorQuery} from '@shipfox/client-ui';\nimport type {WorkflowRunListItem} from '#core/workflow-run.js';\nimport type {WorkflowRunFilterPatch, WorkflowRunsSearch} from '#routes/inputs.js';\n\nexport type WorkflowRunListQuery = QueryLoadErrorQuery & {isPending: boolean};\n\ninterface WorkflowRunListCommonProps {\n workspaceSlug?: string | undefined;\n projectSlug?: string | undefined;\n className?: string | undefined;\n /**\n * The parsed URL search. Pair with `onFiltersChange` to keep it in the URL; omit the\n * handler and the view holds filter state itself, which is what stories and isolated tests\n * mount against.\n */\n search?: WorkflowRunsSearch;\n onFiltersChange?: (patch: WorkflowRunFilterPatch) => void;\n onClearFilters?: () => void;\n hasNextPage?: boolean;\n isFetchingNextPage?: boolean;\n isFetchNextPageError?: boolean;\n onLoadMore?: () => void;\n}\n\nexport interface WorkflowRunListProps extends WorkflowRunListCommonProps {\n projectId: string;\n}\n\nexport interface WorkflowRunListViewProps extends WorkflowRunListCommonProps {\n runs: WorkflowRunListItem[];\n query: WorkflowRunListQuery;\n}\n"],"names":[],"mappings":"AA4BA,WAGC"}
@@ -0,0 +1,22 @@
1
+ export interface WorkflowRunFilterOption {
2
+ value: string;
3
+ label: string;
4
+ }
5
+ export interface WorkflowRunFilterMenuProps {
6
+ label: string;
7
+ options: readonly WorkflowRunFilterOption[];
8
+ selected: readonly string[];
9
+ onChange: (next: string[]) => void;
10
+ /** Shown in place of the option list when nothing is loaded to filter by. */
11
+ emptyMessage: string;
12
+ className?: string | undefined;
13
+ }
14
+ /**
15
+ * One multi-select filter.
16
+ *
17
+ * The trigger states its own value rather than only a count, because a toolbar of five
18
+ * buttons that all read "Branch" forces the user to open each one to find out what is on.
19
+ * Checked items keep the menu open: choosing two branches should not cost two round trips.
20
+ */
21
+ export declare function WorkflowRunFilterMenu({ label, options, selected, onChange, emptyMessage, className, }: WorkflowRunFilterMenuProps): import("react").JSX.Element;
22
+ //# sourceMappingURL=workflow-run-filter-menu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflow-run-filter-menu.d.ts","sourceRoot":"","sources":["../../../src/components/workflow-run-list/workflow-run-filter-menu.tsx"],"names":[],"mappings":"AAUA,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,0BAA0B;IACzC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,SAAS,uBAAuB,EAAE,CAAC;IAC5C,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACnC,6EAA6E;IAC7E,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,EACpC,KAAK,EACL,OAAO,EACP,QAAQ,EACR,QAAQ,EACR,YAAY,EACZ,SAAS,GACV,EAAE,0BAA0B,+BA8C5B"}
@@ -0,0 +1,68 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Button } from '@shipfox/react-ui/button';
3
+ import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuTrigger } from '@shipfox/react-ui/dropdown-menu';
4
+ import { Text } from '@shipfox/react-ui/typography';
5
+ import { cn } from '@shipfox/react-ui/utils';
6
+ /**
7
+ * One multi-select filter.
8
+ *
9
+ * The trigger states its own value rather than only a count, because a toolbar of five
10
+ * buttons that all read "Branch" forces the user to open each one to find out what is on.
11
+ * Checked items keep the menu open: choosing two branches should not cost two round trips.
12
+ */ export function WorkflowRunFilterMenu({ label, options, selected, onChange, emptyMessage, className }) {
13
+ const active = selected.length > 0;
14
+ const triggerText = triggerLabel(label, options, selected);
15
+ function toggle(value) {
16
+ onChange(selected.includes(value) ? selected.filter((entry)=>entry !== value) : [
17
+ ...selected,
18
+ value
19
+ ]);
20
+ }
21
+ return /*#__PURE__*/ _jsxs(DropdownMenu, {
22
+ children: [
23
+ /*#__PURE__*/ _jsx(DropdownMenuTrigger, {
24
+ asChild: true,
25
+ children: /*#__PURE__*/ _jsx(Button, {
26
+ type: "button",
27
+ variant: "secondary",
28
+ size: "sm",
29
+ iconRight: "arrowDownSLine",
30
+ // Mirrors the visible label so the selected value is not sighted-only; an aria-label
31
+ // replaces the button's contents as its accessible name rather than adding to them.
32
+ "aria-label": `${triggerText} filter`,
33
+ className: cn('max-w-[200px]', !active && 'text-foreground-neutral-subtle', className),
34
+ children: /*#__PURE__*/ _jsx("span", {
35
+ className: "min-w-0 truncate",
36
+ children: triggerText
37
+ })
38
+ })
39
+ }),
40
+ /*#__PURE__*/ _jsx(DropdownMenuContent, {
41
+ align: "start",
42
+ className: "max-h-[320px] w-[240px] overflow-y-auto",
43
+ children: options.length === 0 ? /*#__PURE__*/ _jsx(Text, {
44
+ as: "p",
45
+ size: "xs",
46
+ className: "px-8 py-6 text-foreground-neutral-muted",
47
+ children: emptyMessage
48
+ }) : options.map((option)=>/*#__PURE__*/ _jsx(DropdownMenuCheckboxItem, {
49
+ checked: selected.includes(option.value),
50
+ closeOnSelect: false,
51
+ onSelect: ()=>toggle(option.value),
52
+ children: option.label
53
+ }, option.value))
54
+ })
55
+ ]
56
+ });
57
+ }
58
+ function triggerLabel(label, options, selected) {
59
+ if (selected.length === 0) return label;
60
+ if (selected.length === 1) {
61
+ const [value] = selected;
62
+ const option = options.find((entry)=>entry.value === value);
63
+ return `${label}: ${option?.label ?? value}`;
64
+ }
65
+ return `${label} · ${selected.length}`;
66
+ }
67
+
68
+ //# sourceMappingURL=workflow-run-filter-menu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/workflow-run-list/workflow-run-filter-menu.tsx"],"sourcesContent":["import {Button} from '@shipfox/react-ui/button';\nimport {\n DropdownMenu,\n DropdownMenuCheckboxItem,\n DropdownMenuContent,\n DropdownMenuTrigger,\n} from '@shipfox/react-ui/dropdown-menu';\nimport {Text} from '@shipfox/react-ui/typography';\nimport {cn} from '@shipfox/react-ui/utils';\n\nexport interface WorkflowRunFilterOption {\n value: string;\n label: string;\n}\n\nexport interface WorkflowRunFilterMenuProps {\n label: string;\n options: readonly WorkflowRunFilterOption[];\n selected: readonly string[];\n onChange: (next: string[]) => void;\n /** Shown in place of the option list when nothing is loaded to filter by. */\n emptyMessage: string;\n className?: string | undefined;\n}\n\n/**\n * One multi-select filter.\n *\n * The trigger states its own value rather than only a count, because a toolbar of five\n * buttons that all read \"Branch\" forces the user to open each one to find out what is on.\n * Checked items keep the menu open: choosing two branches should not cost two round trips.\n */\nexport function WorkflowRunFilterMenu({\n label,\n options,\n selected,\n onChange,\n emptyMessage,\n className,\n}: WorkflowRunFilterMenuProps) {\n const active = selected.length > 0;\n const triggerText = triggerLabel(label, options, selected);\n\n function toggle(value: string) {\n onChange(\n selected.includes(value) ? selected.filter((entry) => entry !== value) : [...selected, value],\n );\n }\n\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n type=\"button\"\n variant=\"secondary\"\n size=\"sm\"\n iconRight=\"arrowDownSLine\"\n // Mirrors the visible label so the selected value is not sighted-only; an aria-label\n // replaces the button's contents as its accessible name rather than adding to them.\n aria-label={`${triggerText} filter`}\n className={cn('max-w-[200px]', !active && 'text-foreground-neutral-subtle', className)}\n >\n <span className=\"min-w-0 truncate\">{triggerText}</span>\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" className=\"max-h-[320px] w-[240px] overflow-y-auto\">\n {options.length === 0 ? (\n <Text as=\"p\" size=\"xs\" className=\"px-8 py-6 text-foreground-neutral-muted\">\n {emptyMessage}\n </Text>\n ) : (\n options.map((option) => (\n <DropdownMenuCheckboxItem\n key={option.value}\n checked={selected.includes(option.value)}\n closeOnSelect={false}\n onSelect={() => toggle(option.value)}\n >\n {option.label}\n </DropdownMenuCheckboxItem>\n ))\n )}\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n\nfunction triggerLabel(\n label: string,\n options: readonly WorkflowRunFilterOption[],\n selected: readonly string[],\n): string {\n if (selected.length === 0) return label;\n if (selected.length === 1) {\n const [value] = selected;\n const option = options.find((entry) => entry.value === value);\n return `${label}: ${option?.label ?? value}`;\n }\n return `${label} · ${selected.length}`;\n}\n"],"names":["Button","DropdownMenu","DropdownMenuCheckboxItem","DropdownMenuContent","DropdownMenuTrigger","Text","cn","WorkflowRunFilterMenu","label","options","selected","onChange","emptyMessage","className","active","length","triggerText","triggerLabel","toggle","value","includes","filter","entry","asChild","type","variant","size","iconRight","aria-label","span","align","as","map","option","checked","closeOnSelect","onSelect","find"],"mappings":";AAAA,SAAQA,MAAM,QAAO,2BAA2B;AAChD,SACEC,YAAY,EACZC,wBAAwB,EACxBC,mBAAmB,EACnBC,mBAAmB,QACd,kCAAkC;AACzC,SAAQC,IAAI,QAAO,+BAA+B;AAClD,SAAQC,EAAE,QAAO,0BAA0B;AAiB3C;;;;;;CAMC,GACD,OAAO,SAASC,sBAAsB,EACpCC,KAAK,EACLC,OAAO,EACPC,QAAQ,EACRC,QAAQ,EACRC,YAAY,EACZC,SAAS,EACkB;IAC3B,MAAMC,SAASJ,SAASK,MAAM,GAAG;IACjC,MAAMC,cAAcC,aAAaT,OAAOC,SAASC;IAEjD,SAASQ,OAAOC,KAAa;QAC3BR,SACED,SAASU,QAAQ,CAACD,SAAST,SAASW,MAAM,CAAC,CAACC,QAAUA,UAAUH,SAAS;eAAIT;YAAUS;SAAM;IAEjG;IAEA,qBACE,MAAClB;;0BACC,KAACG;gBAAoBmB,OAAO;0BAC1B,cAAA,KAACvB;oBACCwB,MAAK;oBACLC,SAAQ;oBACRC,MAAK;oBACLC,WAAU;oBACV,qFAAqF;oBACrF,oFAAoF;oBACpFC,cAAY,GAAGZ,YAAY,OAAO,CAAC;oBACnCH,WAAWP,GAAG,iBAAiB,CAACQ,UAAU,kCAAkCD;8BAE5E,cAAA,KAACgB;wBAAKhB,WAAU;kCAAoBG;;;;0BAGxC,KAACb;gBAAoB2B,OAAM;gBAAQjB,WAAU;0BAC1CJ,QAAQM,MAAM,KAAK,kBAClB,KAACV;oBAAK0B,IAAG;oBAAIL,MAAK;oBAAKb,WAAU;8BAC9BD;qBAGHH,QAAQuB,GAAG,CAAC,CAACC,uBACX,KAAC/B;wBAECgC,SAASxB,SAASU,QAAQ,CAACa,OAAOd,KAAK;wBACvCgB,eAAe;wBACfC,UAAU,IAAMlB,OAAOe,OAAOd,KAAK;kCAElCc,OAAOzB,KAAK;uBALRyB,OAAOd,KAAK;;;;AAY/B;AAEA,SAASF,aACPT,KAAa,EACbC,OAA2C,EAC3CC,QAA2B;IAE3B,IAAIA,SAASK,MAAM,KAAK,GAAG,OAAOP;IAClC,IAAIE,SAASK,MAAM,KAAK,GAAG;QACzB,MAAM,CAACI,MAAM,GAAGT;QAChB,MAAMuB,SAASxB,QAAQ4B,IAAI,CAAC,CAACf,QAAUA,MAAMH,KAAK,KAAKA;QACvD,OAAO,GAAGX,MAAM,EAAE,EAAEyB,QAAQzB,SAASW,OAAO;IAC9C;IACA,OAAO,GAAGX,MAAM,GAAG,EAAEE,SAASK,MAAM,EAAE;AACxC"}
@@ -0,0 +1,18 @@
1
+ import { type WorkflowRunFilterPatch, type WorkflowRunsSearch } from '#routes/inputs.js';
2
+ import type { WorkflowRunFacets } from './run-display.js';
3
+ export interface WorkflowRunFiltersProps {
4
+ search: WorkflowRunsSearch;
5
+ facets: WorkflowRunFacets;
6
+ onChange: (patch: WorkflowRunFilterPatch) => void;
7
+ onClear: () => void;
8
+ hasActiveFilters: boolean;
9
+ }
10
+ /**
11
+ * The run list filter row.
12
+ *
13
+ * Inline from `md` up, and behind a sheet below it, where a five-control toolbar would eat
14
+ * the viewport the list is supposed to fill. Both layouts render the same controls, so the
15
+ * narrow surface is the full filter set rather than a reduced one.
16
+ */
17
+ export declare function WorkflowRunFilters({ search, facets, onChange, onClear, hasActiveFilters, }: WorkflowRunFiltersProps): import("react").JSX.Element;
18
+ //# sourceMappingURL=workflow-run-filters.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflow-run-filters.d.ts","sourceRoot":"","sources":["../../../src/components/workflow-run-list/workflow-run-filters.tsx"],"names":[],"mappings":"AAgBA,OAAO,EAEL,KAAK,sBAAsB,EAE3B,KAAK,kBAAkB,EACxB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,kBAAkB,CAAC;AAQxD,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,kBAAkB,CAAC;IAC3B,MAAM,EAAE,iBAAiB,CAAC;IAC1B,QAAQ,EAAE,CAAC,KAAK,EAAE,sBAAsB,KAAK,IAAI,CAAC;IAClD,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,gBAAgB,EAAE,OAAO,CAAC;CAC3B;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,MAAM,EACN,MAAM,EACN,QAAQ,EACR,OAAO,EACP,gBAAgB,GACjB,EAAE,uBAAuB,+BA8DzB"}