@shipfox/client-workflows 14.0.1 → 16.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 (272) hide show
  1. package/.storybook/preview.css +4 -0
  2. package/.storybook/preview.tsx +7 -2
  3. package/.turbo/turbo-build.log +1 -1
  4. package/CHANGELOG.md +42 -0
  5. package/dist/components/job-detail/agent-config-failure-callout.d.ts.map +1 -0
  6. package/dist/components/{workflow-run-view → job-detail}/agent-config-failure-callout.js +2 -2
  7. package/dist/components/job-detail/agent-config-failure-callout.js.map +1 -0
  8. package/dist/components/job-detail/job-detail-header.d.ts +15 -0
  9. package/dist/components/job-detail/job-detail-header.d.ts.map +1 -0
  10. package/dist/components/job-detail/job-detail-header.js +124 -0
  11. package/dist/components/job-detail/job-detail-header.js.map +1 -0
  12. package/dist/components/job-detail/job-detail-view.d.ts +16 -0
  13. package/dist/components/job-detail/job-detail-view.d.ts.map +1 -0
  14. package/dist/components/job-detail/job-detail-view.js +420 -0
  15. package/dist/components/job-detail/job-detail-view.js.map +1 -0
  16. package/dist/components/job-detail/job-empty-states.d.ts +10 -0
  17. package/dist/components/job-detail/job-empty-states.d.ts.map +1 -0
  18. package/dist/components/job-detail/job-empty-states.js +195 -0
  19. package/dist/components/job-detail/job-empty-states.js.map +1 -0
  20. package/dist/components/job-detail/job-execution-switcher.d.ts.map +1 -0
  21. package/dist/components/job-detail/job-execution-switcher.js.map +1 -0
  22. package/dist/components/job-detail/job-execution-time-text.d.ts.map +1 -0
  23. package/dist/components/job-detail/job-execution-time-text.js.map +1 -0
  24. package/dist/components/job-detail/job-selection.d.ts +24 -0
  25. package/dist/components/job-detail/job-selection.d.ts.map +1 -0
  26. package/dist/components/job-detail/job-selection.js +66 -0
  27. package/dist/components/job-detail/job-selection.js.map +1 -0
  28. package/dist/components/job-detail/step-attempt-log-panel.d.ts +15 -0
  29. package/dist/components/job-detail/step-attempt-log-panel.d.ts.map +1 -0
  30. package/dist/components/{workflow-run-view → job-detail}/step-attempt-log-panel.js +61 -16
  31. package/dist/components/job-detail/step-attempt-log-panel.js.map +1 -0
  32. package/dist/components/job-graph/job-graph-content.js +1 -1
  33. package/dist/components/job-graph/job-graph-content.js.map +1 -1
  34. package/dist/components/step-list/step-list-model.js +2 -2
  35. package/dist/components/step-list/step-list-model.js.map +1 -1
  36. package/dist/components/step-list/step-list.d.ts +1 -0
  37. package/dist/components/step-list/step-list.d.ts.map +1 -1
  38. package/dist/components/step-list/step-list.js +29 -4
  39. package/dist/components/step-list/step-list.js.map +1 -1
  40. package/dist/components/workflow-run-duration-label.d.ts +9 -4
  41. package/dist/components/workflow-run-duration-label.d.ts.map +1 -1
  42. package/dist/components/workflow-run-duration-label.js +27 -7
  43. package/dist/components/workflow-run-duration-label.js.map +1 -1
  44. package/dist/components/workflow-run-list/workflow-run-row.d.ts.map +1 -1
  45. package/dist/components/workflow-run-list/workflow-run-row.js +6 -5
  46. package/dist/components/workflow-run-list/workflow-run-row.js.map +1 -1
  47. package/dist/components/workflow-run-summary/workflow-run-summary.d.ts.map +1 -1
  48. package/dist/components/workflow-run-summary/workflow-run-summary.js +69 -64
  49. package/dist/components/workflow-run-summary/workflow-run-summary.js.map +1 -1
  50. package/dist/components/workflow-run-tabs/index.d.ts +3 -3
  51. package/dist/components/workflow-run-tabs/index.d.ts.map +1 -1
  52. package/dist/components/workflow-run-tabs/index.js +3 -3
  53. package/dist/components/workflow-run-tabs/index.js.map +1 -1
  54. package/dist/components/workflow-run-tabs/run-annotation-count-chip.d.ts +16 -0
  55. package/dist/components/workflow-run-tabs/run-annotation-count-chip.d.ts.map +1 -0
  56. package/dist/components/workflow-run-tabs/run-annotation-count-chip.js +50 -0
  57. package/dist/components/workflow-run-tabs/run-annotation-count-chip.js.map +1 -0
  58. package/dist/components/workflow-run-tabs/run-annotation-item.d.ts +20 -0
  59. package/dist/components/workflow-run-tabs/run-annotation-item.d.ts.map +1 -0
  60. package/dist/components/workflow-run-tabs/run-annotation-item.js +102 -0
  61. package/dist/components/workflow-run-tabs/run-annotation-item.js.map +1 -0
  62. package/dist/components/workflow-run-tabs/run-annotation-list.d.ts +32 -0
  63. package/dist/components/workflow-run-tabs/run-annotation-list.d.ts.map +1 -0
  64. package/dist/components/workflow-run-tabs/run-annotation-list.js +202 -0
  65. package/dist/components/workflow-run-tabs/run-annotation-list.js.map +1 -0
  66. package/dist/components/workflow-run-tabs/run-annotation-summary-line.d.ts +1 -1
  67. package/dist/components/workflow-run-tabs/run-annotation-summary-line.d.ts.map +1 -1
  68. package/dist/components/workflow-run-tabs/run-annotation-summary-line.js +87 -22
  69. package/dist/components/workflow-run-tabs/run-annotation-summary-line.js.map +1 -1
  70. package/dist/components/workflow-run-tabs/run-annotations-empty.d.ts +3 -1
  71. package/dist/components/workflow-run-tabs/run-annotations-empty.d.ts.map +1 -1
  72. package/dist/components/workflow-run-tabs/run-annotations-empty.js +2 -2
  73. package/dist/components/workflow-run-tabs/run-annotations-empty.js.map +1 -1
  74. package/dist/components/workflow-run-tabs/severity-visuals.d.ts +20 -0
  75. package/dist/components/workflow-run-tabs/severity-visuals.d.ts.map +1 -0
  76. package/dist/components/workflow-run-tabs/severity-visuals.js +31 -0
  77. package/dist/components/workflow-run-tabs/severity-visuals.js.map +1 -0
  78. package/dist/components/workflow-run-view/index.d.ts +0 -1
  79. package/dist/components/workflow-run-view/index.d.ts.map +1 -1
  80. package/dist/components/workflow-run-view/index.js +0 -1
  81. package/dist/components/workflow-run-view/index.js.map +1 -1
  82. package/dist/components/workflow-run-view/run-workspace-nav.d.ts +20 -0
  83. package/dist/components/workflow-run-view/run-workspace-nav.d.ts.map +1 -0
  84. package/dist/components/workflow-run-view/run-workspace-nav.js +277 -0
  85. package/dist/components/workflow-run-view/run-workspace-nav.js.map +1 -0
  86. package/dist/components/workflow-run-view/workflow-run-selection.d.ts.map +1 -1
  87. package/dist/components/workflow-run-view/workflow-run-selection.js +1 -14
  88. package/dist/components/workflow-run-view/workflow-run-selection.js.map +1 -1
  89. package/dist/components/workflow-run-view/workflow-run-view.d.ts +10 -10
  90. package/dist/components/workflow-run-view/workflow-run-view.d.ts.map +1 -1
  91. package/dist/components/workflow-run-view/workflow-run-view.js +351 -239
  92. package/dist/components/workflow-run-view/workflow-run-view.js.map +1 -1
  93. package/dist/components/workflow-status/status-visuals.d.ts.map +1 -1
  94. package/dist/components/workflow-status/status-visuals.js +10 -2
  95. package/dist/components/workflow-status/status-visuals.js.map +1 -1
  96. package/dist/components/workflow-status/workflow-status-icon.d.ts +3 -1
  97. package/dist/components/workflow-status/workflow-status-icon.d.ts.map +1 -1
  98. package/dist/components/workflow-status/workflow-status-icon.js +13 -2
  99. package/dist/components/workflow-status/workflow-status-icon.js.map +1 -1
  100. package/dist/core/entities/job-execution.d.ts +9 -0
  101. package/dist/core/entities/job-execution.d.ts.map +1 -1
  102. package/dist/core/entities/job-execution.js +13 -3
  103. package/dist/core/entities/job-execution.js.map +1 -1
  104. package/dist/core/entities/job.d.ts.map +1 -1
  105. package/dist/core/entities/step-attempt.d.ts.map +1 -1
  106. package/dist/core/entities/step.d.ts +2 -0
  107. package/dist/core/entities/step.d.ts.map +1 -1
  108. package/dist/core/entities/step.js +13 -0
  109. package/dist/core/entities/step.js.map +1 -1
  110. package/dist/core/entities/workflow-run-attempt.d.ts.map +1 -1
  111. package/dist/core/entities/workflow-run.d.ts +56 -1
  112. package/dist/core/entities/workflow-run.d.ts.map +1 -1
  113. package/dist/core/entities/workflow-run.js +98 -0
  114. package/dist/core/entities/workflow-run.js.map +1 -1
  115. package/dist/core/run-annotation.d.ts +73 -0
  116. package/dist/core/run-annotation.d.ts.map +1 -0
  117. package/dist/core/run-annotation.js +125 -0
  118. package/dist/core/run-annotation.js.map +1 -0
  119. package/dist/core/workflow-run.d.ts +4 -4
  120. package/dist/core/workflow-run.d.ts.map +1 -1
  121. package/dist/core/workflow-run.js +3 -3
  122. package/dist/core/workflow-run.js.map +1 -1
  123. package/dist/feature.d.ts +20 -16
  124. package/dist/feature.d.ts.map +1 -1
  125. package/dist/feature.js +5 -0
  126. package/dist/feature.js.map +1 -1
  127. package/dist/hooks/api/run-annotation-mapper.d.ts +10 -0
  128. package/dist/hooks/api/run-annotation-mapper.d.ts.map +1 -0
  129. package/dist/hooks/api/run-annotation-mapper.js +22 -0
  130. package/dist/hooks/api/run-annotation-mapper.js.map +1 -0
  131. package/dist/hooks/api/run-annotations.d.ts +25 -0
  132. package/dist/hooks/api/run-annotations.d.ts.map +1 -0
  133. package/dist/hooks/api/run-annotations.js +78 -0
  134. package/dist/hooks/api/run-annotations.js.map +1 -0
  135. package/dist/hooks/api/workflow-runs.d.ts +5 -3
  136. package/dist/hooks/api/workflow-runs.d.ts.map +1 -1
  137. package/dist/hooks/api/workflow-runs.js +5 -4
  138. package/dist/hooks/api/workflow-runs.js.map +1 -1
  139. package/dist/index.d.ts +1 -0
  140. package/dist/index.d.ts.map +1 -1
  141. package/dist/index.js +1 -0
  142. package/dist/index.js.map +1 -1
  143. package/dist/pages/workflow-job-detail-page.d.ts +11 -0
  144. package/dist/pages/workflow-job-detail-page.d.ts.map +1 -0
  145. package/dist/pages/workflow-job-detail-page.js +66 -0
  146. package/dist/pages/workflow-job-detail-page.js.map +1 -0
  147. package/dist/pages/workflow-run-detail-page.d.ts.map +1 -1
  148. package/dist/pages/workflow-run-detail-page.js +4 -37
  149. package/dist/pages/workflow-run-detail-page.js.map +1 -1
  150. package/dist/routes/inputs.d.ts +52 -34
  151. package/dist/routes/inputs.d.ts.map +1 -1
  152. package/dist/routes/inputs.js +74 -43
  153. package/dist/routes/inputs.js.map +1 -1
  154. package/dist/routes/job-detail.d.ts +10 -0
  155. package/dist/routes/job-detail.d.ts.map +1 -0
  156. package/dist/routes/job-detail.js +27 -0
  157. package/dist/routes/job-detail.js.map +1 -0
  158. package/dist/routes/project-route.d.ts +1 -1
  159. package/dist/routes/run-detail.d.ts.map +1 -1
  160. package/dist/routes/runs.d.ts.map +1 -1
  161. package/dist/routes/workflows.d.ts.map +1 -1
  162. package/dist/tsconfig.test.tsbuildinfo +1 -1
  163. package/package.json +10 -9
  164. package/src/components/{workflow-run-view → job-detail}/agent-config-failure-callout.stories.tsx +1 -1
  165. package/src/components/{workflow-run-view → job-detail}/agent-config-failure-callout.tsx +2 -2
  166. package/src/components/job-detail/job-detail-header.test.tsx +34 -0
  167. package/src/components/job-detail/job-detail-header.tsx +136 -0
  168. package/src/components/job-detail/job-detail-view.stories.tsx +531 -0
  169. package/src/components/job-detail/job-detail-view.tsx +467 -0
  170. package/src/components/job-detail/job-empty-states.test.ts +27 -0
  171. package/src/components/job-detail/job-empty-states.tsx +248 -0
  172. package/src/components/job-detail/job-selection.test.ts +188 -0
  173. package/src/components/job-detail/job-selection.ts +96 -0
  174. package/src/components/{workflow-run-view → job-detail}/step-attempt-log-panel.test.tsx +32 -9
  175. package/src/components/{workflow-run-view → job-detail}/step-attempt-log-panel.tsx +52 -18
  176. package/src/components/job-graph/job-graph-content.tsx +1 -1
  177. package/src/components/step-list/step-list-model.test.ts +1 -1
  178. package/src/components/step-list/step-list-model.ts +2 -2
  179. package/src/components/step-list/step-list.stories.tsx +1 -1
  180. package/src/components/step-list/step-list.test.tsx +33 -0
  181. package/src/components/step-list/step-list.tsx +40 -9
  182. package/src/components/workflow-run-duration-label.tsx +50 -12
  183. package/src/components/workflow-run-list/workflow-run-row.tsx +6 -4
  184. package/src/components/workflow-run-summary/workflow-run-summary.stories.tsx +38 -0
  185. package/src/components/workflow-run-summary/workflow-run-summary.test.tsx +79 -5
  186. package/src/components/workflow-run-summary/workflow-run-summary.tsx +56 -60
  187. package/src/components/workflow-run-tabs/index.ts +3 -3
  188. package/src/components/workflow-run-tabs/run-annotation-count-chip.tsx +63 -0
  189. package/src/components/workflow-run-tabs/run-annotation-item.tsx +123 -0
  190. package/src/components/workflow-run-tabs/run-annotation-list.tsx +258 -0
  191. package/src/components/workflow-run-tabs/run-annotation-summary-line.test.tsx +77 -3
  192. package/src/components/workflow-run-tabs/run-annotation-summary-line.tsx +92 -17
  193. package/src/components/workflow-run-tabs/run-annotations-empty.tsx +6 -2
  194. package/src/components/workflow-run-tabs/run-annotations.stories.tsx +293 -0
  195. package/src/components/workflow-run-tabs/severity-visuals.ts +37 -0
  196. package/src/components/workflow-run-view/index.ts +0 -1
  197. package/src/components/workflow-run-view/run-workspace-nav.test.tsx +286 -0
  198. package/src/components/workflow-run-view/run-workspace-nav.tsx +314 -0
  199. package/src/components/workflow-run-view/workflow-run-selection.ts +1 -23
  200. package/src/components/workflow-run-view/workflow-run-view.test.tsx +237 -1085
  201. package/src/components/workflow-run-view/workflow-run-view.tsx +450 -323
  202. package/src/components/workflow-status/status-visuals.ts +5 -2
  203. package/src/components/workflow-status/workflow-status-icon.stories.tsx +1 -0
  204. package/src/components/workflow-status/workflow-status-icon.tsx +26 -1
  205. package/src/core/entities/job-execution.ts +17 -2
  206. package/src/core/entities/step.ts +26 -0
  207. package/src/core/entities/workflow-run-display.test.ts +159 -0
  208. package/src/core/entities/workflow-run.ts +139 -1
  209. package/src/core/run-annotation.test.ts +315 -0
  210. package/src/core/run-annotation.ts +238 -0
  211. package/src/core/workflow-run.ts +16 -1
  212. package/src/feature.ts +5 -0
  213. package/src/hooks/api/run-annotation-mapper.ts +30 -0
  214. package/src/hooks/api/run-annotations.ts +137 -0
  215. package/src/hooks/api/workflow-runs.ts +6 -2
  216. package/src/index.ts +1 -0
  217. package/src/page-harness-budget.test.ts +1 -0
  218. package/src/pages/workflow-job-detail-page.test.tsx +605 -0
  219. package/src/pages/workflow-job-detail-page.tsx +84 -0
  220. package/src/pages/workflow-run-detail-page.tsx +3 -43
  221. package/src/pages/workflow-run-pages.test.tsx +77 -332
  222. package/src/routes/inputs.test.ts +56 -6
  223. package/src/routes/inputs.ts +71 -19
  224. package/src/routes/job-detail.tsx +29 -0
  225. package/test/pages.tsx +25 -3
  226. package/test/render.tsx +6 -1
  227. package/tsconfig.build.tsbuildinfo +1 -1
  228. package/dist/components/workflow-run-tabs/run-jobs-list.d.ts +0 -18
  229. package/dist/components/workflow-run-tabs/run-jobs-list.d.ts.map +0 -1
  230. package/dist/components/workflow-run-tabs/run-jobs-list.js +0 -107
  231. package/dist/components/workflow-run-tabs/run-jobs-list.js.map +0 -1
  232. package/dist/components/workflow-run-tabs/run-tab-count.d.ts +0 -6
  233. package/dist/components/workflow-run-tabs/run-tab-count.d.ts.map +0 -1
  234. package/dist/components/workflow-run-tabs/run-tab-count.js +0 -27
  235. package/dist/components/workflow-run-tabs/run-tab-count.js.map +0 -1
  236. package/dist/components/workflow-run-tabs/run-tab-strip.d.ts +0 -8
  237. package/dist/components/workflow-run-tabs/run-tab-strip.d.ts.map +0 -1
  238. package/dist/components/workflow-run-tabs/run-tab-strip.js +0 -82
  239. package/dist/components/workflow-run-tabs/run-tab-strip.js.map +0 -1
  240. package/dist/components/workflow-run-view/agent-config-failure-callout.d.ts.map +0 -1
  241. package/dist/components/workflow-run-view/agent-config-failure-callout.js.map +0 -1
  242. package/dist/components/workflow-run-view/job-card.d.ts +0 -17
  243. package/dist/components/workflow-run-view/job-card.d.ts.map +0 -1
  244. package/dist/components/workflow-run-view/job-card.js +0 -496
  245. package/dist/components/workflow-run-view/job-card.js.map +0 -1
  246. package/dist/components/workflow-run-view/job-execution-switcher.d.ts.map +0 -1
  247. package/dist/components/workflow-run-view/job-execution-switcher.js.map +0 -1
  248. package/dist/components/workflow-run-view/job-execution-time-text.d.ts.map +0 -1
  249. package/dist/components/workflow-run-view/job-execution-time-text.js.map +0 -1
  250. package/dist/components/workflow-run-view/step-attempt-log-panel.d.ts +0 -9
  251. package/dist/components/workflow-run-view/step-attempt-log-panel.d.ts.map +0 -1
  252. package/dist/components/workflow-run-view/step-attempt-log-panel.js.map +0 -1
  253. package/dist/core/workflow-run-tabs.d.ts +0 -9
  254. package/dist/core/workflow-run-tabs.d.ts.map +0 -1
  255. package/dist/core/workflow-run-tabs.js +0 -3
  256. package/dist/core/workflow-run-tabs.js.map +0 -1
  257. package/src/components/workflow-run-tabs/run-jobs-list.tsx +0 -151
  258. package/src/components/workflow-run-tabs/run-tab-count.tsx +0 -28
  259. package/src/components/workflow-run-tabs/run-tab-strip.stories.tsx +0 -52
  260. package/src/components/workflow-run-tabs/run-tab-strip.test.tsx +0 -117
  261. package/src/components/workflow-run-tabs/run-tab-strip.tsx +0 -97
  262. package/src/components/workflow-run-view/job-card.stories.tsx +0 -789
  263. package/src/components/workflow-run-view/job-card.test.tsx +0 -115
  264. package/src/components/workflow-run-view/job-card.tsx +0 -614
  265. package/src/core/workflow-run-tabs.ts +0 -8
  266. /package/dist/components/{workflow-run-view → job-detail}/agent-config-failure-callout.d.ts +0 -0
  267. /package/dist/components/{workflow-run-view → job-detail}/job-execution-switcher.d.ts +0 -0
  268. /package/dist/components/{workflow-run-view → job-detail}/job-execution-switcher.js +0 -0
  269. /package/dist/components/{workflow-run-view → job-detail}/job-execution-time-text.d.ts +0 -0
  270. /package/dist/components/{workflow-run-view → job-detail}/job-execution-time-text.js +0 -0
  271. /package/src/components/{workflow-run-view → job-detail}/job-execution-switcher.tsx +0 -0
  272. /package/src/components/{workflow-run-view → job-detail}/job-execution-time-text.tsx +0 -0
