@shipfox/client-workflows 12.0.2 → 14.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 (325) hide show
  1. package/.storybook/main.ts +16 -1
  2. package/.storybook/preview.tsx +3 -3
  3. package/.storybook/workspace-source.ts +31 -0
  4. package/.turbo/turbo-build.log +1 -1
  5. package/CHANGELOG.md +79 -0
  6. package/dist/components/job-graph/job-duration-format.d.ts +2 -2
  7. package/dist/components/job-graph/job-duration-format.d.ts.map +1 -1
  8. package/dist/components/job-graph/job-duration-format.js +4 -2
  9. package/dist/components/job-graph/job-duration-format.js.map +1 -1
  10. package/dist/components/job-graph/job-duration-label.js +1 -1
  11. package/dist/components/job-graph/job-duration-label.js.map +1 -1
  12. package/dist/components/job-graph/job-graph-content.d.ts +2 -1
  13. package/dist/components/job-graph/job-graph-content.d.ts.map +1 -1
  14. package/dist/components/job-graph/job-graph-content.js +3 -3
  15. package/dist/components/job-graph/job-graph-content.js.map +1 -1
  16. package/dist/components/job-graph/job-graph-view.d.ts +2 -1
  17. package/dist/components/job-graph/job-graph-view.d.ts.map +1 -1
  18. package/dist/components/job-graph/job-graph-view.js +2 -2
  19. package/dist/components/job-graph/job-graph-view.js.map +1 -1
  20. package/dist/components/job-graph/job-node.d.ts +1 -1
  21. package/dist/components/job-graph/job-node.d.ts.map +1 -1
  22. package/dist/components/job-graph/job-node.js +10 -9
  23. package/dist/components/job-graph/job-node.js.map +1 -1
  24. package/dist/components/job-graph/types.d.ts +2 -1
  25. package/dist/components/job-graph/types.d.ts.map +1 -1
  26. package/dist/components/job-graph/types.js.map +1 -1
  27. package/dist/components/step-list/step-list.d.ts +2 -2
  28. package/dist/components/step-list/step-list.d.ts.map +1 -1
  29. package/dist/components/step-list/step-list.js.map +1 -1
  30. package/dist/components/workflow-run-duration-label.js +1 -1
  31. package/dist/components/workflow-run-duration-label.js.map +1 -1
  32. package/dist/components/workflow-run-list/job-status-strip.d.ts +21 -0
  33. package/dist/components/workflow-run-list/job-status-strip.d.ts.map +1 -0
  34. package/dist/components/workflow-run-list/job-status-strip.js +185 -0
  35. package/dist/components/workflow-run-list/job-status-strip.js.map +1 -0
  36. package/dist/components/workflow-run-list/run-display.d.ts +27 -3
  37. package/dist/components/workflow-run-list/run-display.d.ts.map +1 -1
  38. package/dist/components/workflow-run-list/run-display.js +78 -7
  39. package/dist/components/workflow-run-list/run-display.js.map +1 -1
  40. package/dist/components/workflow-run-list/types.d.ts +23 -24
  41. package/dist/components/workflow-run-list/types.d.ts.map +1 -1
  42. package/dist/components/workflow-run-list/types.js.map +1 -1
  43. package/dist/components/workflow-run-list/workflow-run-filter-menu.d.ts +22 -0
  44. package/dist/components/workflow-run-list/workflow-run-filter-menu.d.ts.map +1 -0
  45. package/dist/components/workflow-run-list/workflow-run-filter-menu.js +68 -0
  46. package/dist/components/workflow-run-list/workflow-run-filter-menu.js.map +1 -0
  47. package/dist/components/workflow-run-list/workflow-run-filters.d.ts +18 -0
  48. package/dist/components/workflow-run-list/workflow-run-filters.d.ts.map +1 -0
  49. package/dist/components/workflow-run-list/workflow-run-filters.js +236 -0
  50. package/dist/components/workflow-run-list/workflow-run-filters.js.map +1 -0
  51. package/dist/components/workflow-run-list/workflow-run-list-content.d.ts +8 -4
  52. package/dist/components/workflow-run-list/workflow-run-list-content.d.ts.map +1 -1
  53. package/dist/components/workflow-run-list/workflow-run-list-content.js +40 -12
  54. package/dist/components/workflow-run-list/workflow-run-list-content.js.map +1 -1
  55. package/dist/components/workflow-run-list/workflow-run-list-states.d.ts +29 -2
  56. package/dist/components/workflow-run-list/workflow-run-list-states.d.ts.map +1 -1
  57. package/dist/components/workflow-run-list/workflow-run-list-states.js +103 -44
  58. package/dist/components/workflow-run-list/workflow-run-list-states.js.map +1 -1
  59. package/dist/components/workflow-run-list/workflow-run-list-view.d.ts +1 -1
  60. package/dist/components/workflow-run-list/workflow-run-list-view.d.ts.map +1 -1
  61. package/dist/components/workflow-run-list/workflow-run-list-view.js +60 -43
  62. package/dist/components/workflow-run-list/workflow-run-list-view.js.map +1 -1
  63. package/dist/components/workflow-run-list/workflow-run-list.d.ts +1 -1
  64. package/dist/components/workflow-run-list/workflow-run-list.d.ts.map +1 -1
  65. package/dist/components/workflow-run-list/workflow-run-list.js +20 -8
  66. package/dist/components/workflow-run-list/workflow-run-list.js.map +1 -1
  67. package/dist/components/workflow-run-list/workflow-run-row.d.ts +19 -9
  68. package/dist/components/workflow-run-list/workflow-run-row.d.ts.map +1 -1
  69. package/dist/components/workflow-run-list/workflow-run-row.js +179 -112
  70. package/dist/components/workflow-run-list/workflow-run-row.js.map +1 -1
  71. package/dist/components/workflow-run-number-label.d.ts +15 -0
  72. package/dist/components/workflow-run-number-label.d.ts.map +1 -0
  73. package/dist/components/workflow-run-number-label.js +41 -0
  74. package/dist/components/workflow-run-number-label.js.map +1 -0
  75. package/dist/components/workflow-run-summary/workflow-run-attempt-switcher.d.ts +3 -3
  76. package/dist/components/workflow-run-summary/workflow-run-attempt-switcher.d.ts.map +1 -1
  77. package/dist/components/workflow-run-summary/workflow-run-attempt-switcher.js +10 -9
  78. package/dist/components/workflow-run-summary/workflow-run-attempt-switcher.js.map +1 -1
  79. package/dist/components/workflow-run-summary/workflow-run-summary.d.ts +4 -9
  80. package/dist/components/workflow-run-summary/workflow-run-summary.d.ts.map +1 -1
  81. package/dist/components/workflow-run-summary/workflow-run-summary.js +107 -74
  82. package/dist/components/workflow-run-summary/workflow-run-summary.js.map +1 -1
  83. package/dist/components/workflow-run-tabs/index.d.ts +6 -0
  84. package/dist/components/workflow-run-tabs/index.d.ts.map +1 -0
  85. package/dist/components/workflow-run-tabs/index.js +7 -0
  86. package/dist/components/workflow-run-tabs/index.js.map +1 -0
  87. package/dist/components/workflow-run-tabs/run-annotation-summary-line.d.ts +11 -0
  88. package/dist/components/workflow-run-tabs/run-annotation-summary-line.d.ts.map +1 -0
  89. package/dist/components/workflow-run-tabs/run-annotation-summary-line.js +71 -0
  90. package/dist/components/workflow-run-tabs/run-annotation-summary-line.js.map +1 -0
  91. package/dist/components/workflow-run-tabs/run-annotations-empty.d.ts +2 -0
  92. package/dist/components/workflow-run-tabs/run-annotations-empty.d.ts.map +1 -0
  93. package/dist/components/workflow-run-tabs/run-annotations-empty.js +11 -0
  94. package/dist/components/workflow-run-tabs/run-annotations-empty.js.map +1 -0
  95. package/dist/components/workflow-run-tabs/run-jobs-list.d.ts +18 -0
  96. package/dist/components/workflow-run-tabs/run-jobs-list.d.ts.map +1 -0
  97. package/dist/components/workflow-run-tabs/run-jobs-list.js +107 -0
  98. package/dist/components/workflow-run-tabs/run-jobs-list.js.map +1 -0
  99. package/dist/components/workflow-run-tabs/run-tab-count.d.ts +6 -0
  100. package/dist/components/workflow-run-tabs/run-tab-count.d.ts.map +1 -0
  101. package/dist/components/workflow-run-tabs/run-tab-count.js +27 -0
  102. package/dist/components/workflow-run-tabs/run-tab-count.js.map +1 -0
  103. package/dist/components/workflow-run-tabs/run-tab-strip.d.ts +8 -0
  104. package/dist/components/workflow-run-tabs/run-tab-strip.d.ts.map +1 -0
  105. package/dist/components/workflow-run-tabs/run-tab-strip.js +82 -0
  106. package/dist/components/workflow-run-tabs/run-tab-strip.js.map +1 -0
  107. package/dist/components/workflow-run-view/agent-config-failure-callout.d.ts +2 -2
  108. package/dist/components/workflow-run-view/agent-config-failure-callout.d.ts.map +1 -1
  109. package/dist/components/workflow-run-view/agent-config-failure-callout.js +4 -4
  110. package/dist/components/workflow-run-view/agent-config-failure-callout.js.map +1 -1
  111. package/dist/components/workflow-run-view/job-card.d.ts +2 -2
  112. package/dist/components/workflow-run-view/job-card.d.ts.map +1 -1
  113. package/dist/components/workflow-run-view/job-card.js +51 -32
  114. package/dist/components/workflow-run-view/job-card.js.map +1 -1
  115. package/dist/components/workflow-run-view/job-execution-switcher.d.ts +1 -1
  116. package/dist/components/workflow-run-view/job-execution-switcher.d.ts.map +1 -1
  117. package/dist/components/workflow-run-view/job-execution-switcher.js +20 -8
  118. package/dist/components/workflow-run-view/job-execution-switcher.js.map +1 -1
  119. package/dist/components/workflow-run-view/workflow-run-selection.d.ts.map +1 -1
  120. package/dist/components/workflow-run-view/workflow-run-selection.js +40 -1
  121. package/dist/components/workflow-run-view/workflow-run-selection.js.map +1 -1
  122. package/dist/components/workflow-run-view/workflow-run-states.d.ts +1 -0
  123. package/dist/components/workflow-run-view/workflow-run-states.d.ts.map +1 -1
  124. package/dist/components/workflow-run-view/workflow-run-states.js +10 -1
  125. package/dist/components/workflow-run-view/workflow-run-states.js.map +1 -1
  126. package/dist/components/workflow-run-view/workflow-run-view.d.ts +10 -5
  127. package/dist/components/workflow-run-view/workflow-run-view.d.ts.map +1 -1
  128. package/dist/components/workflow-run-view/workflow-run-view.js +136 -85
  129. package/dist/components/workflow-run-view/workflow-run-view.js.map +1 -1
  130. package/dist/components/workflow-source-panel/index.d.ts +1 -0
  131. package/dist/components/workflow-source-panel/index.d.ts.map +1 -1
  132. package/dist/components/workflow-source-panel/index.js +1 -0
  133. package/dist/components/workflow-source-panel/index.js.map +1 -1
  134. package/dist/components/workflow-source-panel/workflow-source-content.d.ts +12 -0
  135. package/dist/components/workflow-source-panel/workflow-source-content.d.ts.map +1 -0
  136. package/dist/components/workflow-source-panel/workflow-source-content.js +53 -0
  137. package/dist/components/workflow-source-panel/workflow-source-content.js.map +1 -0
  138. package/dist/components/workflow-source-panel/workflow-source-panel.d.ts +1 -1
  139. package/dist/components/workflow-source-panel/workflow-source-panel.d.ts.map +1 -1
  140. package/dist/components/workflow-source-panel/workflow-source-panel.js +7 -54
  141. package/dist/components/workflow-source-panel/workflow-source-panel.js.map +1 -1
  142. package/dist/components/workflow-status/status-visuals.d.ts +4 -4
  143. package/dist/components/workflow-status/status-visuals.d.ts.map +1 -1
  144. package/dist/components/workflow-status/status-visuals.js +7 -0
  145. package/dist/components/workflow-status/status-visuals.js.map +1 -1
  146. package/dist/components/workflow-status/workflow-status-icon.d.ts +3 -4
  147. package/dist/components/workflow-status/workflow-status-icon.d.ts.map +1 -1
  148. package/dist/components/workflow-status/workflow-status-icon.js +6 -4
  149. package/dist/components/workflow-status/workflow-status-icon.js.map +1 -1
  150. package/dist/core/entities/job-execution.d.ts +10 -0
  151. package/dist/core/entities/job-execution.d.ts.map +1 -1
  152. package/dist/core/entities/job-execution.js +11 -0
  153. package/dist/core/entities/job-execution.js.map +1 -1
  154. package/dist/core/entities/job.d.ts +4 -2
  155. package/dist/core/entities/job.d.ts.map +1 -1
  156. package/dist/core/entities/job.js +8 -1
  157. package/dist/core/entities/job.js.map +1 -1
  158. package/dist/core/entities/step.d.ts +1 -1
  159. package/dist/core/entities/step.d.ts.map +1 -1
  160. package/dist/core/entities/step.js +2 -0
  161. package/dist/core/entities/step.js.map +1 -1
  162. package/dist/core/entities/workflow-run.d.ts +52 -4
  163. package/dist/core/entities/workflow-run.d.ts.map +1 -1
  164. package/dist/core/entities/workflow-run.js +22 -3
  165. package/dist/core/entities/workflow-run.js.map +1 -1
  166. package/dist/core/workflow-run-tabs.d.ts +9 -0
  167. package/dist/core/workflow-run-tabs.d.ts.map +1 -0
  168. package/dist/core/workflow-run-tabs.js +3 -0
  169. package/dist/core/workflow-run-tabs.js.map +1 -0
  170. package/dist/core/workflow-run.d.ts +6 -6
  171. package/dist/core/workflow-run.d.ts.map +1 -1
  172. package/dist/core/workflow-run.js +3 -3
  173. package/dist/core/workflow-run.js.map +1 -1
  174. package/dist/feature.d.ts +7 -7
  175. package/dist/feature.js +5 -5
  176. package/dist/feature.js.map +1 -1
  177. package/dist/hooks/api/workflow-run-mapper.d.ts +4 -3
  178. package/dist/hooks/api/workflow-run-mapper.d.ts.map +1 -1
  179. package/dist/hooks/api/workflow-run-mapper.js +31 -3
  180. package/dist/hooks/api/workflow-run-mapper.js.map +1 -1
  181. package/dist/hooks/api/workflow-runs.d.ts +4 -4
  182. package/dist/hooks/api/workflow-runs.d.ts.map +1 -1
  183. package/dist/hooks/api/workflow-runs.js +12 -4
  184. package/dist/hooks/api/workflow-runs.js.map +1 -1
  185. package/dist/index.d.ts +4 -3
  186. package/dist/index.d.ts.map +1 -1
  187. package/dist/index.js +3 -2
  188. package/dist/index.js.map +1 -1
  189. package/dist/pages/project-workflows-page.js +46 -1
  190. package/dist/pages/project-workflows-page.js.map +1 -1
  191. package/dist/pages/workflow-run-detail-page.d.ts +11 -0
  192. package/dist/pages/workflow-run-detail-page.d.ts.map +1 -0
  193. package/dist/pages/workflow-run-detail-page.js +52 -0
  194. package/dist/pages/workflow-run-detail-page.js.map +1 -0
  195. package/dist/pages/workflow-run-list-page.d.ts +10 -0
  196. package/dist/pages/workflow-run-list-page.d.ts.map +1 -0
  197. package/dist/pages/workflow-run-list-page.js +43 -0
  198. package/dist/pages/workflow-run-list-page.js.map +1 -0
  199. package/dist/routes/inputs.d.ts +94 -5
  200. package/dist/routes/inputs.d.ts.map +1 -1
  201. package/dist/routes/inputs.js +199 -13
  202. package/dist/routes/inputs.js.map +1 -1
  203. package/dist/routes/project-route.d.ts +6 -0
  204. package/dist/routes/project-route.d.ts.map +1 -0
  205. package/dist/routes/project-route.js +25 -0
  206. package/dist/routes/project-route.js.map +1 -0
  207. package/dist/routes/run-detail.d.ts.map +1 -1
  208. package/dist/routes/run-detail.js +12 -7
  209. package/dist/routes/run-detail.js.map +1 -1
  210. package/dist/routes/runs.d.ts.map +1 -1
  211. package/dist/routes/runs.js +11 -6
  212. package/dist/routes/runs.js.map +1 -1
  213. package/dist/routes/workflows.d.ts.map +1 -1
  214. package/dist/routes/workflows.js +6 -5
  215. package/dist/routes/workflows.js.map +1 -1
  216. package/dist/tsconfig.test.tsbuildinfo +1 -1
  217. package/package.json +9 -9
  218. package/src/components/job-graph/job-duration-format.ts +11 -2
  219. package/src/components/job-graph/job-duration-label.tsx +5 -1
  220. package/src/components/job-graph/job-graph-content.tsx +7 -4
  221. package/src/components/job-graph/job-graph-view.test.tsx +19 -8
  222. package/src/components/job-graph/job-graph-view.tsx +6 -3
  223. package/src/components/job-graph/job-graph.stories.tsx +24 -20
  224. package/src/components/job-graph/job-node.stories.tsx +11 -6
  225. package/src/components/job-graph/job-node.test.tsx +82 -12
  226. package/src/components/job-graph/job-node.tsx +22 -15
  227. package/src/components/job-graph/types.ts +5 -1
  228. package/src/components/step-list/step-list.stories.tsx +2 -3
  229. package/src/components/step-list/step-list.tsx +3 -2
  230. package/src/components/workflow-run-duration-label.tsx +1 -1
  231. package/src/components/workflow-run-list/job-status-strip.tsx +189 -0
  232. package/src/components/workflow-run-list/run-display.test.ts +169 -30
  233. package/src/components/workflow-run-list/run-display.ts +116 -9
  234. package/src/components/workflow-run-list/types.ts +21 -17
  235. package/src/components/workflow-run-list/workflow-run-filter-menu.tsx +100 -0
  236. package/src/components/workflow-run-list/workflow-run-filters.tsx +243 -0
  237. package/src/components/workflow-run-list/workflow-run-list-content.tsx +44 -16
  238. package/src/components/workflow-run-list/workflow-run-list-states.tsx +128 -27
  239. package/src/components/workflow-run-list/workflow-run-list-view.test.tsx +441 -104
  240. package/src/components/workflow-run-list/workflow-run-list-view.tsx +73 -43
  241. package/src/components/workflow-run-list/workflow-run-list.stories.tsx +140 -20
  242. package/src/components/workflow-run-list/workflow-run-list.tsx +20 -10
  243. package/src/components/workflow-run-list/workflow-run-row.tsx +173 -106
  244. package/src/components/workflow-run-number-label.test.tsx +45 -0
  245. package/src/components/workflow-run-number-label.tsx +47 -0
  246. package/src/components/workflow-run-summary/workflow-run-attempt-switcher.test.tsx +7 -8
  247. package/src/components/workflow-run-summary/workflow-run-attempt-switcher.tsx +15 -14
  248. package/src/components/workflow-run-summary/workflow-run-summary.stories.tsx +29 -38
  249. package/src/components/workflow-run-summary/workflow-run-summary.test.tsx +30 -24
  250. package/src/components/workflow-run-summary/workflow-run-summary.tsx +102 -77
  251. package/src/components/workflow-run-tabs/index.ts +5 -0
  252. package/src/components/workflow-run-tabs/run-annotation-summary-line.test.tsx +69 -0
  253. package/src/components/workflow-run-tabs/run-annotation-summary-line.tsx +97 -0
  254. package/src/components/workflow-run-tabs/run-annotations-empty.tsx +11 -0
  255. package/src/components/workflow-run-tabs/run-jobs-list.tsx +151 -0
  256. package/src/components/workflow-run-tabs/run-tab-count.tsx +28 -0
  257. package/src/components/workflow-run-tabs/run-tab-strip.stories.tsx +52 -0
  258. package/src/components/workflow-run-tabs/run-tab-strip.test.tsx +117 -0
  259. package/src/components/workflow-run-tabs/run-tab-strip.tsx +97 -0
  260. package/src/components/workflow-run-view/agent-config-failure-callout.stories.tsx +2 -3
  261. package/src/components/workflow-run-view/agent-config-failure-callout.tsx +4 -4
  262. package/src/components/workflow-run-view/job-card.stories.tsx +11 -10
  263. package/src/components/workflow-run-view/job-card.test.tsx +115 -0
  264. package/src/components/workflow-run-view/job-card.tsx +74 -35
  265. package/src/components/workflow-run-view/job-execution-switcher.tsx +31 -9
  266. package/src/components/workflow-run-view/workflow-run-selection.test.ts +48 -0
  267. package/src/components/workflow-run-view/workflow-run-selection.ts +48 -1
  268. package/src/components/workflow-run-view/workflow-run-states.tsx +12 -1
  269. package/src/components/workflow-run-view/workflow-run-view.test.tsx +152 -94
  270. package/src/components/workflow-run-view/workflow-run-view.tsx +219 -95
  271. package/src/components/workflow-source-panel/index.ts +1 -0
  272. package/src/components/workflow-source-panel/workflow-source-content.tsx +84 -0
  273. package/src/components/workflow-source-panel/workflow-source-panel.tsx +14 -81
  274. package/src/components/workflow-status/status-visuals.test.ts +9 -0
  275. package/src/components/workflow-status/status-visuals.ts +6 -4
  276. package/src/components/workflow-status/workflow-status-icon.stories.tsx +10 -2
  277. package/src/components/workflow-status/workflow-status-icon.test.tsx +18 -0
  278. package/src/components/workflow-status/workflow-status-icon.tsx +17 -24
  279. package/src/core/entities/job-execution.ts +22 -0
  280. package/src/core/entities/job-status.test.ts +104 -0
  281. package/src/core/entities/job.ts +18 -3
  282. package/src/core/entities/step.ts +4 -0
  283. package/src/core/entities/workflow-run.ts +76 -7
  284. package/src/core/workflow-run-tabs.ts +8 -0
  285. package/src/core/workflow-run.test.ts +24 -9
  286. package/src/core/workflow-run.ts +17 -2
  287. package/src/feature.ts +5 -5
  288. package/src/hooks/api/workflow-run-mapper.ts +30 -3
  289. package/src/hooks/api/workflow-runs.test.tsx +2 -0
  290. package/src/hooks/api/workflow-runs.ts +12 -7
  291. package/src/index.ts +7 -2
  292. package/src/page-harness-budget.test.ts +1 -1
  293. package/src/pages/project-workflows-page.test.tsx +59 -5
  294. package/src/pages/project-workflows-page.tsx +34 -1
  295. package/src/pages/workflow-run-detail-page.tsx +72 -0
  296. package/src/pages/workflow-run-list-page.tsx +65 -0
  297. package/src/pages/workflow-run-pages.test.tsx +823 -0
  298. package/src/routes/inputs.test.ts +240 -12
  299. package/src/routes/inputs.ts +215 -11
  300. package/src/routes/project-route.tsx +25 -0
  301. package/src/routes/run-detail.tsx +15 -8
  302. package/src/routes/runs.tsx +14 -7
  303. package/src/routes/workflows.tsx +5 -4
  304. package/src/storybook-config.test.ts +27 -0
  305. package/test/fixtures/workflow-run.ts +76 -7
  306. package/test/pages.tsx +18 -8
  307. package/test/render.tsx +8 -2
  308. package/tsconfig.build.tsbuildinfo +1 -1
  309. package/tsconfig.test.json +7 -1
  310. package/dist/components/workflow-run-list/workflow-run-list-header.d.ts +0 -10
  311. package/dist/components/workflow-run-list/workflow-run-list-header.d.ts.map +0 -1
  312. package/dist/components/workflow-run-list/workflow-run-list-header.js +0 -55
  313. package/dist/components/workflow-run-list/workflow-run-list-header.js.map +0 -1
  314. package/dist/pages/workflow-run-first-time-use.d.ts +0 -6
  315. package/dist/pages/workflow-run-first-time-use.d.ts.map +0 -1
  316. package/dist/pages/workflow-run-first-time-use.js +0 -17
  317. package/dist/pages/workflow-run-first-time-use.js.map +0 -1
  318. package/dist/pages/workflow-run-page.d.ts +0 -10
  319. package/dist/pages/workflow-run-page.d.ts.map +0 -1
  320. package/dist/pages/workflow-run-page.js +0 -90
  321. package/dist/pages/workflow-run-page.js.map +0 -1
  322. package/src/components/workflow-run-list/workflow-run-list-header.tsx +0 -53
  323. package/src/pages/workflow-run-first-time-use.tsx +0 -17
  324. package/src/pages/workflow-run-page.test.tsx +0 -519
  325. package/src/pages/workflow-run-page.tsx +0 -94
