@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
@@ -1,789 +0,0 @@
1
- import type {
2
- WorkflowExecutionEventDto,
3
- WorkflowRunJobDetailDto,
4
- WorkflowRunJobExecutionDetailDto,
5
- WorkflowRunStepDetailDto,
6
- } from '@shipfox/api-workflows-dto';
7
- import {Text} from '@shipfox/react-ui/typography';
8
- import type {Meta, StoryObj} from '@storybook/react';
9
- import {type ReactNode, useState} from 'react';
10
- import {screen, userEvent, within} from 'storybook/test';
11
- import {type Job, resolveJobExecution} from '#core/workflow-run.js';
12
- import {
13
- workflowJob,
14
- workflowJobExecutionDto,
15
- workflowStepAttemptDto,
16
- workflowStepDto,
17
- } from '#test/fixtures/workflow-run.js';
18
- import type {StepExpandedContext} from '../step-list/index.js';
19
- import {JobCard} from './job-card.js';
20
-
21
- const SWITCH_EXECUTION_PATTERN = /Switch job execution/;
22
- const EXECUTION_FIVE_PATTERN = /#5/;
23
-
24
- const QUEUED_AT = '2026-06-26T11:54:00.000Z';
25
- const STARTED_AT = '2026-06-26T11:57:46.000Z';
26
- const FINISHED_AT = '2026-06-26T12:00:00.000Z';
27
- const LISTENING_BASE_AT = Date.parse('2026-06-26T10:00:00.000Z');
28
-
29
- const meta = {
30
- title: 'Workflows/JobCard',
31
- component: JobCard,
32
- parameters: {
33
- layout: 'centered',
34
- },
35
- decorators: [
36
- (Story) => (
37
- <div className="w-760 bg-background-neutral-base p-16">
38
- <Story />
39
- </div>
40
- ),
41
- ],
42
- args: {
43
- job: singleExecutionJob(),
44
- selectedJobExecution: singleExecutionJob().jobExecutions[0],
45
- selectedAttemptId: null,
46
- onSelectedJobExecutionChange: undefined,
47
- onSelectedAttemptChange: undefined,
48
- renderExpandedStep: StepDetailPlaceholder,
49
- },
50
- } satisfies Meta<typeof JobCard>;
51
-
52
- export default meta;
53
- type Story = StoryObj<typeof meta>;
54
- type JobCardStoryContext = Parameters<NonNullable<Story['play']>>[0];
55
-
56
- async function openJobExecutionSwitcher(ctx: JobCardStoryContext) {
57
- const canvas = within(ctx.canvasElement);
58
-
59
- await userEvent.click(await canvas.findByRole('button', {name: SWITCH_EXECUTION_PATTERN}));
60
- await screen.findByRole('menu');
61
- await screen.findByRole('menuitem', {name: EXECUTION_FIVE_PATTERN});
62
- }
63
-
64
- export const Playground: Story = {
65
- render: () => <JobCardStory job={singleExecutionJob()} />,
66
- };
67
-
68
- export const States: Story = {
69
- render: () => (
70
- <div className="flex flex-col gap-16">
71
- <StorySection label="single execution">
72
- <JobCardStory job={singleExecutionJob()} />
73
- </StorySection>
74
- <StorySection label="execution name with missing job name">
75
- <JobCardStory job={keyFallbackJob()} />
76
- </StorySection>
77
- <StorySection label="queueing execution">
78
- <JobCardStory job={queueingExecutionJob()} initialJobExecutionId="exec-queueing" />
79
- </StorySection>
80
- <StorySection label="failed then succeeded">
81
- <JobCardStory job={failedThenSucceededJob()} initialJobExecutionId="exec-2" />
82
- </StorySection>
83
- <StorySection label="running retry">
84
- <JobCardStory job={runningRetryJob()} initialJobExecutionId="exec-2" />
85
- </StorySection>
86
- </div>
87
- ),
88
- };
89
-
90
- export const EmptyStates: Story = {
91
- render: () => (
92
- <div className="flex flex-col gap-16">
93
- <StorySection label="pending, no execution">
94
- <JobCardStory job={pendingNoExecutionJob()} />
95
- </StorySection>
96
- <StorySection label="listening, waiting for trigger events">
97
- <JobCardStory job={listeningJobNoExecutions()} />
98
- </StorySection>
99
- <StorySection label="listener resolved, no executions">
100
- <JobCardStory job={listeningJobResolvedNoExecutions()} />
101
- </StorySection>
102
- <StorySection label="skipped, no execution">
103
- <JobCardStory job={zeroExecutionJob()} />
104
- </StorySection>
105
- <StorySection label="cancelled, no execution">
106
- <JobCardStory job={cancelledNoExecutionJob()} />
107
- </StorySection>
108
- <StorySection label="finished, missing execution record">
109
- <JobCardStory job={finishedWithoutExecutionJob()} />
110
- </StorySection>
111
- <StorySection label="finished, no steps">
112
- <JobCardStory job={finishedWithoutStepsJob()} initialJobExecutionId="exec-empty" />
113
- </StorySection>
114
- <StorySection label="carried over">
115
- <JobCardStory job={carriedOverJob()} />
116
- </StorySection>
117
- </div>
118
- ),
119
- };
120
-
121
- export const ListeningProgression: Story = {
122
- render: () => (
123
- <div className="flex flex-col gap-16">
124
- <StorySection label="pending, no executions">
125
- <JobCardStory job={listeningJobPending()} />
126
- </StorySection>
127
- <StorySection label="listening, 0 executions">
128
- <JobCardStory job={listeningJobNoExecutions()} />
129
- </StorySection>
130
- <StorySection label="listening, 1 running execution">
131
- <JobCardStory job={listeningJobOneExecution()} initialJobExecutionId="exec-listen-one-1" />
132
- </StorySection>
133
- <StorySection label="listening, mixed executions">
134
- <JobCardStory job={listeningJobMixedExecutions()} initialJobExecutionId="exec-listen-6" />
135
- </StorySection>
136
- <StorySection label="failed, resolved">
137
- <JobCardStory job={listeningJobResolved()} initialJobExecutionId="exec-listen-resolved-4" />
138
- </StorySection>
139
- </div>
140
- ),
141
- };
142
-
143
- export const ManyExecutions: Story = {
144
- render: () => (
145
- <div className="flex flex-col gap-16">
146
- <StorySection label="listening, 64 executions, short name">
147
- <JobCardStory
148
- job={manyExecutionsListeningJob('gate', 'job-many-short')}
149
- initialJobExecutionId="exec-many-job-many-short-64"
150
- />
151
- </StorySection>
152
- <StorySection label="listening, 64 executions, medium name">
153
- <JobCardStory
154
- job={manyExecutionsListeningJob('release-gates', 'job-many-medium')}
155
- initialJobExecutionId="exec-many-job-many-medium-64"
156
- />
157
- </StorySection>
158
- <StorySection label="listening, 64 executions, long name">
159
- <JobCardStory
160
- job={manyExecutionsListeningJob(
161
- 'release-production-multi-region-with-canary-validation',
162
- 'job-many-long',
163
- )}
164
- initialJobExecutionId="exec-many-job-many-long-64"
165
- />
166
- </StorySection>
167
- </div>
168
- ),
169
- };
170
-
171
- export const SourceAction: Story = {
172
- render: () => (
173
- <JobCardStory
174
- job={sourceActionJob()}
175
- initialJobExecutionId="exec-source-action"
176
- initialAttemptId="attempt-checkout-exec-source-action"
177
- sourceAvailable
178
- sourcePanelId="workflow-source-panel-story"
179
- />
180
- ),
181
- };
182
-
183
- export const Content: Story = {
184
- decorators: [
185
- (Story) => (
186
- <div className="w-440 bg-background-neutral-base p-16">
187
- <Story />
188
- </div>
189
- ),
190
- ],
191
- render: () => (
192
- <div className="flex flex-col gap-16">
193
- <StorySection label="regular long name">
194
- <JobCardStory job={longNameManyExecutionsJob()} initialJobExecutionId="exec-6" />
195
- </StorySection>
196
- <StorySection label="listening long name">
197
- <JobCardStory job={longNameListeningJob()} initialJobExecutionId="exec-long-listening-3" />
198
- </StorySection>
199
- </div>
200
- ),
201
- };
202
-
203
- export const TestListeningSwitcherOpen: Story = {
204
- render: () => (
205
- <JobCardStory job={listeningJobMixedExecutions()} initialJobExecutionId="exec-listen-6" />
206
- ),
207
- play: openJobExecutionSwitcher,
208
- };
209
-
210
- function JobCardStory({
211
- job,
212
- initialJobExecutionId,
213
- initialAttemptId = null,
214
- sourceAvailable,
215
- sourcePanelId,
216
- }: {
217
- job: Job;
218
- initialJobExecutionId?: string | undefined;
219
- initialAttemptId?: string | null | undefined;
220
- sourceAvailable?: boolean | undefined;
221
- sourcePanelId?: string | undefined;
222
- }) {
223
- const [selectedJobExecutionId, setSelectedJobExecutionId] = useState(initialJobExecutionId);
224
- const [selectedAttemptId, setSelectedAttemptId] = useState<string | null>(initialAttemptId);
225
- const selectedJobExecution = resolveJobExecution(job, selectedJobExecutionId);
226
-
227
- function selectJobExecution(jobExecutionId: string | undefined) {
228
- setSelectedJobExecutionId(jobExecutionId);
229
- setSelectedAttemptId(null);
230
- }
231
-
232
- return (
233
- <JobCard
234
- job={job}
235
- selectedJobExecution={selectedJobExecution}
236
- selectedAttemptId={selectedAttemptId}
237
- onSelectedJobExecutionChange={selectJobExecution}
238
- onSelectedAttemptChange={(attemptId) => setSelectedAttemptId(attemptId ?? null)}
239
- renderExpandedStep={StepDetailPlaceholder}
240
- sourceAvailable={sourceAvailable}
241
- sourcePanelId={sourcePanelId}
242
- focusedSourceStepId={null}
243
- onOpenStepSource={() => undefined}
244
- />
245
- );
246
- }
247
-
248
- function StorySection({label, children}: {label: string; children: ReactNode}) {
249
- return (
250
- <div className="flex min-w-0 flex-col gap-8">
251
- <Text size="xs" className="font-code text-foreground-neutral-muted">
252
- {label}
253
- </Text>
254
- {children}
255
- </div>
256
- );
257
- }
258
-
259
- function StepDetailPlaceholder({attempt, attemptStatus}: StepExpandedContext) {
260
- return (
261
- <div className="rounded-6 border border-border-neutral-base bg-background-components-base px-12 py-10">
262
- <Text size="xs" className="font-code text-foreground-neutral-muted">
263
- attempt #{attempt} - {attemptStatus}
264
- </Text>
265
- <Text size="sm" className="text-foreground-neutral-subtle">
266
- Step detail content renders here in the run viewer.
267
- </Text>
268
- </div>
269
- );
270
- }
271
-
272
- function singleExecutionJob(): Job {
273
- return makeJob({
274
- id: 'job-build',
275
- name: 'build',
276
- status: 'succeeded',
277
- job_executions: [
278
- makeExecution({
279
- id: 'exec-1',
280
- job_id: 'job-build',
281
- sequence: 1,
282
- status: 'succeeded',
283
- queued_at: QUEUED_AT,
284
- started_at: STARTED_AT,
285
- finished_at: FINISHED_AT,
286
- steps: [
287
- makeStep('checkout', 'succeeded', 0, 'exec-1'),
288
- makeStep('install', 'succeeded', 1, 'exec-1'),
289
- makeStep('test', 'succeeded', 2, 'exec-1'),
290
- ],
291
- }),
292
- ],
293
- });
294
- }
295
-
296
- function keyFallbackJob(): Job {
297
- return makeJob({
298
- id: 'job-key-fallback',
299
- key: 'release-production',
300
- name: null,
301
- status: 'running',
302
- job_executions: [
303
- makeExecution({
304
- id: 'exec-key-fallback',
305
- job_id: 'job-key-fallback',
306
- sequence: 1,
307
- status: 'running',
308
- queued_at: QUEUED_AT,
309
- started_at: STARTED_AT,
310
- steps: [
311
- makeStep('package', 'succeeded', 0, 'exec-key-fallback'),
312
- makeStep('deploy', 'running', 1, 'exec-key-fallback'),
313
- ],
314
- }),
315
- ],
316
- });
317
- }
318
-
319
- function queueingExecutionJob(): Job {
320
- return makeJob({
321
- id: 'job-queueing',
322
- name: 'allocate-runner',
323
- status: 'pending',
324
- job_executions: [
325
- makeExecution({
326
- id: 'exec-queueing',
327
- job_id: 'job-queueing',
328
- sequence: 1,
329
- status: 'pending',
330
- queued_at: QUEUED_AT,
331
- started_at: null,
332
- finished_at: null,
333
- steps: [],
334
- }),
335
- ],
336
- });
337
- }
338
-
339
- function failedThenSucceededJob(): Job {
340
- return makeJob({
341
- id: 'job-deploy',
342
- name: 'deploy',
343
- status: 'succeeded',
344
- job_executions: [
345
- makeExecution({
346
- id: 'exec-1',
347
- job_id: 'job-deploy',
348
- sequence: 1,
349
- status: 'failed',
350
- status_reason: 'step_failed',
351
- queued_at: '2026-06-26T11:46:00.000Z',
352
- started_at: '2026-06-26T11:48:00.000Z',
353
- finished_at: '2026-06-26T11:50:30.000Z',
354
- steps: [
355
- makeStep('deploy', 'failed', 0, 'exec-1', {
356
- attempts: [
357
- workflowStepAttemptDto({
358
- id: 'attempt-deploy-1',
359
- step_id: 'step-deploy-1',
360
- status: 'failed',
361
- exit_code: 1,
362
- error: {message: 'Deployment failed'},
363
- }),
364
- ],
365
- }),
366
- ],
367
- }),
368
- makeExecution({
369
- id: 'exec-2',
370
- job_id: 'job-deploy',
371
- sequence: 2,
372
- status: 'succeeded',
373
- queued_at: QUEUED_AT,
374
- started_at: STARTED_AT,
375
- finished_at: FINISHED_AT,
376
- steps: [
377
- makeStep('deploy', 'succeeded', 0, 'exec-2'),
378
- makeStep('smoke-test', 'succeeded', 1, 'exec-2'),
379
- ],
380
- }),
381
- ],
382
- });
383
- }
384
-
385
- function runningRetryJob(): Job {
386
- return makeJob({
387
- id: 'job-release',
388
- name: 'release-production',
389
- status: 'running',
390
- job_executions: [
391
- makeExecution({
392
- id: 'exec-1',
393
- job_id: 'job-release',
394
- sequence: 1,
395
- status: 'failed',
396
- status_reason: 'step_failed',
397
- queued_at: '2026-06-26T11:46:00.000Z',
398
- started_at: '2026-06-26T11:48:00.000Z',
399
- finished_at: '2026-06-26T11:52:00.000Z',
400
- steps: [makeStep('package', 'failed', 0, 'exec-1')],
401
- }),
402
- makeExecution({
403
- id: 'exec-2',
404
- job_id: 'job-release',
405
- sequence: 2,
406
- status: 'running',
407
- queued_at: QUEUED_AT,
408
- started_at: STARTED_AT,
409
- steps: [
410
- makeStep('package', 'succeeded', 0, 'exec-2'),
411
- makeStep('publish', 'running', 1, 'exec-2', {
412
- current_attempt: 1,
413
- attempts: [
414
- workflowStepAttemptDto({
415
- id: 'attempt-publish-running',
416
- step_id: 'step-publish-exec-2',
417
- status: 'running',
418
- exit_code: null,
419
- finished_at: null,
420
- }),
421
- ],
422
- }),
423
- makeStep('notify', 'pending', 2, 'exec-2', {attempts: []}),
424
- ],
425
- }),
426
- ],
427
- });
428
- }
429
-
430
- function sourceActionJob(): Job {
431
- return makeJob({
432
- id: 'job-source-action',
433
- name: 'build',
434
- status: 'succeeded',
435
- job_executions: [
436
- makeExecution({
437
- id: 'exec-source-action',
438
- job_id: 'job-source-action',
439
- sequence: 1,
440
- status: 'succeeded',
441
- queued_at: QUEUED_AT,
442
- started_at: STARTED_AT,
443
- finished_at: FINISHED_AT,
444
- steps: [
445
- makeStep('checkout', 'succeeded', 0, 'exec-source-action', {
446
- source_location: {start_line: 12, end_line: 16},
447
- }),
448
- makeStep('install', 'succeeded', 1, 'exec-source-action'),
449
- makeStep('test', 'succeeded', 2, 'exec-source-action'),
450
- ],
451
- }),
452
- ],
453
- });
454
- }
455
-
456
- function pendingNoExecutionJob(): Job {
457
- return makeJob({
458
- id: 'job-pending-empty',
459
- name: 'wait-for-runner',
460
- status: 'pending',
461
- job_executions: [],
462
- });
463
- }
464
-
465
- function zeroExecutionJob(): Job {
466
- return makeJob({
467
- id: 'job-skipped',
468
- name: 'deploy-preview',
469
- status: 'skipped',
470
- status_reason: 'dependency_not_completed',
471
- dependencies: ['build'],
472
- job_executions: [],
473
- });
474
- }
475
-
476
- function cancelledNoExecutionJob(): Job {
477
- return makeJob({
478
- id: 'job-cancelled-empty',
479
- name: 'deploy-production',
480
- status: 'cancelled',
481
- status_reason: 'run_cancelled',
482
- job_executions: [],
483
- });
484
- }
485
-
486
- function finishedWithoutStepsJob(): Job {
487
- return makeJob({
488
- id: 'job-empty-steps',
489
- name: 'noop',
490
- status: 'succeeded',
491
- job_executions: [
492
- makeExecution({
493
- id: 'exec-empty',
494
- job_id: 'job-empty-steps',
495
- sequence: 1,
496
- status: 'succeeded',
497
- queued_at: QUEUED_AT,
498
- started_at: STARTED_AT,
499
- finished_at: FINISHED_AT,
500
- steps: [],
501
- }),
502
- ],
503
- });
504
- }
505
-
506
- function finishedWithoutExecutionJob(): Job {
507
- return makeJob({
508
- id: 'job-missing-execution',
509
- name: 'publish-artifacts',
510
- status: 'succeeded',
511
- job_executions: [],
512
- });
513
- }
514
-
515
- function carriedOverJob(): Job {
516
- return makeJob({
517
- id: 'job-test',
518
- name: 'test',
519
- status: 'succeeded',
520
- carried_over: true,
521
- job_executions: [],
522
- });
523
- }
524
-
525
- function listeningJobPending(): Job {
526
- return makeJob({
527
- id: 'job-listening-pending',
528
- name: 'release-gates',
529
- mode: 'listening',
530
- status: 'pending',
531
- listener_status: 'inactive',
532
- job_executions: [],
533
- });
534
- }
535
-
536
- function listeningJobNoExecutions(): Job {
537
- return makeJob({
538
- id: 'job-listening-empty',
539
- key: 'deploy-window',
540
- name: null,
541
- mode: 'listening',
542
- status: 'running',
543
- listener_status: 'listening',
544
- job_executions: [],
545
- });
546
- }
547
-
548
- function listeningJobResolvedNoExecutions(): Job {
549
- return makeJob({
550
- id: 'job-listening-resolved-empty',
551
- name: 'release-gates',
552
- mode: 'listening',
553
- status: 'succeeded',
554
- listener_status: 'resolved',
555
- resolution_reason: 'max_executions',
556
- job_executions: [],
557
- });
558
- }
559
-
560
- function listeningJobOneExecution(): Job {
561
- return makeJob({
562
- id: 'job-listening-one',
563
- name: 'release-gates',
564
- mode: 'listening',
565
- status: 'running',
566
- listener_status: 'listening',
567
- job_executions: [makeListeningExecution(1, 'running', 'job-listening-one', 'exec-listen-one')],
568
- });
569
- }
570
-
571
- function listeningJobMixedExecutions(): Job {
572
- return makeJob({
573
- id: 'job-listening-mixed',
574
- name: 'release-gates',
575
- mode: 'listening',
576
- status: 'running',
577
- listener_status: 'listening',
578
- job_executions: [
579
- ...Array.from({length: 2}, (_, index) =>
580
- makeListeningExecution(index + 1, 'running', 'job-listening-mixed'),
581
- ),
582
- ...Array.from({length: 3}, (_, index) =>
583
- makeListeningExecution(index + 3, 'succeeded', 'job-listening-mixed'),
584
- ),
585
- makeListeningExecution(6, 'failed', 'job-listening-mixed', 'exec-listen', {
586
- trigger_events: [
587
- makeTriggerEvent(6, 'deployment_status'),
588
- makeTriggerEvent(7, 'check_run'),
589
- ],
590
- }),
591
- ],
592
- });
593
- }
594
-
595
- function listeningJobResolved(): Job {
596
- return makeJob({
597
- id: 'job-listening-resolved',
598
- name: 'release-production-gates',
599
- mode: 'listening',
600
- status: 'failed',
601
- listener_status: 'resolved',
602
- resolution_reason: 'max_executions',
603
- job_executions: Array.from({length: 4}, (_, index) =>
604
- makeListeningExecution(
605
- index + 1,
606
- index === 3 ? 'failed' : 'succeeded',
607
- 'job-listening-resolved',
608
- 'exec-listen-resolved',
609
- ),
610
- ),
611
- });
612
- }
613
-
614
- function manyExecutionsListeningJob(name: string, id: string): Job {
615
- return makeJob({
616
- id,
617
- name,
618
- mode: 'listening',
619
- status: 'running',
620
- listener_status: 'listening',
621
- job_executions: Array.from({length: 64}, (_, index) => {
622
- const sequence = index + 1;
623
- const status = manyExecutionStatus(sequence);
624
- return makeListeningExecution(sequence, status, id, `exec-many-${id}`, {
625
- name: variedExecutionName(sequence),
626
- });
627
- }),
628
- });
629
- }
630
-
631
- function longNameManyExecutionsJob(): Job {
632
- return makeJob({
633
- id: 'job-long',
634
- name: 'release-production-multi-region-with-canary-validation-and-post-deploy-observability',
635
- status: 'running',
636
- job_executions: Array.from({length: 6}, (_, index) => {
637
- const sequence = index + 1;
638
- const status: WorkflowRunJobExecutionDetailDto['status'] =
639
- sequence < 6 ? 'failed' : 'running';
640
- return makeExecution({
641
- id: `exec-${sequence}`,
642
- job_id: 'job-long',
643
- sequence,
644
- status,
645
- status_reason: status === 'failed' ? 'step_failed' : null,
646
- queued_at: `2026-06-26T11:${String(36 + index * 3).padStart(2, '0')}:00.000Z`,
647
- started_at: `2026-06-26T11:${String(38 + index * 3).padStart(2, '0')}:00.000Z`,
648
- finished_at:
649
- status === 'failed'
650
- ? `2026-06-26T11:${String(40 + index * 3).padStart(2, '0')}:00.000Z`
651
- : null,
652
- steps: [
653
- makeStep(
654
- 'run-production-canary-smoke-tests-with-cross-region-checkout-payment-and-notification-validation',
655
- status,
656
- 0,
657
- `exec-${sequence}`,
658
- ),
659
- ],
660
- });
661
- }),
662
- });
663
- }
664
-
665
- function longNameListeningJob(): Job {
666
- return makeJob({
667
- id: 'job-long-listening',
668
- name: 'release-production-multi-region-with-canary-validation-and-post-deploy-observability',
669
- mode: 'listening',
670
- status: 'running',
671
- listener_status: 'listening',
672
- job_executions: Array.from({length: 3}, (_, index) =>
673
- makeListeningExecution(
674
- index + 1,
675
- index === 2 ? 'running' : 'succeeded',
676
- 'job-long-listening',
677
- 'exec-long-listening',
678
- ),
679
- ),
680
- });
681
- }
682
-
683
- function makeListeningExecution(
684
- sequence: number,
685
- status: WorkflowRunJobExecutionDetailDto['status'],
686
- jobId: string,
687
- idPrefix = 'exec-listen',
688
- overrides: Partial<WorkflowRunJobExecutionDetailDto> = {},
689
- ): WorkflowRunJobExecutionDetailDto {
690
- return makeExecution({
691
- id: `${idPrefix}-${sequence}`,
692
- job_id: jobId,
693
- sequence,
694
- name: `event-${sequence}`,
695
- status,
696
- status_reason: status === 'failed' ? 'step_failed' : null,
697
- queued_at: listeningTimestamp(sequence, 0),
698
- started_at: listeningTimestamp(sequence, 2),
699
- finished_at: status === 'running' ? null : listeningTimestamp(sequence, 3),
700
- trigger_events: [makeTriggerEvent(sequence, variedExecutionName(sequence))],
701
- steps: [makeStep('handle-event', status, 0, `${idPrefix}-${sequence}`)],
702
- ...overrides,
703
- });
704
- }
705
-
706
- function makeTriggerEvent(
707
- sequence: number,
708
- event = `event-${sequence}`,
709
- ): WorkflowExecutionEventDto {
710
- return {
711
- source: 'github',
712
- event,
713
- delivery_id: `delivery-${sequence}`,
714
- received_at: listeningTimestamp(sequence, 1),
715
- project: null,
716
- repository: null,
717
- ref: null,
718
- commit: null,
719
- data: {sequence},
720
- };
721
- }
722
-
723
- function manyExecutionStatus(sequence: number): WorkflowRunJobExecutionDetailDto['status'] {
724
- if (sequence === 64) return 'running';
725
- if (sequence % 11 === 0) return 'failed';
726
- if (sequence % 7 === 0) return 'cancelled';
727
- return 'succeeded';
728
- }
729
-
730
- function variedExecutionName(sequence: number): string {
731
- const names = [
732
- 'push',
733
- 'gate-opened',
734
- 'deployment-status-received',
735
- 'manual-approval-received-from-release-coordinator',
736
- 'production-canary-validation-completed-for-eu-west-and-us-east',
737
- ];
738
-
739
- return names[(sequence - 1) % names.length] ?? 'event';
740
- }
741
-
742
- function listeningTimestamp(sequence: number, offsetMinutes: number): string {
743
- return new Date(LISTENING_BASE_AT + (sequence + offsetMinutes) * 60_000).toISOString();
744
- }
745
-
746
- function makeJob(overrides: Partial<WorkflowRunJobDetailDto>): Job {
747
- return workflowJob(overrides);
748
- }
749
-
750
- function makeExecution(
751
- overrides: Partial<WorkflowRunJobExecutionDetailDto> & {
752
- id: string;
753
- job_id: string;
754
- sequence: number;
755
- },
756
- ): WorkflowRunJobExecutionDetailDto {
757
- return workflowJobExecutionDto(overrides);
758
- }
759
-
760
- function makeStep(
761
- name: string,
762
- status: string,
763
- position: number,
764
- jobExecutionId: string,
765
- overrides: Partial<WorkflowRunStepDetailDto> = {},
766
- ): WorkflowRunStepDetailDto {
767
- const stepId = `step-${name}-${jobExecutionId}`;
768
- return workflowStepDto({
769
- id: stepId,
770
- job_execution_id: jobExecutionId,
771
- name,
772
- position,
773
- status,
774
- attempts:
775
- overrides.attempts ??
776
- (status === 'pending'
777
- ? []
778
- : [
779
- workflowStepAttemptDto({
780
- id: `attempt-${name}-${jobExecutionId}`,
781
- step_id: stepId,
782
- status,
783
- exit_code: status === 'failed' ? 1 : null,
784
- finished_at: status === 'running' ? null : FINISHED_AT,
785
- }),
786
- ]),
787
- ...overrides,
788
- });
789
- }