@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
@@ -26,8 +26,6 @@ import {WorkflowRunSummary} from './workflow-run-summary.js';
26
26
  const ROOT_RUN_ID = '11111111-1111-4111-8111-111111111111';
27
27
  const CURRENT_RUN_ID = '22222222-2222-4222-8222-222222222222';
28
28
  const NEXT_RUN_ID = '33333333-3333-4333-8333-333333333333';
29
- const WORKSPACE_ID = '44444444-4444-4444-8444-444444444444';
30
- const PROJECT_ID = '55555555-5555-4555-8555-555555555555';
31
29
  const SWITCH_ATTEMPT_PATTERN = /Switch attempt/;
32
30
  const ATTEMPT_3_PATTERN = /Attempt 3/;
33
31
  const STORYBOOK_NOW = '2026-06-26T12:00:00.000Z';
@@ -58,10 +56,10 @@ const RUN_ATTEMPTS_RESPONSE = runAttemptsResponseDto({
58
56
  });
59
57
 
60
58
  const withFrame: Decorator = (Story) => (
61
- <div className="min-h-screen bg-background-neutral-base">
59
+ <div className="min-h-screen bg-background-neutral-background">
62
60
  <div className="mx-auto flex min-h-screen w-full max-w-[1120px] flex-col overflow-hidden border-x border-border-neutral-base bg-background-subtle-base">
63
61
  <Story />
64
- <div className="min-h-0 flex-1 bg-background-neutral-base p-16" />
62
+ <div className="min-h-0 flex-1 bg-background-neutral-background p-16" />
65
63
  </div>
66
64
  </div>
67
65
  );
@@ -71,12 +69,12 @@ const withAttemptApi: Decorator = (Story) => {
71
69
  const rootRoute = createRootRoute({component: Outlet});
72
70
  const runRoute = createRoute({
73
71
  getParentRoute: () => rootRoute,
74
- path: '/workspaces/$wid/projects/$pid/runs/$workflowRunId',
72
+ path: '/w/$workspaceSlug/p/$projectSlug/runs/$workflowRunId',
75
73
  component: () => <Story />,
76
74
  });
77
75
  const router = createRouter({
78
76
  history: createMemoryHistory({
79
- initialEntries: [`/workspaces/${WORKSPACE_ID}/projects/${PROJECT_ID}/runs/${CURRENT_RUN_ID}`],
77
+ initialEntries: [`/w/acme/p/project/runs/${CURRENT_RUN_ID}`],
80
78
  }),
81
79
  routeTree: rootRoute.addChildren([runRoute]),
82
80
  });
@@ -155,37 +153,13 @@ const ATTEMPT_SUMMARY_ARGS = {
155
153
  }),
156
154
  status: 'failed',
157
155
  }),
158
- workspaceId: WORKSPACE_ID,
159
- projectId: PROJECT_ID,
156
+ workspaceSlug: 'acme',
157
+ projectSlug: 'project',
160
158
  latestAttempt: 3,
161
159
  };
162
160
 
163
161
  export const Playground: Story = {};
164
162
 
