@shipfox/client-workflows 0.2.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.
- package/.storybook/main.ts +1 -0
- package/.storybook/preview.css +10 -0
- package/.storybook/preview.tsx +97 -0
- package/.swcrc +42 -0
- package/.turbo/turbo-build.log +2 -0
- package/.turbo/turbo-type$colon$emit.log +1 -0
- package/.turbo/turbo-type.log +1 -0
- package/CHANGELOG.md +162 -0
- package/LICENSE +21 -0
- package/dist/components/identifier/identifier.d.ts +6 -0
- package/dist/components/identifier/identifier.d.ts.map +1 -0
- package/dist/components/identifier/identifier.js +144 -0
- package/dist/components/identifier/identifier.js.map +1 -0
- package/dist/components/identifier/index.d.ts +2 -0
- package/dist/components/identifier/index.d.ts.map +1 -0
- package/dist/components/identifier/index.js +3 -0
- package/dist/components/identifier/index.js.map +1 -0
- package/dist/components/job-graph/graph-model.d.ts +27 -0
- package/dist/components/job-graph/graph-model.d.ts.map +1 -0
- package/dist/components/job-graph/graph-model.js +107 -0
- package/dist/components/job-graph/graph-model.js.map +1 -0
- package/dist/components/job-graph/index.d.ts +3 -0
- package/dist/components/job-graph/index.d.ts.map +1 -0
- package/dist/components/job-graph/index.js +3 -0
- package/dist/components/job-graph/index.js.map +1 -0
- package/dist/components/job-graph/job-duration-format.d.ts +4 -0
- package/dist/components/job-graph/job-duration-format.d.ts.map +1 -0
- package/dist/components/job-graph/job-duration-format.js +41 -0
- package/dist/components/job-graph/job-duration-format.js.map +1 -0
- package/dist/components/job-graph/job-duration-label.d.ts +5 -0
- package/dist/components/job-graph/job-duration-label.d.ts.map +1 -0
- package/dist/components/job-graph/job-duration-label.js +33 -0
- package/dist/components/job-graph/job-duration-label.js.map +1 -0
- package/dist/components/job-graph/job-graph-content.d.ts +9 -0
- package/dist/components/job-graph/job-graph-content.d.ts.map +1 -0
- package/dist/components/job-graph/job-graph-content.js +181 -0
- package/dist/components/job-graph/job-graph-content.js.map +1 -0
- package/dist/components/job-graph/job-graph-view.d.ts +11 -0
- package/dist/components/job-graph/job-graph-view.d.ts.map +1 -0
- package/dist/components/job-graph/job-graph-view.js +24 -0
- package/dist/components/job-graph/job-graph-view.js.map +1 -0
- package/dist/components/job-graph/job-graph.d.ts +3 -0
- package/dist/components/job-graph/job-graph.d.ts.map +1 -0
- package/dist/components/job-graph/job-graph.js +21 -0
- package/dist/components/job-graph/job-graph.js.map +1 -0
- package/dist/components/job-graph/job-node.d.ts +16 -0
- package/dist/components/job-graph/job-node.d.ts.map +1 -0
- package/dist/components/job-graph/job-node.js +196 -0
- package/dist/components/job-graph/job-node.js.map +1 -0
- package/dist/components/job-graph/types.d.ts +9 -0
- package/dist/components/job-graph/types.d.ts.map +1 -0
- package/dist/components/job-graph/types.js +3 -0
- package/dist/components/job-graph/types.js.map +1 -0
- package/dist/components/step-list/index.d.ts +3 -0
- package/dist/components/step-list/index.d.ts.map +1 -0
- package/dist/components/step-list/index.js +3 -0
- package/dist/components/step-list/index.js.map +1 -0
- package/dist/components/step-list/step-list-model.d.ts +36 -0
- package/dist/components/step-list/step-list-model.d.ts.map +1 -0
- package/dist/components/step-list/step-list-model.js +135 -0
- package/dist/components/step-list/step-list-model.js.map +1 -0
- package/dist/components/step-list/step-list.d.ts +32 -0
- package/dist/components/step-list/step-list.d.ts.map +1 -0
- package/dist/components/step-list/step-list.js +409 -0
- package/dist/components/step-list/step-list.js.map +1 -0
- package/dist/components/workflow-run-duration-label.d.ts +8 -0
- package/dist/components/workflow-run-duration-label.d.ts.map +1 -0
- package/dist/components/workflow-run-duration-label.js +84 -0
- package/dist/components/workflow-run-duration-label.js.map +1 -0
- package/dist/components/workflow-run-list/index.d.ts +2 -0
- package/dist/components/workflow-run-list/index.d.ts.map +1 -0
- package/dist/components/workflow-run-list/index.js +3 -0
- package/dist/components/workflow-run-list/index.js.map +1 -0
- package/dist/components/workflow-run-list/run-display.d.ts +5 -0
- package/dist/components/workflow-run-list/run-display.d.ts.map +1 -0
- package/dist/components/workflow-run-list/run-display.js +19 -0
- package/dist/components/workflow-run-list/run-display.js.map +1 -0
- package/dist/components/workflow-run-list/types.d.ts +21 -0
- package/dist/components/workflow-run-list/types.d.ts.map +1 -0
- package/dist/components/workflow-run-list/types.js +3 -0
- package/dist/components/workflow-run-list/types.js.map +1 -0
- package/dist/components/workflow-run-list/workflow-run-list-content.d.ts +14 -0
- package/dist/components/workflow-run-list/workflow-run-list-content.d.ts.map +1 -0
- package/dist/components/workflow-run-list/workflow-run-list-content.js +37 -0
- package/dist/components/workflow-run-list/workflow-run-list-content.js.map +1 -0
- package/dist/components/workflow-run-list/workflow-run-list-header.d.ts +10 -0
- package/dist/components/workflow-run-list/workflow-run-list-header.d.ts.map +1 -0
- package/dist/components/workflow-run-list/workflow-run-list-header.js +55 -0
- package/dist/components/workflow-run-list/workflow-run-list-header.js.map +1 -0
- package/dist/components/workflow-run-list/workflow-run-list-states.d.ts +15 -0
- package/dist/components/workflow-run-list/workflow-run-list-states.d.ts.map +1 -0
- package/dist/components/workflow-run-list/workflow-run-list-states.js +103 -0
- package/dist/components/workflow-run-list/workflow-run-list-states.js.map +1 -0
- package/dist/components/workflow-run-list/workflow-run-list-view.d.ts +3 -0
- package/dist/components/workflow-run-list/workflow-run-list-view.d.ts.map +1 -0
- package/dist/components/workflow-run-list/workflow-run-list-view.js +46 -0
- package/dist/components/workflow-run-list/workflow-run-list-view.js.map +1 -0
- package/dist/components/workflow-run-list/workflow-run-list.d.ts +3 -0
- package/dist/components/workflow-run-list/workflow-run-list.d.ts.map +1 -0
- package/dist/components/workflow-run-list/workflow-run-list.js +20 -0
- package/dist/components/workflow-run-list/workflow-run-list.js.map +1 -0
- package/dist/components/workflow-run-list/workflow-run-row.d.ts +14 -0
- package/dist/components/workflow-run-list/workflow-run-row.d.ts.map +1 -0
- package/dist/components/workflow-run-list/workflow-run-row.js +172 -0
- package/dist/components/workflow-run-list/workflow-run-row.js.map +1 -0
- package/dist/components/workflow-run-summary/index.d.ts +2 -0
- package/dist/components/workflow-run-summary/index.d.ts.map +1 -0
- package/dist/components/workflow-run-summary/index.js +3 -0
- package/dist/components/workflow-run-summary/index.js.map +1 -0
- package/dist/components/workflow-run-summary/workflow-run-attempt-switcher.d.ts +9 -0
- package/dist/components/workflow-run-summary/workflow-run-attempt-switcher.d.ts.map +1 -0
- package/dist/components/workflow-run-summary/workflow-run-attempt-switcher.js +138 -0
- package/dist/components/workflow-run-summary/workflow-run-attempt-switcher.js.map +1 -0
- package/dist/components/workflow-run-summary/workflow-run-summary.d.ts +20 -0
- package/dist/components/workflow-run-summary/workflow-run-summary.d.ts.map +1 -0
- package/dist/components/workflow-run-summary/workflow-run-summary.js +252 -0
- package/dist/components/workflow-run-summary/workflow-run-summary.js.map +1 -0
- package/dist/components/workflow-run-view/agent-config-failure-callout.d.ts +7 -0
- package/dist/components/workflow-run-view/agent-config-failure-callout.d.ts.map +1 -0
- package/dist/components/workflow-run-view/agent-config-failure-callout.js +84 -0
- package/dist/components/workflow-run-view/agent-config-failure-callout.js.map +1 -0
- package/dist/components/workflow-run-view/index.d.ts +3 -0
- package/dist/components/workflow-run-view/index.d.ts.map +1 -0
- package/dist/components/workflow-run-view/index.js +4 -0
- package/dist/components/workflow-run-view/index.js.map +1 -0
- package/dist/components/workflow-run-view/job-card.d.ts +17 -0
- package/dist/components/workflow-run-view/job-card.d.ts.map +1 -0
- package/dist/components/workflow-run-view/job-card.js +476 -0
- package/dist/components/workflow-run-view/job-card.js.map +1 -0
- package/dist/components/workflow-run-view/job-execution-switcher.d.ts +10 -0
- package/dist/components/workflow-run-view/job-execution-switcher.d.ts.map +1 -0
- package/dist/components/workflow-run-view/job-execution-switcher.js +171 -0
- package/dist/components/workflow-run-view/job-execution-switcher.js.map +1 -0
- package/dist/components/workflow-run-view/job-execution-time-text.d.ts +6 -0
- package/dist/components/workflow-run-view/job-execution-time-text.d.ts.map +1 -0
- package/dist/components/workflow-run-view/job-execution-time-text.js +24 -0
- package/dist/components/workflow-run-view/job-execution-time-text.js.map +1 -0
- package/dist/components/workflow-run-view/step-attempt-log-panel.d.ts +9 -0
- package/dist/components/workflow-run-view/step-attempt-log-panel.d.ts.map +1 -0
- package/dist/components/workflow-run-view/step-attempt-log-panel.js +152 -0
- package/dist/components/workflow-run-view/step-attempt-log-panel.js.map +1 -0
- package/dist/components/workflow-run-view/workflow-run-selection.d.ts +14 -0
- package/dist/components/workflow-run-view/workflow-run-selection.d.ts.map +1 -0
- package/dist/components/workflow-run-view/workflow-run-selection.js +56 -0
- package/dist/components/workflow-run-view/workflow-run-selection.js.map +1 -0
- package/dist/components/workflow-run-view/workflow-run-states.d.ts +12 -0
- package/dist/components/workflow-run-view/workflow-run-states.d.ts.map +1 -0
- package/dist/components/workflow-run-view/workflow-run-states.js +91 -0
- package/dist/components/workflow-run-view/workflow-run-states.js.map +1 -0
- package/dist/components/workflow-run-view/workflow-run-view.d.ts +15 -0
- package/dist/components/workflow-run-view/workflow-run-view.d.ts.map +1 -0
- package/dist/components/workflow-run-view/workflow-run-view.js +285 -0
- package/dist/components/workflow-run-view/workflow-run-view.js.map +1 -0
- package/dist/components/workflow-source-panel/index.d.ts +2 -0
- package/dist/components/workflow-source-panel/index.d.ts.map +1 -0
- package/dist/components/workflow-source-panel/index.js +3 -0
- package/dist/components/workflow-source-panel/index.js.map +1 -0
- package/dist/components/workflow-source-panel/workflow-source-panel.d.ts +13 -0
- package/dist/components/workflow-source-panel/workflow-source-panel.d.ts.map +1 -0
- package/dist/components/workflow-source-panel/workflow-source-panel.js +91 -0
- package/dist/components/workflow-source-panel/workflow-source-panel.js.map +1 -0
- package/dist/components/workflow-status/status-visuals.d.ts +12 -0
- package/dist/components/workflow-status/status-visuals.d.ts.map +1 -0
- package/dist/components/workflow-status/status-visuals.js +54 -0
- package/dist/components/workflow-status/status-visuals.js.map +1 -0
- package/dist/components/workflow-status/workflow-status-icon.d.ts +22 -0
- package/dist/components/workflow-status/workflow-status-icon.d.ts.map +1 -0
- package/dist/components/workflow-status/workflow-status-icon.js +100 -0
- package/dist/components/workflow-status/workflow-status-icon.js.map +1 -0
- package/dist/core/entities/job-execution.d.ts +69 -0
- package/dist/core/entities/job-execution.d.ts.map +1 -0
- package/dist/core/entities/job-execution.js +83 -0
- package/dist/core/entities/job-execution.js.map +1 -0
- package/dist/core/entities/job.d.ts +56 -0
- package/dist/core/entities/job.d.ts.map +1 -0
- package/dist/core/entities/job.js +71 -0
- package/dist/core/entities/job.js.map +1 -0
- package/dist/core/entities/step-attempt.d.ts +49 -0
- package/dist/core/entities/step-attempt.d.ts.map +1 -0
- package/dist/core/entities/step-attempt.js +48 -0
- package/dist/core/entities/step-attempt.js.map +1 -0
- package/dist/core/entities/step.d.ts +41 -0
- package/dist/core/entities/step.d.ts.map +1 -0
- package/dist/core/entities/step.js +51 -0
- package/dist/core/entities/step.js.map +1 -0
- package/dist/core/entities/workflow-run-attempt.d.ts +44 -0
- package/dist/core/entities/workflow-run-attempt.d.ts.map +1 -0
- package/dist/core/entities/workflow-run-attempt.js +51 -0
- package/dist/core/entities/workflow-run-attempt.js.map +1 -0
- package/dist/core/entities/workflow-run.d.ts +61 -0
- package/dist/core/entities/workflow-run.d.ts.map +1 -0
- package/dist/core/entities/workflow-run.js +104 -0
- package/dist/core/entities/workflow-run.js.map +1 -0
- package/dist/core/workflow-run-url-state.d.ts +13 -0
- package/dist/core/workflow-run-url-state.d.ts.map +1 -0
- package/dist/core/workflow-run-url-state.js +43 -0
- package/dist/core/workflow-run-url-state.js.map +1 -0
- package/dist/core/workflow-run.d.ts +13 -0
- package/dist/core/workflow-run.d.ts.map +1 -0
- package/dist/core/workflow-run.js +8 -0
- package/dist/core/workflow-run.js.map +1 -0
- package/dist/env.d.js +2 -0
- package/dist/env.d.js.map +1 -0
- package/dist/feature.d.ts +20 -0
- package/dist/feature.d.ts.map +1 -0
- package/dist/feature.js +27 -0
- package/dist/feature.js.map +1 -0
- package/dist/hooks/api/workflow-runs.d.ts +109 -0
- package/dist/hooks/api/workflow-runs.d.ts.map +1 -0
- package/dist/hooks/api/workflow-runs.js +394 -0
- package/dist/hooks/api/workflow-runs.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/pages/workflow-run-first-time-use.d.ts +6 -0
- package/dist/pages/workflow-run-first-time-use.d.ts.map +1 -0
- package/dist/pages/workflow-run-first-time-use.js +17 -0
- package/dist/pages/workflow-run-first-time-use.js.map +1 -0
- package/dist/pages/workflow-run-page.d.ts +8 -0
- package/dist/pages/workflow-run-page.d.ts.map +1 -0
- package/dist/pages/workflow-run-page.js +83 -0
- package/dist/pages/workflow-run-page.js.map +1 -0
- package/dist/routes/run-detail.d.ts +8 -0
- package/dist/routes/run-detail.d.ts.map +1 -0
- package/dist/routes/run-detail.js +21 -0
- package/dist/routes/run-detail.js.map +1 -0
- package/dist/routes/runs.d.ts +8 -0
- package/dist/routes/runs.d.ts.map +1 -0
- package/dist/routes/runs.js +20 -0
- package/dist/routes/runs.js.map +1 -0
- package/dist/tsconfig.test.tsbuildinfo +1 -0
- package/package.json +114 -0
- package/src/components/identifier/identifier.tsx +178 -0
- package/src/components/identifier/index.ts +1 -0
- package/src/components/job-graph/graph-model.test.ts +189 -0
- package/src/components/job-graph/graph-model.ts +178 -0
- package/src/components/job-graph/index.ts +2 -0
- package/src/components/job-graph/job-duration-format.ts +54 -0
- package/src/components/job-graph/job-duration-label.tsx +29 -0
- package/src/components/job-graph/job-graph-content.tsx +225 -0
- package/src/components/job-graph/job-graph-view.test.tsx +298 -0
- package/src/components/job-graph/job-graph-view.tsx +45 -0
- package/src/components/job-graph/job-graph.stories.tsx +182 -0
- package/src/components/job-graph/job-graph.tsx +24 -0
- package/src/components/job-graph/job-node.stories.tsx +364 -0
- package/src/components/job-graph/job-node.test.tsx +347 -0
- package/src/components/job-graph/job-node.tsx +202 -0
- package/src/components/job-graph/types.ts +9 -0
- package/src/components/step-list/index.ts +6 -0
- package/src/components/step-list/step-list-model.test.ts +369 -0
- package/src/components/step-list/step-list-model.ts +205 -0
- package/src/components/step-list/step-list.stories.tsx +419 -0
- package/src/components/step-list/step-list.test.tsx +566 -0
- package/src/components/step-list/step-list.tsx +494 -0
- package/src/components/workflow-run-duration-label.tsx +118 -0
- package/src/components/workflow-run-list/index.ts +1 -0
- package/src/components/workflow-run-list/run-display.test.ts +53 -0
- package/src/components/workflow-run-list/run-display.ts +22 -0
- package/src/components/workflow-run-list/types.ts +22 -0
- package/src/components/workflow-run-list/workflow-run-list-content.tsx +58 -0
- package/src/components/workflow-run-list/workflow-run-list-header.tsx +53 -0
- package/src/components/workflow-run-list/workflow-run-list-states.tsx +86 -0
- package/src/components/workflow-run-list/workflow-run-list-view.test.tsx +178 -0
- package/src/components/workflow-run-list/workflow-run-list-view.tsx +57 -0
- package/src/components/workflow-run-list/workflow-run-list.stories.tsx +111 -0
- package/src/components/workflow-run-list/workflow-run-list.tsx +29 -0
- package/src/components/workflow-run-list/workflow-run-row.tsx +176 -0
- package/src/components/workflow-run-summary/index.ts +1 -0
- package/src/components/workflow-run-summary/workflow-run-attempt-switcher.test.tsx +230 -0
- package/src/components/workflow-run-summary/workflow-run-attempt-switcher.tsx +146 -0
- package/src/components/workflow-run-summary/workflow-run-summary.stories.tsx +414 -0
- package/src/components/workflow-run-summary/workflow-run-summary.test.tsx +354 -0
- package/src/components/workflow-run-summary/workflow-run-summary.tsx +295 -0
- package/src/components/workflow-run-view/agent-config-failure-callout.stories.tsx +140 -0
- package/src/components/workflow-run-view/agent-config-failure-callout.tsx +93 -0
- package/src/components/workflow-run-view/index.ts +2 -0
- package/src/components/workflow-run-view/job-card.stories.tsx +788 -0
- package/src/components/workflow-run-view/job-card.tsx +566 -0
- package/src/components/workflow-run-view/job-execution-switcher.tsx +170 -0
- package/src/components/workflow-run-view/job-execution-time-text.tsx +34 -0
- package/src/components/workflow-run-view/step-attempt-log-panel.test.tsx +290 -0
- package/src/components/workflow-run-view/step-attempt-log-panel.tsx +152 -0
- package/src/components/workflow-run-view/workflow-run-selection.test.ts +280 -0
- package/src/components/workflow-run-view/workflow-run-selection.ts +89 -0
- package/src/components/workflow-run-view/workflow-run-states.tsx +70 -0
- package/src/components/workflow-run-view/workflow-run-view.test.tsx +1214 -0
- package/src/components/workflow-run-view/workflow-run-view.tsx +341 -0
- package/src/components/workflow-source-panel/index.ts +1 -0
- package/src/components/workflow-source-panel/workflow-source-panel.stories.tsx +110 -0
- package/src/components/workflow-source-panel/workflow-source-panel.test.tsx +96 -0
- package/src/components/workflow-source-panel/workflow-source-panel.tsx +136 -0
- package/src/components/workflow-status/status-visuals.test.ts +38 -0
- package/src/components/workflow-status/status-visuals.ts +36 -0
- package/src/components/workflow-status/workflow-status-icon.stories.tsx +90 -0
- package/src/components/workflow-status/workflow-status-icon.tsx +135 -0
- package/src/core/entities/job-execution.ts +139 -0
- package/src/core/entities/job.ts +149 -0
- package/src/core/entities/step-attempt.ts +89 -0
- package/src/core/entities/step.ts +105 -0
- package/src/core/entities/workflow-run-attempt.ts +87 -0
- package/src/core/entities/workflow-run.ts +183 -0
- package/src/core/workflow-run-url-state.ts +61 -0
- package/src/core/workflow-run.test.ts +640 -0
- package/src/core/workflow-run.ts +66 -0
- package/src/env.d.ts +8 -0
- package/src/feature.ts +26 -0
- package/src/hooks/api/workflow-runs.test.tsx +410 -0
- package/src/hooks/api/workflow-runs.ts +485 -0
- package/src/index.ts +45 -0
- package/src/page-harness-budget.test.ts +33 -0
- package/src/pages/workflow-run-first-time-use.tsx +17 -0
- package/src/pages/workflow-run-page.test.tsx +517 -0
- package/src/pages/workflow-run-page.tsx +89 -0
- package/src/routes/run-detail.tsx +15 -0
- package/src/routes/runs.tsx +11 -0
- package/test/fixtures/logs.ts +13 -0
- package/test/fixtures/workflow-run.ts +311 -0
- package/test/pages.tsx +79 -0
- package/test/render.tsx +40 -0
- package/test/setup.ts +3 -0
- package/tsconfig.build.json +9 -0
- package/tsconfig.build.tsbuildinfo +1 -0
- package/tsconfig.json +3 -0
- package/tsconfig.test.json +8 -0
- package/vercel.json +8 -0
- package/vitest.config.ts +70 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type {QueryLoadErrorQuery} from '@shipfox/client-ui';
|
|
2
|
+
import type {WorkflowRunListItem} from '#core/workflow-run.js';
|
|
3
|
+
|
|
4
|
+
export type WorkflowRunListStatusFilter = 'all' | 'failed' | 'running';
|
|
5
|
+
|
|
6
|
+
export interface WorkflowRunListProps {
|
|
7
|
+
workspaceId: string;
|
|
8
|
+
projectId: string;
|
|
9
|
+
selectedWorkflowRunId?: string | undefined;
|
|
10
|
+
className?: string | undefined;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type WorkflowRunListQuery = QueryLoadErrorQuery & {isPending: boolean};
|
|
14
|
+
|
|
15
|
+
export interface WorkflowRunListViewProps {
|
|
16
|
+
runs: WorkflowRunListItem[];
|
|
17
|
+
query: WorkflowRunListQuery;
|
|
18
|
+
workspaceId: string;
|
|
19
|
+
projectId: string;
|
|
20
|
+
selectedWorkflowRunId?: string | undefined;
|
|
21
|
+
className?: string | undefined;
|
|
22
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import {QueryLoadError} from '@shipfox/client-ui';
|
|
2
|
+
import type {WorkflowRunListItem} from '#core/workflow-run.js';
|
|
3
|
+
import type {WorkflowRunListQuery} from './types.js';
|
|
4
|
+
import {
|
|
5
|
+
WorkflowRunListEmpty,
|
|
6
|
+
WorkflowRunListNoMatches,
|
|
7
|
+
WorkflowRunListSkeleton,
|
|
8
|
+
WorkflowRunListStaleError,
|
|
9
|
+
} from './workflow-run-list-states.js';
|
|
10
|
+
import {WorkflowRunRowList} from './workflow-run-row.js';
|
|
11
|
+
|
|
12
|
+
interface WorkflowRunListContentProps {
|
|
13
|
+
query: WorkflowRunListQuery;
|
|
14
|
+
totalRuns: number;
|
|
15
|
+
runs: WorkflowRunListItem[];
|
|
16
|
+
workspaceId: string;
|
|
17
|
+
projectId: string;
|
|
18
|
+
selectedWorkflowRunId?: string | undefined;
|
|
19
|
+
onClearFilters: () => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function WorkflowRunListContent({
|
|
23
|
+
query,
|
|
24
|
+
totalRuns,
|
|
25
|
+
runs,
|
|
26
|
+
workspaceId,
|
|
27
|
+
projectId,
|
|
28
|
+
selectedWorkflowRunId,
|
|
29
|
+
onClearFilters,
|
|
30
|
+
}: WorkflowRunListContentProps) {
|
|
31
|
+
const {isPending, isError} = query;
|
|
32
|
+
// A refetch that fails after a prior success keeps the rows on screen behind a slim
|
|
33
|
+
// banner. QueryLoadError owns the inverse case (errored before anything loaded) and
|
|
34
|
+
// self-gates to nothing here once data exists.
|
|
35
|
+
const refreshFailed = isError && query.data !== undefined;
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<div className="min-h-0 flex-1 overflow-y-auto">
|
|
39
|
+
{isPending ? <WorkflowRunListSkeleton /> : null}
|
|
40
|
+
{!isPending ? (
|
|
41
|
+
<QueryLoadError query={query} subject="workflow runs" icon="pulseLine" />
|
|
42
|
+
) : null}
|
|
43
|
+
{!isPending && refreshFailed ? <WorkflowRunListStaleError query={query} /> : null}
|
|
44
|
+
{!isPending && !isError && totalRuns === 0 ? <WorkflowRunListEmpty /> : null}
|
|
45
|
+
{!isPending && totalRuns > 0 && runs.length === 0 ? (
|
|
46
|
+
<WorkflowRunListNoMatches onClear={onClearFilters} />
|
|
47
|
+
) : null}
|
|
48
|
+
{!isPending && runs.length > 0 ? (
|
|
49
|
+
<WorkflowRunRowList
|
|
50
|
+
runs={runs}
|
|
51
|
+
workspaceId={workspaceId}
|
|
52
|
+
projectId={projectId}
|
|
53
|
+
selectedWorkflowRunId={selectedWorkflowRunId}
|
|
54
|
+
/>
|
|
55
|
+
) : null}
|
|
56
|
+
</div>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import {Button} from '@shipfox/react-ui/button';
|
|
2
|
+
import {Icon} from '@shipfox/react-ui/icon';
|
|
3
|
+
import {Input} from '@shipfox/react-ui/input';
|
|
4
|
+
import type {WorkflowRunListStatusFilter} from './types.js';
|
|
5
|
+
|
|
6
|
+
const STATUS_FILTERS: Array<{value: WorkflowRunListStatusFilter; label: string}> = [
|
|
7
|
+
{value: 'all', label: 'All'},
|
|
8
|
+
{value: 'failed', label: 'Failed'},
|
|
9
|
+
{value: 'running', label: 'Running'},
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
interface WorkflowRunListHeaderProps {
|
|
13
|
+
query: string;
|
|
14
|
+
onQueryChange: (value: string) => void;
|
|
15
|
+
statusFilter: WorkflowRunListStatusFilter;
|
|
16
|
+
onStatusFilterChange: (value: WorkflowRunListStatusFilter) => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function WorkflowRunListHeader({
|
|
20
|
+
query,
|
|
21
|
+
onQueryChange,
|
|
22
|
+
statusFilter,
|
|
23
|
+
onStatusFilterChange,
|
|
24
|
+
}: WorkflowRunListHeaderProps) {
|
|
25
|
+
return (
|
|
26
|
+
<div className="flex flex-col gap-8 border-b border-border-neutral-base px-12 py-12">
|
|
27
|
+
<Input
|
|
28
|
+
value={query}
|
|
29
|
+
onChange={(event) => onQueryChange(event.target.value)}
|
|
30
|
+
placeholder="Run id or trigger..."
|
|
31
|
+
aria-label="Search runs"
|
|
32
|
+
size="small"
|
|
33
|
+
iconLeft={<Icon name="searchLine" className="size-14 text-foreground-neutral-muted" />}
|
|
34
|
+
/>
|
|
35
|
+
|
|
36
|
+
<fieldset className="flex items-center gap-8">
|
|
37
|
+
<legend className="sr-only">Run status filter</legend>
|
|
38
|
+
{STATUS_FILTERS.map((filter) => (
|
|
39
|
+
<Button
|
|
40
|
+
key={filter.value}
|
|
41
|
+
type="button"
|
|
42
|
+
size="2xs"
|
|
43
|
+
variant={statusFilter === filter.value ? 'primary' : 'transparent'}
|
|
44
|
+
aria-pressed={statusFilter === filter.value}
|
|
45
|
+
onClick={() => onStatusFilterChange(filter.value)}
|
|
46
|
+
>
|
|
47
|
+
{filter.label}
|
|
48
|
+
</Button>
|
|
49
|
+
))}
|
|
50
|
+
</fieldset>
|
|
51
|
+
</div>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type {QueryLoadErrorQuery} from '@shipfox/client-ui';
|
|
2
|
+
import {Button} from '@shipfox/react-ui/button';
|
|
3
|
+
import {Callout} from '@shipfox/react-ui/callout';
|
|
4
|
+
import {EmptyState} from '@shipfox/react-ui/empty-state';
|
|
5
|
+
import {Skeleton} from '@shipfox/react-ui/skeleton';
|
|
6
|
+
import {Text} from '@shipfox/react-ui/typography';
|
|
7
|
+
|
|
8
|
+
export function WorkflowRunListSkeleton() {
|
|
9
|
+
return (
|
|
10
|
+
<div className="flex flex-col gap-4 p-8" role="status" aria-label="Loading runs">
|
|
11
|
+
{Array.from({length: 6}).map((_, index) => (
|
|
12
|
+
<div
|
|
13
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: skeleton row, stable position
|
|
14
|
+
key={index}
|
|
15
|
+
className="flex flex-col gap-3 rounded-8 border border-transparent px-10 py-7"
|
|
16
|
+
>
|
|
17
|
+
<div className="flex h-20 items-center gap-7">
|
|
18
|
+
<Skeleton className="size-6 shrink-0 rounded-full" />
|
|
19
|
+
<Skeleton className="h-12 w-1/3" />
|
|
20
|
+
</div>
|
|
21
|
+
<div className="flex h-20 items-center gap-6 pl-13">
|
|
22
|
+
<Skeleton className="h-12 flex-1" />
|
|
23
|
+
<Skeleton className="h-12 w-40" />
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
))}
|
|
27
|
+
</div>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Slim non-blocking banner shown when a background refetch fails after a prior success,
|
|
33
|
+
* so the already-loaded rows stay on screen with the retry inline rather than being
|
|
34
|
+
* replaced by a full-placeholder error.
|
|
35
|
+
*/
|
|
36
|
+
export function WorkflowRunListStaleError({query}: {query: QueryLoadErrorQuery}) {
|
|
37
|
+
return (
|
|
38
|
+
<div className="border-b border-border-neutral-base p-8">
|
|
39
|
+
<Callout role="alert" type="error">
|
|
40
|
+
<div className="flex items-center justify-between gap-8">
|
|
41
|
+
<Text size="xs">Could not refresh workflow runs.</Text>
|
|
42
|
+
<Button
|
|
43
|
+
type="button"
|
|
44
|
+
size="2xs"
|
|
45
|
+
variant="secondary"
|
|
46
|
+
isLoading={query.isFetching}
|
|
47
|
+
onClick={() => {
|
|
48
|
+
void query.refetch();
|
|
49
|
+
}}
|
|
50
|
+
>
|
|
51
|
+
Retry
|
|
52
|
+
</Button>
|
|
53
|
+
</div>
|
|
54
|
+
</Callout>
|
|
55
|
+
</div>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function WorkflowRunListEmpty() {
|
|
60
|
+
return (
|
|
61
|
+
<div className="p-16">
|
|
62
|
+
<EmptyState
|
|
63
|
+
icon="pulseLine"
|
|
64
|
+
title="No runs yet"
|
|
65
|
+
description="Runs from this project's workflows will appear here as soon as one is launched."
|
|
66
|
+
/>
|
|
67
|
+
</div>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function WorkflowRunListNoMatches({onClear}: {onClear: () => void}) {
|
|
72
|
+
return (
|
|
73
|
+
<div className="p-16">
|
|
74
|
+
<EmptyState
|
|
75
|
+
icon="filterOffLine"
|
|
76
|
+
title="No matching runs"
|
|
77
|
+
description="No runs match your current search or status filter."
|
|
78
|
+
action={
|
|
79
|
+
<Button type="button" size="sm" variant="secondary" onClick={onClear}>
|
|
80
|
+
Clear filters
|
|
81
|
+
</Button>
|
|
82
|
+
}
|
|
83
|
+
/>
|
|
84
|
+
</div>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import {screen} from '@testing-library/react';
|
|
2
|
+
import userEvent from '@testing-library/user-event';
|
|
3
|
+
import type {WorkflowRunListItem, WorkflowRunStatus} from '#core/workflow-run.js';
|
|
4
|
+
import {workflowRunListItem} from '#test/fixtures/workflow-run.js';
|
|
5
|
+
import {renderWithRouter} from '#test/render.js';
|
|
6
|
+
import type {WorkflowRunListQuery} from './types.js';
|
|
7
|
+
import {WorkflowRunListView} from './workflow-run-list-view.js';
|
|
8
|
+
|
|
9
|
+
const WORKSPACE_ID = '11111111-1111-4111-8111-111111111111';
|
|
10
|
+
const PROJECT_ID = '44444444-4444-4444-8444-444444444444';
|
|
11
|
+
|
|
12
|
+
function loadedQuery(): WorkflowRunListQuery {
|
|
13
|
+
return {
|
|
14
|
+
isPending: false,
|
|
15
|
+
isError: false,
|
|
16
|
+
isFetching: false,
|
|
17
|
+
data: {pages: [], pageParams: []},
|
|
18
|
+
error: null,
|
|
19
|
+
refetch: () => undefined,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe('WorkflowRunListView', () => {
|
|
24
|
+
afterEach(() => {
|
|
25
|
+
vi.useRealTimers();
|
|
26
|
+
vi.restoreAllMocks();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('narrows the list to the selected status filter', async () => {
|
|
30
|
+
const user = userEvent.setup();
|
|
31
|
+
renderListView([
|
|
32
|
+
run('running', 'deploy-web'),
|
|
33
|
+
run('failed', 'integration-tests'),
|
|
34
|
+
run('succeeded', 'build-image'),
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
await user.click(await screen.findByRole('button', {name: 'Failed'}));
|
|
38
|
+
|
|
39
|
+
expect(screen.getByText('integration-tests')).toBeInTheDocument();
|
|
40
|
+
expect(screen.queryByText('deploy-web')).not.toBeInTheDocument();
|
|
41
|
+
expect(screen.queryByText('build-image')).not.toBeInTheDocument();
|
|
42
|
+
expect(screen.getByRole('button', {name: 'Failed'})).toHaveAttribute('aria-pressed', 'true');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('the Running filter keeps in-progress pending runs alongside running ones', async () => {
|
|
46
|
+
const user = userEvent.setup();
|
|
47
|
+
renderListView([
|
|
48
|
+
run('running', 'deploy-web'),
|
|
49
|
+
run('pending', 'queued-build'),
|
|
50
|
+
run('succeeded', 'build-image'),
|
|
51
|
+
]);
|
|
52
|
+
|
|
53
|
+
await user.click(await screen.findByRole('button', {name: 'Running'}));
|
|
54
|
+
|
|
55
|
+
expect(screen.getByText('deploy-web')).toBeInTheDocument();
|
|
56
|
+
expect(screen.getByText('queued-build')).toBeInTheDocument();
|
|
57
|
+
expect(screen.queryByText('build-image')).not.toBeInTheDocument();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('renders optimistic temp runs without a navigable link', async () => {
|
|
61
|
+
renderListView([run('pending', 'queued-build', 'temp-1234'), run('running', 'deploy-web')]);
|
|
62
|
+
|
|
63
|
+
// The canonical run is a link to its detail page; the optimistic temp run is shown but
|
|
64
|
+
// not yet navigable (its detail page does not exist until the canonical row replaces it).
|
|
65
|
+
const links = await screen.findAllByRole('link');
|
|
66
|
+
expect(links.some((link) => link.textContent?.includes('deploy-web'))).toBe(true);
|
|
67
|
+
expect(links.some((link) => link.textContent?.includes('queued-build'))).toBe(false);
|
|
68
|
+
expect(screen.getByText('queued-build')).toBeInTheDocument();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('shows a finished run duration in the row metadata', async () => {
|
|
72
|
+
renderListView([
|
|
73
|
+
run('succeeded', 'build-image', 'run-build-image', {
|
|
74
|
+
started_at: '2026-05-07T01:00:00.000Z',
|
|
75
|
+
finished_at: '2026-05-07T01:02:14.000Z',
|
|
76
|
+
}),
|
|
77
|
+
]);
|
|
78
|
+
|
|
79
|
+
const duration = await screen.findByText('2m 14s');
|
|
80
|
+
expect(duration).toBeInTheDocument();
|
|
81
|
+
expect(duration).toHaveAttribute('aria-label', 'ran 2m 14s');
|
|
82
|
+
expect(
|
|
83
|
+
screen.getByRole('link', {
|
|
84
|
+
name: (name) => name.includes('build-image') && name.includes('ran 2m 14s'),
|
|
85
|
+
}),
|
|
86
|
+
).toBeInTheDocument();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('shows a live running run duration in the row metadata', async () => {
|
|
90
|
+
vi.spyOn(Date, 'now').mockReturnValue(Date.parse('2026-05-07T01:02:14.000Z'));
|
|
91
|
+
|
|
92
|
+
renderListView([
|
|
93
|
+
run('running', 'deploy-web', 'run-deploy-web', {
|
|
94
|
+
started_at: '2026-05-07T01:00:00.000Z',
|
|
95
|
+
finished_at: null,
|
|
96
|
+
}),
|
|
97
|
+
]);
|
|
98
|
+
|
|
99
|
+
const duration = await screen.findByText('2m 14s');
|
|
100
|
+
expect(duration).toBeInTheDocument();
|
|
101
|
+
expect(duration).toHaveAttribute('aria-label', 'running 2m 14s');
|
|
102
|
+
expect(
|
|
103
|
+
screen.getByRole('link', {
|
|
104
|
+
name: (name) => name.includes('deploy-web') && name.includes('running 2m 14s'),
|
|
105
|
+
}),
|
|
106
|
+
).toBeInTheDocument();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test('scopes the trigger tooltip to the trigger label', async () => {
|
|
110
|
+
const user = userEvent.setup();
|
|
111
|
+
renderListView([run('failed', 'integration-tests')]);
|
|
112
|
+
|
|
113
|
+
await user.hover(
|
|
114
|
+
await screen.findByRole('link', {
|
|
115
|
+
name: (name) =>
|
|
116
|
+
name.includes('integration-tests') && name.includes('github') && name.includes('push'),
|
|
117
|
+
}),
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
expect(screen.queryByRole('tooltip')).not.toBeInTheDocument();
|
|
121
|
+
|
|
122
|
+
await user.hover(screen.getByText('push'));
|
|
123
|
+
|
|
124
|
+
expect(await screen.findByRole('tooltip')).toHaveTextContent('github_acme · push');
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test('restores every row after the filters are cleared', async () => {
|
|
128
|
+
const user = userEvent.setup();
|
|
129
|
+
renderListView([
|
|
130
|
+
run('running', 'deploy-web'),
|
|
131
|
+
run('failed', 'integration-tests'),
|
|
132
|
+
run('succeeded', 'build-image'),
|
|
133
|
+
]);
|
|
134
|
+
await user.type(await screen.findByLabelText('Search runs'), 'no-such-run');
|
|
135
|
+
|
|
136
|
+
expect(screen.getByText('No matching runs')).toBeInTheDocument();
|
|
137
|
+
|
|
138
|
+
await user.click(await screen.findByRole('button', {name: 'Clear filters'}));
|
|
139
|
+
|
|
140
|
+
expect(screen.getByText('deploy-web')).toBeInTheDocument();
|
|
141
|
+
expect(screen.getByText('integration-tests')).toBeInTheDocument();
|
|
142
|
+
expect(screen.getByText('build-image')).toBeInTheDocument();
|
|
143
|
+
expect(screen.getByLabelText('Search runs')).toHaveValue('');
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
function renderListView(runs: WorkflowRunListItem[]) {
|
|
148
|
+
// Row links need router context; the query and data stay injected by props.
|
|
149
|
+
renderWithRouter(
|
|
150
|
+
<WorkflowRunListView
|
|
151
|
+
runs={runs}
|
|
152
|
+
query={loadedQuery()}
|
|
153
|
+
workspaceId={WORKSPACE_ID}
|
|
154
|
+
projectId={PROJECT_ID}
|
|
155
|
+
/>,
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function run(
|
|
160
|
+
status: WorkflowRunStatus,
|
|
161
|
+
name: string,
|
|
162
|
+
id = `run-${name}`,
|
|
163
|
+
overrides: NonNullable<Parameters<typeof workflowRunListItem>[0]> = {},
|
|
164
|
+
): WorkflowRunListItem {
|
|
165
|
+
return workflowRunListItem({
|
|
166
|
+
id,
|
|
167
|
+
project_id: PROJECT_ID,
|
|
168
|
+
definition_id: '55555555-5555-4555-8555-555555555555',
|
|
169
|
+
name,
|
|
170
|
+
status,
|
|
171
|
+
trigger_provider: 'github',
|
|
172
|
+
trigger_source: 'github_acme',
|
|
173
|
+
trigger_event: 'push',
|
|
174
|
+
created_at: '2026-05-07T01:01:00.000Z',
|
|
175
|
+
updated_at: '2026-05-07T01:02:00.000Z',
|
|
176
|
+
...overrides,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import {TimeTickerProvider} from '@shipfox/react-ui/time-ticker';
|
|
2
|
+
import {cn} from '@shipfox/react-ui/utils';
|
|
3
|
+
import {useState} from 'react';
|
|
4
|
+
import {runMatchesSearch, runMatchesStatusFilter} from './run-display.js';
|
|
5
|
+
import type {WorkflowRunListStatusFilter, WorkflowRunListViewProps} from './types.js';
|
|
6
|
+
import {WorkflowRunListContent} from './workflow-run-list-content.js';
|
|
7
|
+
import {WorkflowRunListHeader} from './workflow-run-list-header.js';
|
|
8
|
+
|
|
9
|
+
export function WorkflowRunListView({
|
|
10
|
+
runs,
|
|
11
|
+
query,
|
|
12
|
+
workspaceId,
|
|
13
|
+
projectId,
|
|
14
|
+
selectedWorkflowRunId,
|
|
15
|
+
className,
|
|
16
|
+
}: WorkflowRunListViewProps) {
|
|
17
|
+
const [search, setSearch] = useState('');
|
|
18
|
+
const [statusFilter, setStatusFilter] = useState<WorkflowRunListStatusFilter>('all');
|
|
19
|
+
|
|
20
|
+
const filteredRuns = runs.filter((run) => {
|
|
21
|
+
if (!runMatchesStatusFilter(run.status, statusFilter)) return false;
|
|
22
|
+
return runMatchesSearch(run, search);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
function handleClearFilters() {
|
|
26
|
+
setSearch('');
|
|
27
|
+
setStatusFilter('all');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<TimeTickerProvider intervalMs={1000} reducedMotionIntervalMs={10_000}>
|
|
32
|
+
<aside
|
|
33
|
+
className={cn(
|
|
34
|
+
'flex w-304 shrink-0 flex-col border-r border-border-neutral-base bg-background-subtle-base',
|
|
35
|
+
className,
|
|
36
|
+
)}
|
|
37
|
+
aria-label="Workflow runs"
|
|
38
|
+
>
|
|
39
|
+
<WorkflowRunListHeader
|
|
40
|
+
query={search}
|
|
41
|
+
onQueryChange={setSearch}
|
|
42
|
+
statusFilter={statusFilter}
|
|
43
|
+
onStatusFilterChange={setStatusFilter}
|
|
44
|
+
/>
|
|
45
|
+
<WorkflowRunListContent
|
|
46
|
+
query={query}
|
|
47
|
+
totalRuns={runs.length}
|
|
48
|
+
runs={filteredRuns}
|
|
49
|
+
workspaceId={workspaceId}
|
|
50
|
+
projectId={projectId}
|
|
51
|
+
selectedWorkflowRunId={selectedWorkflowRunId}
|
|
52
|
+
onClearFilters={handleClearFilters}
|
|
53
|
+
/>
|
|
54
|
+
</aside>
|
|
55
|
+
</TimeTickerProvider>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import {Code} from '@shipfox/react-ui/typography';
|
|
2
|
+
import type {Meta, StoryObj} from '@storybook/react';
|
|
3
|
+
import type {ReactNode} from 'react';
|
|
4
|
+
import {expect, userEvent, within} from 'storybook/test';
|
|
5
|
+
import type {WorkflowRunListItem, WorkflowRunStatus} from '#core/workflow-run.js';
|
|
6
|
+
import {sequencedWorkflowRunListItem} from '#test/fixtures/workflow-run.js';
|
|
7
|
+
import type {WorkflowRunListQuery} from './types.js';
|
|
8
|
+
import {WorkflowRunListView} from './workflow-run-list-view.js';
|
|
9
|
+
|
|
10
|
+
// Stand-in for the react-query result the view reads. `data !== undefined` is the
|
|
11
|
+
// "loaded at least once" signal that splits a fresh load error from a stale refresh.
|
|
12
|
+
function makeQuery(overrides: Partial<WorkflowRunListQuery> = {}): WorkflowRunListQuery {
|
|
13
|
+
return {
|
|
14
|
+
isPending: false,
|
|
15
|
+
isError: false,
|
|
16
|
+
isFetching: false,
|
|
17
|
+
data: {pages: [], pageParams: []},
|
|
18
|
+
error: null,
|
|
19
|
+
refetch: () => undefined,
|
|
20
|
+
...overrides,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function makeRun(status: WorkflowRunStatus, name: string, minutesAgo: number): WorkflowRunListItem {
|
|
25
|
+
return sequencedWorkflowRunListItem(status, name, minutesAgo);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const SAMPLE_RUNS: WorkflowRunListItem[] = [
|
|
29
|
+
makeRun('running', 'deploy-web', 1),
|
|
30
|
+
makeRun('failed', 'integration-tests', 4),
|
|
31
|
+
makeRun('succeeded', 'build-image', 12),
|
|
32
|
+
makeRun('cancelled', 'lint-and-type', 38),
|
|
33
|
+
makeRun('pending', 'release-prod', 0),
|
|
34
|
+
makeRun('succeeded', 'release-production-multi-region-with-canary-and-smoke-tests', 95),
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
// One story on the full rail. The data states (loading / empty / errors / runs) are
|
|
38
|
+
// driven by args; search, status filter, "clear filters" and "no matches" are live in
|
|
39
|
+
// the rendered toolbar since the view owns that state. The decorator gives the rail a
|
|
40
|
+
// real height so `flex-1` content scrolls the way it does in the app shell.
|
|
41
|
+
const meta = {
|
|
42
|
+
title: 'Workflows/WorkflowRunList',
|
|
43
|
+
component: WorkflowRunListView,
|
|
44
|
+
parameters: {layout: 'centered'},
|
|
45
|
+
decorators: [
|
|
46
|
+
(Story) => (
|
|
47
|
+
<div className="flex h-600">
|
|
48
|
+
<Story />
|
|
49
|
+
</div>
|
|
50
|
+
),
|
|
51
|
+
],
|
|
52
|
+
args: {
|
|
53
|
+
runs: SAMPLE_RUNS,
|
|
54
|
+
query: makeQuery(),
|
|
55
|
+
workspaceId: 'ws-demo',
|
|
56
|
+
projectId: 'proj-demo',
|
|
57
|
+
},
|
|
58
|
+
} satisfies Meta<typeof WorkflowRunListView>;
|
|
59
|
+
|
|
60
|
+
export default meta;
|
|
61
|
+
type Story = StoryObj<typeof meta>;
|
|
62
|
+
|
|
63
|
+
export const Playground: Story = {};
|
|
64
|
+
|
|
65
|
+
export const Selected: Story = {
|
|
66
|
+
args: {selectedWorkflowRunId: SAMPLE_RUNS[1].id},
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export const DataStates: Story = {
|
|
70
|
+
render: (args) => (
|
|
71
|
+
<div className="grid grid-cols-2 gap-24">
|
|
72
|
+
<StateExample label="Loading">
|
|
73
|
+
<WorkflowRunListView {...args} query={makeQuery({isPending: true, data: undefined})} />
|
|
74
|
+
</StateExample>
|
|
75
|
+
<StateExample label="Empty">
|
|
76
|
+
<WorkflowRunListView {...args} runs={[]} />
|
|
77
|
+
</StateExample>
|
|
78
|
+
<StateExample label="Load error">
|
|
79
|
+
<WorkflowRunListView
|
|
80
|
+
{...args}
|
|
81
|
+
runs={[]}
|
|
82
|
+
query={makeQuery({isError: true, data: undefined})}
|
|
83
|
+
/>
|
|
84
|
+
</StateExample>
|
|
85
|
+
<StateExample label="Stale error">
|
|
86
|
+
<WorkflowRunListView {...args} query={makeQuery({isError: true})} />
|
|
87
|
+
</StateExample>
|
|
88
|
+
</div>
|
|
89
|
+
),
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export const TestNoMatches: Story = {
|
|
93
|
+
play: async ({canvasElement}) => {
|
|
94
|
+
const canvas = within(canvasElement);
|
|
95
|
+
await userEvent.type(canvas.getByLabelText('Search runs'), 'no-such-run');
|
|
96
|
+
await expect(await canvas.findByText('No matching runs')).toBeInTheDocument();
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
function StateExample({label, children}: {label: string; children: ReactNode}) {
|
|
101
|
+
return (
|
|
102
|
+
<div className="flex w-304 flex-col gap-8">
|
|
103
|
+
<Code variant="label" className="text-foreground-neutral-subtle">
|
|
104
|
+
{label}
|
|
105
|
+
</Code>
|
|
106
|
+
<div className="flex h-560 rounded-8 border border-border-neutral-base bg-background-neutral-base">
|
|
107
|
+
{children}
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {useMemo} from 'react';
|
|
2
|
+
import type {WorkflowRunListItem} from '#core/workflow-run.js';
|
|
3
|
+
import {useWorkflowRunsInfiniteQuery} from '#hooks/api/workflow-runs.js';
|
|
4
|
+
import type {WorkflowRunListProps} from './types.js';
|
|
5
|
+
import {WorkflowRunListView} from './workflow-run-list-view.js';
|
|
6
|
+
|
|
7
|
+
export function WorkflowRunList({
|
|
8
|
+
workspaceId,
|
|
9
|
+
projectId,
|
|
10
|
+
selectedWorkflowRunId,
|
|
11
|
+
className,
|
|
12
|
+
}: WorkflowRunListProps) {
|
|
13
|
+
const query = useWorkflowRunsInfiniteQuery(projectId, {});
|
|
14
|
+
const runs = useMemo<WorkflowRunListItem[]>(
|
|
15
|
+
() => query.data?.pages.flatMap((page) => page.runs) ?? [],
|
|
16
|
+
[query.data],
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<WorkflowRunListView
|
|
21
|
+
runs={runs}
|
|
22
|
+
query={query}
|
|
23
|
+
workspaceId={workspaceId}
|
|
24
|
+
projectId={projectId}
|
|
25
|
+
selectedWorkflowRunId={selectedWorkflowRunId}
|
|
26
|
+
className={className}
|
|
27
|
+
/>
|
|
28
|
+
);
|
|
29
|
+
}
|