@@ -1,5 +1,5 @@
1
1
  import type { ReactNode } from 'react';
2
- import { type Job, type JobExecution, type Step, type StepSourceLocation } from '#core/workflow-run.js';
2
+ import { type Job, type JobDisplayStatus, type JobExecution, type Step, type StepSourceLocation } from '#core/workflow-run.js';
3
3
  export interface StepExpandedContext {
4
4
  step: Step;
5
5
  stepId: string;
@@ -14,7 +14,7 @@ export interface StepExpandedContext {
14
14
  export interface StepListEmptyState {
15
15
  title: string;
16
16
  description: string;
17
- status?: Job['status'] | undefined;
17
+ status?: JobDisplayStatus | undefined;
18
18
  }
19
19
  export interface StepListProps {
20
20
  job: Job;
@@ -1 +1 @@
1
- {"version":3,"file":"step-list.d.ts","sourceRoot":"","sources":["../../../src/components/step-list/step-list.tsx"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,OAAO,CAAC;AAGrC,OAAO,EAEL,KAAK,GAAG,EACR,KAAK,YAAY,EACjB,KAAK,IAAI,EACT,KAAK,kBAAkB,EACxB,MAAM,uBAAuB,CAAC;AAW/B,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,IAAI,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,kBAAkB,GAAG,IAAI,CAAC;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC7C,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,GAAG,CAAC,QAAQ,CAAC,GAAG,SAAS,CAAC;CACpC;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,GAAG,CAAC;IACT,YAAY,CAAC,EAAE,YAAY,GAAG,SAAS,CAAC;IACxC,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAC9C,wBAAwB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9C,uBAAuB,CAAC,EAAE,CAAC,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IAChF,uBAAuB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9C,UAAU,CAAC,EAAE,kBAAkB,GAAG,SAAS,CAAC;IAC5C,kBAAkB,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,mBAAmB,KAAK,SAAS,CAAC,GAAG,SAAS,CAAC;IAC/E,UAAU,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC;AAED,wBAAgB,QAAQ,CAAC,EACvB,GAAG,EACH,YAAY,EACZ,iBAAiB,EACjB,wBAAwB,EACxB,uBAAuB,EACvB,uBAA+B,EAC/B,UAAU,EACV,kBAAkB,EAClB,UAAiB,EACjB,SAAS,GACV,EAAE,aAAa,+BAqBf"}
1
+ {"version":3,"file":"step-list.d.ts","sourceRoot":"","sources":["../../../src/components/step-list/step-list.tsx"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,OAAO,CAAC;AAGrC,OAAO,EAEL,KAAK,GAAG,EACR,KAAK,gBAAgB,EACrB,KAAK,YAAY,EACjB,KAAK,IAAI,EACT,KAAK,kBAAkB,EACxB,MAAM,uBAAuB,CAAC;AAW/B,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,IAAI,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,kBAAkB,GAAG,IAAI,CAAC;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC7C,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,gBAAgB,GAAG,SAAS,CAAC;CACvC;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,GAAG,CAAC;IACT,YAAY,CAAC,EAAE,YAAY,GAAG,SAAS,CAAC;IACxC,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAC9C,wBAAwB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9C,uBAAuB,CAAC,EAAE,CAAC,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IAChF,uBAAuB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9C,UAAU,CAAC,EAAE,kBAAkB,GAAG,SAAS,CAAC;IAC5C,kBAAkB,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,mBAAmB,KAAK,SAAS,CAAC,GAAG,SAAS,CAAC;IAC/E,UAAU,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC;AAED,wBAAgB,QAAQ,CAAC,EACvB,GAAG,EACH,YAAY,EACZ,iBAAiB,EACjB,wBAAwB,EACxB,uBAAuB,EACvB,uBAA+B,EAC/B,UAAU,EACV,kBAAkB,EAClB,UAAiB,EACjB,SAAS,GACV,EAAE,aAAa,+BAqBf"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/step-list/step-list.tsx"],"sourcesContent":["import {\n Accordion,\n AccordionContent,\n AccordionItem,\n AccordionTrigger,\n} from '@shipfox/react-ui/accordion';\nimport {Badge, type BadgeVariant} from '@shipfox/react-ui/badge';\nimport {Dot} from '@shipfox/react-ui/dot';\nimport {EmptyState} from '@shipfox/react-ui/empty-state';\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {TimeTickerProvider, useTimeTick} from '@shipfox/react-ui/time-ticker';\nimport {Tooltip, TooltipContent, TooltipTrigger} from '@shipfox/react-ui/tooltip';\nimport {Code, Text} from '@shipfox/react-ui/typography';\nimport {cn, humanDuration} from '@shipfox/react-ui/utils';\nimport type {ReactNode} from 'react';\nimport {useEffect, useId, useMemo, useRef, useState} from 'react';\nimport {WorkflowStatusIcon} from '#components/workflow-status/workflow-status-icon.js';\nimport {\n isWorkflowStatus,\n type Job,\n type JobExecution,\n type Step,\n type StepSourceLocation,\n} from '#core/workflow-run.js';\nimport {formatJobExecutionTimeLabel} from '../job-graph/job-duration-format.js';\nimport {\n buildStepListModel,\n defaultStepListJobExecution,\n humanizeStatus,\n type StepAttemptModel,\n type StepListEntryModel,\n type StepListModel,\n} from './step-list-model.js';\n\nexport interface StepExpandedContext {\n step: Step;\n stepId: string;\n stepLabel: string;\n sourceLocation: StepSourceLocation | null;\n attempt: number;\n attemptId: string;\n attemptError: Record<string, unknown> | null;\n attemptStatus: string;\n carriedOver: boolean;\n}\n\nexport interface StepListEmptyState {\n title: string;\n description: string;\n status?: Job['status'] | undefined;\n}\n\nexport interface StepListProps {\n job: Job;\n jobExecution?: JobExecution | undefined;\n selectedAttemptId?: string | null | undefined;\n defaultSelectedAttemptId?: string | undefined;\n onSelectedAttemptChange?: ((attemptId: string | undefined) => void) | undefined;\n autoSelectActiveAttempt?: boolean | undefined;\n emptyState?: StepListEmptyState | undefined;\n renderExpandedStep?: ((context: StepExpandedContext) => ReactNode) | undefined;\n showHeader?: boolean | undefined;\n className?: string | undefined;\n}\n\nexport function StepList({\n job,\n jobExecution,\n selectedAttemptId,\n defaultSelectedAttemptId,\n onSelectedAttemptChange,\n autoSelectActiveAttempt = false,\n emptyState,\n renderExpandedStep,\n showHeader = true,\n className,\n}: StepListProps) {\n const selectedJobExecution = jobExecution ?? defaultStepListJobExecution(job);\n const model = useMemo(\n () => buildStepListModel({job, jobExecution: selectedJobExecution}),\n [job, selectedJobExecution],\n );\n\n return (\n <StepListContent\n key={model.jobExecutionId}\n model={model}\n selectedAttemptId={selectedAttemptId}\n defaultSelectedAttemptId={defaultSelectedAttemptId}\n onSelectedAttemptChange={onSelectedAttemptChange}\n autoSelectActiveAttempt={autoSelectActiveAttempt}\n emptyState={emptyState}\n renderExpandedStep={renderExpandedStep}\n showHeader={showHeader}\n className={className}\n />\n );\n}\n\nfunction StepListContent({\n model,\n selectedAttemptId,\n defaultSelectedAttemptId,\n onSelectedAttemptChange,\n autoSelectActiveAttempt,\n emptyState,\n renderExpandedStep,\n showHeader,\n className,\n}: Omit<StepListProps, 'job' | 'jobExecution'> & {model: StepListModel}) {\n const titleId = useId();\n const [localSelectedAttemptIds, setLocalSelectedAttemptIds] = useState<string[]>(() =>\n selectedAttemptId\n ? [selectedAttemptId]\n : defaultSelectedAttemptId\n ? [defaultSelectedAttemptId]\n : [],\n );\n const [userSelectedAttempt, setUserSelectedAttempt] = useState(false);\n const lastNotifiedSelectedAttemptId = useRef<string | null>(null);\n const shouldUseControlledCollapsedState =\n selectedAttemptId === null && lastNotifiedSelectedAttemptId.current === null;\n const autoSelectedAttemptIds =\n selectedAttemptId === undefined &&\n autoSelectActiveAttempt &&\n !userSelectedAttempt &&\n model.activeEntryId\n ? [model.activeEntryId]\n : [];\n const selectedAttemptIds = shouldUseControlledCollapsedState\n ? []\n : localSelectedAttemptIds.length > 0\n ? localSelectedAttemptIds\n : autoSelectedAttemptIds;\n const hasExpandedContent = renderExpandedStep !== undefined;\n\n useEffect(() => {\n if (selectedAttemptId !== undefined) return;\n\n setLocalSelectedAttemptIds(defaultSelectedAttemptId ? [defaultSelectedAttemptId] : []);\n setUserSelectedAttempt(false);\n }, [defaultSelectedAttemptId, selectedAttemptId]);\n\n useEffect(() => {\n if (selectedAttemptId === undefined) return;\n\n const nextSelectedAttemptId = selectedAttemptId ?? null;\n if (lastNotifiedSelectedAttemptId.current === nextSelectedAttemptId) {\n lastNotifiedSelectedAttemptId.current = null;\n return;\n }\n\n setLocalSelectedAttemptIds(selectedAttemptId ? [selectedAttemptId] : []);\n setUserSelectedAttempt(true);\n }, [selectedAttemptId]);\n\n function selectAttempt(nextAttemptIds: string[]) {\n const nextAttemptId = nextSelectedAttemptId(selectedAttemptIds, nextAttemptIds);\n setUserSelectedAttempt(true);\n setLocalSelectedAttemptIds(nextAttemptIds);\n lastNotifiedSelectedAttemptId.current = nextAttemptId ?? null;\n onSelectedAttemptChange?.(nextAttemptId);\n }\n\n return (\n <TimeTickerProvider intervalMs={1000} reducedMotionIntervalMs={10_000}>\n <section\n aria-labelledby={showHeader ? titleId : undefined}\n className={cn(\n 'flex min-h-0 flex-col rounded-8 border border-border-neutral-base bg-background-components-base',\n className,\n )}\n >\n {showHeader ? (\n <div className=\"flex min-h-40 items-center border-b border-border-neutral-base px-16 py-8\">\n <Text as=\"h2\" id={titleId} size=\"sm\" bold className=\"text-foreground-neutral-base\">\n {model.jobName}\n </Text>\n </div>\n ) : null}\n\n {model.entries.length === 0 ? (\n <StepListEmptyStateView emptyState={emptyState} />\n ) : (\n <Accordion\n type=\"multiple\"\n value={selectedAttemptIds}\n onValueChange={selectAttempt}\n asChild\n >\n <ol>\n {model.entries.map((entry) => {\n const selected = selectedAttemptIds.includes(entry.id);\n return (\n <StepRow\n key={entry.id}\n entry={entry}\n selected={selected}\n hasExpandedContent={hasExpandedContent}\n onSelect={() => {\n selectAttempt(\n hasExpandedContent\n ? toggleAttemptId(selectedAttemptIds, entry.id)\n : selected\n ? []\n : [entry.id],\n );\n }}\n expandedContent={\n selected\n ? renderExpandedStep?.({\n step: entry.step,\n stepId: entry.step.id,\n stepLabel: entry.step.label,\n sourceLocation: entry.step.sourceLocation,\n attempt: entry.attempt,\n attemptId: entry.id,\n attemptError: entry.error,\n attemptStatus: entry.statusVisual.kind,\n carriedOver: entry.carriedOver,\n })\n : null\n }\n />\n );\n })}\n </ol>\n </Accordion>\n )}\n </section>\n </TimeTickerProvider>\n );\n}\n\nfunction StepListEmptyStateView({\n emptyState = {\n title: 'No steps recorded',\n description: 'This job has not recorded any steps.',\n },\n}: {\n emptyState?: StepListEmptyState | undefined;\n}) {\n if (!emptyState.status) {\n return (\n <EmptyState\n className=\"min-h-120 px-16 py-20\"\n icon=\"componentLine\"\n title={emptyState.title}\n description={emptyState.description}\n variant=\"compact\"\n />\n );\n }\n\n return (\n <div className=\"flex min-h-120 flex-col items-center justify-center gap-10 px-16 py-20\">\n <StepListEmptyStateIcon status={emptyState.status} />\n <div className=\"text-center\">\n <Text size=\"sm\" className=\"text-foreground-neutral-subtle\">\n {emptyState.title}\n </Text>\n <Text size=\"xs\" className=\"text-foreground-neutral-muted\">\n {emptyState.description}\n </Text>\n </div>\n </div>\n );\n}\n\nfunction StepListEmptyStateIcon({status}: {status: Job['status']}) {\n if (status !== 'running') {\n return (\n <div className=\"flex size-32 items-center justify-center rounded-6 border border-border-neutral-strong bg-background-neutral-base p-8\">\n <WorkflowStatusIcon status={status} size={20} tooltip={false} />\n </div>\n );\n }\n\n return (\n <div className=\"flex size-32 items-center justify-center rounded-6 border border-border-neutral-strong bg-background-neutral-base p-8 text-foreground-neutral-muted\">\n <Icon name=\"timerLine\" size={18} aria-hidden=\"true\" />\n </div>\n );\n}\n\nfunction StepRow({\n entry,\n selected,\n hasExpandedContent,\n onSelect,\n expandedContent,\n}: {\n entry: StepListEntryModel;\n selected: boolean;\n hasExpandedContent: boolean;\n onSelect: () => void;\n expandedContent: ReactNode;\n}) {\n const shouldShowLabelTooltip = entry.step.label.length > 32;\n const rowContent = (\n <>\n <Icon\n name=\"chevronRight\"\n aria-hidden=\"true\"\n className={cn(\n 'size-14 shrink-0 text-foreground-neutral-muted transition-transform',\n selected && 'rotate-90',\n )}\n />\n <StepStatusIcon entry={entry} />\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex min-w-0 items-center gap-8\">\n <Text size=\"sm\" bold className=\"truncate text-foreground-neutral-base\">\n {entry.step.label}\n </Text>\n {entry.step.attempts.length > 1 ? <StepAttemptChip attempt={entry} /> : null}\n {entry.carriedOver ? <CarriedOverBadge /> : null}\n </div>\n </div>\n <StepAttemptDurationLabel attempt={entry} />\n </>\n );\n const rowClasses = cn(\n 'group grid min-h-44 w-full grid-cols-[14px_14px_minmax(0,1fr)_auto] items-center gap-x-8 px-12 py-6 text-left transition-colors hover:bg-background-components-hover focus-visible:shadow-border-interactive-with-active focus-visible:outline-none',\n selected && 'bg-background-components-hover',\n entry.carriedOver && 'opacity-[0.55]',\n );\n const button = hasExpandedContent ? (\n <AccordionTrigger\n showIcon={false}\n aria-label={entryAccessibleLabel(entry)}\n className={rowClasses}\n >\n {rowContent}\n </AccordionTrigger>\n ) : (\n <button\n type=\"button\"\n aria-expanded={false}\n aria-label={entryAccessibleLabel(entry)}\n onClick={onSelect}\n className={rowClasses}\n >\n {rowContent}\n </button>\n );\n const triggerNode = shouldShowLabelTooltip ? (\n <Tooltip>\n <TooltipTrigger asChild>{button}</TooltipTrigger>\n <TooltipContent>\n <span className=\"block max-w-320 break-words\">{entry.step.label}</span>\n </TooltipContent>\n </Tooltip>\n ) : (\n button\n );\n const row = (\n <>\n {triggerNode}\n {selected && expandedContent ? (\n <AccordionContent className=\"border-t border-border-neutral-base bg-background-neutral-base px-12 py-12\">\n <div className=\"grid grid-cols-[14px_14px_minmax(0,1fr)_auto] gap-x-8\">\n <span aria-hidden=\"true\" />\n <span aria-hidden=\"true\" />\n <div className=\"min-w-0\">{expandedContent}</div>\n <span aria-hidden=\"true\" />\n </div>\n </AccordionContent>\n ) : null}\n </>\n );\n\n if (hasExpandedContent) {\n return (\n <AccordionItem value={entry.id} asChild>\n <li>{row}</li>\n </AccordionItem>\n );\n }\n\n return <li className=\"border-b border-border-neutral-base last:border-b-0\">{row}</li>;\n}\n\nfunction CarriedOverBadge() {\n return (\n <Tooltip>\n <TooltipTrigger asChild>\n <span className=\"shrink-0\">\n <Badge variant=\"neutral\" size=\"2xs\">\n reused\n </Badge>\n </span>\n </TooltipTrigger>\n <TooltipContent>\n Carried over from a previous attempt; did not run in this attempt.\n </TooltipContent>\n </Tooltip>\n );\n}\n\nfunction toggleAttemptId(selectedAttemptIds: readonly string[], attemptId: string): string[] {\n if (selectedAttemptIds.includes(attemptId)) {\n return selectedAttemptIds.filter((selectedAttemptId) => selectedAttemptId !== attemptId);\n }\n return [...selectedAttemptIds, attemptId];\n}\n\nfunction nextSelectedAttemptId(\n selectedAttemptIds: readonly string[],\n nextAttemptIds: readonly string[],\n): string | undefined {\n if (nextAttemptIds.length === 0) return undefined;\n\n const openedAttemptId = nextAttemptIds.find(\n (attemptId) => !selectedAttemptIds.includes(attemptId),\n );\n return openedAttemptId ?? nextAttemptIds.at(-1);\n}\n\nfunction StepStatusIcon({entry}: {entry: StepListEntryModel}) {\n if (isWorkflowStatus(entry.statusVisual.kind)) {\n return (\n <WorkflowStatusIcon\n status={entry.statusVisual.kind}\n ripple={entry.statusVisual.ripple}\n size={14}\n />\n );\n }\n\n return (\n <span role=\"img\" aria-label={entry.statusVisual.label} className=\"inline-flex shrink-0\">\n <Dot\n variant={entry.statusVisual.dot}\n ripple={entry.statusVisual.ripple}\n className=\"size-12 shrink-0\"\n />\n </span>\n );\n}\n\nconst attemptChipClasses: Record<NonNullable<BadgeVariant>, string> = {\n neutral: 'bg-tag-neutral-bg border-tag-neutral-border',\n info: 'bg-tag-blue-bg border-tag-blue-border',\n feature: 'bg-tag-purple-bg border-tag-purple-border',\n success: 'bg-tag-success-bg border-tag-success-border',\n warning: 'bg-tag-warning-bg border-tag-warning-border',\n error: 'bg-tag-error-bg border-tag-error-border',\n};\n\nfunction StepAttemptChip({attempt}: {attempt: StepAttemptModel}) {\n return (\n <div className=\"flex shrink-0 items-center gap-4\" aria-hidden=\"true\">\n <span\n className={cn(\n 'inline-flex h-18 min-w-24 items-center justify-center rounded-4 border px-5 font-code text-xs leading-16 text-foreground-neutral-base',\n attemptChipClasses[attempt.statusVisual.badge ?? 'neutral'],\n )}\n >\n #{attempt.attempt}\n </span>\n </div>\n );\n}\n\nfunction entryAccessibleLabel(entry: StepListEntryModel): string {\n const parts = [entry.step.label, entry.statusVisual.label, `attempt ${entry.attempt}`];\n if (entry.step.error?.category) parts.push(humanizeStatus(entry.step.error.category));\n return parts.join(', ');\n}\n\nfunction StepAttemptDurationLabel({attempt}: {attempt: StepAttemptModel}) {\n const duration = attempt.displayDuration;\n if (duration === null) return null;\n\n if (duration.state === 'live') {\n return <LiveDurationText fromIso={duration.fromIso} />;\n }\n\n return <DurationText>{formatJobExecutionTimeLabel(duration)}</DurationText>;\n}\n\nfunction LiveDurationText({fromIso}: {fromIso: string}) {\n useTimeTick();\n return <DurationText>{humanDuration(fromIso)}</DurationText>;\n}\n\nfunction DurationText({children}: {children: string}) {\n return (\n <Code as=\"span\" variant=\"label\" className=\"shrink-0 tabular-nums text-foreground-neutral-muted\">\n {children}\n </Code>\n );\n}\n"],"names":["Accordion","AccordionContent","AccordionItem","AccordionTrigger","Badge","Dot","EmptyState","Icon","TimeTickerProvider","useTimeTick","Tooltip","TooltipContent","TooltipTrigger","Code","Text","cn","humanDuration","useEffect","useId","useMemo","useRef","useState","WorkflowStatusIcon","isWorkflowStatus","formatJobExecutionTimeLabel","buildStepListModel","defaultStepListJobExecution","humanizeStatus","StepList","job","jobExecution","selectedAttemptId","defaultSelectedAttemptId","onSelectedAttemptChange","autoSelectActiveAttempt","emptyState","renderExpandedStep","showHeader","className","selectedJobExecution","model","StepListContent","jobExecutionId","titleId","localSelectedAttemptIds","setLocalSelectedAttemptIds","userSelectedAttempt","setUserSelectedAttempt","lastNotifiedSelectedAttemptId","shouldUseControlledCollapsedState","current","autoSelectedAttemptIds","undefined","activeEntryId","selectedAttemptIds","length","hasExpandedContent","nextSelectedAttemptId","selectAttempt","nextAttemptIds","nextAttemptId","intervalMs","reducedMotionIntervalMs","section","aria-labelledby","div","as","id","size","bold","jobName","entries","StepListEmptyStateView","type","value","onValueChange","asChild","ol","map","entry","selected","includes","StepRow","onSelect","toggleAttemptId","expandedContent","step","stepId","stepLabel","label","sourceLocation","attempt","attemptId","attemptError","error","attemptStatus","statusVisual","kind","carriedOver","title","description","status","icon","variant","StepListEmptyStateIcon","tooltip","name","aria-hidden","shouldShowLabelTooltip","rowContent","StepStatusIcon","attempts","StepAttemptChip","CarriedOverBadge","StepAttemptDurationLabel","rowClasses","button","showIcon","aria-label","entryAccessibleLabel","aria-expanded","onClick","triggerNode","span","row","li","filter","openedAttemptId","find","at","ripple","role","dot","attemptChipClasses","neutral","info","feature","success","warning","badge","parts","category","push","join","duration","displayDuration","state","LiveDurationText","fromIso","DurationText","children"],"mappings":";AAAA,SACEA,SAAS,EACTC,gBAAgB,EAChBC,aAAa,EACbC,gBAAgB,QACX,8BAA8B;AACrC,SAAQC,KAAK,QAA0B,0BAA0B;AACjE,SAAQC,GAAG,QAAO,wBAAwB;AAC1C,SAAQC,UAAU,QAAO,gCAAgC;AACzD,SAAQC,IAAI,QAAO,yBAAyB;AAC5C,SAAQC,kBAAkB,EAAEC,WAAW,QAAO,gCAAgC;AAC9E,SAAQC,OAAO,EAAEC,cAAc,EAAEC,cAAc,QAAO,4BAA4B;AAClF,SAAQC,IAAI,EAAEC,IAAI,QAAO,+BAA+B;AACxD,SAAQC,EAAE,EAAEC,aAAa,QAAO,0BAA0B;AAE1D,SAAQC,SAAS,EAAEC,KAAK,EAAEC,OAAO,EAAEC,MAAM,EAAEC,QAAQ,QAAO,QAAQ;AAClE,SAAQC,kBAAkB,QAAO,sDAAsD;AACvF,SACEC,gBAAgB,QAKX,wBAAwB;AAC/B,SAAQC,2BAA2B,QAAO,sCAAsC;AAChF,SACEC,kBAAkB,EAClBC,2BAA2B,EAC3BC,cAAc,QAIT,uBAAuB;AAiC9B,OAAO,SAASC,SAAS,EACvBC,GAAG,EACHC,YAAY,EACZC,iBAAiB,EACjBC,wBAAwB,EACxBC,uBAAuB,EACvBC,0BAA0B,KAAK,EAC/BC,UAAU,EACVC,kBAAkB,EAClBC,aAAa,IAAI,EACjBC,SAAS,EACK;IACd,MAAMC,uBAAuBT,gBAAgBJ,4BAA4BG;IACzE,MAAMW,QAAQrB,QACZ,IAAMM,mBAAmB;YAACI;YAAKC,cAAcS;QAAoB,IACjE;QAACV;QAAKU;KAAqB;IAG7B,qBACE,KAACE;QAECD,OAAOA;QACPT,mBAAmBA;QACnBC,0BAA0BA;QAC1BC,yBAAyBA;QACzBC,yBAAyBA;QACzBC,YAAYA;QACZC,oBAAoBA;QACpBC,YAAYA;QACZC,WAAWA;OATNE,MAAME,cAAc;AAY/B;AAEA,SAASD,gBAAgB,EACvBD,KAAK,EACLT,iBAAiB,EACjBC,wBAAwB,EACxBC,uBAAuB,EACvBC,uBAAuB,EACvBC,UAAU,EACVC,kBAAkB,EAClBC,UAAU,EACVC,SAAS,EAC4D;IACrE,MAAMK,UAAUzB;IAChB,MAAM,CAAC0B,yBAAyBC,2BAA2B,GAAGxB,SAAmB,IAC/EU,oBACI;YAACA;SAAkB,GACnBC,2BACE;YAACA;SAAyB,GAC1B,EAAE;IAEV,MAAM,CAACc,qBAAqBC,uBAAuB,GAAG1B,SAAS;IAC/D,MAAM2B,gCAAgC5B,OAAsB;IAC5D,MAAM6B,oCACJlB,sBAAsB,QAAQiB,8BAA8BE,OAAO,KAAK;IAC1E,MAAMC,yBACJpB,sBAAsBqB,aACtBlB,2BACA,CAACY,uBACDN,MAAMa,aAAa,GACf;QAACb,MAAMa,aAAa;KAAC,GACrB,EAAE;IACR,MAAMC,qBAAqBL,oCACvB,EAAE,GACFL,wBAAwBW,MAAM,GAAG,IAC/BX,0BACAO;IACN,MAAMK,qBAAqBpB,uBAAuBgB;IAElDnC,UAAU;QACR,IAAIc,sBAAsBqB,WAAW;QAErCP,2BAA2Bb,2BAA2B;YAACA;SAAyB,GAAG,EAAE;QACrFe,uBAAuB;IACzB,GAAG;QAACf;QAA0BD;KAAkB;IAEhDd,UAAU;QACR,IAAIc,sBAAsBqB,WAAW;QAErC,MAAMK,wBAAwB1B,qBAAqB;QACnD,IAAIiB,8BAA8BE,OAAO,KAAKO,uBAAuB;YACnET,8BAA8BE,OAAO,GAAG;YACxC;QACF;QAEAL,2BAA2Bd,oBAAoB;YAACA;SAAkB,GAAG,EAAE;QACvEgB,uBAAuB;IACzB,GAAG;QAAChB;KAAkB;IAEtB,SAAS2B,cAAcC,cAAwB;QAC7C,MAAMC,gBAAgBH,sBAAsBH,oBAAoBK;QAChEZ,uBAAuB;QACvBF,2BAA2Bc;QAC3BX,8BAA8BE,OAAO,GAAGU,iBAAiB;QACzD3B,0BAA0B2B;IAC5B;IAEA,qBACE,KAACpD;QAAmBqD,YAAY;QAAMC,yBAAyB;kBAC7D,cAAA,MAACC;YACCC,mBAAiB3B,aAAaM,UAAUS;YACxCd,WAAWvB,GACT,mGACAuB;;gBAGDD,2BACC,KAAC4B;oBAAI3B,WAAU;8BACb,cAAA,KAACxB;wBAAKoD,IAAG;wBAAKC,IAAIxB;wBAASyB,MAAK;wBAAKC,IAAI;wBAAC/B,WAAU;kCACjDE,MAAM8B,OAAO;;qBAGhB;gBAEH9B,MAAM+B,OAAO,CAAChB,MAAM,KAAK,kBACxB,KAACiB;oBAAuBrC,YAAYA;mCAEpC,KAACnC;oBACCyE,MAAK;oBACLC,OAAOpB;oBACPqB,eAAejB;oBACfkB,OAAO;8BAEP,cAAA,KAACC;kCACErC,MAAM+B,OAAO,CAACO,GAAG,CAAC,CAACC;4BAClB,MAAMC,WAAW1B,mBAAmB2B,QAAQ,CAACF,MAAMZ,EAAE;4BACrD,qBACE,KAACe;gCAECH,OAAOA;gCACPC,UAAUA;gCACVxB,oBAAoBA;gCACpB2B,UAAU;oCACRzB,cACEF,qBACI4B,gBAAgB9B,oBAAoByB,MAAMZ,EAAE,IAC5Ca,WACE,EAAE,GACF;wCAACD,MAAMZ,EAAE;qCAAC;gCAEpB;gCACAkB,iBACEL,WACI5C,qBAAqB;oCACnBkD,MAAMP,MAAMO,IAAI;oCAChBC,QAAQR,MAAMO,IAAI,CAACnB,EAAE;oCACrBqB,WAAWT,MAAMO,IAAI,CAACG,KAAK;oCAC3BC,gBAAgBX,MAAMO,IAAI,CAACI,cAAc;oCACzCC,SAASZ,MAAMY,OAAO;oCACtBC,WAAWb,MAAMZ,EAAE;oCACnB0B,cAAcd,MAAMe,KAAK;oCACzBC,eAAehB,MAAMiB,YAAY,CAACC,IAAI;oCACtCC,aAAanB,MAAMmB,WAAW;gCAChC,KACA;+BA1BDnB,MAAMZ,EAAE;wBA8BnB;;;;;;AAOd;AAEA,SAASK,uBAAuB,EAC9BrC,aAAa;IACXgE,OAAO;IACPC,aAAa;AACf,CAAC,EAGF;IACC,IAAI,CAACjE,WAAWkE,MAAM,EAAE;QACtB,qBACE,KAAC/F;YACCgC,WAAU;YACVgE,MAAK;YACLH,OAAOhE,WAAWgE,KAAK;YACvBC,aAAajE,WAAWiE,WAAW;YACnCG,SAAQ;;IAGd;IAEA,qBACE,MAACtC;QAAI3B,WAAU;;0BACb,KAACkE;gBAAuBH,QAAQlE,WAAWkE,MAAM;;0BACjD,MAACpC;gBAAI3B,WAAU;;kCACb,KAACxB;wBAAKsD,MAAK;wBAAK9B,WAAU;kCACvBH,WAAWgE,KAAK;;kCAEnB,KAACrF;wBAAKsD,MAAK;wBAAK9B,WAAU;kCACvBH,WAAWiE,WAAW;;;;;;AAKjC;AAEA,SAASI,uBAAuB,EAACH,MAAM,EAA0B;IAC/D,IAAIA,WAAW,WAAW;QACxB,qBACE,KAACpC;YAAI3B,WAAU;sBACb,cAAA,KAAChB;gBAAmB+E,QAAQA;gBAAQjC,MAAM;gBAAIqC,SAAS;;;IAG7D;IAEA,qBACE,KAACxC;QAAI3B,WAAU;kBACb,cAAA,KAAC/B;YAAKmG,MAAK;YAAYtC,MAAM;YAAIuC,eAAY;;;AAGnD;AAEA,SAASzB,QAAQ,EACfH,KAAK,EACLC,QAAQ,EACRxB,kBAAkB,EAClB2B,QAAQ,EACRE,eAAe,EAOhB;IACC,MAAMuB,yBAAyB7B,MAAMO,IAAI,CAACG,KAAK,CAAClC,MAAM,GAAG;IACzD,MAAMsD,2BACJ;;0BACE,KAACtG;gBACCmG,MAAK;gBACLC,eAAY;gBACZrE,WAAWvB,GACT,uEACAiE,YAAY;;0BAGhB,KAAC8B;gBAAe/B,OAAOA;;0BACvB,KAACd;gBAAI3B,WAAU;0BACb,cAAA,MAAC2B;oBAAI3B,WAAU;;sCACb,KAACxB;4BAAKsD,MAAK;4BAAKC,IAAI;4BAAC/B,WAAU;sCAC5ByC,MAAMO,IAAI,CAACG,KAAK;;wBAElBV,MAAMO,IAAI,CAACyB,QAAQ,CAACxD,MAAM,GAAG,kBAAI,KAACyD;4BAAgBrB,SAASZ;6BAAY;wBACvEA,MAAMmB,WAAW,iBAAG,KAACe,wBAAsB;;;;0BAGhD,KAACC;gBAAyBvB,SAASZ;;;;IAGvC,MAAMoC,aAAapG,GACjB,uPACAiE,YAAY,kCACZD,MAAMmB,WAAW,IAAI;IAEvB,MAAMkB,SAAS5D,mCACb,KAACrD;QACCkH,UAAU;QACVC,cAAYC,qBAAqBxC;QACjCzC,WAAW6E;kBAEVN;uBAGH,KAACO;QACC3C,MAAK;QACL+C,iBAAe;QACfF,cAAYC,qBAAqBxC;QACjC0C,SAAStC;QACT7C,WAAW6E;kBAEVN;;IAGL,MAAMa,cAAcd,uCAClB,MAAClG;;0BACC,KAACE;gBAAegE,OAAO;0BAAEwC;;0BACzB,KAACzG;0BACC,cAAA,KAACgH;oBAAKrF,WAAU;8BAA+ByC,MAAMO,IAAI,CAACG,KAAK;;;;SAInE2B;IAEF,MAAMQ,oBACJ;;YACGF;YACA1C,YAAYK,gCACX,KAACpF;gBAAiBqC,WAAU;0BAC1B,cAAA,MAAC2B;oBAAI3B,WAAU;;sCACb,KAACqF;4BAAKhB,eAAY;;sCAClB,KAACgB;4BAAKhB,eAAY;;sCAClB,KAAC1C;4BAAI3B,WAAU;sCAAW+C;;sCAC1B,KAACsC;4BAAKhB,eAAY;;;;iBAGpB;;;IAIR,IAAInD,oBAAoB;QACtB,qBACE,KAACtD;YAAcwE,OAAOK,MAAMZ,EAAE;YAAES,OAAO;sBACrC,cAAA,KAACiD;0BAAID;;;IAGX;IAEA,qBAAO,KAACC;QAAGvF,WAAU;kBAAuDsF;;AAC9E;AAEA,SAASX;IACP,qBACE,MAACvG;;0BACC,KAACE;gBAAegE,OAAO;0BACrB,cAAA,KAAC+C;oBAAKrF,WAAU;8BACd,cAAA,KAAClC;wBAAMmG,SAAQ;wBAAUnC,MAAK;kCAAM;;;;0BAKxC,KAACzD;0BAAe;;;;AAKtB;AAEA,SAASyE,gBAAgB9B,kBAAqC,EAAEsC,SAAiB;IAC/E,IAAItC,mBAAmB2B,QAAQ,CAACW,YAAY;QAC1C,OAAOtC,mBAAmBwE,MAAM,CAAC,CAAC/F,oBAAsBA,sBAAsB6D;IAChF;IACA,OAAO;WAAItC;QAAoBsC;KAAU;AAC3C;AAEA,SAASnC,sBACPH,kBAAqC,EACrCK,cAAiC;IAEjC,IAAIA,eAAeJ,MAAM,KAAK,GAAG,OAAOH;IAExC,MAAM2E,kBAAkBpE,eAAeqE,IAAI,CACzC,CAACpC,YAAc,CAACtC,mBAAmB2B,QAAQ,CAACW;IAE9C,OAAOmC,mBAAmBpE,eAAesE,EAAE,CAAC,CAAC;AAC/C;AAEA,SAASnB,eAAe,EAAC/B,KAAK,EAA8B;IAC1D,IAAIxD,iBAAiBwD,MAAMiB,YAAY,CAACC,IAAI,GAAG;QAC7C,qBACE,KAAC3E;YACC+E,QAAQtB,MAAMiB,YAAY,CAACC,IAAI;YAC/BiC,QAAQnD,MAAMiB,YAAY,CAACkC,MAAM;YACjC9D,MAAM;;IAGZ;IAEA,qBACE,KAACuD;QAAKQ,MAAK;QAAMb,cAAYvC,MAAMiB,YAAY,CAACP,KAAK;QAAEnD,WAAU;kBAC/D,cAAA,KAACjC;YACCkG,SAASxB,MAAMiB,YAAY,CAACoC,GAAG;YAC/BF,QAAQnD,MAAMiB,YAAY,CAACkC,MAAM;YACjC5F,WAAU;;;AAIlB;AAEA,MAAM+F,qBAAgE;IACpEC,SAAS;IACTC,MAAM;IACNC,SAAS;IACTC,SAAS;IACTC,SAAS;IACT5C,OAAO;AACT;AAEA,SAASkB,gBAAgB,EAACrB,OAAO,EAA8B;IAC7D,qBACE,KAAC1B;QAAI3B,WAAU;QAAmCqE,eAAY;kBAC5D,cAAA,MAACgB;YACCrF,WAAWvB,GACT,yIACAsH,kBAAkB,CAAC1C,QAAQK,YAAY,CAAC2C,KAAK,IAAI,UAAU;;gBAE9D;gBACGhD,QAAQA,OAAO;;;;AAIzB;AAEA,SAAS4B,qBAAqBxC,KAAyB;IACrD,MAAM6D,QAAQ;QAAC7D,MAAMO,IAAI,CAACG,KAAK;QAAEV,MAAMiB,YAAY,CAACP,KAAK;QAAE,CAAC,QAAQ,EAAEV,MAAMY,OAAO,EAAE;KAAC;IACtF,IAAIZ,MAAMO,IAAI,CAACQ,KAAK,EAAE+C,UAAUD,MAAME,IAAI,CAACnH,eAAeoD,MAAMO,IAAI,CAACQ,KAAK,CAAC+C,QAAQ;IACnF,OAAOD,MAAMG,IAAI,CAAC;AACpB;AAEA,SAAS7B,yBAAyB,EAACvB,OAAO,EAA8B;IACtE,MAAMqD,WAAWrD,QAAQsD,eAAe;IACxC,IAAID,aAAa,MAAM,OAAO;IAE9B,IAAIA,SAASE,KAAK,KAAK,QAAQ;QAC7B,qBAAO,KAACC;YAAiBC,SAASJ,SAASI,OAAO;;IACpD;IAEA,qBAAO,KAACC;kBAAc7H,4BAA4BwH;;AACpD;AAEA,SAASG,iBAAiB,EAACC,OAAO,EAAoB;IACpD3I;IACA,qBAAO,KAAC4I;kBAAcrI,cAAcoI;;AACtC;AAEA,SAASC,aAAa,EAACC,QAAQ,EAAqB;IAClD,qBACE,KAACzI;QAAKqD,IAAG;QAAOqC,SAAQ;QAAQjE,WAAU;kBACvCgH;;AAGP"}
1
+ {"version":3,"sources":["../../../src/components/step-list/step-list.tsx"],"sourcesContent":["import {\n Accordion,\n AccordionContent,\n AccordionItem,\n AccordionTrigger,\n} from '@shipfox/react-ui/accordion';\nimport {Badge, type BadgeVariant} from '@shipfox/react-ui/badge';\nimport {Dot} from '@shipfox/react-ui/dot';\nimport {EmptyState} from '@shipfox/react-ui/empty-state';\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {TimeTickerProvider, useTimeTick} from '@shipfox/react-ui/time-ticker';\nimport {Tooltip, TooltipContent, TooltipTrigger} from '@shipfox/react-ui/tooltip';\nimport {Code, Text} from '@shipfox/react-ui/typography';\nimport {cn, humanDuration} from '@shipfox/react-ui/utils';\nimport type {ReactNode} from 'react';\nimport {useEffect, useId, useMemo, useRef, useState} from 'react';\nimport {WorkflowStatusIcon} from '#components/workflow-status/workflow-status-icon.js';\nimport {\n isWorkflowStatus,\n type Job,\n type JobDisplayStatus,\n type JobExecution,\n type Step,\n type StepSourceLocation,\n} from '#core/workflow-run.js';\nimport {formatJobExecutionTimeLabel} from '../job-graph/job-duration-format.js';\nimport {\n buildStepListModel,\n defaultStepListJobExecution,\n humanizeStatus,\n type StepAttemptModel,\n type StepListEntryModel,\n type StepListModel,\n} from './step-list-model.js';\n\nexport interface StepExpandedContext {\n step: Step;\n stepId: string;\n stepLabel: string;\n sourceLocation: StepSourceLocation | null;\n attempt: number;\n attemptId: string;\n attemptError: Record<string, unknown> | null;\n attemptStatus: string;\n carriedOver: boolean;\n}\n\nexport interface StepListEmptyState {\n title: string;\n description: string;\n status?: JobDisplayStatus | undefined;\n}\n\nexport interface StepListProps {\n job: Job;\n jobExecution?: JobExecution | undefined;\n selectedAttemptId?: string | null | undefined;\n defaultSelectedAttemptId?: string | undefined;\n onSelectedAttemptChange?: ((attemptId: string | undefined) => void) | undefined;\n autoSelectActiveAttempt?: boolean | undefined;\n emptyState?: StepListEmptyState | undefined;\n renderExpandedStep?: ((context: StepExpandedContext) => ReactNode) | undefined;\n showHeader?: boolean | undefined;\n className?: string | undefined;\n}\n\nexport function StepList({\n job,\n jobExecution,\n selectedAttemptId,\n defaultSelectedAttemptId,\n onSelectedAttemptChange,\n autoSelectActiveAttempt = false,\n emptyState,\n renderExpandedStep,\n showHeader = true,\n className,\n}: StepListProps) {\n const selectedJobExecution = jobExecution ?? defaultStepListJobExecution(job);\n const model = useMemo(\n () => buildStepListModel({job, jobExecution: selectedJobExecution}),\n [job, selectedJobExecution],\n );\n\n return (\n <StepListContent\n key={model.jobExecutionId}\n model={model}\n selectedAttemptId={selectedAttemptId}\n defaultSelectedAttemptId={defaultSelectedAttemptId}\n onSelectedAttemptChange={onSelectedAttemptChange}\n autoSelectActiveAttempt={autoSelectActiveAttempt}\n emptyState={emptyState}\n renderExpandedStep={renderExpandedStep}\n showHeader={showHeader}\n className={className}\n />\n );\n}\n\nfunction StepListContent({\n model,\n selectedAttemptId,\n defaultSelectedAttemptId,\n onSelectedAttemptChange,\n autoSelectActiveAttempt,\n emptyState,\n renderExpandedStep,\n showHeader,\n className,\n}: Omit<StepListProps, 'job' | 'jobExecution'> & {model: StepListModel}) {\n const titleId = useId();\n const [localSelectedAttemptIds, setLocalSelectedAttemptIds] = useState<string[]>(() =>\n selectedAttemptId\n ? [selectedAttemptId]\n : defaultSelectedAttemptId\n ? [defaultSelectedAttemptId]\n : [],\n );\n const [userSelectedAttempt, setUserSelectedAttempt] = useState(false);\n const lastNotifiedSelectedAttemptId = useRef<string | null>(null);\n const shouldUseControlledCollapsedState =\n selectedAttemptId === null && lastNotifiedSelectedAttemptId.current === null;\n const autoSelectedAttemptIds =\n selectedAttemptId === undefined &&\n autoSelectActiveAttempt &&\n !userSelectedAttempt &&\n model.activeEntryId\n ? [model.activeEntryId]\n : [];\n const selectedAttemptIds = shouldUseControlledCollapsedState\n ? []\n : localSelectedAttemptIds.length > 0\n ? localSelectedAttemptIds\n : autoSelectedAttemptIds;\n const hasExpandedContent = renderExpandedStep !== undefined;\n\n useEffect(() => {\n if (selectedAttemptId !== undefined) return;\n\n setLocalSelectedAttemptIds(defaultSelectedAttemptId ? [defaultSelectedAttemptId] : []);\n setUserSelectedAttempt(false);\n }, [defaultSelectedAttemptId, selectedAttemptId]);\n\n useEffect(() => {\n if (selectedAttemptId === undefined) return;\n\n const nextSelectedAttemptId = selectedAttemptId ?? null;\n if (lastNotifiedSelectedAttemptId.current === nextSelectedAttemptId) {\n lastNotifiedSelectedAttemptId.current = null;\n return;\n }\n\n setLocalSelectedAttemptIds(selectedAttemptId ? [selectedAttemptId] : []);\n setUserSelectedAttempt(true);\n }, [selectedAttemptId]);\n\n function selectAttempt(nextAttemptIds: string[]) {\n const nextAttemptId = nextSelectedAttemptId(selectedAttemptIds, nextAttemptIds);\n setUserSelectedAttempt(true);\n setLocalSelectedAttemptIds(nextAttemptIds);\n lastNotifiedSelectedAttemptId.current = nextAttemptId ?? null;\n onSelectedAttemptChange?.(nextAttemptId);\n }\n\n return (\n <TimeTickerProvider intervalMs={1000} reducedMotionIntervalMs={10_000}>\n <section\n aria-labelledby={showHeader ? titleId : undefined}\n className={cn(\n 'flex min-h-0 flex-col rounded-8 border border-border-neutral-base bg-background-components-base',\n className,\n )}\n >\n {showHeader ? (\n <div className=\"flex min-h-40 items-center border-b border-border-neutral-base px-16 py-8\">\n <Text as=\"h2\" id={titleId} size=\"sm\" bold className=\"text-foreground-neutral-base\">\n {model.jobName}\n </Text>\n </div>\n ) : null}\n\n {model.entries.length === 0 ? (\n <StepListEmptyStateView emptyState={emptyState} />\n ) : (\n <Accordion\n type=\"multiple\"\n value={selectedAttemptIds}\n onValueChange={selectAttempt}\n asChild\n >\n <ol>\n {model.entries.map((entry) => {\n const selected = selectedAttemptIds.includes(entry.id);\n return (\n <StepRow\n key={entry.id}\n entry={entry}\n selected={selected}\n hasExpandedContent={hasExpandedContent}\n onSelect={() => {\n selectAttempt(\n hasExpandedContent\n ? toggleAttemptId(selectedAttemptIds, entry.id)\n : selected\n ? []\n : [entry.id],\n );\n }}\n expandedContent={\n selected\n ? renderExpandedStep?.({\n step: entry.step,\n stepId: entry.step.id,\n stepLabel: entry.step.label,\n sourceLocation: entry.step.sourceLocation,\n attempt: entry.attempt,\n attemptId: entry.id,\n attemptError: entry.error,\n attemptStatus: entry.statusVisual.kind,\n carriedOver: entry.carriedOver,\n })\n : null\n }\n />\n );\n })}\n </ol>\n </Accordion>\n )}\n </section>\n </TimeTickerProvider>\n );\n}\n\nfunction StepListEmptyStateView({\n emptyState = {\n title: 'No steps recorded',\n description: 'This job has not recorded any steps.',\n },\n}: {\n emptyState?: StepListEmptyState | undefined;\n}) {\n if (!emptyState.status) {\n return (\n <EmptyState\n className=\"min-h-120 px-16 py-20\"\n icon=\"componentLine\"\n title={emptyState.title}\n description={emptyState.description}\n variant=\"compact\"\n />\n );\n }\n\n return (\n <div className=\"flex min-h-120 flex-col items-center justify-center gap-10 px-16 py-20\">\n <StepListEmptyStateIcon status={emptyState.status} />\n <div className=\"text-center\">\n <Text size=\"sm\" className=\"text-foreground-neutral-subtle\">\n {emptyState.title}\n </Text>\n <Text size=\"xs\" className=\"text-foreground-neutral-muted\">\n {emptyState.description}\n </Text>\n </div>\n </div>\n );\n}\n\nfunction StepListEmptyStateIcon({status}: {status: JobDisplayStatus}) {\n if (status !== 'running') {\n return (\n <div className=\"flex size-32 items-center justify-center rounded-6 border border-border-neutral-strong bg-background-neutral-base p-8\">\n <WorkflowStatusIcon status={status} size={20} tooltip={false} />\n </div>\n );\n }\n\n return (\n <div className=\"flex size-32 items-center justify-center rounded-6 border border-border-neutral-strong bg-background-neutral-base p-8 text-foreground-neutral-muted\">\n <Icon name=\"timerLine\" size={18} aria-hidden=\"true\" />\n </div>\n );\n}\n\nfunction StepRow({\n entry,\n selected,\n hasExpandedContent,\n onSelect,\n expandedContent,\n}: {\n entry: StepListEntryModel;\n selected: boolean;\n hasExpandedContent: boolean;\n onSelect: () => void;\n expandedContent: ReactNode;\n}) {\n const shouldShowLabelTooltip = entry.step.label.length > 32;\n const rowContent = (\n <>\n <Icon\n name=\"chevronRight\"\n aria-hidden=\"true\"\n className={cn(\n 'size-14 shrink-0 text-foreground-neutral-muted transition-transform',\n selected && 'rotate-90',\n )}\n />\n <StepStatusIcon entry={entry} />\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex min-w-0 items-center gap-8\">\n <Text size=\"sm\" bold className=\"truncate text-foreground-neutral-base\">\n {entry.step.label}\n </Text>\n {entry.step.attempts.length > 1 ? <StepAttemptChip attempt={entry} /> : null}\n {entry.carriedOver ? <CarriedOverBadge /> : null}\n </div>\n </div>\n <StepAttemptDurationLabel attempt={entry} />\n </>\n );\n const rowClasses = cn(\n 'group grid min-h-44 w-full grid-cols-[14px_14px_minmax(0,1fr)_auto] items-center gap-x-8 px-12 py-6 text-left transition-colors hover:bg-background-components-hover focus-visible:shadow-border-interactive-with-active focus-visible:outline-none',\n selected && 'bg-background-components-hover',\n entry.carriedOver && 'opacity-[0.55]',\n );\n const button = hasExpandedContent ? (\n <AccordionTrigger\n showIcon={false}\n aria-label={entryAccessibleLabel(entry)}\n className={rowClasses}\n >\n {rowContent}\n </AccordionTrigger>\n ) : (\n <button\n type=\"button\"\n aria-expanded={false}\n aria-label={entryAccessibleLabel(entry)}\n onClick={onSelect}\n className={rowClasses}\n >\n {rowContent}\n </button>\n );\n const triggerNode = shouldShowLabelTooltip ? (\n <Tooltip>\n <TooltipTrigger asChild>{button}</TooltipTrigger>\n <TooltipContent>\n <span className=\"block max-w-320 break-words\">{entry.step.label}</span>\n </TooltipContent>\n </Tooltip>\n ) : (\n button\n );\n const row = (\n <>\n {triggerNode}\n {selected && expandedContent ? (\n <AccordionContent className=\"border-t border-border-neutral-base bg-background-neutral-base px-12 py-12\">\n <div className=\"grid grid-cols-[14px_14px_minmax(0,1fr)_auto] gap-x-8\">\n <span aria-hidden=\"true\" />\n <span aria-hidden=\"true\" />\n <div className=\"min-w-0\">{expandedContent}</div>\n <span aria-hidden=\"true\" />\n </div>\n </AccordionContent>\n ) : null}\n </>\n );\n\n if (hasExpandedContent) {\n return (\n <AccordionItem value={entry.id} asChild>\n <li>{row}</li>\n </AccordionItem>\n );\n }\n\n return <li className=\"border-b border-border-neutral-base last:border-b-0\">{row}</li>;\n}\n\nfunction CarriedOverBadge() {\n return (\n <Tooltip>\n <TooltipTrigger asChild>\n <span className=\"shrink-0\">\n <Badge variant=\"neutral\" size=\"2xs\">\n reused\n </Badge>\n </span>\n </TooltipTrigger>\n <TooltipContent>\n Carried over from a previous attempt; did not run in this attempt.\n </TooltipContent>\n </Tooltip>\n );\n}\n\nfunction toggleAttemptId(selectedAttemptIds: readonly string[], attemptId: string): string[] {\n if (selectedAttemptIds.includes(attemptId)) {\n return selectedAttemptIds.filter((selectedAttemptId) => selectedAttemptId !== attemptId);\n }\n return [...selectedAttemptIds, attemptId];\n}\n\nfunction nextSelectedAttemptId(\n selectedAttemptIds: readonly string[],\n nextAttemptIds: readonly string[],\n): string | undefined {\n if (nextAttemptIds.length === 0) return undefined;\n\n const openedAttemptId = nextAttemptIds.find(\n (attemptId) => !selectedAttemptIds.includes(attemptId),\n );\n return openedAttemptId ?? nextAttemptIds.at(-1);\n}\n\nfunction StepStatusIcon({entry}: {entry: StepListEntryModel}) {\n if (isWorkflowStatus(entry.statusVisual.kind)) {\n return (\n <WorkflowStatusIcon\n status={entry.statusVisual.kind}\n ripple={entry.statusVisual.ripple}\n size={14}\n />\n );\n }\n\n return (\n <span role=\"img\" aria-label={entry.statusVisual.label} className=\"inline-flex shrink-0\">\n <Dot\n variant={entry.statusVisual.dot}\n ripple={entry.statusVisual.ripple}\n className=\"size-12 shrink-0\"\n />\n </span>\n );\n}\n\nconst attemptChipClasses: Record<NonNullable<BadgeVariant>, string> = {\n neutral: 'bg-tag-neutral-bg border-tag-neutral-border',\n info: 'bg-tag-blue-bg border-tag-blue-border',\n feature: 'bg-tag-purple-bg border-tag-purple-border',\n success: 'bg-tag-success-bg border-tag-success-border',\n warning: 'bg-tag-warning-bg border-tag-warning-border',\n error: 'bg-tag-error-bg border-tag-error-border',\n};\n\nfunction StepAttemptChip({attempt}: {attempt: StepAttemptModel}) {\n return (\n <div className=\"flex shrink-0 items-center gap-4\" aria-hidden=\"true\">\n <span\n className={cn(\n 'inline-flex h-18 min-w-24 items-center justify-center rounded-4 border px-5 font-code text-xs leading-16 text-foreground-neutral-base',\n attemptChipClasses[attempt.statusVisual.badge ?? 'neutral'],\n )}\n >\n #{attempt.attempt}\n </span>\n </div>\n );\n}\n\nfunction entryAccessibleLabel(entry: StepListEntryModel): string {\n const parts = [entry.step.label, entry.statusVisual.label, `attempt ${entry.attempt}`];\n if (entry.step.error?.category) parts.push(humanizeStatus(entry.step.error.category));\n return parts.join(', ');\n}\n\nfunction StepAttemptDurationLabel({attempt}: {attempt: StepAttemptModel}) {\n const duration = attempt.displayDuration;\n if (duration === null) return null;\n\n if (duration.state === 'live') {\n return <LiveDurationText fromIso={duration.fromIso} />;\n }\n\n return <DurationText>{formatJobExecutionTimeLabel(duration)}</DurationText>;\n}\n\nfunction LiveDurationText({fromIso}: {fromIso: string}) {\n useTimeTick();\n return <DurationText>{humanDuration(fromIso)}</DurationText>;\n}\n\nfunction DurationText({children}: {children: string}) {\n return (\n <Code as=\"span\" variant=\"label\" className=\"shrink-0 tabular-nums text-foreground-neutral-muted\">\n {children}\n </Code>\n );\n}\n"],"names":["Accordion","AccordionContent","AccordionItem","AccordionTrigger","Badge","Dot","EmptyState","Icon","TimeTickerProvider","useTimeTick","Tooltip","TooltipContent","TooltipTrigger","Code","Text","cn","humanDuration","useEffect","useId","useMemo","useRef","useState","WorkflowStatusIcon","isWorkflowStatus","formatJobExecutionTimeLabel","buildStepListModel","defaultStepListJobExecution","humanizeStatus","StepList","job","jobExecution","selectedAttemptId","defaultSelectedAttemptId","onSelectedAttemptChange","autoSelectActiveAttempt","emptyState","renderExpandedStep","showHeader","className","selectedJobExecution","model","StepListContent","jobExecutionId","titleId","localSelectedAttemptIds","setLocalSelectedAttemptIds","userSelectedAttempt","setUserSelectedAttempt","lastNotifiedSelectedAttemptId","shouldUseControlledCollapsedState","current","autoSelectedAttemptIds","undefined","activeEntryId","selectedAttemptIds","length","hasExpandedContent","nextSelectedAttemptId","selectAttempt","nextAttemptIds","nextAttemptId","intervalMs","reducedMotionIntervalMs","section","aria-labelledby","div","as","id","size","bold","jobName","entries","StepListEmptyStateView","type","value","onValueChange","asChild","ol","map","entry","selected","includes","StepRow","onSelect","toggleAttemptId","expandedContent","step","stepId","stepLabel","label","sourceLocation","attempt","attemptId","attemptError","error","attemptStatus","statusVisual","kind","carriedOver","title","description","status","icon","variant","StepListEmptyStateIcon","tooltip","name","aria-hidden","shouldShowLabelTooltip","rowContent","StepStatusIcon","attempts","StepAttemptChip","CarriedOverBadge","StepAttemptDurationLabel","rowClasses","button","showIcon","aria-label","entryAccessibleLabel","aria-expanded","onClick","triggerNode","span","row","li","filter","openedAttemptId","find","at","ripple","role","dot","attemptChipClasses","neutral","info","feature","success","warning","badge","parts","category","push","join","duration","displayDuration","state","LiveDurationText","fromIso","DurationText","children"],"mappings":"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"}
@@ -59,7 +59,7 @@ function DurationText({ children, className, ariaLabel }) {
59
59
  as: "span",
60
60
  variant: "label",
61
61
  "aria-label": ariaLabel,
62
- className: cn('inline-flex shrink-0 items-center gap-4 tabular-nums text-foreground-neutral-muted', className),
62
+ className: cn('inline-flex shrink-0 items-center gap-4 tabular-nums text-foreground-neutral-subtle', className),
63
63
  children: [
64
64
  /*#__PURE__*/ _jsx(Icon, {
65
65
  name: "timerLine",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/workflow-run-duration-label.tsx"],"sourcesContent":["import {Icon} from '@shipfox/react-ui/icon';\nimport {useTimeTick} from '@shipfox/react-ui/time-ticker';\nimport {Code} from '@shipfox/react-ui/typography';\nimport {cn, humanDuration} from '@shipfox/react-ui/utils';\nimport type {WorkflowRunAttemptDisplayDuration} from '#core/workflow-run.js';\n\nexport function WorkflowRunDurationLabel({\n duration,\n className,\n}: {\n duration: WorkflowRunAttemptDisplayDuration | null;\n className?: string | undefined;\n}) {\n if (duration === null) return null;\n\n switch (duration.state) {\n case 'fixed': {\n const display = formatFixedDurationLabel(duration.elapsed);\n return (\n <DurationText className={className} ariaLabel={`ran ${display}`}>\n {display}\n </DurationText>\n );\n }\n case 'live':\n return <LiveDurationText duration={duration} className={className} />;\n default: {\n const exhaustive: never = duration;\n return exhaustive;\n }\n }\n}\n\nexport function useWorkflowRunDurationAccessibleLabel(\n duration: WorkflowRunAttemptDisplayDuration | null,\n): string | undefined {\n useTimeTick();\n return workflowRunDurationAccessibleLabel(duration);\n}\n\nexport function workflowRunDurationAccessibleLabel(\n duration: WorkflowRunAttemptDisplayDuration | null,\n): string | undefined {\n if (duration === null) return undefined;\n\n switch (duration.state) {\n case 'live':\n return `running ${humanDuration(duration.fromIso)}`;\n case 'fixed':\n return `ran ${formatFixedDurationLabel(duration.elapsed)}`;\n default: {\n const exhaustive: never = duration;\n return exhaustive;\n }\n }\n}\n\nfunction LiveDurationText({\n duration,\n className,\n}: {\n duration: Extract<WorkflowRunAttemptDisplayDuration, {state: 'live'}>;\n className?: string | undefined;\n}) {\n useTimeTick();\n const display = humanDuration(duration.fromIso);\n return (\n <DurationText className={className} ariaLabel={`running ${display}`}>\n {display}\n </DurationText>\n );\n}\n\nfunction DurationText({\n children,\n className,\n ariaLabel,\n}: {\n children: string;\n className?: string | undefined;\n ariaLabel?: string | undefined;\n}) {\n return (\n <Code\n as=\"span\"\n variant=\"label\"\n aria-label={ariaLabel}\n className={cn(\n 'inline-flex shrink-0 items-center gap-4 tabular-nums text-foreground-neutral-muted',\n className,\n )}\n >\n <Icon name=\"timerLine\" className=\"size-12 shrink-0\" aria-hidden=\"true\" />\n {children}\n </Code>\n );\n}\n\nfunction formatFixedDurationLabel({\n years = 0,\n months = 0,\n weeks = 0,\n days = 0,\n hours = 0,\n minutes = 0,\n seconds = 0,\n}: Extract<WorkflowRunAttemptDisplayDuration, {state: 'fixed'}>['elapsed']): string {\n const totalDays = years * 365 + months * 30 + weeks * 7 + days;\n const totalHours = totalDays * 24 + hours;\n\n if (totalHours > 0) return `${totalHours}h ${pad2(minutes)}m`;\n if (minutes > 0) return `${minutes}m ${pad2(seconds)}s`;\n return `${seconds}s`;\n}\n\nfunction pad2(value: number): string {\n return value.toString().padStart(2, '0');\n}\n"],"names":["Icon","useTimeTick","Code","cn","humanDuration","WorkflowRunDurationLabel","duration","className","state","display","formatFixedDurationLabel","elapsed","DurationText","ariaLabel","LiveDurationText","exhaustive","useWorkflowRunDurationAccessibleLabel","workflowRunDurationAccessibleLabel","undefined","fromIso","children","as","variant","aria-label","name","aria-hidden","years","months","weeks","days","hours","minutes","seconds","totalDays","totalHours","pad2","value","toString","padStart"],"mappings":";AAAA,SAAQA,IAAI,QAAO,yBAAyB;AAC5C,SAAQC,WAAW,QAAO,gCAAgC;AAC1D,SAAQC,IAAI,QAAO,+BAA+B;AAClD,SAAQC,EAAE,EAAEC,aAAa,QAAO,0BAA0B;AAG1D,OAAO,SAASC,yBAAyB,EACvCC,QAAQ,EACRC,SAAS,EAIV;IACC,IAAID,aAAa,MAAM,OAAO;IAE9B,OAAQA,SAASE,KAAK;QACpB,KAAK;YAAS;gBACZ,MAAMC,UAAUC,yBAAyBJ,SAASK,OAAO;gBACzD,qBACE,KAACC;oBAAaL,WAAWA;oBAAWM,WAAW,CAAC,IAAI,EAAEJ,SAAS;8BAC5DA;;YAGP;QACA,KAAK;YACH,qBAAO,KAACK;gBAAiBR,UAAUA;gBAAUC,WAAWA;;QAC1D;YAAS;gBACP,MAAMQ,aAAoBT;gBAC1B,OAAOS;YACT;IACF;AACF;AAEA,OAAO,SAASC,sCACdV,QAAkD;IAElDL;IACA,OAAOgB,mCAAmCX;AAC5C;AAEA,OAAO,SAASW,mCACdX,QAAkD;IAElD,IAAIA,aAAa,MAAM,OAAOY;IAE9B,OAAQZ,SAASE,KAAK;QACpB,KAAK;YACH,OAAO,CAAC,QAAQ,EAAEJ,cAAcE,SAASa,OAAO,GAAG;QACrD,KAAK;YACH,OAAO,CAAC,IAAI,EAAET,yBAAyBJ,SAASK,OAAO,GAAG;QAC5D;YAAS;gBACP,MAAMI,aAAoBT;gBAC1B,OAAOS;YACT;IACF;AACF;AAEA,SAASD,iBAAiB,EACxBR,QAAQ,EACRC,SAAS,EAIV;IACCN;IACA,MAAMQ,UAAUL,cAAcE,SAASa,OAAO;IAC9C,qBACE,KAACP;QAAaL,WAAWA;QAAWM,WAAW,CAAC,QAAQ,EAAEJ,SAAS;kBAChEA;;AAGP;AAEA,SAASG,aAAa,EACpBQ,QAAQ,EACRb,SAAS,EACTM,SAAS,EAKV;IACC,qBACE,MAACX;QACCmB,IAAG;QACHC,SAAQ;QACRC,cAAYV;QACZN,WAAWJ,GACT,sFACAI;;0BAGF,KAACP;gBAAKwB,MAAK;gBAAYjB,WAAU;gBAAmBkB,eAAY;;YAC/DL;;;AAGP;AAEA,SAASV,yBAAyB,EAChCgB,QAAQ,CAAC,EACTC,SAAS,CAAC,EACVC,QAAQ,CAAC,EACTC,OAAO,CAAC,EACRC,QAAQ,CAAC,EACTC,UAAU,CAAC,EACXC,UAAU,CAAC,EAC6D;IACxE,MAAMC,YAAYP,QAAQ,MAAMC,SAAS,KAAKC,QAAQ,IAAIC;IAC1D,MAAMK,aAAaD,YAAY,KAAKH;IAEpC,IAAII,aAAa,GAAG,OAAO,GAAGA,WAAW,EAAE,EAAEC,KAAKJ,SAAS,CAAC,CAAC;IAC7D,IAAIA,UAAU,GAAG,OAAO,GAAGA,QAAQ,EAAE,EAAEI,KAAKH,SAAS,CAAC,CAAC;IACvD,OAAO,GAAGA,QAAQ,CAAC,CAAC;AACtB;AAEA,SAASG,KAAKC,KAAa;IACzB,OAAOA,MAAMC,QAAQ,GAAGC,QAAQ,CAAC,GAAG;AACtC"}
1
+ {"version":3,"sources":["../../src/components/workflow-run-duration-label.tsx"],"sourcesContent":["import {Icon} from '@shipfox/react-ui/icon';\nimport {useTimeTick} from '@shipfox/react-ui/time-ticker';\nimport {Code} from '@shipfox/react-ui/typography';\nimport {cn, humanDuration} from '@shipfox/react-ui/utils';\nimport type {WorkflowRunAttemptDisplayDuration} from '#core/workflow-run.js';\n\nexport function WorkflowRunDurationLabel({\n duration,\n className,\n}: {\n duration: WorkflowRunAttemptDisplayDuration | null;\n className?: string | undefined;\n}) {\n if (duration === null) return null;\n\n switch (duration.state) {\n case 'fixed': {\n const display = formatFixedDurationLabel(duration.elapsed);\n return (\n <DurationText className={className} ariaLabel={`ran ${display}`}>\n {display}\n </DurationText>\n );\n }\n case 'live':\n return <LiveDurationText duration={duration} className={className} />;\n default: {\n const exhaustive: never = duration;\n return exhaustive;\n }\n }\n}\n\nexport function useWorkflowRunDurationAccessibleLabel(\n duration: WorkflowRunAttemptDisplayDuration | null,\n): string | undefined {\n useTimeTick();\n return workflowRunDurationAccessibleLabel(duration);\n}\n\nexport function workflowRunDurationAccessibleLabel(\n duration: WorkflowRunAttemptDisplayDuration | null,\n): string | undefined {\n if (duration === null) return undefined;\n\n switch (duration.state) {\n case 'live':\n return `running ${humanDuration(duration.fromIso)}`;\n case 'fixed':\n return `ran ${formatFixedDurationLabel(duration.elapsed)}`;\n default: {\n const exhaustive: never = duration;\n return exhaustive;\n }\n }\n}\n\nfunction LiveDurationText({\n duration,\n className,\n}: {\n duration: Extract<WorkflowRunAttemptDisplayDuration, {state: 'live'}>;\n className?: string | undefined;\n}) {\n useTimeTick();\n const display = humanDuration(duration.fromIso);\n return (\n <DurationText className={className} ariaLabel={`running ${display}`}>\n {display}\n </DurationText>\n );\n}\n\nfunction DurationText({\n children,\n className,\n ariaLabel,\n}: {\n children: string;\n className?: string | undefined;\n ariaLabel?: string | undefined;\n}) {\n return (\n <Code\n as=\"span\"\n variant=\"label\"\n aria-label={ariaLabel}\n className={cn(\n 'inline-flex shrink-0 items-center gap-4 tabular-nums text-foreground-neutral-subtle',\n className,\n )}\n >\n <Icon name=\"timerLine\" className=\"size-12 shrink-0\" aria-hidden=\"true\" />\n {children}\n </Code>\n );\n}\n\nfunction formatFixedDurationLabel({\n years = 0,\n months = 0,\n weeks = 0,\n days = 0,\n hours = 0,\n minutes = 0,\n seconds = 0,\n}: Extract<WorkflowRunAttemptDisplayDuration, {state: 'fixed'}>['elapsed']): string {\n const totalDays = years * 365 + months * 30 + weeks * 7 + days;\n const totalHours = totalDays * 24 + hours;\n\n if (totalHours > 0) return `${totalHours}h ${pad2(minutes)}m`;\n if (minutes > 0) return `${minutes}m ${pad2(seconds)}s`;\n return `${seconds}s`;\n}\n\nfunction pad2(value: number): string {\n return value.toString().padStart(2, '0');\n}\n"],"names":["Icon","useTimeTick","Code","cn","humanDuration","WorkflowRunDurationLabel","duration","className","state","display","formatFixedDurationLabel","elapsed","DurationText","ariaLabel","LiveDurationText","exhaustive","useWorkflowRunDurationAccessibleLabel","workflowRunDurationAccessibleLabel","undefined","fromIso","children","as","variant","aria-label","name","aria-hidden","years","months","weeks","days","hours","minutes","seconds","totalDays","totalHours","pad2","value","toString","padStart"],"mappings":";AAAA,SAAQA,IAAI,QAAO,yBAAyB;AAC5C,SAAQC,WAAW,QAAO,gCAAgC;AAC1D,SAAQC,IAAI,QAAO,+BAA+B;AAClD,SAAQC,EAAE,EAAEC,aAAa,QAAO,0BAA0B;AAG1D,OAAO,SAASC,yBAAyB,EACvCC,QAAQ,EACRC,SAAS,EAIV;IACC,IAAID,aAAa,MAAM,OAAO;IAE9B,OAAQA,SAASE,KAAK;QACpB,KAAK;YAAS;gBACZ,MAAMC,UAAUC,yBAAyBJ,SAASK,OAAO;gBACzD,qBACE,KAACC;oBAAaL,WAAWA;oBAAWM,WAAW,CAAC,IAAI,EAAEJ,SAAS;8BAC5DA;;YAGP;QACA,KAAK;YACH,qBAAO,KAACK;gBAAiBR,UAAUA;gBAAUC,WAAWA;;QAC1D;YAAS;gBACP,MAAMQ,aAAoBT;gBAC1B,OAAOS;YACT;IACF;AACF;AAEA,OAAO,SAASC,sCACdV,QAAkD;IAElDL;IACA,OAAOgB,mCAAmCX;AAC5C;AAEA,OAAO,SAASW,mCACdX,QAAkD;IAElD,IAAIA,aAAa,MAAM,OAAOY;IAE9B,OAAQZ,SAASE,KAAK;QACpB,KAAK;YACH,OAAO,CAAC,QAAQ,EAAEJ,cAAcE,SAASa,OAAO,GAAG;QACrD,KAAK;YACH,OAAO,CAAC,IAAI,EAAET,yBAAyBJ,SAASK,OAAO,GAAG;QAC5D;YAAS;gBACP,MAAMI,aAAoBT;gBAC1B,OAAOS;YACT;IACF;AACF;AAEA,SAASD,iBAAiB,EACxBR,QAAQ,EACRC,SAAS,EAIV;IACCN;IACA,MAAMQ,UAAUL,cAAcE,SAASa,OAAO;IAC9C,qBACE,KAACP;QAAaL,WAAWA;QAAWM,WAAW,CAAC,QAAQ,EAAEJ,SAAS;kBAChEA;;AAGP;AAEA,SAASG,aAAa,EACpBQ,QAAQ,EACRb,SAAS,EACTM,SAAS,EAKV;IACC,qBACE,MAACX;QACCmB,IAAG;QACHC,SAAQ;QACRC,cAAYV;QACZN,WAAWJ,GACT,uFACAI;;0BAGF,KAACP;gBAAKwB,MAAK;gBAAYjB,WAAU;gBAAmBkB,eAAY;;YAC/DL;;;AAGP;AAEA,SAASV,yBAAyB,EAChCgB,QAAQ,CAAC,EACTC,SAAS,CAAC,EACVC,QAAQ,CAAC,EACTC,OAAO,CAAC,EACRC,QAAQ,CAAC,EACTC,UAAU,CAAC,EACXC,UAAU,CAAC,EAC6D;IACxE,MAAMC,YAAYP,QAAQ,MAAMC,SAAS,KAAKC,QAAQ,IAAIC;IAC1D,MAAMK,aAAaD,YAAY,KAAKH;IAEpC,IAAII,aAAa,GAAG,OAAO,GAAGA,WAAW,EAAE,EAAEC,KAAKJ,SAAS,CAAC,CAAC;IAC7D,IAAIA,UAAU,GAAG,OAAO,GAAGA,QAAQ,EAAE,EAAEI,KAAKH,SAAS,CAAC,CAAC;IACvD,OAAO,GAAGA,QAAQ,CAAC,CAAC;AACtB;AAEA,SAASG,KAAKC,KAAa;IACzB,OAAOA,MAAMC,QAAQ,GAAGC,QAAQ,CAAC,GAAG;AACtC"}
@@ -0,0 +1,21 @@
1
+ import type { WorkflowRunJobs } from '#core/workflow-run.js';
2
+ export interface JobStatusStripProps {
3
+ jobs: WorkflowRunJobs;
4
+ className?: string | undefined;
5
+ }
6
+ /**
7
+ * A run's jobs as a row of status glyphs, in graph order.
8
+ *
9
+ * This is the reason the list is worth its width: it answers "where did this run fail"
10
+ * without opening the run. Glyphs are silent to assistive tech and carry no tooltip of their
11
+ * own; the strip speaks once, as a summary, so a 40-job run does not read out 40 labels.
12
+ *
13
+ * What it draws comes from the preview; what it says comes from the counts, which cover every
14
+ * job including those the server never sent. That split is what lets the overflow glyph
15
+ * report a failure sitting past the preview instead of quietly dropping it.
16
+ */
17
+ export declare function JobStatusStrip({ jobs, className }: JobStatusStripProps): import("react").JSX.Element | null;
18
+ /** "12 jobs: 9 succeeded, 2 failed, 1 running" — a count is not an accessible name on its own. */
19
+ export declare function jobStatusSummary(jobs: WorkflowRunJobs): string;
20
+ export declare function overflowCountLabel(hiddenCount: number): string;
21
+ //# sourceMappingURL=job-status-strip.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"job-status-strip.d.ts","sourceRoot":"","sources":["../../../src/components/workflow-run-list/job-status-strip.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAmC,eAAe,EAAC,MAAM,uBAAuB,CAAC;AAyC7F,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,eAAe,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,EAAC,IAAI,EAAE,SAAS,EAAC,EAAE,mBAAmB,sCAqDpE;AAgCD,kGAAkG;AAClG,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM,CAO9D;AAyBD,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAI9D"}
@@ -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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"}