165
- export const WithSourceButton: Story = {
166
- args: {
167
- run: workflowRunDetail({
168
- status: 'succeeded',
169
- source_snapshot: {format: 'yaml', content: 'jobs:\n build:\n steps: []'},
170
- }),
171
- sourceAvailable: true,
172
- sourceOpen: false,
173
- sourcePanelId: 'workflow-source-panel',
174
- },
175
- };
176
-
177
- export const SourceOpen: Story = {
178
- args: {
179
- run: workflowRunDetail({
180
- status: 'succeeded',
181
- source_snapshot: {format: 'yaml', content: 'jobs:\n build:\n steps: []'},
182
- }),
183
- sourceAvailable: true,
184
- sourceOpen: true,
185
- sourcePanelId: 'workflow-source-panel',
186
- },
187
- };
188
-
189
163
  export const WithAttempts: Story = {
190
164
  decorators: [withAttemptApi],
191
165
  args: ATTEMPT_SUMMARY_ARGS,
@@ -325,7 +299,7 @@ const ACTION_VARIANTS = [
325
299
  >;
326
300
  }>;
327
301
 
328
- export const ActionVariantsWithSource: Story = {
302
+ export const ActionVariants: Story = {
329
303
  render: () => (
330
304
  <div className="flex flex-col">
331
305
  {ACTION_VARIANTS.map(({label, run, props}, index) => (
@@ -335,9 +309,6 @@ export const ActionVariantsWithSource: Story = {
335
309
  ...run,
336
310
  id: `22222222-2222-4222-8222-${String(index + 2).padStart(12, '0')}`,
337
311
  }}
338
- sourceAvailable
339
- sourceOpen={label === 'Running'}
340
- sourcePanelId={`workflow-source-panel-${index}`}
341
312
  {...props}
342
313
  />
343
314
  ))}
@@ -352,8 +323,6 @@ export const ActionVariantsWithAttempts: Story = {
352
323
  {ACTION_VARIANTS.map(({label, run, props}, index) => (
353
324
  <WorkflowRunSummary
354
325
  key={label}
355
- workspaceId={WORKSPACE_ID}
356
- projectId={PROJECT_ID}
357
326
  run={{
358
327
  ...run,
359
328
  id: `22222222-2222-4222-8222-${String(index + 2).padStart(12, '0')}`,
@@ -369,6 +338,8 @@ export const ActionVariantsWithAttempts: Story = {
369
338
  rerunMode: null,
370
339
  }),
371
340
  }}
341
+ workspaceSlug="acme"
342
+ projectSlug="project"
372
343
  latestAttempt={3}
373
344
  {...props}
374
345
  />
@@ -412,3 +383,23 @@ export const LongTriggerMetadata: Story = {
412
383
  }),
413
384
  },
414
385
  };
386
+
387
+ export const NarrowLongContent: Story = {
388
+ decorators: [
389
+ (Story) => (
390
+ <div className="w-[360px] max-w-full overflow-hidden border-x border-border-neutral-base">
391
+ <Story />
392
+ </div>
393
+ ),
394
+ ],
395
+ args: {
396
+ run: workflowRunDetail({
397
+ status: 'running',
398
+ name: 'release-production-multi-region-with-canary-and-post-deploy-validation',
399
+ trigger_provider: 'github',
400
+ trigger_source: 'github-enterprise-cloud-production-organization',
401
+ trigger_event: 'workflow_dispatch_with_release_candidate_payload',
402
+ }),
403
+ onCancel: noop,
404
+ },
405
+ };
@@ -29,13 +29,16 @@ describe('WorkflowRunSummary', () => {
29
29
  restoreElementWidthDescriptors();
30
30
  });
31
31
 
32
- test('renders status, trigger metadata, and trigger time', async () => {
33
- renderSummary();
32
+ test('renders status, run number, trigger metadata, and trigger time', async () => {
33
+ renderSummary({workflow_name: 'CI', number: 5184});
34
34
 
35
35
  const summary = await screen.findByRole('region', {name: 'deploy-web'});
36
36
 
37
+ expect(summary).toHaveClass('bg-background-neutral-background', 'px-16');
38
+ expect(summary.firstElementChild).not.toHaveClass('max-w-[1120px]');
37
39
  expect(within(summary).getByRole('heading', {name: 'deploy-web'})).toBeInTheDocument();
38
40
  expect(within(summary).getAllByText('Running')).not.toHaveLength(0);
41
+ expect(within(summary).getByText('CI #5184')).toBeInTheDocument();
39
42
  expect(within(summary).getByText('fire')).toBeInTheDocument();
40
43
  expect(within(summary).queryByText('manual')).not.toBeInTheDocument();
41
44
  expect(within(summary).getByText(RELATIVE_TIME_TEXT_PATTERN)).toBeInTheDocument();
@@ -46,6 +49,30 @@ describe('WorkflowRunSummary', () => {
46
49
  ).not.toBeInTheDocument();
47
50
  });
48
51
 
52
+ test('omits the run number before the server assigns one', async () => {
53
+ const run = {...workflowRunDetail({workflow_name: 'CI'}), number: null};
54
+ render(<WorkflowRunSummary run={run} />);
55
+
56
+ const summary = await screen.findByRole('region', {name: 'deploy-web'});
57
+
58
+ expect(within(summary).queryByText('CI #1')).not.toBeInTheDocument();
59
+ });
60
+
61
+ test('separates a standalone run number from the run timestamp', async () => {
62
+ const run = workflowRunDetail({
63
+ workflow_name: 'CI',
64
+ trigger_source: '',
65
+ trigger_event: '',
66
+ });
67
+ render(<WorkflowRunSummary run={run} />);
68
+
69
+ const summary = await screen.findByRole('region', {name: 'deploy-web'});
70
+
71
+ expect(within(summary).getByText('CI #1')).toBeInTheDocument();
72
+ const timestamp = within(summary).getByText(RELATIVE_TIME_TEXT_PATTERN);
73
+ expect(timestamp.previousElementSibling).toHaveAttribute('aria-hidden', 'true');
74
+ });
75
+
49
76
  test('uses the selected run attempt for summary status and trigger time', async () => {
50
77
  const rootCreatedAt = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
51
78
  const attemptCreatedAt = new Date(Date.now() - 5 * 60 * 1000).toISOString();
@@ -103,27 +130,6 @@ describe('WorkflowRunSummary', () => {
103
130
  );
104
131
  });
105
132
 
106
- test('renders source control only when source is available', async () => {
107
- const user = userEvent.setup();
108
- const onSourceToggle = vi.fn();
109
- renderSummary(
110
- {source_snapshot: {format: 'yaml', content: 'name: deploy-web'}},
111
- {
112
- sourceAvailable: true,
113
- sourceOpen: false,
114
- sourcePanelId: 'workflow-source-panel',
115
- onSourceToggle,
116
- },
117
- );
118
-
119
- const sourceButton = await screen.findByRole('button', {name: 'View source'});
120
- await user.click(sourceButton);
121
-
122
- expect(sourceButton).toHaveAttribute('aria-controls', 'workflow-source-panel');
123
- expect(sourceButton).toHaveAttribute('aria-expanded', 'false');
124
- expect(onSourceToggle).toHaveBeenCalledTimes(1);
125
- });
126
-
127
133
  test('shows the selected attempt duration, not the top-level run duration', async () => {
128
134
  renderSummary({
129
135
  started_at: '2026-05-07T00:00:00.000Z',
@@ -168,7 +174,7 @@ describe('WorkflowRunSummary', () => {
168
174
  expect(duration).toHaveAttribute('aria-label', 'running 2m 14s');
169
175
  });
170
176
 
171
- test('omits source control when source is unavailable', async () => {
177
+ test('does not render a whole-run source control', async () => {
172
178
  renderSummary();
173
179
 
174
180
  await screen.findByRole('region', {name: 'deploy-web'});
@@ -12,8 +12,9 @@ import {RelativeTime} from '@shipfox/react-ui/relative-time';
12
12
  import {TimeTickerProvider} from '@shipfox/react-ui/time-ticker';
13
13
  import {Tooltip, TooltipContent, TooltipTrigger} from '@shipfox/react-ui/tooltip';
14
14
  import {Header, Text} from '@shipfox/react-ui/typography';
15
- import type {Ref} from 'react';
15
+ import {Link} from '@tanstack/react-router';
16
16
  import {useId} from 'react';
17
+ import {WorkflowRunNumberLabel} from '#components/workflow-run-number-label.js';
17
18
  import {
18
19
  isWorkflowRunTerminal,
19
20
  type Job,
@@ -21,6 +22,7 @@ import {
21
22
  type WorkflowRunDetail,
22
23
  type WorkflowRunRerunMode,
23
24
  } from '#core/workflow-run.js';
25
+ import {validateWorkflowRunsSearch, workflowRunListSearchParams} from '#routes/inputs.js';
24
26
  import {WorkflowRunDurationLabel} from '../workflow-run-duration-label.js';
25
27
  import {getWorkflowStatusVisual} from '../workflow-status/status-visuals.js';
26
28
  import {WorkflowRunAttemptSwitcher} from './workflow-run-attempt-switcher.js';
@@ -32,14 +34,9 @@ const STATUS_BADGE_LABEL_WIDTH_CH = Math.max(
32
34
  type WorkflowRunAction = 'cancel' | 'rerun-all' | 'rerun-menu' | 'none';
33
35
 
34
36
  export interface WorkflowRunSummaryProps {
35
- workspaceId?: string | undefined;
36
- projectId?: string | undefined;
37
+ workspaceSlug?: string | undefined;
38
+ projectSlug?: string | undefined;
37
39
  run: WorkflowRunDetail;
38
- sourceAvailable?: boolean | undefined;
39
- sourceOpen?: boolean | undefined;
40
- sourcePanelId?: string | undefined;
41
- sourceButtonRef?: Ref<HTMLButtonElement> | undefined;
42
- onSourceToggle?: (() => void) | undefined;
43
40
  cancelling?: boolean | undefined;
44
41
  onCancel?: (() => void) | undefined;
45
42
  rerunPending?: boolean | undefined;
@@ -48,14 +45,9 @@ export interface WorkflowRunSummaryProps {
48
45
  }
49
46
 
50
47
  export function WorkflowRunSummary({
51
- workspaceId,
52
- projectId,
48
+ workspaceSlug,
49
+ projectSlug,
53
50
  run,
54
- sourceAvailable = false,
55
- sourceOpen = false,
56
- sourcePanelId,
57
- sourceButtonRef,
58
- onSourceToggle,
59
51
  cancelling = false,
60
52
  onCancel,
61
53
  rerunPending = false,
@@ -65,9 +57,10 @@ export function WorkflowRunSummary({
65
57
  const headingId = useId();
66
58
  const status = getWorkflowStatusVisual(run.runAttempt.status);
67
59
  const action = workflowRunActionForRun(run);
60
+ const hasAction = canRenderWorkflowRunAction(action, onCancel, onRerun);
68
61
  const attemptSwitcher =
69
- latestAttempt && latestAttempt > 1 && workspaceId && projectId
70
- ? {workspaceId, projectId, latestAttempt}
62
+ latestAttempt && latestAttempt > 1 && workspaceSlug && projectSlug
63
+ ? {workspaceSlug, projectSlug, latestAttempt}
71
64
  : null;
72
65
  const displayDuration = run.runAttempt.displayDuration;
73
66
  const {ref: headingTextRef, isTruncated: isHeadingTruncated} =
@@ -75,68 +68,88 @@ export function WorkflowRunSummary({
75
68
 
76
69
  return (
77
70
  <TimeTickerProvider intervalMs={1000} reducedMotionIntervalMs={10_000}>
78
- <section
79
- aria-labelledby={headingId}
80
- className="border-b border-border-neutral-base bg-background-subtle-base px-16 py-8"
81
- >
82
- <div className="grid min-w-0 grid-cols-[minmax(0,1fr)_auto] items-center gap-x-12 gap-y-4 overflow-hidden">
83
- <div className="col-start-1 row-start-1 flex min-w-0 items-center gap-8">
84
- <Badge variant={status.badge} size="xs">
85
- <span className="text-center" style={{width: `${STATUS_BADGE_LABEL_WIDTH_CH}ch`}}>
86
- {status.label}
87
- </span>
88
- </Badge>
89
-
90
- <Tooltip>
91
- <TooltipTrigger asChild>
92
- <Header id={headingId} variant="h3" className="min-w-0 truncate">
93
- <span
94
- ref={headingTextRef}
95
- title={isHeadingTruncated ? run.name : undefined}
96
- className="block min-w-0 truncate"
71
+ <section aria-labelledby={headingId} className="bg-background-neutral-background px-16 py-12">
72
+ <div className="grid min-w-0 grid-cols-[minmax(0,1fr)_auto] items-center gap-x-12 gap-y-8 overflow-hidden max-[480px]:grid-cols-1">
73
+ <nav
74
+ aria-label="Breadcrumb"
75
+ className="col-start-1 row-start-1 min-w-0 max-[480px]:col-start-auto max-[480px]:row-start-auto"
76
+ >
77
+ <ol className="flex min-w-0 items-center gap-8">
78
+ {workspaceSlug && projectSlug ? (
79
+ <li className="shrink-0">
80
+ <Link
81
+ to="/w/$workspaceSlug/p/$projectSlug/runs"
82
+ params={{workspaceSlug, projectSlug}}
83
+ search={
84
+ ((previous: Record<string, unknown>) =>
85
+ workflowRunListSearchParams(validateWorkflowRunsSearch(previous))) as never
86
+ }
87
+ className="rounded-4 text-xs font-medium text-foreground-neutral-subtle underline decoration-border-neutral-strong underline-offset-4 outline-none hover:text-foreground-neutral-base focus-visible:shadow-border-interactive-with-active"
97
88
  >
98
- {run.name}
99
- </span>
100
- </Header>
101
- </TooltipTrigger>
102
- {isHeadingTruncated ? (
103
- <TooltipContent>
104
- <Text as="span" size="xs" className="max-w-[360px] break-words">
105
- {run.name}
106
- </Text>
107
- </TooltipContent>
89
+ Runs
90
+ </Link>
91
+ </li>
108
92
  ) : null}
109
- </Tooltip>
110
- </div>
93
+ {workspaceSlug && projectSlug ? (
94
+ <li aria-hidden="true" className="shrink-0 text-xs text-foreground-neutral-subtle">
95
+ /
96
+ </li>
97
+ ) : null}
98
+ <li aria-current="page" className="flex min-w-0 items-center gap-8">
99
+ <Badge variant={status.badge} size="xs">
100
+ <span className="text-center" style={{width: `${STATUS_BADGE_LABEL_WIDTH_CH}ch`}}>
101
+ {status.label}
102
+ </span>
103
+ </Badge>
111
104
 
112
- <div className="col-start-2 row-start-1 flex min-w-max items-center gap-6 justify-self-end">
113
- <WorkflowRunActionSlot
114
- action={action}
115
- cancelling={cancelling}
116
- onCancel={onCancel}
117
- rerunPending={rerunPending}
118
- onRerun={onRerun}
119
- />
120
- {sourceAvailable ? (
121
- <Button
122
- ref={sourceButtonRef}
123
- type="button"
124
- variant="secondary"
125
- size="xs"
126
- aria-controls={sourcePanelId}
127
- aria-expanded={sourceOpen}
128
- onClick={onSourceToggle}
129
- >
130
- View source
131
- </Button>
105
+ <Tooltip>
106
+ <TooltipTrigger asChild>
107
+ <Header id={headingId} variant="h3" className="min-w-0 truncate">
108
+ <span
109
+ ref={headingTextRef}
110
+ title={isHeadingTruncated ? run.name : undefined}
111
+ className="block min-w-0 truncate"
112
+ >
113
+ {run.name}
114
+ </span>
115
+ </Header>
116
+ </TooltipTrigger>
117
+ {isHeadingTruncated ? (
118
+ <TooltipContent>
119
+ <Text as="span" size="xs" className="max-w-[360px] break-words">
120
+ {run.name}
121
+ </Text>
122
+ </TooltipContent>
123
+ ) : null}
124
+ </Tooltip>
125
+ </li>
126
+ </ol>
127
+ </nav>
128
+
129
+ {hasAction ? (
130
+ <div className="col-start-2 row-start-1 flex min-w-max items-center gap-6 justify-self-end max-[480px]:col-start-auto max-[480px]:row-start-auto max-[480px]:justify-self-start">
131
+ <WorkflowRunActionSlot
132
+ action={action}
133
+ cancelling={cancelling}
134
+ onCancel={onCancel}
135
+ rerunPending={rerunPending}
136
+ onRerun={onRerun}
137
+ />
138
+ </div>
139
+ ) : null}
140
+
141
+ <div className="col-span-2 row-start-2 flex min-w-0 items-center gap-12 overflow-hidden text-foreground-neutral-subtle max-[480px]:col-span-1 max-[480px]:row-start-auto">
142
+ {run.number !== null ? (
143
+ <>
144
+ <WorkflowRunNumberLabel run={run} />
145
+ {attemptSwitcher || run.triggerDisplayLabel ? <MetadataSeparator /> : null}
146
+ </>
132
147
  ) : null}
133
- </div>
134
148
 
135
- <div className="col-span-2 row-start-2 flex min-w-0 items-center gap-12 overflow-hidden text-foreground-neutral-muted">
136
149
  {attemptSwitcher ? (
137
150
  <WorkflowRunAttemptSwitcher
138
- workspaceId={attemptSwitcher.workspaceId}
139
- projectId={attemptSwitcher.projectId}
151
+ workspaceSlug={attemptSwitcher.workspaceSlug}
152
+ projectSlug={attemptSwitcher.projectSlug}
140
153
  run={run}
141
154
  latestAttempt={attemptSwitcher.latestAttempt}
142
155
  />
@@ -151,7 +164,7 @@ export function WorkflowRunSummary({
151
164
  <button
152
165
  type="button"
153
166
  aria-label={run.triggerLabel}
154
- className="inline-flex max-w-full min-w-0 items-center gap-4 rounded-6 border-0 bg-transparent p-0 text-left text-foreground-neutral-muted outline-none focus-visible:shadow-button-neutral-focus"
167
+ className="inline-flex max-w-full min-w-0 cursor-help items-center gap-4 rounded-6 border-0 bg-transparent p-0 text-left text-foreground-neutral-subtle outline-none focus-visible:shadow-button-neutral-focus"
155
168
  >
156
169
  <TriggerSourceIcon
157
170
  provider={run.triggerProvider}
@@ -174,10 +187,12 @@ export function WorkflowRunSummary({
174
187
  </>
175
188
  ) : null}
176
189
 
177
- {attemptSwitcher || run.triggerDisplayLabel ? <MetadataSeparator /> : null}
190
+ {run.number !== null || attemptSwitcher || run.triggerDisplayLabel ? (
191
+ <MetadataSeparator />
192
+ ) : null}
178
193
  <RelativeTime
179
194
  value={run.runAttempt.createdAt}
180
- className="shrink-0 whitespace-nowrap text-xs leading-20 text-foreground-neutral-muted"
195
+ className="shrink-0 whitespace-nowrap text-xs leading-20 text-foreground-neutral-subtle"
181
196
  />
182
197
 
183
198
  {displayDuration ? (
@@ -185,7 +200,7 @@ export function WorkflowRunSummary({
185
200
  <MetadataSeparator />
186
201
  <WorkflowRunDurationLabel
187
202
  duration={displayDuration}
188
- className="text-foreground-neutral-muted"
203
+ className="text-foreground-neutral-subtle"
189
204
  />
190
205
  </>
191
206
  ) : null}
@@ -280,6 +295,16 @@ function workflowRunActionForRun(run: WorkflowRunDetail): WorkflowRunAction {
280
295
  return 'rerun-menu';
281
296
  }
282
297
 
298
+ function canRenderWorkflowRunAction(
299
+ action: WorkflowRunAction,
300
+ onCancel: (() => void) | undefined,
301
+ onRerun: ((mode: WorkflowRunRerunMode) => void) | undefined,
302
+ ): boolean {
303
+ if (action === 'cancel') return onCancel !== undefined;
304
+ if (action === 'rerun-all' || action === 'rerun-menu') return onRerun !== undefined;
305
+ return false;
306
+ }
307
+
283
308
  function hasFailedOrCancelledJobs(run: WorkflowRunDetail): boolean {
284
309
  if (!workflowRunHasJobs(run)) return false;
285
310
 
@@ -290,6 +315,6 @@ function workflowRunHasJobs(run: WorkflowRunDetail): run is WorkflowRunDetail &
290
315
  return 'jobs' in run && Array.isArray(run.jobs);
291
316
  }
292
317
 
293
- function MetadataSeparator() {
318
+ export function MetadataSeparator() {
294
319
  return <span aria-hidden="true" className="h-12 w-px shrink-0 bg-border-neutral-base" />;
295
320
  }
@@ -0,0 +1,5 @@
1
+ export {RunAnnotationSummaryLine} from './run-annotation-summary-line.js';
2
+ export {RunAnnotationsEmpty} from './run-annotations-empty.js';
3
+ export {RunJobsList} from './run-jobs-list.js';
4
+ export {RunTabCount} from './run-tab-count.js';
5
+ export {RunTabStrip} from './run-tab-strip.js';
@@ -0,0 +1,69 @@
1
+ import {
2
+ createMemoryHistory,
3
+ createRootRoute,
4
+ createRoute,
5
+ createRouter,
6
+ Outlet,
7
+ RouterProvider,
8
+ } from '@tanstack/react-router';
9
+ import {act, render, screen} from '@testing-library/react';
10
+ import type {RunAnnotationSummary} from '#core/workflow-run-tabs.js';
11
+ import type {WorkflowRunsSearch} from '#routes/inputs.js';
12
+ import {RunAnnotationSummaryLine} from './run-annotation-summary-line.js';
13
+
14
+ const ANNOTATION_SUMMARY: RunAnnotationSummary = {
15
+ total: 5,
16
+ error: 2,
17
+ warning: 1,
18
+ info: 2,
19
+ success: 0,
20
+ };
21
+
22
+ describe('RunAnnotationSummaryLine', () => {
23
+ test('links the severity breakdown into the Annotations panel', async () => {
24
+ await renderSummaryLine();
25
+
26
+ expect(screen.getByText('5 annotations')).toBeInTheDocument();
27
+ expect(screen.getByRole('link', {name: '2 errors'})).toHaveAttribute(
28
+ 'href',
29
+ '/w/acme/p/project/runs/run-1?tab=annotations&severity=error',
30
+ );
31
+ });
32
+
33
+ test('clears a stale annotation when linking to a severity', async () => {
34
+ await renderSummaryLine({annotation: 'annotation-old'});
35
+
36
+ expect(screen.getByRole('link', {name: '2 errors'})).toHaveAttribute(
37
+ 'href',
38
+ '/w/acme/p/project/runs/run-1?tab=annotations&severity=error',
39
+ );
40
+ });
41
+ });
42
+
43
+ async function renderSummaryLine(search: WorkflowRunsSearch = {}) {
44
+ const rootRoute = createRootRoute({component: Outlet});
45
+ const runRoute = createRoute({
46
+ getParentRoute: () => rootRoute,
47
+ path: '/w/$workspaceSlug/p/$projectSlug/runs/$workflowRunId',
48
+ component: () => (
49
+ <RunAnnotationSummaryLine
50
+ summary={ANNOTATION_SUMMARY}
51
+ workspaceSlug="acme"
52
+ projectSlug="project"
53
+ workflowRunId="run-1"
54
+ search={search}
55
+ />
56
+ ),
57
+ });
58
+ const router = createRouter({
59
+ history: createMemoryHistory({initialEntries: ['/w/acme/p/project/runs/run-1']}),
60
+ routeTree: rootRoute.addChildren([runRoute]),
61
+ });
62
+ await router.load();
63
+ let result: ReturnType<typeof render> | undefined;
64
+ await act(() => {
65
+ result = render(<RouterProvider router={router} />);
66
+ });
67
+ if (!result) throw new Error('Run annotation summary did not render.');
68
+ return result;
69
+ }
@@ -0,0 +1,97 @@
1
+ import {Text} from '@shipfox/react-ui/typography';
2
+ import {Link} from '@tanstack/react-router';
3
+ import type {RunAnnotationSummary} from '#core/workflow-run-tabs.js';
4
+ import {
5
+ WORKFLOW_RUN_ANNOTATION_SEVERITIES,
6
+ type WorkflowRunAnnotationSeverity,
7
+ type WorkflowRunsSearch,
8
+ workflowRunSearchParams,
9
+ } from '#routes/inputs.js';
10
+ import {MetadataSeparator} from '../workflow-run-summary/workflow-run-summary.js';
11
+
12
+ export interface RunAnnotationSummaryLineProps {
13
+ summary?: RunAnnotationSummary | undefined;
14
+ workspaceSlug?: string | undefined;
15
+ projectSlug?: string | undefined;
16
+ workflowRunId?: string | undefined;
17
+ search?: WorkflowRunsSearch | undefined;
18
+ }
19
+
20
+ export function RunAnnotationSummaryLine({
21
+ summary,
22
+ workspaceSlug,
23
+ projectSlug,
24
+ workflowRunId,
25
+ search = {},
26
+ }: RunAnnotationSummaryLineProps) {
27
+ if (!summary) return null;
28
+
29
+ const visibleSeverities = WORKFLOW_RUN_ANNOTATION_SEVERITIES.filter(
30
+ (severity) => summary[severity] > 0,
31
+ );
32
+
33
+ return (
34
+ <div className="flex flex-wrap items-center gap-x-8 gap-y-4 text-foreground-neutral-subtle">
35
+ <Text as="span" size="xs" className="shrink-0 text-foreground-neutral-subtle">
36
+ {summary.total} {pluralize('annotation', summary.total)}
37
+ </Text>
38
+ {visibleSeverities.map((severity) => (
39
+ <span key={severity} className="inline-flex shrink-0 items-center gap-8">
40
+ <MetadataSeparator />
41
+ <SeverityLink
42
+ count={summary[severity]}
43
+ severity={severity}
44
+ workspaceSlug={workspaceSlug}
45
+ projectSlug={projectSlug}
46
+ workflowRunId={workflowRunId}
47
+ search={search}
48
+ />
49
+ </span>
50
+ ))}
51
+ </div>
52
+ );
53
+ }
54
+
55
+ function SeverityLink({
56
+ count,
57
+ severity,
58
+ workspaceSlug,
59
+ projectSlug,
60
+ workflowRunId,
61
+ search,
62
+ }: {
63
+ count: number;
64
+ severity: WorkflowRunAnnotationSeverity;
65
+ workspaceSlug?: string | undefined;
66
+ projectSlug?: string | undefined;
67
+ workflowRunId?: string | undefined;
68
+ search: WorkflowRunsSearch;
69
+ }) {
70
+ const label = `${count} ${pluralize(severity, count)}`;
71
+ if (!workspaceSlug || !projectSlug || !workflowRunId) {
72
+ return <span className="text-foreground-highlight-interactive">{label}</span>;
73
+ }
74
+
75
+ const searchWithoutAnnotation = {...search};
76
+ delete searchWithoutAnnotation.annotation;
77
+
78
+ return (
79
+ <Link
80
+ to="/w/$workspaceSlug/p/$projectSlug/runs/$workflowRunId"
81
+ params={{workspaceSlug, projectSlug, workflowRunId}}
82
+ search={
83
+ workflowRunSearchParams(
84
+ {...searchWithoutAnnotation, tab: 'annotations', severity},
85
+ search,
86
+ ) as never
87
+ }
88
+ className="text-foreground-highlight-interactive outline-none hover:underline focus-visible:shadow-border-interactive-with-active"
89
+ >
90
+ {label}
91
+ </Link>
92
+ );
93
+ }
94
+
95
+ function pluralize(word: string, count: number): string {
96
+ return count === 1 ? word : `${word}s`;
97
+ }
@@ -0,0 +1,11 @@
1
+ import {EmptyState} from '@shipfox/react-ui/empty-state';
2
+
3
+ export function RunAnnotationsEmpty() {
4
+ return (
5
+ <EmptyState
6
+ icon="fileDamageLine"
7
+ title="No annotations"
8
+ description="This run has no annotations to show."
9
+ />
10
+ );
11
+ }