@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,171 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuTrigger } from '@shipfox/react-ui/dropdown-menu';
|
|
3
|
+
import { Icon } from '@shipfox/react-ui/icon';
|
|
4
|
+
import { Text } from '@shipfox/react-ui/typography';
|
|
5
|
+
import { cn } from '@shipfox/react-ui/utils';
|
|
6
|
+
import { useMemo } from 'react';
|
|
7
|
+
import { WorkflowStatusIcon } from '#components/workflow-status/workflow-status-icon.js';
|
|
8
|
+
import { JobExecutionTimeText } from './job-execution-time-text.js';
|
|
9
|
+
export function JobExecutionSwitcher({ job, selectedJobExecution, onSelectedJobExecutionChange, variant = 'compact', className }) {
|
|
10
|
+
const executions = useMemo(()=>[
|
|
11
|
+
...job.jobExecutions
|
|
12
|
+
].sort((left, right)=>right.sequence - left.sequence), [
|
|
13
|
+
job.jobExecutions
|
|
14
|
+
]);
|
|
15
|
+
const selected = executions.find((jobExecution)=>jobExecution.id === selectedJobExecution) ?? executions[0] ?? null;
|
|
16
|
+
if (selected === null) return null;
|
|
17
|
+
if (!job.executionCountVisible) {
|
|
18
|
+
return /*#__PURE__*/ _jsx("div", {
|
|
19
|
+
className: cn('flex min-w-0 items-center gap-6 text-sm leading-20 text-foreground-neutral-subtle', className),
|
|
20
|
+
children: /*#__PURE__*/ _jsx(ExecutionSummary, {
|
|
21
|
+
execution: selected
|
|
22
|
+
})
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
return /*#__PURE__*/ _jsxs(DropdownMenu, {
|
|
26
|
+
children: [
|
|
27
|
+
/*#__PURE__*/ _jsxs(DropdownMenuTrigger, {
|
|
28
|
+
className: cn('inline-flex min-w-0 max-w-full items-center rounded-6 text-left transition-colors focus-visible:shadow-border-interactive-with-active focus-visible:outline-none', variant === 'title' ? '-mx-6 -my-4 gap-8 px-6 py-4 hover:bg-background-components-hover' : 'min-h-28 gap-6 px-8 py-4 text-sm leading-20 text-foreground-neutral-subtle hover:bg-background-components-hover', className),
|
|
29
|
+
"aria-label": `Switch job execution, currently execution ${selected.sequence}`,
|
|
30
|
+
children: [
|
|
31
|
+
variant === 'title' ? /*#__PURE__*/ _jsx(TitleExecutionSummary, {
|
|
32
|
+
job: job,
|
|
33
|
+
execution: selected
|
|
34
|
+
}) : /*#__PURE__*/ _jsx(ExecutionSummary, {
|
|
35
|
+
execution: selected
|
|
36
|
+
}),
|
|
37
|
+
/*#__PURE__*/ _jsx(Icon, {
|
|
38
|
+
name: "arrowDownSLine",
|
|
39
|
+
className: "size-14 shrink-0 text-foreground-neutral-muted"
|
|
40
|
+
})
|
|
41
|
+
]
|
|
42
|
+
}),
|
|
43
|
+
/*#__PURE__*/ _jsxs(DropdownMenuContent, {
|
|
44
|
+
align: "start",
|
|
45
|
+
size: "lg",
|
|
46
|
+
className: "max-h-[320px] overflow-y-auto",
|
|
47
|
+
children: [
|
|
48
|
+
/*#__PURE__*/ _jsxs(DropdownMenuLabel, {
|
|
49
|
+
children: [
|
|
50
|
+
executions.length,
|
|
51
|
+
" execution",
|
|
52
|
+
executions.length === 1 ? '' : 's'
|
|
53
|
+
]
|
|
54
|
+
}),
|
|
55
|
+
executions.map((jobExecution)=>{
|
|
56
|
+
const isSelected = jobExecution.id === selected.id;
|
|
57
|
+
return /*#__PURE__*/ _jsx(DropdownMenuItem, {
|
|
58
|
+
asChild: true,
|
|
59
|
+
onSelect: ()=>onSelectedJobExecutionChange(jobExecution.id),
|
|
60
|
+
children: /*#__PURE__*/ _jsxs("button", {
|
|
61
|
+
type: "button",
|
|
62
|
+
"aria-current": isSelected ? 'true' : undefined,
|
|
63
|
+
className: "w-full text-left",
|
|
64
|
+
children: [
|
|
65
|
+
/*#__PURE__*/ _jsx(WorkflowStatusIcon, {
|
|
66
|
+
status: jobExecution.status,
|
|
67
|
+
size: 14,
|
|
68
|
+
tooltip: false
|
|
69
|
+
}),
|
|
70
|
+
/*#__PURE__*/ _jsxs("span", {
|
|
71
|
+
className: "font-code text-xs leading-20 text-foreground-neutral-base tabular-nums",
|
|
72
|
+
children: [
|
|
73
|
+
"#",
|
|
74
|
+
jobExecution.sequence
|
|
75
|
+
]
|
|
76
|
+
}),
|
|
77
|
+
/*#__PURE__*/ _jsx("span", {
|
|
78
|
+
className: "min-w-0 truncate text-xs leading-20 text-foreground-neutral-base",
|
|
79
|
+
children: job.displayName
|
|
80
|
+
}),
|
|
81
|
+
!isSelected && jobExecution.statusReason ? /*#__PURE__*/ _jsx("span", {
|
|
82
|
+
className: "min-w-0 flex-1 truncate text-xs leading-20 text-foreground-neutral-muted",
|
|
83
|
+
children: jobExecution.statusReason
|
|
84
|
+
}) : /*#__PURE__*/ _jsx("span", {
|
|
85
|
+
className: "min-w-0 flex-1"
|
|
86
|
+
}),
|
|
87
|
+
isSelected ? /*#__PURE__*/ _jsx(Icon, {
|
|
88
|
+
name: "check",
|
|
89
|
+
className: "size-14 shrink-0 text-foreground-neutral-base"
|
|
90
|
+
}) : null,
|
|
91
|
+
/*#__PURE__*/ _jsx(JobExecutionDuration, {
|
|
92
|
+
execution: jobExecution,
|
|
93
|
+
className: "ml-auto shrink-0 font-code text-xs leading-20 text-foreground-neutral-muted tabular-nums"
|
|
94
|
+
})
|
|
95
|
+
]
|
|
96
|
+
})
|
|
97
|
+
}, jobExecution.id);
|
|
98
|
+
})
|
|
99
|
+
]
|
|
100
|
+
})
|
|
101
|
+
]
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
function TitleExecutionSummary({ job, execution }) {
|
|
105
|
+
return /*#__PURE__*/ _jsxs("span", {
|
|
106
|
+
className: "flex min-w-0 items-center gap-8",
|
|
107
|
+
children: [
|
|
108
|
+
/*#__PURE__*/ _jsxs(Text, {
|
|
109
|
+
as: "span",
|
|
110
|
+
size: "sm",
|
|
111
|
+
bold: true,
|
|
112
|
+
className: "shrink-0 text-foreground-neutral-base",
|
|
113
|
+
children: [
|
|
114
|
+
"#",
|
|
115
|
+
execution.sequence
|
|
116
|
+
]
|
|
117
|
+
}),
|
|
118
|
+
/*#__PURE__*/ _jsx(Text, {
|
|
119
|
+
as: "span",
|
|
120
|
+
size: "sm",
|
|
121
|
+
bold: true,
|
|
122
|
+
className: "min-w-0 truncate text-foreground-neutral-base",
|
|
123
|
+
children: job.displayName
|
|
124
|
+
})
|
|
125
|
+
]
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
function ExecutionSummary({ execution }) {
|
|
129
|
+
return /*#__PURE__*/ _jsxs("span", {
|
|
130
|
+
className: "flex min-w-0 items-center gap-6",
|
|
131
|
+
children: [
|
|
132
|
+
/*#__PURE__*/ _jsx(WorkflowStatusIcon, {
|
|
133
|
+
status: execution.status,
|
|
134
|
+
size: 14,
|
|
135
|
+
tooltip: false
|
|
136
|
+
}),
|
|
137
|
+
/*#__PURE__*/ _jsxs("span", {
|
|
138
|
+
className: "shrink-0 font-code text-xs leading-20 text-foreground-neutral-base tabular-nums",
|
|
139
|
+
children: [
|
|
140
|
+
"Execution #",
|
|
141
|
+
execution.sequence
|
|
142
|
+
]
|
|
143
|
+
}),
|
|
144
|
+
execution.statusReason ? /*#__PURE__*/ _jsxs(Text, {
|
|
145
|
+
as: "span",
|
|
146
|
+
size: "xs",
|
|
147
|
+
className: "min-w-0 truncate text-foreground-neutral-muted",
|
|
148
|
+
children: [
|
|
149
|
+
"· ",
|
|
150
|
+
execution.statusReason
|
|
151
|
+
]
|
|
152
|
+
}) : null,
|
|
153
|
+
/*#__PURE__*/ _jsx(JobExecutionDuration, {
|
|
154
|
+
execution: execution,
|
|
155
|
+
className: "shrink-0 font-code text-xs leading-20 text-foreground-neutral-muted tabular-nums"
|
|
156
|
+
})
|
|
157
|
+
]
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
function JobExecutionDuration({ execution, className }) {
|
|
161
|
+
const duration = execution.displayDuration;
|
|
162
|
+
if (!duration) return null;
|
|
163
|
+
return /*#__PURE__*/ _jsx("span", {
|
|
164
|
+
className: className,
|
|
165
|
+
children: /*#__PURE__*/ _jsx(JobExecutionTimeText, {
|
|
166
|
+
time: duration
|
|
167
|
+
})
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
//# sourceMappingURL=job-execution-switcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/workflow-run-view/job-execution-switcher.tsx"],"sourcesContent":["import {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuTrigger,\n} from '@shipfox/react-ui/dropdown-menu';\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {Text} from '@shipfox/react-ui/typography';\nimport {cn} from '@shipfox/react-ui/utils';\nimport {useMemo} from 'react';\nimport {WorkflowStatusIcon} from '#components/workflow-status/workflow-status-icon.js';\nimport type {Job, JobExecution} from '#core/workflow-run.js';\nimport {JobExecutionTimeText} from './job-execution-time-text.js';\n\nexport interface JobExecutionSwitcherProps {\n job: Job;\n selectedJobExecution: string | null;\n onSelectedJobExecutionChange: (jobExecutionId: string) => void;\n variant?: 'compact' | 'title' | undefined;\n className?: string | undefined;\n}\n\nexport function JobExecutionSwitcher({\n job,\n selectedJobExecution,\n onSelectedJobExecutionChange,\n variant = 'compact',\n className,\n}: JobExecutionSwitcherProps) {\n const executions = useMemo(\n () => [...job.jobExecutions].sort((left, right) => right.sequence - left.sequence),\n [job.jobExecutions],\n );\n const selected =\n executions.find((jobExecution) => jobExecution.id === selectedJobExecution) ??\n executions[0] ??\n null;\n\n if (selected === null) return null;\n\n if (!job.executionCountVisible) {\n return (\n <div\n className={cn(\n 'flex min-w-0 items-center gap-6 text-sm leading-20 text-foreground-neutral-subtle',\n className,\n )}\n >\n <ExecutionSummary execution={selected} />\n </div>\n );\n }\n\n return (\n <DropdownMenu>\n <DropdownMenuTrigger\n className={cn(\n 'inline-flex min-w-0 max-w-full items-center rounded-6 text-left transition-colors focus-visible:shadow-border-interactive-with-active focus-visible:outline-none',\n variant === 'title'\n ? '-mx-6 -my-4 gap-8 px-6 py-4 hover:bg-background-components-hover'\n : 'min-h-28 gap-6 px-8 py-4 text-sm leading-20 text-foreground-neutral-subtle hover:bg-background-components-hover',\n className,\n )}\n aria-label={`Switch job execution, currently execution ${selected.sequence}`}\n >\n {variant === 'title' ? (\n <TitleExecutionSummary job={job} execution={selected} />\n ) : (\n <ExecutionSummary execution={selected} />\n )}\n <Icon name=\"arrowDownSLine\" className=\"size-14 shrink-0 text-foreground-neutral-muted\" />\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" size=\"lg\" className=\"max-h-[320px] overflow-y-auto\">\n <DropdownMenuLabel>\n {executions.length} execution{executions.length === 1 ? '' : 's'}\n </DropdownMenuLabel>\n {executions.map((jobExecution) => {\n const isSelected = jobExecution.id === selected.id;\n\n return (\n <DropdownMenuItem\n key={jobExecution.id}\n asChild\n onSelect={() => onSelectedJobExecutionChange(jobExecution.id)}\n >\n <button\n type=\"button\"\n aria-current={isSelected ? 'true' : undefined}\n className=\"w-full text-left\"\n >\n <WorkflowStatusIcon status={jobExecution.status} size={14} tooltip={false} />\n <span className=\"font-code text-xs leading-20 text-foreground-neutral-base tabular-nums\">\n #{jobExecution.sequence}\n </span>\n <span className=\"min-w-0 truncate text-xs leading-20 text-foreground-neutral-base\">\n {job.displayName}\n </span>\n {!isSelected && jobExecution.statusReason ? (\n <span className=\"min-w-0 flex-1 truncate text-xs leading-20 text-foreground-neutral-muted\">\n {jobExecution.statusReason}\n </span>\n ) : (\n <span className=\"min-w-0 flex-1\" />\n )}\n {isSelected ? (\n <Icon name=\"check\" className=\"size-14 shrink-0 text-foreground-neutral-base\" />\n ) : null}\n <JobExecutionDuration\n execution={jobExecution}\n className=\"ml-auto shrink-0 font-code text-xs leading-20 text-foreground-neutral-muted tabular-nums\"\n />\n </button>\n </DropdownMenuItem>\n );\n })}\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n\nfunction TitleExecutionSummary({job, execution}: {job: Job; execution: JobExecution}) {\n return (\n <span className=\"flex min-w-0 items-center gap-8\">\n <Text as=\"span\" size=\"sm\" bold className=\"shrink-0 text-foreground-neutral-base\">\n #{execution.sequence}\n </Text>\n <Text as=\"span\" size=\"sm\" bold className=\"min-w-0 truncate text-foreground-neutral-base\">\n {job.displayName}\n </Text>\n </span>\n );\n}\n\nfunction ExecutionSummary({execution}: {execution: JobExecution}) {\n return (\n <span className=\"flex min-w-0 items-center gap-6\">\n <WorkflowStatusIcon status={execution.status} size={14} tooltip={false} />\n <span className=\"shrink-0 font-code text-xs leading-20 text-foreground-neutral-base tabular-nums\">\n Execution #{execution.sequence}\n </span>\n {execution.statusReason ? (\n <Text as=\"span\" size=\"xs\" className=\"min-w-0 truncate text-foreground-neutral-muted\">\n · {execution.statusReason}\n </Text>\n ) : null}\n <JobExecutionDuration\n execution={execution}\n className=\"shrink-0 font-code text-xs leading-20 text-foreground-neutral-muted tabular-nums\"\n />\n </span>\n );\n}\n\nfunction JobExecutionDuration({\n execution,\n className,\n}: {\n execution: JobExecution;\n className?: string | undefined;\n}) {\n const duration = execution.displayDuration;\n if (!duration) return null;\n\n return (\n <span className={className}>\n <JobExecutionTimeText time={duration} />\n </span>\n );\n}\n"],"names":["DropdownMenu","DropdownMenuContent","DropdownMenuItem","DropdownMenuLabel","DropdownMenuTrigger","Icon","Text","cn","useMemo","WorkflowStatusIcon","JobExecutionTimeText","JobExecutionSwitcher","job","selectedJobExecution","onSelectedJobExecutionChange","variant","className","executions","jobExecutions","sort","left","right","sequence","selected","find","jobExecution","id","executionCountVisible","div","ExecutionSummary","execution","aria-label","TitleExecutionSummary","name","align","size","length","map","isSelected","asChild","onSelect","button","type","aria-current","undefined","status","tooltip","span","displayName","statusReason","JobExecutionDuration","as","bold","duration","displayDuration","time"],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { JobExecutionTime } from '#core/workflow-run.js';
|
|
2
|
+
export declare function JobExecutionTimeText({ time }: {
|
|
3
|
+
time: JobExecutionTime;
|
|
4
|
+
}): string;
|
|
5
|
+
export declare function formatJobExecutionTime(time: JobExecutionTime): string;
|
|
6
|
+
//# sourceMappingURL=job-execution-time-text.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"job-execution-time-text.d.ts","sourceRoot":"","sources":["../../../src/components/workflow-run-view/job-execution-time-text.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,uBAAuB,CAAC;AAE5D,wBAAgB,oBAAoB,CAAC,EAAC,IAAI,EAAC,EAAE;IAAC,IAAI,EAAE,gBAAgB,CAAA;CAAC,UAIpE;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,gBAAgB,GAAG,MAAM,CAMrE"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { useTimeTick } from '@shipfox/react-ui/time-ticker';
|
|
2
|
+
import { formatDuration } from '@shipfox/react-ui/utils';
|
|
3
|
+
export function JobExecutionTimeText({ time }) {
|
|
4
|
+
useTimeTick();
|
|
5
|
+
return formatJobExecutionTime(time);
|
|
6
|
+
}
|
|
7
|
+
export function formatJobExecutionTime(time) {
|
|
8
|
+
if (time.state === 'live') {
|
|
9
|
+
return formatDuration(Date.now() - Date.parse(time.fromIso));
|
|
10
|
+
}
|
|
11
|
+
return formatElapsedDuration(time.elapsed);
|
|
12
|
+
}
|
|
13
|
+
function formatElapsedDuration(duration) {
|
|
14
|
+
const days = duration.days ?? 0;
|
|
15
|
+
const hours = duration.hours ?? 0;
|
|
16
|
+
const minutes = duration.minutes ?? 0;
|
|
17
|
+
const seconds = duration.seconds ?? 0;
|
|
18
|
+
if (days > 0) return hours > 0 ? `${days}d ${hours}h` : `${days}d`;
|
|
19
|
+
if (hours > 0) return minutes > 0 ? `${hours}h ${minutes}m` : `${hours}h`;
|
|
20
|
+
if (minutes > 0) return seconds > 0 ? `${minutes}m ${seconds}s` : `${minutes}m`;
|
|
21
|
+
return `${seconds}s`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
//# sourceMappingURL=job-execution-time-text.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/workflow-run-view/job-execution-time-text.tsx"],"sourcesContent":["import {useTimeTick} from '@shipfox/react-ui/time-ticker';\nimport {formatDuration} from '@shipfox/react-ui/utils';\nimport type {JobExecutionTime} from '#core/workflow-run.js';\n\nexport function JobExecutionTimeText({time}: {time: JobExecutionTime}) {\n useTimeTick();\n\n return formatJobExecutionTime(time);\n}\n\nexport function formatJobExecutionTime(time: JobExecutionTime): string {\n if (time.state === 'live') {\n return formatDuration(Date.now() - Date.parse(time.fromIso));\n }\n\n return formatElapsedDuration(time.elapsed);\n}\n\nfunction formatElapsedDuration(duration: {\n days?: number | undefined;\n hours?: number | undefined;\n minutes?: number | undefined;\n seconds?: number | undefined;\n}): string {\n const days = duration.days ?? 0;\n const hours = duration.hours ?? 0;\n const minutes = duration.minutes ?? 0;\n const seconds = duration.seconds ?? 0;\n\n if (days > 0) return hours > 0 ? `${days}d ${hours}h` : `${days}d`;\n if (hours > 0) return minutes > 0 ? `${hours}h ${minutes}m` : `${hours}h`;\n if (minutes > 0) return seconds > 0 ? `${minutes}m ${seconds}s` : `${minutes}m`;\n return `${seconds}s`;\n}\n"],"names":["useTimeTick","formatDuration","JobExecutionTimeText","time","formatJobExecutionTime","state","Date","now","parse","fromIso","formatElapsedDuration","elapsed","duration","days","hours","minutes","seconds"],"mappings":"AAAA,SAAQA,WAAW,QAAO,gCAAgC;AAC1D,SAAQC,cAAc,QAAO,0BAA0B;AAGvD,OAAO,SAASC,qBAAqB,EAACC,IAAI,EAA2B;IACnEH;IAEA,OAAOI,uBAAuBD;AAChC;AAEA,OAAO,SAASC,uBAAuBD,IAAsB;IAC3D,IAAIA,KAAKE,KAAK,KAAK,QAAQ;QACzB,OAAOJ,eAAeK,KAAKC,GAAG,KAAKD,KAAKE,KAAK,CAACL,KAAKM,OAAO;IAC5D;IAEA,OAAOC,sBAAsBP,KAAKQ,OAAO;AAC3C;AAEA,SAASD,sBAAsBE,QAK9B;IACC,MAAMC,OAAOD,SAASC,IAAI,IAAI;IAC9B,MAAMC,QAAQF,SAASE,KAAK,IAAI;IAChC,MAAMC,UAAUH,SAASG,OAAO,IAAI;IACpC,MAAMC,UAAUJ,SAASI,OAAO,IAAI;IAEpC,IAAIH,OAAO,GAAG,OAAOC,QAAQ,IAAI,GAAGD,KAAK,EAAE,EAAEC,MAAM,CAAC,CAAC,GAAG,GAAGD,KAAK,CAAC,CAAC;IAClE,IAAIC,QAAQ,GAAG,OAAOC,UAAU,IAAI,GAAGD,MAAM,EAAE,EAAEC,QAAQ,CAAC,CAAC,GAAG,GAAGD,MAAM,CAAC,CAAC;IACzE,IAAIC,UAAU,GAAG,OAAOC,UAAU,IAAI,GAAGD,QAAQ,EAAE,EAAEC,QAAQ,CAAC,CAAC,GAAG,GAAGD,QAAQ,CAAC,CAAC;IAC/E,OAAO,GAAGC,QAAQ,CAAC,CAAC;AACtB"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface StepAttemptLogPanelProps {
|
|
2
|
+
stepId: string;
|
|
3
|
+
attempt: number;
|
|
4
|
+
attemptStatus: string;
|
|
5
|
+
initialErrorRetryCount?: number | undefined;
|
|
6
|
+
initialErrorRetryDelayMs?: number | undefined;
|
|
7
|
+
}
|
|
8
|
+
export declare function StepAttemptLogPanel({ stepId, attempt, attemptStatus, initialErrorRetryCount, initialErrorRetryDelayMs, }: StepAttemptLogPanelProps): import("react").JSX.Element;
|
|
9
|
+
//# sourceMappingURL=step-attempt-log-panel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"step-attempt-log-panel.d.ts","sourceRoot":"","sources":["../../../src/components/workflow-run-view/step-attempt-log-panel.tsx"],"names":[],"mappings":"AAgBA,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,sBAAsB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5C,wBAAwB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC/C;AAED,wBAAgB,mBAAmB,CAAC,EAClC,MAAM,EACN,OAAO,EACP,aAAa,EACb,sBAAsD,EACtD,wBAA2D,GAC5D,EAAE,wBAAwB,+BAiF1B"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { isMissingStepLogStreamError, LogView, LogViewSkeleton, useStepAttemptLogsQuery } from '@shipfox/client-logs';
|
|
3
|
+
import { Button } from '@shipfox/react-ui/button';
|
|
4
|
+
import { Callout } from '@shipfox/react-ui/callout';
|
|
5
|
+
import { Text } from '@shipfox/react-ui/typography';
|
|
6
|
+
import { useEffect, useRef } from 'react';
|
|
7
|
+
const TAIL_FOLLOW_THRESHOLD_PX = 24;
|
|
8
|
+
const INITIAL_LOG_ERROR_RETRY_COUNT = 5;
|
|
9
|
+
const INITIAL_LOG_ERROR_RETRY_DELAY_MS = 1_500;
|
|
10
|
+
const logSurfaceClasses = 'max-h-[40vh] rounded-8 md:max-h-[280px]';
|
|
11
|
+
export function StepAttemptLogPanel({ stepId, attempt, attemptStatus, initialErrorRetryCount = INITIAL_LOG_ERROR_RETRY_COUNT, initialErrorRetryDelayMs = INITIAL_LOG_ERROR_RETRY_DELAY_MS }) {
|
|
12
|
+
const panelRef = useRef(null);
|
|
13
|
+
const shouldFollowTailRef = useRef(true);
|
|
14
|
+
const missingStreamRetryCount = attemptStatus === 'running' ? undefined : initialErrorRetryCount;
|
|
15
|
+
const retryMissingStream = attemptStatus === 'running' || isTerminalAttemptStatus(attemptStatus);
|
|
16
|
+
const query = useStepAttemptLogsQuery(stepId, attempt, {
|
|
17
|
+
retryMissingStream,
|
|
18
|
+
missingStreamRetryCount,
|
|
19
|
+
missingStreamRetryDelayMs: initialErrorRetryDelayMs,
|
|
20
|
+
initialErrorRetryCount,
|
|
21
|
+
initialErrorRetryDelayMs
|
|
22
|
+
});
|
|
23
|
+
const records = query.data?.records ?? [];
|
|
24
|
+
const recordCount = records.length;
|
|
25
|
+
const anchorToFailure = attemptStatus === 'failed';
|
|
26
|
+
const missingActiveStream = retryMissingStream && query.data === undefined && isMissingStepLogStreamError(query.error);
|
|
27
|
+
const initialError = query.isError && query.data === undefined && !missingActiveStream;
|
|
28
|
+
const staleError = query.isError && query.data !== undefined;
|
|
29
|
+
useEffect(()=>{
|
|
30
|
+
if (recordCount === 0) return undefined;
|
|
31
|
+
if (anchorToFailure) return undefined;
|
|
32
|
+
if (!shouldFollowTailRef.current) return undefined;
|
|
33
|
+
const frame = scheduleAnimationFrame(()=>{
|
|
34
|
+
const scrollElement = panelRef.current?.querySelector('[data-slot="log-rows"]');
|
|
35
|
+
if (!scrollElement) return;
|
|
36
|
+
scrollElement.scrollTop = scrollElement.scrollHeight;
|
|
37
|
+
});
|
|
38
|
+
return ()=>{
|
|
39
|
+
cancelScheduledFrame(frame);
|
|
40
|
+
};
|
|
41
|
+
}, [
|
|
42
|
+
anchorToFailure,
|
|
43
|
+
recordCount
|
|
44
|
+
]);
|
|
45
|
+
function handleLogScroll(event) {
|
|
46
|
+
const element = event.currentTarget;
|
|
47
|
+
const distanceFromBottom = element.scrollHeight - element.scrollTop - element.clientHeight;
|
|
48
|
+
shouldFollowTailRef.current = distanceFromBottom <= TAIL_FOLLOW_THRESHOLD_PX;
|
|
49
|
+
}
|
|
50
|
+
if (query.isPending) {
|
|
51
|
+
return /*#__PURE__*/ _jsx(StepLogsLoadingSurface, {
|
|
52
|
+
label: "Loading logs"
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
if (missingActiveStream) {
|
|
56
|
+
return /*#__PURE__*/ _jsx(StepLogsLoadingSurface, {
|
|
57
|
+
label: "Waiting for logs"
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
if (initialError) {
|
|
61
|
+
return /*#__PURE__*/ _jsx(StepLogsError, {
|
|
62
|
+
retrying: query.isFetching,
|
|
63
|
+
onRetry: ()=>void query.refetch()
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
67
|
+
ref: panelRef,
|
|
68
|
+
className: "flex min-w-0 flex-col gap-8",
|
|
69
|
+
children: [
|
|
70
|
+
staleError ? /*#__PURE__*/ _jsx(Callout, {
|
|
71
|
+
role: "alert",
|
|
72
|
+
type: "warning",
|
|
73
|
+
className: "px-10 py-8",
|
|
74
|
+
children: /*#__PURE__*/ _jsxs("div", {
|
|
75
|
+
className: "flex min-w-0 flex-1 items-center justify-between gap-8",
|
|
76
|
+
children: [
|
|
77
|
+
/*#__PURE__*/ _jsx(Text, {
|
|
78
|
+
size: "xs",
|
|
79
|
+
children: "Could not refresh logs."
|
|
80
|
+
}),
|
|
81
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
82
|
+
type: "button",
|
|
83
|
+
size: "2xs",
|
|
84
|
+
variant: "secondary",
|
|
85
|
+
isLoading: query.isFetching,
|
|
86
|
+
onClick: ()=>void query.refetch(),
|
|
87
|
+
children: "Retry"
|
|
88
|
+
})
|
|
89
|
+
]
|
|
90
|
+
})
|
|
91
|
+
}) : null,
|
|
92
|
+
/*#__PURE__*/ _jsx(LogView, {
|
|
93
|
+
records: records,
|
|
94
|
+
emptyState: query.data?.complete ? 'complete' : 'pending',
|
|
95
|
+
anchorToFailure: anchorToFailure,
|
|
96
|
+
className: logSurfaceClasses,
|
|
97
|
+
onScroll: handleLogScroll
|
|
98
|
+
})
|
|
99
|
+
]
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
function StepLogsLoadingSurface({ label }) {
|
|
103
|
+
return /*#__PURE__*/ _jsx("div", {
|
|
104
|
+
role: "status",
|
|
105
|
+
"aria-label": label,
|
|
106
|
+
children: /*#__PURE__*/ _jsx(LogViewSkeleton, {
|
|
107
|
+
className: logSurfaceClasses
|
|
108
|
+
})
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
function scheduleAnimationFrame(callback) {
|
|
112
|
+
if (typeof globalThis.requestAnimationFrame === 'function') {
|
|
113
|
+
return globalThis.requestAnimationFrame(callback);
|
|
114
|
+
}
|
|
115
|
+
return window.setTimeout(()=>callback(Date.now()), 0);
|
|
116
|
+
}
|
|
117
|
+
function cancelScheduledFrame(frame) {
|
|
118
|
+
if (typeof globalThis.cancelAnimationFrame === 'function') {
|
|
119
|
+
globalThis.cancelAnimationFrame(frame);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
window.clearTimeout(frame);
|
|
123
|
+
}
|
|
124
|
+
function isTerminalAttemptStatus(status) {
|
|
125
|
+
return status === 'succeeded' || status === 'failed' || status === 'cancelled';
|
|
126
|
+
}
|
|
127
|
+
function StepLogsError({ retrying, onRetry }) {
|
|
128
|
+
return /*#__PURE__*/ _jsx(Callout, {
|
|
129
|
+
role: "alert",
|
|
130
|
+
type: "error",
|
|
131
|
+
className: "px-10 py-8",
|
|
132
|
+
children: /*#__PURE__*/ _jsxs("div", {
|
|
133
|
+
className: "flex min-w-0 flex-1 items-center justify-between gap-8",
|
|
134
|
+
children: [
|
|
135
|
+
/*#__PURE__*/ _jsx(Text, {
|
|
136
|
+
size: "xs",
|
|
137
|
+
children: "Could not load logs."
|
|
138
|
+
}),
|
|
139
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
140
|
+
type: "button",
|
|
141
|
+
size: "2xs",
|
|
142
|
+
variant: "secondary",
|
|
143
|
+
isLoading: retrying,
|
|
144
|
+
onClick: onRetry,
|
|
145
|
+
children: "Retry"
|
|
146
|
+
})
|
|
147
|
+
]
|
|
148
|
+
})
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
//# sourceMappingURL=step-attempt-log-panel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/workflow-run-view/step-attempt-log-panel.tsx"],"sourcesContent":["import {\n isMissingStepLogStreamError,\n LogView,\n LogViewSkeleton,\n useStepAttemptLogsQuery,\n} from '@shipfox/client-logs';\nimport {Button} from '@shipfox/react-ui/button';\nimport {Callout} from '@shipfox/react-ui/callout';\nimport {Text} from '@shipfox/react-ui/typography';\nimport {type UIEvent, useEffect, useRef} from 'react';\n\nconst TAIL_FOLLOW_THRESHOLD_PX = 24;\nconst INITIAL_LOG_ERROR_RETRY_COUNT = 5;\nconst INITIAL_LOG_ERROR_RETRY_DELAY_MS = 1_500;\nconst logSurfaceClasses = 'max-h-[40vh] rounded-8 md:max-h-[280px]';\n\nexport interface StepAttemptLogPanelProps {\n stepId: string;\n attempt: number;\n attemptStatus: string;\n initialErrorRetryCount?: number | undefined;\n initialErrorRetryDelayMs?: number | undefined;\n}\n\nexport function StepAttemptLogPanel({\n stepId,\n attempt,\n attemptStatus,\n initialErrorRetryCount = INITIAL_LOG_ERROR_RETRY_COUNT,\n initialErrorRetryDelayMs = INITIAL_LOG_ERROR_RETRY_DELAY_MS,\n}: StepAttemptLogPanelProps) {\n const panelRef = useRef<HTMLDivElement>(null);\n const shouldFollowTailRef = useRef(true);\n const missingStreamRetryCount = attemptStatus === 'running' ? undefined : initialErrorRetryCount;\n const retryMissingStream = attemptStatus === 'running' || isTerminalAttemptStatus(attemptStatus);\n const query = useStepAttemptLogsQuery(stepId, attempt, {\n retryMissingStream,\n missingStreamRetryCount,\n missingStreamRetryDelayMs: initialErrorRetryDelayMs,\n initialErrorRetryCount,\n initialErrorRetryDelayMs,\n });\n const records = query.data?.records ?? [];\n const recordCount = records.length;\n const anchorToFailure = attemptStatus === 'failed';\n const missingActiveStream =\n retryMissingStream && query.data === undefined && isMissingStepLogStreamError(query.error);\n const initialError = query.isError && query.data === undefined && !missingActiveStream;\n const staleError = query.isError && query.data !== undefined;\n\n useEffect(() => {\n if (recordCount === 0) return undefined;\n if (anchorToFailure) return undefined;\n if (!shouldFollowTailRef.current) return undefined;\n\n const frame = scheduleAnimationFrame(() => {\n const scrollElement = panelRef.current?.querySelector<HTMLElement>('[data-slot=\"log-rows\"]');\n if (!scrollElement) return;\n scrollElement.scrollTop = scrollElement.scrollHeight;\n });\n\n return () => {\n cancelScheduledFrame(frame);\n };\n }, [anchorToFailure, recordCount]);\n\n function handleLogScroll(event: UIEvent<HTMLDivElement>) {\n const element = event.currentTarget;\n const distanceFromBottom = element.scrollHeight - element.scrollTop - element.clientHeight;\n shouldFollowTailRef.current = distanceFromBottom <= TAIL_FOLLOW_THRESHOLD_PX;\n }\n\n if (query.isPending) {\n return <StepLogsLoadingSurface label=\"Loading logs\" />;\n }\n\n if (missingActiveStream) {\n return <StepLogsLoadingSurface label=\"Waiting for logs\" />;\n }\n\n if (initialError) {\n return <StepLogsError retrying={query.isFetching} onRetry={() => void query.refetch()} />;\n }\n\n return (\n <div ref={panelRef} className=\"flex min-w-0 flex-col gap-8\">\n {staleError ? (\n <Callout role=\"alert\" type=\"warning\" className=\"px-10 py-8\">\n <div className=\"flex min-w-0 flex-1 items-center justify-between gap-8\">\n <Text size=\"xs\">Could not refresh logs.</Text>\n <Button\n type=\"button\"\n size=\"2xs\"\n variant=\"secondary\"\n isLoading={query.isFetching}\n onClick={() => void query.refetch()}\n >\n Retry\n </Button>\n </div>\n </Callout>\n ) : null}\n <LogView\n records={records}\n emptyState={query.data?.complete ? 'complete' : 'pending'}\n anchorToFailure={anchorToFailure}\n className={logSurfaceClasses}\n onScroll={handleLogScroll}\n />\n </div>\n );\n}\n\nfunction StepLogsLoadingSurface({label}: {label: string}) {\n return (\n <div role=\"status\" aria-label={label}>\n <LogViewSkeleton className={logSurfaceClasses} />\n </div>\n );\n}\n\nfunction scheduleAnimationFrame(callback: FrameRequestCallback): number {\n if (typeof globalThis.requestAnimationFrame === 'function') {\n return globalThis.requestAnimationFrame(callback);\n }\n return window.setTimeout(() => callback(Date.now()), 0);\n}\n\nfunction cancelScheduledFrame(frame: number) {\n if (typeof globalThis.cancelAnimationFrame === 'function') {\n globalThis.cancelAnimationFrame(frame);\n return;\n }\n window.clearTimeout(frame);\n}\n\nfunction isTerminalAttemptStatus(status: string): boolean {\n return status === 'succeeded' || status === 'failed' || status === 'cancelled';\n}\n\nfunction StepLogsError({retrying, onRetry}: {retrying: boolean; onRetry: () => void}) {\n return (\n <Callout role=\"alert\" type=\"error\" className=\"px-10 py-8\">\n <div className=\"flex min-w-0 flex-1 items-center justify-between gap-8\">\n <Text size=\"xs\">Could not load logs.</Text>\n <Button type=\"button\" size=\"2xs\" variant=\"secondary\" isLoading={retrying} onClick={onRetry}>\n Retry\n </Button>\n </div>\n </Callout>\n );\n}\n"],"names":["isMissingStepLogStreamError","LogView","LogViewSkeleton","useStepAttemptLogsQuery","Button","Callout","Text","useEffect","useRef","TAIL_FOLLOW_THRESHOLD_PX","INITIAL_LOG_ERROR_RETRY_COUNT","INITIAL_LOG_ERROR_RETRY_DELAY_MS","logSurfaceClasses","StepAttemptLogPanel","stepId","attempt","attemptStatus","initialErrorRetryCount","initialErrorRetryDelayMs","panelRef","shouldFollowTailRef","missingStreamRetryCount","undefined","retryMissingStream","isTerminalAttemptStatus","query","missingStreamRetryDelayMs","records","data","recordCount","length","anchorToFailure","missingActiveStream","error","initialError","isError","staleError","current","frame","scheduleAnimationFrame","scrollElement","querySelector","scrollTop","scrollHeight","cancelScheduledFrame","handleLogScroll","event","element","currentTarget","distanceFromBottom","clientHeight","isPending","StepLogsLoadingSurface","label","StepLogsError","retrying","isFetching","onRetry","refetch","div","ref","className","role","type","size","variant","isLoading","onClick","emptyState","complete","onScroll","aria-label","callback","globalThis","requestAnimationFrame","window","setTimeout","Date","now","cancelAnimationFrame","clearTimeout","status"],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type Job, type JobExecution, type Step, type StepAttempt, type WorkflowRunDetail } from '#core/workflow-run.js';
|
|
2
|
+
import type { WorkflowRunSelectionInput } from '#core/workflow-run-url-state.js';
|
|
3
|
+
export interface ResolvedWorkflowRunSelection {
|
|
4
|
+
job: Job | undefined;
|
|
5
|
+
jobExecution: JobExecution | undefined;
|
|
6
|
+
step: Step | undefined;
|
|
7
|
+
attempt: StepAttempt | undefined;
|
|
8
|
+
selectedAttemptId: string | null;
|
|
9
|
+
}
|
|
10
|
+
export declare function resolveWorkflowRunSelection({ run, selection, }: {
|
|
11
|
+
run: WorkflowRunDetail;
|
|
12
|
+
selection: WorkflowRunSelectionInput;
|
|
13
|
+
}): ResolvedWorkflowRunSelection;
|
|
14
|
+
//# sourceMappingURL=workflow-run-selection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflow-run-selection.d.ts","sourceRoot":"","sources":["../../../src/components/workflow-run-view/workflow-run-selection.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,GAAG,EACR,KAAK,YAAY,EAEjB,KAAK,IAAI,EACT,KAAK,WAAW,EAChB,KAAK,iBAAiB,EACvB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAC,yBAAyB,EAAC,MAAM,iCAAiC,CAAC;AAE/E,MAAM,WAAW,4BAA4B;IAC3C,GAAG,EAAE,GAAG,GAAG,SAAS,CAAC;IACrB,YAAY,EAAE,YAAY,GAAG,SAAS,CAAC;IACvC,IAAI,EAAE,IAAI,GAAG,SAAS,CAAC;IACvB,OAAO,EAAE,WAAW,GAAG,SAAS,CAAC;IACjC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAED,wBAAgB,2BAA2B,CAAC,EAC1C,GAAG,EACH,SAAS,GACV,EAAE;IACD,GAAG,EAAE,iBAAiB,CAAC;IACvB,SAAS,EAAE,yBAAyB,CAAC;CACtC,GAAG,4BAA4B,CAyB/B"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { resolveJobExecution } from '#core/workflow-run.js';
|
|
2
|
+
export function resolveWorkflowRunSelection({ run, selection }) {
|
|
3
|
+
const jobById = new Map(run.jobs.map((job)=>[
|
|
4
|
+
job.id,
|
|
5
|
+
job
|
|
6
|
+
]));
|
|
7
|
+
const stepMatch = findStep(run, selection.stepId);
|
|
8
|
+
if (stepMatch) {
|
|
9
|
+
const attempt = resolveStepAttempt(stepMatch.step, selection.stepAttemptId);
|
|
10
|
+
return {
|
|
11
|
+
job: stepMatch.job,
|
|
12
|
+
jobExecution: stepMatch.jobExecution,
|
|
13
|
+
step: stepMatch.step,
|
|
14
|
+
attempt,
|
|
15
|
+
selectedAttemptId: attempt?.id ?? null
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
const job = (selection.jobId ? jobById.get(selection.jobId) : undefined) ?? run.jobs.at(0);
|
|
19
|
+
const jobExecution = job ? resolveJobExecution(job, selection.jobExecutionId) : undefined;
|
|
20
|
+
return {
|
|
21
|
+
job,
|
|
22
|
+
jobExecution,
|
|
23
|
+
step: undefined,
|
|
24
|
+
attempt: undefined,
|
|
25
|
+
selectedAttemptId: null
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function findStep(run, stepId) {
|
|
29
|
+
if (!stepId) return undefined;
|
|
30
|
+
for (const job of run.jobs){
|
|
31
|
+
for (const jobExecution of job.jobExecutions){
|
|
32
|
+
const step = jobExecution.steps.find((candidate)=>candidate.id === stepId);
|
|
33
|
+
if (step) return {
|
|
34
|
+
job,
|
|
35
|
+
jobExecution,
|
|
36
|
+
step
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
function resolveStepAttempt(step, attemptId) {
|
|
43
|
+
const attemptById = attemptId ? step.attempts.find((attempt)=>attempt.id === attemptId) : undefined;
|
|
44
|
+
if (attemptById) return attemptById;
|
|
45
|
+
const currentAttempt = step.attempts.find((attempt)=>attempt.attempt === step.currentAttempt);
|
|
46
|
+
if (currentAttempt) return currentAttempt;
|
|
47
|
+
return step.attempts.reduce((latest, attempt)=>{
|
|
48
|
+
if (!latest) return attempt;
|
|
49
|
+
return compareAttempts(attempt, latest) > 0 ? attempt : latest;
|
|
50
|
+
}, undefined);
|
|
51
|
+
}
|
|
52
|
+
function compareAttempts(left, right) {
|
|
53
|
+
return left.attempt - right.attempt || left.executionOrder - right.executionOrder || left.id.localeCompare(right.id);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
//# sourceMappingURL=workflow-run-selection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/workflow-run-view/workflow-run-selection.ts"],"sourcesContent":["import {\n type Job,\n type JobExecution,\n resolveJobExecution,\n type Step,\n type StepAttempt,\n type WorkflowRunDetail,\n} from '#core/workflow-run.js';\nimport type {WorkflowRunSelectionInput} from '#core/workflow-run-url-state.js';\n\nexport interface ResolvedWorkflowRunSelection {\n job: Job | undefined;\n jobExecution: JobExecution | undefined;\n step: Step | undefined;\n attempt: StepAttempt | undefined;\n selectedAttemptId: string | null;\n}\n\nexport function resolveWorkflowRunSelection({\n run,\n selection,\n}: {\n run: WorkflowRunDetail;\n selection: WorkflowRunSelectionInput;\n}): ResolvedWorkflowRunSelection {\n const jobById = new Map(run.jobs.map((job) => [job.id, job]));\n const stepMatch = findStep(run, selection.stepId);\n\n if (stepMatch) {\n const attempt = resolveStepAttempt(stepMatch.step, selection.stepAttemptId);\n return {\n job: stepMatch.job,\n jobExecution: stepMatch.jobExecution,\n step: stepMatch.step,\n attempt,\n selectedAttemptId: attempt?.id ?? null,\n };\n }\n\n const job = (selection.jobId ? jobById.get(selection.jobId) : undefined) ?? run.jobs.at(0);\n const jobExecution = job ? resolveJobExecution(job, selection.jobExecutionId) : undefined;\n\n return {\n job,\n jobExecution,\n step: undefined,\n attempt: undefined,\n selectedAttemptId: null,\n };\n}\n\nfunction findStep(\n run: WorkflowRunDetail,\n stepId: string | undefined,\n): {job: Job; jobExecution: JobExecution; step: Step} | undefined {\n if (!stepId) return undefined;\n\n for (const job of run.jobs) {\n for (const jobExecution of job.jobExecutions) {\n const step = jobExecution.steps.find((candidate) => candidate.id === stepId);\n if (step) return {job, jobExecution, step};\n }\n }\n\n return undefined;\n}\n\nfunction resolveStepAttempt(step: Step, attemptId: string | undefined): StepAttempt | undefined {\n const attemptById = attemptId\n ? step.attempts.find((attempt) => attempt.id === attemptId)\n : undefined;\n if (attemptById) return attemptById;\n\n const currentAttempt = step.attempts.find((attempt) => attempt.attempt === step.currentAttempt);\n if (currentAttempt) return currentAttempt;\n\n return step.attempts.reduce<StepAttempt | undefined>((latest, attempt) => {\n if (!latest) return attempt;\n return compareAttempts(attempt, latest) > 0 ? attempt : latest;\n }, undefined);\n}\n\nfunction compareAttempts(left: StepAttempt, right: StepAttempt): number {\n return (\n left.attempt - right.attempt ||\n left.executionOrder - right.executionOrder ||\n left.id.localeCompare(right.id)\n );\n}\n"],"names":["resolveJobExecution","resolveWorkflowRunSelection","run","selection","jobById","Map","jobs","map","job","id","stepMatch","findStep","stepId","attempt","resolveStepAttempt","step","stepAttemptId","jobExecution","selectedAttemptId","jobId","get","undefined","at","jobExecutionId","jobExecutions","steps","find","candidate","attemptId","attemptById","attempts","currentAttempt","reduce","latest","compareAttempts","left","right","executionOrder","localeCompare"],"mappings":"AAAA,SAGEA,mBAAmB,QAId,wBAAwB;AAW/B,OAAO,SAASC,4BAA4B,EAC1CC,GAAG,EACHC,SAAS,EAIV;IACC,MAAMC,UAAU,IAAIC,IAAIH,IAAII,IAAI,CAACC,GAAG,CAAC,CAACC,MAAQ;YAACA,IAAIC,EAAE;YAAED;SAAI;IAC3D,MAAME,YAAYC,SAAST,KAAKC,UAAUS,MAAM;IAEhD,IAAIF,WAAW;QACb,MAAMG,UAAUC,mBAAmBJ,UAAUK,IAAI,EAAEZ,UAAUa,aAAa;QAC1E,OAAO;YACLR,KAAKE,UAAUF,GAAG;YAClBS,cAAcP,UAAUO,YAAY;YACpCF,MAAML,UAAUK,IAAI;YACpBF;YACAK,mBAAmBL,SAASJ,MAAM;QACpC;IACF;IAEA,MAAMD,MAAM,AAACL,CAAAA,UAAUgB,KAAK,GAAGf,QAAQgB,GAAG,CAACjB,UAAUgB,KAAK,IAAIE,SAAQ,KAAMnB,IAAII,IAAI,CAACgB,EAAE,CAAC;IACxF,MAAML,eAAeT,MAAMR,oBAAoBQ,KAAKL,UAAUoB,cAAc,IAAIF;IAEhF,OAAO;QACLb;QACAS;QACAF,MAAMM;QACNR,SAASQ;QACTH,mBAAmB;IACrB;AACF;AAEA,SAASP,SACPT,GAAsB,EACtBU,MAA0B;IAE1B,IAAI,CAACA,QAAQ,OAAOS;IAEpB,KAAK,MAAMb,OAAON,IAAII,IAAI,CAAE;QAC1B,KAAK,MAAMW,gBAAgBT,IAAIgB,aAAa,CAAE;YAC5C,MAAMT,OAAOE,aAAaQ,KAAK,CAACC,IAAI,CAAC,CAACC,YAAcA,UAAUlB,EAAE,KAAKG;YACrE,IAAIG,MAAM,OAAO;gBAACP;gBAAKS;gBAAcF;YAAI;QAC3C;IACF;IAEA,OAAOM;AACT;AAEA,SAASP,mBAAmBC,IAAU,EAAEa,SAA6B;IACnE,MAAMC,cAAcD,YAChBb,KAAKe,QAAQ,CAACJ,IAAI,CAAC,CAACb,UAAYA,QAAQJ,EAAE,KAAKmB,aAC/CP;IACJ,IAAIQ,aAAa,OAAOA;IAExB,MAAME,iBAAiBhB,KAAKe,QAAQ,CAACJ,IAAI,CAAC,CAACb,UAAYA,QAAQA,OAAO,KAAKE,KAAKgB,cAAc;IAC9F,IAAIA,gBAAgB,OAAOA;IAE3B,OAAOhB,KAAKe,QAAQ,CAACE,MAAM,CAA0B,CAACC,QAAQpB;QAC5D,IAAI,CAACoB,QAAQ,OAAOpB;QACpB,OAAOqB,gBAAgBrB,SAASoB,UAAU,IAAIpB,UAAUoB;IAC1D,GAAGZ;AACL;AAEA,SAASa,gBAAgBC,IAAiB,EAAEC,KAAkB;IAC5D,OACED,KAAKtB,OAAO,GAAGuB,MAAMvB,OAAO,IAC5BsB,KAAKE,cAAc,GAAGD,MAAMC,cAAc,IAC1CF,KAAK1B,EAAE,CAAC6B,aAAa,CAACF,MAAM3B,EAAE;AAElC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { QueryLoadErrorQuery } from '@shipfox/client-ui';
|
|
2
|
+
export declare function WorkflowRunSkeleton(): import("react").JSX.Element;
|
|
3
|
+
export declare function WorkflowRunNotFound(): import("react").JSX.Element;
|
|
4
|
+
/**
|
|
5
|
+
* Slim non-blocking banner shown when a background refetch fails after the run already
|
|
6
|
+
* loaded (active-run polling can hit a transient API error), so the loaded run stays on
|
|
7
|
+
* screen with an inline retry instead of being wiped by a full error placeholder.
|
|
8
|
+
*/
|
|
9
|
+
export declare function WorkflowRunStaleError({ query }: {
|
|
10
|
+
query: QueryLoadErrorQuery;
|
|
11
|
+
}): import("react").JSX.Element;
|
|
12
|
+
//# sourceMappingURL=workflow-run-states.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflow-run-states.d.ts","sourceRoot":"","sources":["../../../src/components/workflow-run-view/workflow-run-states.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,oBAAoB,CAAC;AAO5D,wBAAgB,mBAAmB,gCAwBlC;AAED,wBAAgB,mBAAmB,gCAQlC;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,EAAC,KAAK,EAAC,EAAE;IAAC,KAAK,EAAE,mBAAmB,CAAA;CAAC,+BAqB1E"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
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
|
+
export function WorkflowRunSkeleton() {
|
|
8
|
+
return /*#__PURE__*/ _jsx("section", {
|
|
9
|
+
"aria-label": "Loading workflow run",
|
|
10
|
+
className: "border-b border-border-neutral-base bg-background-subtle-base px-16 py-12",
|
|
11
|
+
children: /*#__PURE__*/ _jsxs("div", {
|
|
12
|
+
className: "flex min-w-0 flex-wrap items-center gap-x-12 gap-y-8",
|
|
13
|
+
children: [
|
|
14
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
15
|
+
className: "flex min-w-0 items-center gap-8",
|
|
16
|
+
children: [
|
|
17
|
+
/*#__PURE__*/ _jsx(Skeleton, {
|
|
18
|
+
className: "size-8 rounded-full"
|
|
19
|
+
}),
|
|
20
|
+
/*#__PURE__*/ _jsx(Skeleton, {
|
|
21
|
+
className: "h-24 w-180 rounded-6"
|
|
22
|
+
})
|
|
23
|
+
]
|
|
24
|
+
}),
|
|
25
|
+
/*#__PURE__*/ _jsx(Skeleton, {
|
|
26
|
+
className: "h-24 w-88 rounded-6"
|
|
27
|
+
}),
|
|
28
|
+
/*#__PURE__*/ _jsx("span", {
|
|
29
|
+
"aria-hidden": "true",
|
|
30
|
+
className: "hidden h-20 w-px shrink-0 bg-border-neutral-base sm:block"
|
|
31
|
+
}),
|
|
32
|
+
/*#__PURE__*/ _jsx(Skeleton, {
|
|
33
|
+
className: "h-20 w-64 rounded-4"
|
|
34
|
+
}),
|
|
35
|
+
/*#__PURE__*/ _jsx(Skeleton, {
|
|
36
|
+
className: "h-20 w-112 rounded-4"
|
|
37
|
+
}),
|
|
38
|
+
/*#__PURE__*/ _jsx("span", {
|
|
39
|
+
className: "min-w-0 flex-1"
|
|
40
|
+
}),
|
|
41
|
+
/*#__PURE__*/ _jsx(Skeleton, {
|
|
42
|
+
className: "h-20 w-88 rounded-4"
|
|
43
|
+
}),
|
|
44
|
+
/*#__PURE__*/ _jsx(Skeleton, {
|
|
45
|
+
className: "h-20 w-88 rounded-4"
|
|
46
|
+
})
|
|
47
|
+
]
|
|
48
|
+
})
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
export function WorkflowRunNotFound() {
|
|
52
|
+
return /*#__PURE__*/ _jsx(EmptyState, {
|
|
53
|
+
icon: "pulseLine",
|
|
54
|
+
title: "Run not found",
|
|
55
|
+
description: "This run does not exist or is no longer available."
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Slim non-blocking banner shown when a background refetch fails after the run already
|
|
60
|
+
* loaded (active-run polling can hit a transient API error), so the loaded run stays on
|
|
61
|
+
* screen with an inline retry instead of being wiped by a full error placeholder.
|
|
62
|
+
*/ export function WorkflowRunStaleError({ query }) {
|
|
63
|
+
return /*#__PURE__*/ _jsx("div", {
|
|
64
|
+
className: "border-b border-border-neutral-base p-8",
|
|
65
|
+
children: /*#__PURE__*/ _jsx(Callout, {
|
|
66
|
+
role: "alert",
|
|
67
|
+
type: "error",
|
|
68
|
+
children: /*#__PURE__*/ _jsxs("div", {
|
|
69
|
+
className: "flex items-center justify-between gap-8",
|
|
70
|
+
children: [
|
|
71
|
+
/*#__PURE__*/ _jsx(Text, {
|
|
72
|
+
size: "xs",
|
|
73
|
+
children: "Could not refresh this run."
|
|
74
|
+
}),
|
|
75
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
76
|
+
type: "button",
|
|
77
|
+
size: "2xs",
|
|
78
|
+
variant: "secondary",
|
|
79
|
+
isLoading: query.isFetching,
|
|
80
|
+
onClick: ()=>{
|
|
81
|
+
void query.refetch();
|
|
82
|
+
},
|
|
83
|
+
children: "Retry"
|
|
84
|
+
})
|
|
85
|
+
]
|
|
86
|
+
})
|
|
87
|
+
})
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
//# sourceMappingURL=workflow-run-states.js.map
|