@@ -0,0 +1,605 @@
1
+ import {configureApiClient} from '@shipfox/client-api';
2
+ import {act, screen} from '@testing-library/react';
3
+ import userEvent from '@testing-library/user-event';
4
+ import {workflowRunsQueryKeys} from '#hooks/api/workflow-runs.js';
5
+ import type {WorkflowJobSearch, WorkflowRunsSearch} from '#routes/inputs.js';
6
+ import {
7
+ runAttemptsResponseDto,
8
+ workflowJobDto,
9
+ workflowJobExecutionDto,
10
+ workflowRunAttemptDto,
11
+ workflowRunDetailDto,
12
+ workflowStepAttemptDto,
13
+ workflowStepDto,
14
+ } from '#test/fixtures/workflow-run.js';
15
+ import {jsonResponse, PROJECT_TEST_WSLUG, renderProjectPage} from '#test/pages.js';
16
+ import {WorkflowJobDetailPage} from './workflow-job-detail-page.js';
17
+ import {WorkflowRunDetailPage} from './workflow-run-detail-page.js';
18
+
19
+ const RUN_ID = '66666666-6666-4666-8666-666666666666';
20
+ const JOB_ID = '88888888-8888-4888-8888-888888888888';
21
+ const EXECUTION_ID = '77777777-7777-4777-8777-777777777777';
22
+ const STEP_ID = '99999999-9999-4999-8999-999999999999';
23
+ const ATTEMPT_ID = 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa';
24
+ const LIVE_JOB_ID = 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb';
25
+ const LIVE_EXECUTION_ID = 'cccccccc-cccc-4ccc-8ccc-cccccccccccc';
26
+ const LIVE_BUILD_STEP_ID = 'dddddddd-dddd-4ddd-8ddd-dddddddddddd';
27
+ const LIVE_DEPLOY_STEP_ID = 'eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee';
28
+ const LIVE_BUILD_ATTEMPT_ID = 'ffffffff-ffff-4fff-8fff-ffffffffffff';
29
+ const LIVE_DEPLOY_ATTEMPT_ID = '11111111-2222-4333-8444-555555555555';
30
+ const NEWER_JOB_ID = '22222222-3333-4444-8555-666666666666';
31
+ const SIBLING_JOB_ID = '33333333-4444-4555-8666-777777777777';
32
+ const BACK_TO_SUMMARY_PATTERN = /Back to run summary/;
33
+ const RUN_MOVED_ON_PATTERN = /Run moved on to/;
34
+ const LINT_LINK_PATTERN = /lint/;
35
+ const RELEASE_LINK_PATTERN = /release/;
36
+ const ANNOTATION_LINK_PATTERN = /annotation/;
37
+
38
+ describe('WorkflowJobDetailPage', () => {
39
+ beforeEach(() => {
40
+ jobAnnotations.value = [];
41
+ });
42
+
43
+ test('links the job to its annotations without rendering one', async () => {
44
+ jobAnnotations.value = [
45
+ {
46
+ id: 'aaaaaaaa-1111-4aaa-8aaa-000000000001',
47
+ job_id: JOB_ID,
48
+ job_execution_id: EXECUTION_ID,
49
+ origin_step_id: STEP_ID,
50
+ origin_step_attempt: 2,
51
+ context: 'smoke check',
52
+ style: 'error',
53
+ sequence: 1,
54
+ body: 'Task nine failed.',
55
+ },
56
+ {
57
+ id: 'aaaaaaaa-1111-4aaa-8aaa-000000000002',
58
+ job_id: SIBLING_JOB_ID,
59
+ job_execution_id: EXECUTION_ID,
60
+ origin_step_id: STEP_ID,
61
+ origin_step_attempt: 1,
62
+ context: 'other job',
63
+ style: 'info',
64
+ sequence: 2,
65
+ body: 'Not this job.',
66
+ },
67
+ ];
68
+ configureApiClient({fetchImpl: vi.fn(jobDetailFetch)});
69
+
70
+ renderJobPath(`?jobExecution=${EXECUTION_ID}&runAttempt=1`);
71
+
72
+ const chip = await screen.findByRole('link', {
73
+ name: 'View 1 annotation, highest severity error',
74
+ });
75
+ expect(chip.getAttribute('href')).toContain(`tab=annotations`);
76
+ expect(chip.getAttribute('href')).toContain(`job=${JOB_ID}`);
77
+ expect(screen.queryByText('Task nine failed.')).not.toBeInTheDocument();
78
+ });
79
+
80
+ test('omits the annotation chip when the job produced none', async () => {
81
+ configureApiClient({fetchImpl: vi.fn(jobDetailFetch)});
82
+
83
+ renderJobPath(`?jobExecution=${EXECUTION_ID}&runAttempt=1`);
84
+
85
+ await screen.findByRole('heading', {name: 'release'});
86
+ expect(screen.queryByRole('link', {name: ANNOTATION_LINK_PATTERN})).not.toBeInTheDocument();
87
+ });
88
+
89
+ test('resolves an exact job execution and step attempt from a deep link', async () => {
90
+ configureApiClient({fetchImpl: vi.fn(jobDetailFetch)});
91
+
92
+ renderJobPath(
93
+ `?jobExecution=${EXECUTION_ID}&step=${STEP_ID}&stepAttempt=${ATTEMPT_ID}&runAttempt=1`,
94
+ );
95
+
96
+ expect(await screen.findByRole('heading', {name: 'release'})).toHaveFocus();
97
+ expect(
98
+ screen.getByRole('button', {
99
+ name: 'Switch job execution, currently execution 2: release',
100
+ }),
101
+ ).toBeInTheDocument();
102
+ expect(
103
+ await screen.findByRole('region', {name: 'tests output, attempt 2'}),
104
+ ).toBeInTheDocument();
105
+ expect(screen.getByRole('region', {name: 'release logs'})).toBeInTheDocument();
106
+ expect(screen.queryByRole('tab')).not.toBeInTheDocument();
107
+ expect(screen.getByRole('img', {name: 'Job status: Succeeded'})).toBeInTheDocument();
108
+ expect(screen.getByRole('heading', {name: 'deploy-web'})).toBeInTheDocument();
109
+ expect(screen.getByRole('navigation', {name: 'Run workspace'})).toBeInTheDocument();
110
+ expect(screen.getByRole('link', {name: 'Summary'})).toHaveAttribute(
111
+ 'href',
112
+ expect.stringContaining(`runs/${RUN_ID}?runAttempt=%221%22`),
113
+ );
114
+ expect(screen.getByRole('heading', {name: 'Jobs'})).toBeInTheDocument();
115
+ expect(screen.getByRole('link', {name: RELEASE_LINK_PATTERN})).toHaveAttribute(
116
+ 'aria-current',
117
+ 'page',
118
+ );
119
+ expect(screen.getByText('5s')).toBeInTheDocument();
120
+ expect(screen.getAllByText('1m 10s')).not.toHaveLength(0);
121
+ });
122
+
123
+ test('shows a retarget notice when polling advances to the next running step', async () => {
124
+ let detailRequestCount = 0;
125
+ const fetchImpl = vi.fn((input: RequestInfo | URL) => {
126
+ const url = new URL((input as Request).url);
127
+ if (url.pathname.includes('/logs')) {
128
+ return Promise.resolve(jsonResponse({code: 'not-found'}, {status: 404}));
129
+ }
130
+ detailRequestCount += 1;
131
+ return Promise.resolve(
132
+ jsonResponse(
133
+ detailRequestCount === 1 ? liveInitialJobDetailDto() : liveAdvancedJobDetailDto(),
134
+ ),
135
+ );
136
+ });
137
+ configureApiClient({fetchImpl: fetchImpl as typeof fetch});
138
+
139
+ const {queryClient} = renderJobPath('?runAttempt=1', LIVE_JOB_ID);
140
+ expect(
141
+ await screen.findByRole('region', {name: 'build output, attempt 1'}),
142
+ ).toBeInTheDocument();
143
+
144
+ await act(async () => {
145
+ await queryClient.invalidateQueries({queryKey: workflowRunsQueryKeys.detail(RUN_ID, 1)});
146
+ });
147
+
148
+ expect(await screen.findByText(RUN_MOVED_ON_PATTERN)).toBeInTheDocument();
149
+ expect(screen.getAllByText('deploy')).not.toHaveLength(0);
150
+ });
151
+
152
+ test('shows a retarget notice when polling advances the running attempt', async () => {
153
+ let detailRequestCount = 0;
154
+ const fetchImpl = vi.fn((input: RequestInfo | URL) => {
155
+ const url = new URL((input as Request).url);
156
+ if (url.pathname.includes('/logs')) {
157
+ return Promise.resolve(jsonResponse({code: 'not-found'}, {status: 404}));
158
+ }
159
+ detailRequestCount += 1;
160
+ return Promise.resolve(
161
+ jsonResponse(
162
+ detailRequestCount === 1
163
+ ? liveRetriedInitialJobDetailDto()
164
+ : liveRetriedAdvancedJobDetailDto(),
165
+ ),
166
+ );
167
+ });
168
+ configureApiClient({fetchImpl: fetchImpl as typeof fetch});
169
+
170
+ const {queryClient} = renderJobPath('?runAttempt=1', LIVE_JOB_ID);
171
+ expect(
172
+ await screen.findByRole('region', {name: 'build output, attempt 1'}),
173
+ ).toBeInTheDocument();
174
+
175
+ await act(async () => {
176
+ await queryClient.invalidateQueries({queryKey: workflowRunsQueryKeys.detail(RUN_ID, 1)});
177
+ });
178
+
179
+ expect(await screen.findByText(RUN_MOVED_ON_PATTERN)).toBeInTheDocument();
180
+ });
181
+
182
+ test('moves focus to the heading after navigating from the job rail', async () => {
183
+ const user = userEvent.setup();
184
+ configureApiClient({
185
+ fetchImpl: vi.fn(() => Promise.resolve(jsonResponse(multiJobDetailDto()))),
186
+ });
187
+
188
+ renderJobPath();
189
+ await screen.findByRole('heading', {name: 'release'});
190
+
191
+ await user.click(screen.getByRole('link', {name: LINT_LINK_PATTERN}));
192
+
193
+ expect(await screen.findByRole('heading', {name: 'lint'})).toHaveFocus();
194
+ });
195
+
196
+ test('shows a newer-attempt notice when the run has a newer attempt', async () => {
197
+ configureApiClient({fetchImpl: vi.fn(newerAttemptJobDetailFetch)});
198
+
199
+ renderJobPath();
200
+
201
+ expect(await screen.findByText('A newer run attempt is available.')).toBeInTheDocument();
202
+ expect(screen.getByRole('link', {name: 'View attempt #2'})).toBeInTheDocument();
203
+ });
204
+
205
+ test('renders the job-not-found state for a job outside the run', async () => {
206
+ configureApiClient({fetchImpl: vi.fn(jobDetailFetch)});
207
+
208
+ renderJobPath('', 'missing-job');
209
+
210
+ expect(await screen.findByText('Job not found')).toBeInTheDocument();
211
+ expect(screen.getByRole('link', {name: BACK_TO_SUMMARY_PATTERN})).toHaveAttribute(
212
+ 'href',
213
+ expect.stringContaining(`runs/${RUN_ID}`),
214
+ );
215
+ });
216
+
217
+ test('treats the removed job Annotations URL as the job log document', async () => {
218
+ configureApiClient({fetchImpl: vi.fn(jobDetailFetch)});
219
+
220
+ const {router} = renderJobPath('?runAttempt=1&tab=annotations');
221
+ await screen.findByRole('heading', {name: 'release'});
222
+
223
+ expect(router.state.location.pathname).toBe(
224
+ `/w/${PROJECT_TEST_WSLUG}/p/project/runs/${RUN_ID}/jobs/${JOB_ID}`,
225
+ );
226
+ expect(router.state.location.search).toEqual({runAttempt: 1, tab: 'annotations'});
227
+ expect(screen.getByRole('region', {name: 'release logs'})).toBeInTheDocument();
228
+ expect(screen.queryByText('This job has no annotations to show.')).not.toBeInTheDocument();
229
+ });
230
+
231
+ test('explains a failure that happened before the first step started', async () => {
232
+ configureApiClient({
233
+ fetchImpl: vi.fn(() => Promise.resolve(jsonResponse(failedBeforeStepsDetailDto()))),
234
+ });
235
+
236
+ renderJobPath();
237
+
238
+ expect(await screen.findByText('Job failed before its first step started')).toBeInTheDocument();
239
+ expect(
240
+ screen.getByText(
241
+ 'The runner stopped responding before work began. Check runner availability before re-running the workflow.',
242
+ ),
243
+ ).toBeInTheDocument();
244
+ });
245
+
246
+ test('browser back from the job page returns to the run page', async () => {
247
+ configureApiClient({fetchImpl: vi.fn(jobDetailFetch)});
248
+
249
+ const {router} = renderJobPath();
250
+ await screen.findByRole('heading', {name: 'release'});
251
+ await act(async () => {
252
+ await router.navigate({
253
+ to: '/w/$workspaceSlug/p/$projectSlug/runs/$workflowRunId',
254
+ params: {
255
+ workspaceSlug: PROJECT_TEST_WSLUG,
256
+ projectSlug: 'project',
257
+ workflowRunId: RUN_ID,
258
+ },
259
+ });
260
+ await router.navigate({
261
+ to: '/w/$workspaceSlug/p/$projectSlug/runs/$workflowRunId/jobs/$jobId',
262
+ params: {
263
+ workspaceSlug: PROJECT_TEST_WSLUG,
264
+ projectSlug: 'project',
265
+ workflowRunId: RUN_ID,
266
+ jobId: JOB_ID,
267
+ },
268
+ });
269
+ router.history.back();
270
+ });
271
+
272
+ expect(await screen.findByRole('region', {name: 'Workflow jobs'})).toBeInTheDocument();
273
+ });
274
+ });
275
+
276
+ function renderJobPath(path = '', jobId = JOB_ID) {
277
+ return renderProjectPage(
278
+ `/w/${PROJECT_TEST_WSLUG}/p/project/runs/${RUN_ID}/jobs/${jobId}${path}`,
279
+ ({search, workflowRunId, jobId: routeJobId}) =>
280
+ routeJobId ? (
281
+ <WorkflowJobDetailPage
282
+ projectId="44444444-4444-4444-8444-444444444444"
283
+ workspaceSlug={PROJECT_TEST_WSLUG}
284
+ projectSlug="project"
285
+ workflowRunId={workflowRunId ?? RUN_ID}
286
+ jobId={routeJobId}
287
+ search={search as WorkflowJobSearch}
288
+ />
289
+ ) : (
290
+ <WorkflowRunDetailPage
291
+ projectId="44444444-4444-4444-8444-444444444444"
292
+ workspaceSlug={PROJECT_TEST_WSLUG}
293
+ projectSlug="project"
294
+ workflowRunId={workflowRunId ?? RUN_ID}
295
+ search={search as WorkflowRunsSearch}
296
+ />
297
+ ),
298
+ );
299
+ }
300
+
301
+ /** Annotations for the job page, which renders their count and never their bodies. */
302
+ const jobAnnotations: {value: unknown[]} = {value: []};
303
+
304
+ function annotationsResponse() {
305
+ return {annotations: jobAnnotations.value, has_more: false, next_cursor: null};
306
+ }
307
+
308
+ function jobDetailFetch(input: RequestInfo | URL) {
309
+ const request = input as Request;
310
+ const url = new URL(request.url);
311
+
312
+ if (url.pathname.endsWith('/annotations')) {
313
+ return Promise.resolve(jsonResponse(annotationsResponse()));
314
+ }
315
+
316
+ if (url.pathname.endsWith('/attempts')) {
317
+ return Promise.resolve(
318
+ jsonResponse(
319
+ runAttemptsResponseDto({
320
+ attempts: [
321
+ workflowRunAttemptDto({
322
+ workflow_run_id: RUN_ID,
323
+ id: 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb',
324
+ status: 'succeeded',
325
+ }),
326
+ ],
327
+ }),
328
+ ),
329
+ );
330
+ }
331
+
332
+ if (url.pathname.includes('/logs')) {
333
+ return Promise.resolve(
334
+ jsonResponse({
335
+ mode: 'inline',
336
+ ndjson: `${JSON.stringify({v: 1, ts: 1782043200000, type: 'output', stream: 'stdout', data: 'tests passed\\n'})}\\n`,
337
+ next_cursor: 0,
338
+ has_more: false,
339
+ state: 'closed',
340
+ truncated: false,
341
+ }),
342
+ );
343
+ }
344
+
345
+ return Promise.resolve(jsonResponse(jobDetailDto()));
346
+ }
347
+
348
+ function newerAttemptJobDetailFetch(input: RequestInfo | URL) {
349
+ const request = input as Request;
350
+ const url = new URL(request.url);
351
+
352
+ if (url.pathname.endsWith('/annotations')) {
353
+ return Promise.resolve(jsonResponse(annotationsResponse()));
354
+ }
355
+
356
+ if (url.pathname.endsWith('/attempts')) {
357
+ return Promise.resolve(
358
+ jsonResponse(
359
+ runAttemptsResponseDto({
360
+ attempts: [
361
+ workflowRunAttemptDto({workflow_run_id: RUN_ID, attempt: 1, status: 'succeeded'}),
362
+ workflowRunAttemptDto({workflow_run_id: RUN_ID, attempt: 2, status: 'succeeded'}),
363
+ ],
364
+ }),
365
+ ),
366
+ );
367
+ }
368
+
369
+ if (url.searchParams.get('attempt') === '2') {
370
+ return Promise.resolve(
371
+ jsonResponse(
372
+ workflowRunDetailDto({
373
+ id: RUN_ID,
374
+ status: 'succeeded',
375
+ latest_attempt: 2,
376
+ current_attempt: 2,
377
+ run_attempt: workflowRunAttemptDto({
378
+ workflow_run_id: RUN_ID,
379
+ attempt: 2,
380
+ status: 'succeeded',
381
+ }),
382
+ jobs: [
383
+ workflowJobDto({
384
+ id: NEWER_JOB_ID,
385
+ key: 'release',
386
+ name: 'release',
387
+ status: 'succeeded',
388
+ }),
389
+ ],
390
+ }),
391
+ ),
392
+ );
393
+ }
394
+
395
+ return Promise.resolve(jsonResponse(jobDetailDto()));
396
+ }
397
+
398
+ function liveInitialJobDetailDto() {
399
+ return liveJobDetailDto({buildStatus: 'running', deployStatus: 'pending'});
400
+ }
401
+
402
+ function liveAdvancedJobDetailDto() {
403
+ return liveJobDetailDto({buildStatus: 'succeeded', deployStatus: 'running'});
404
+ }
405
+
406
+ function liveRetriedInitialJobDetailDto() {
407
+ return liveJobDetailDto({
408
+ buildStatus: 'running',
409
+ deployStatus: 'pending',
410
+ buildAttempt: 1,
411
+ buildAttemptId: LIVE_BUILD_ATTEMPT_ID,
412
+ });
413
+ }
414
+
415
+ function liveRetriedAdvancedJobDetailDto() {
416
+ return liveJobDetailDto({
417
+ buildStatus: 'running',
418
+ deployStatus: 'pending',
419
+ buildAttempt: 2,
420
+ buildAttemptId: '22222222-aaaa-4aaa-8aaa-aaaaaaaaaaaa',
421
+ });
422
+ }
423
+
424
+ function liveJobDetailDto({
425
+ buildStatus,
426
+ deployStatus,
427
+ buildAttempt = 1,
428
+ buildAttemptId = LIVE_BUILD_ATTEMPT_ID,
429
+ }: {
430
+ buildStatus: 'running' | 'succeeded';
431
+ deployStatus: 'pending' | 'running';
432
+ buildAttempt?: number;
433
+ buildAttemptId?: string;
434
+ }) {
435
+ const buildStep = workflowStepDto({
436
+ id: LIVE_BUILD_STEP_ID,
437
+ key: 'build',
438
+ name: 'build',
439
+ position: 0,
440
+ status: buildStatus,
441
+ current_attempt: buildAttempt,
442
+ attempts: [
443
+ workflowStepAttemptDto({
444
+ id: buildAttemptId,
445
+ step_id: LIVE_BUILD_STEP_ID,
446
+ attempt: buildAttempt,
447
+ status: buildStatus,
448
+ }),
449
+ ],
450
+ });
451
+ const deployStep = workflowStepDto({
452
+ id: LIVE_DEPLOY_STEP_ID,
453
+ key: 'deploy',
454
+ name: 'deploy',
455
+ position: 1,
456
+ status: deployStatus,
457
+ attempts:
458
+ deployStatus === 'running'
459
+ ? [
460
+ workflowStepAttemptDto({
461
+ id: LIVE_DEPLOY_ATTEMPT_ID,
462
+ step_id: LIVE_DEPLOY_STEP_ID,
463
+ attempt: 1,
464
+ status: 'running',
465
+ }),
466
+ ]
467
+ : [],
468
+ });
469
+
470
+ return workflowRunDetailDto({
471
+ id: RUN_ID,
472
+ status: 'running',
473
+ current_attempt: 1,
474
+ latest_attempt: 1,
475
+ run_attempt: workflowRunAttemptDto({
476
+ workflow_run_id: RUN_ID,
477
+ attempt: 1,
478
+ status: 'running',
479
+ }),
480
+ jobs: [
481
+ workflowJobDto({
482
+ id: LIVE_JOB_ID,
483
+ key: 'release',
484
+ name: 'release',
485
+ status: 'running',
486
+ job_executions: [
487
+ workflowJobExecutionDto({
488
+ id: LIVE_EXECUTION_ID,
489
+ job_id: LIVE_JOB_ID,
490
+ sequence: 1,
491
+ status: 'running',
492
+ steps: [buildStep, deployStep],
493
+ }),
494
+ ],
495
+ }),
496
+ ],
497
+ });
498
+ }
499
+
500
+ function jobDetailDto() {
501
+ const step = workflowStepDto({
502
+ id: STEP_ID,
503
+ name: 'tests',
504
+ key: 'tests',
505
+ status: 'succeeded',
506
+ attempts: [
507
+ workflowStepAttemptDto({
508
+ id: ATTEMPT_ID,
509
+ step_id: STEP_ID,
510
+ attempt: 2,
511
+ status: 'succeeded',
512
+ }),
513
+ ],
514
+ });
515
+ const execution = workflowJobExecutionDto({
516
+ id: EXECUTION_ID,
517
+ job_id: JOB_ID,
518
+ sequence: 2,
519
+ name: 'release',
520
+ status: 'succeeded',
521
+ queued_at: '2026-06-21T12:00:00.000Z',
522
+ started_at: '2026-06-21T12:00:05.000Z',
523
+ finished_at: '2026-06-21T12:01:15.000Z',
524
+ steps: [step],
525
+ });
526
+
527
+ return workflowRunDetailDto({
528
+ id: RUN_ID,
529
+ status: 'succeeded',
530
+ current_attempt: 1,
531
+ latest_attempt: 1,
532
+ run_attempt: workflowRunAttemptDto({
533
+ id: 'cccccccc-cccc-4ccc-8ccc-cccccccccccc',
534
+ workflow_run_id: RUN_ID,
535
+ attempt: 1,
536
+ status: 'succeeded',
537
+ }),
538
+ jobs: [
539
+ workflowJobDto({
540
+ id: JOB_ID,
541
+ key: 'release',
542
+ name: 'release',
543
+ status: 'succeeded',
544
+ job_executions: [
545
+ workflowJobExecutionDto({
546
+ id: 'dddddddd-dddd-4ddd-8ddd-dddddddddddd',
547
+ job_id: JOB_ID,
548
+ sequence: 1,
549
+ name: 'release',
550
+ status: 'failed',
551
+ }),
552
+ execution,
553
+ ],
554
+ }),
555
+ ],
556
+ });
557
+ }
558
+
559
+ function failedBeforeStepsDetailDto() {
560
+ return workflowRunDetailDto({
561
+ id: RUN_ID,
562
+ status: 'failed',
563
+ run_attempt: workflowRunAttemptDto({
564
+ workflow_run_id: RUN_ID,
565
+ attempt: 1,
566
+ status: 'failed',
567
+ }),
568
+ jobs: [
569
+ workflowJobDto({
570
+ id: JOB_ID,
571
+ key: 'release',
572
+ name: 'release',
573
+ status: 'failed',
574
+ status_reason: 'runner_lost',
575
+ job_executions: [
576
+ workflowJobExecutionDto({
577
+ id: EXECUTION_ID,
578
+ job_id: JOB_ID,
579
+ sequence: 1,
580
+ status: 'failed',
581
+ status_reason: 'runner_lost',
582
+ steps: [],
583
+ }),
584
+ ],
585
+ }),
586
+ ],
587
+ });
588
+ }
589
+
590
+ function multiJobDetailDto() {
591
+ const detail = jobDetailDto();
592
+ return {
593
+ ...detail,
594
+ jobs: [
595
+ ...detail.jobs,
596
+ workflowJobDto({
597
+ id: SIBLING_JOB_ID,
598
+ key: 'lint',
599
+ name: 'lint',
600
+ status: 'succeeded',
601
+ position: 1,
602
+ }),
603
+ ],
604
+ };
605
+ }
@@ -0,0 +1,84 @@
1
+ import {useNavigate} from '@tanstack/react-router';
2
+ import {useCallback, useMemo} from 'react';
3
+ import {JobDetailView} from '#components/job-detail/job-detail-view.js';
4
+ import {WorkflowRunView} from '#components/workflow-run-view/index.js';
5
+ import {isWorkflowRunTerminal} from '#core/workflow-run.js';
6
+ import {useWorkflowRunAttemptQuery, useWorkflowRunAttemptsQuery} from '#hooks/api/workflow-runs.js';
7
+ import {type WorkflowJobSearch, workflowJobSearchParams} from '#routes/inputs.js';
8
+
9
+ export interface WorkflowJobDetailPageProps {
10
+ projectId: string;
11
+ workspaceSlug: string;
12
+ projectSlug: string;
13
+ workflowRunId: string;
14
+ jobId: string;
15
+ search?: WorkflowJobSearch;
16
+ }
17
+
18
+ export function WorkflowJobDetailPage({
19
+ projectId,
20
+ workspaceSlug,
21
+ projectSlug,
22
+ workflowRunId,
23
+ jobId,
24
+ search = {},
25
+ }: WorkflowJobDetailPageProps) {
26
+ const navigate = useNavigate();
27
+ const runQuery = useWorkflowRunAttemptQuery({
28
+ workflowRunId,
29
+ runAttempt: search.runAttempt,
30
+ });
31
+ const attemptsQuery = useWorkflowRunAttemptsQuery({
32
+ workflowRunId,
33
+ enabled: Boolean(runQuery.data && isWorkflowRunTerminal(runQuery.data.runAttempt.status)),
34
+ });
35
+ const newerAttempt = useMemo(() => {
36
+ const currentAttempt = runQuery.data?.runAttempt.attempt ?? search.runAttempt ?? 1;
37
+ return attemptsQuery.data
38
+ ?.filter((attempt) => attempt.attempt > currentAttempt)
39
+ .sort((left, right) => right.attempt - left.attempt)[0]?.attempt;
40
+ }, [attemptsQuery.data, runQuery.data?.runAttempt.attempt, search.runAttempt]);
41
+ const newerRunQuery = useWorkflowRunAttemptQuery({
42
+ workflowRunId,
43
+ runAttempt: newerAttempt,
44
+ enabled: newerAttempt !== undefined,
45
+ });
46
+
47
+ const currentJobKey = runQuery.data?.jobs.find((job) => job.id === jobId)?.key;
48
+ const newerJob = currentJobKey
49
+ ? newerRunQuery.data?.jobs.find((job) => job.key === currentJobKey)
50
+ : undefined;
51
+
52
+ const onSelectionChange = useCallback(
53
+ (nextSelection: WorkflowJobSearch) => {
54
+ navigate({search: workflowJobSearchParams(nextSelection) as never});
55
+ },
56
+ [navigate],
57
+ );
58
+ return (
59
+ <div className="flex min-h-0 flex-1 overflow-hidden bg-background-neutral-background">
60
+ <WorkflowRunView
61
+ projectId={projectId}
62
+ workspaceSlug={workspaceSlug}
63
+ projectSlug={projectSlug}
64
+ workflowRunId={workflowRunId}
65
+ runAttempt={search.runAttempt}
66
+ activeJobId={jobId}
67
+ jobSearch={search}
68
+ jobContent={
69
+ <JobDetailView
70
+ workspaceSlug={workspaceSlug}
71
+ projectSlug={projectSlug}
72
+ workflowRunId={workflowRunId}
73
+ jobId={jobId}
74
+ search={search}
75
+ query={runQuery}
76
+ newerAttempt={newerAttempt}
77
+ newerJob={newerJob}
78
+ onSelectionChange={onSelectionChange}
79
+ />
80
+ }
81
+ />
82
+ </div>
83
+ );
84
+ }