@shipfox/client-workflows 14.0.0 → 15.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.storybook/preview.tsx +7 -2
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +35 -0
- package/dist/components/job-detail/agent-config-failure-callout.d.ts.map +1 -0
- package/dist/components/{workflow-run-view → job-detail}/agent-config-failure-callout.js +2 -2
- package/dist/components/job-detail/agent-config-failure-callout.js.map +1 -0
- package/dist/components/job-detail/job-detail-header.d.ts +8 -0
- package/dist/components/job-detail/job-detail-header.d.ts.map +1 -0
- package/dist/components/job-detail/job-detail-header.js +111 -0
- package/dist/components/job-detail/job-detail-header.js.map +1 -0
- package/dist/components/job-detail/job-detail-view.d.ts +16 -0
- package/dist/components/job-detail/job-detail-view.d.ts.map +1 -0
- package/dist/components/job-detail/job-detail-view.js +405 -0
- package/dist/components/job-detail/job-detail-view.js.map +1 -0
- package/dist/components/job-detail/job-empty-states.d.ts +10 -0
- package/dist/components/job-detail/job-empty-states.d.ts.map +1 -0
- package/dist/components/job-detail/job-empty-states.js +195 -0
- package/dist/components/job-detail/job-empty-states.js.map +1 -0
- package/dist/components/job-detail/job-execution-switcher.d.ts.map +1 -0
- package/dist/components/job-detail/job-execution-switcher.js.map +1 -0
- package/dist/components/job-detail/job-execution-time-text.d.ts.map +1 -0
- package/dist/components/job-detail/job-execution-time-text.js.map +1 -0
- package/dist/components/job-detail/job-selection.d.ts +24 -0
- package/dist/components/job-detail/job-selection.d.ts.map +1 -0
- package/dist/components/job-detail/job-selection.js +66 -0
- package/dist/components/job-detail/job-selection.js.map +1 -0
- package/dist/components/job-detail/step-attempt-log-panel.d.ts +15 -0
- package/dist/components/job-detail/step-attempt-log-panel.d.ts.map +1 -0
- package/dist/components/{workflow-run-view → job-detail}/step-attempt-log-panel.js +61 -16
- package/dist/components/job-detail/step-attempt-log-panel.js.map +1 -0
- package/dist/components/job-graph/job-graph-content.js +1 -1
- package/dist/components/job-graph/job-graph-content.js.map +1 -1
- package/dist/components/step-list/step-list-model.js +2 -2
- package/dist/components/step-list/step-list-model.js.map +1 -1
- package/dist/components/step-list/step-list.d.ts +1 -0
- package/dist/components/step-list/step-list.d.ts.map +1 -1
- package/dist/components/step-list/step-list.js +29 -4
- package/dist/components/step-list/step-list.js.map +1 -1
- package/dist/components/workflow-run-duration-label.d.ts +9 -4
- package/dist/components/workflow-run-duration-label.d.ts.map +1 -1
- package/dist/components/workflow-run-duration-label.js +27 -7
- package/dist/components/workflow-run-duration-label.js.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-row.d.ts.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-row.js +6 -5
- package/dist/components/workflow-run-list/workflow-run-row.js.map +1 -1
- package/dist/components/workflow-run-summary/workflow-run-summary.d.ts.map +1 -1
- package/dist/components/workflow-run-summary/workflow-run-summary.js +68 -64
- package/dist/components/workflow-run-summary/workflow-run-summary.js.map +1 -1
- package/dist/components/workflow-run-tabs/index.d.ts +0 -3
- package/dist/components/workflow-run-tabs/index.d.ts.map +1 -1
- package/dist/components/workflow-run-tabs/index.js +0 -3
- package/dist/components/workflow-run-tabs/index.js.map +1 -1
- package/dist/components/workflow-run-tabs/run-annotations-empty.d.ts +3 -1
- package/dist/components/workflow-run-tabs/run-annotations-empty.d.ts.map +1 -1
- package/dist/components/workflow-run-tabs/run-annotations-empty.js +2 -2
- package/dist/components/workflow-run-tabs/run-annotations-empty.js.map +1 -1
- package/dist/components/workflow-run-view/index.d.ts +0 -1
- package/dist/components/workflow-run-view/index.d.ts.map +1 -1
- package/dist/components/workflow-run-view/index.js +0 -1
- package/dist/components/workflow-run-view/index.js.map +1 -1
- package/dist/components/workflow-run-view/run-workspace-nav.d.ts +20 -0
- package/dist/components/workflow-run-view/run-workspace-nav.d.ts.map +1 -0
- package/dist/components/workflow-run-view/run-workspace-nav.js +271 -0
- package/dist/components/workflow-run-view/run-workspace-nav.js.map +1 -0
- package/dist/components/workflow-run-view/workflow-run-selection.d.ts.map +1 -1
- package/dist/components/workflow-run-view/workflow-run-selection.js +1 -14
- package/dist/components/workflow-run-view/workflow-run-selection.js.map +1 -1
- package/dist/components/workflow-run-view/workflow-run-view.d.ts +10 -8
- package/dist/components/workflow-run-view/workflow-run-view.d.ts.map +1 -1
- package/dist/components/workflow-run-view/workflow-run-view.js +250 -236
- package/dist/components/workflow-run-view/workflow-run-view.js.map +1 -1
- package/dist/components/workflow-status/status-visuals.d.ts.map +1 -1
- package/dist/components/workflow-status/status-visuals.js +10 -2
- package/dist/components/workflow-status/status-visuals.js.map +1 -1
- package/dist/components/workflow-status/workflow-status-icon.d.ts +3 -1
- package/dist/components/workflow-status/workflow-status-icon.d.ts.map +1 -1
- package/dist/components/workflow-status/workflow-status-icon.js +13 -2
- package/dist/components/workflow-status/workflow-status-icon.js.map +1 -1
- package/dist/core/entities/job-execution.d.ts +9 -0
- package/dist/core/entities/job-execution.d.ts.map +1 -1
- package/dist/core/entities/job-execution.js +13 -3
- package/dist/core/entities/job-execution.js.map +1 -1
- package/dist/core/entities/job.d.ts.map +1 -1
- package/dist/core/entities/step-attempt.d.ts.map +1 -1
- package/dist/core/entities/step.d.ts +2 -0
- package/dist/core/entities/step.d.ts.map +1 -1
- package/dist/core/entities/step.js +13 -0
- package/dist/core/entities/step.js.map +1 -1
- package/dist/core/entities/workflow-run-attempt.d.ts.map +1 -1
- package/dist/core/entities/workflow-run.d.ts +56 -1
- package/dist/core/entities/workflow-run.d.ts.map +1 -1
- package/dist/core/entities/workflow-run.js +98 -0
- package/dist/core/entities/workflow-run.js.map +1 -1
- package/dist/core/workflow-run.d.ts +4 -4
- package/dist/core/workflow-run.d.ts.map +1 -1
- package/dist/core/workflow-run.js +3 -3
- package/dist/core/workflow-run.js.map +1 -1
- package/dist/feature.d.ts +20 -16
- package/dist/feature.d.ts.map +1 -1
- package/dist/feature.js +5 -0
- package/dist/feature.js.map +1 -1
- package/dist/hooks/api/workflow-runs.d.ts +5 -3
- package/dist/hooks/api/workflow-runs.d.ts.map +1 -1
- package/dist/hooks/api/workflow-runs.js +5 -4
- package/dist/hooks/api/workflow-runs.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/pages/workflow-job-detail-page.d.ts +11 -0
- package/dist/pages/workflow-job-detail-page.d.ts.map +1 -0
- package/dist/pages/workflow-job-detail-page.js +66 -0
- package/dist/pages/workflow-job-detail-page.js.map +1 -0
- package/dist/pages/workflow-run-detail-page.d.ts.map +1 -1
- package/dist/pages/workflow-run-detail-page.js +4 -37
- package/dist/pages/workflow-run-detail-page.js.map +1 -1
- package/dist/routes/inputs.d.ts +47 -34
- package/dist/routes/inputs.d.ts.map +1 -1
- package/dist/routes/inputs.js +69 -37
- package/dist/routes/inputs.js.map +1 -1
- package/dist/routes/job-detail.d.ts +10 -0
- package/dist/routes/job-detail.d.ts.map +1 -0
- package/dist/routes/job-detail.js +27 -0
- package/dist/routes/job-detail.js.map +1 -0
- package/dist/routes/project-route.d.ts +1 -1
- package/dist/routes/run-detail.d.ts.map +1 -1
- package/dist/routes/runs.d.ts.map +1 -1
- package/dist/routes/workflows.d.ts.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +7 -7
- package/src/components/{workflow-run-view → job-detail}/agent-config-failure-callout.stories.tsx +1 -1
- package/src/components/{workflow-run-view → job-detail}/agent-config-failure-callout.tsx +2 -2
- package/src/components/job-detail/job-detail-header.test.tsx +31 -0
- package/src/components/job-detail/job-detail-header.tsx +111 -0
- package/src/components/job-detail/job-detail-view.stories.tsx +526 -0
- package/src/components/job-detail/job-detail-view.tsx +450 -0
- package/src/components/job-detail/job-empty-states.test.ts +27 -0
- package/src/components/job-detail/job-empty-states.tsx +248 -0
- package/src/components/job-detail/job-selection.test.ts +188 -0
- package/src/components/job-detail/job-selection.ts +96 -0
- package/src/components/{workflow-run-view → job-detail}/step-attempt-log-panel.test.tsx +32 -9
- package/src/components/{workflow-run-view → job-detail}/step-attempt-log-panel.tsx +52 -18
- package/src/components/job-graph/job-graph-content.tsx +1 -1
- package/src/components/step-list/step-list-model.test.ts +1 -1
- package/src/components/step-list/step-list-model.ts +2 -2
- package/src/components/step-list/step-list.stories.tsx +1 -1
- package/src/components/step-list/step-list.test.tsx +33 -0
- package/src/components/step-list/step-list.tsx +40 -9
- package/src/components/workflow-run-duration-label.tsx +50 -12
- package/src/components/workflow-run-list/workflow-run-row.tsx +6 -4
- package/src/components/workflow-run-summary/workflow-run-summary.stories.tsx +38 -0
- package/src/components/workflow-run-summary/workflow-run-summary.test.tsx +79 -5
- package/src/components/workflow-run-summary/workflow-run-summary.tsx +56 -60
- package/src/components/workflow-run-tabs/index.ts +0 -3
- package/src/components/workflow-run-tabs/run-annotations-empty.tsx +6 -2
- package/src/components/workflow-run-view/index.ts +0 -1
- package/src/components/workflow-run-view/run-workspace-nav.test.tsx +242 -0
- package/src/components/workflow-run-view/run-workspace-nav.tsx +301 -0
- package/src/components/workflow-run-view/workflow-run-selection.ts +1 -23
- package/src/components/workflow-run-view/workflow-run-view.test.tsx +62 -1136
- package/src/components/workflow-run-view/workflow-run-view.tsx +330 -321
- package/src/components/workflow-status/status-visuals.ts +5 -2
- package/src/components/workflow-status/workflow-status-icon.stories.tsx +1 -0
- package/src/components/workflow-status/workflow-status-icon.tsx +26 -1
- package/src/core/entities/job-execution.ts +17 -2
- package/src/core/entities/step.ts +26 -0
- package/src/core/entities/workflow-run-display.test.ts +159 -0
- package/src/core/entities/workflow-run.ts +139 -1
- package/src/core/workflow-run.ts +16 -1
- package/src/feature.ts +5 -0
- package/src/hooks/api/workflow-runs.ts +6 -2
- package/src/index.ts +1 -0
- package/src/page-harness-budget.test.ts +1 -0
- package/src/pages/workflow-job-detail-page.test.tsx +539 -0
- package/src/pages/workflow-job-detail-page.tsx +84 -0
- package/src/pages/workflow-run-detail-page.tsx +3 -43
- package/src/pages/workflow-run-pages.test.tsx +77 -332
- package/src/routes/inputs.test.ts +56 -6
- package/src/routes/inputs.ts +64 -17
- package/src/routes/job-detail.tsx +29 -0
- package/test/pages.tsx +25 -3
- package/test/render.tsx +6 -1
- package/tsconfig.build.tsbuildinfo +1 -1
- package/dist/components/workflow-run-tabs/run-jobs-list.d.ts +0 -18
- package/dist/components/workflow-run-tabs/run-jobs-list.d.ts.map +0 -1
- package/dist/components/workflow-run-tabs/run-jobs-list.js +0 -107
- package/dist/components/workflow-run-tabs/run-jobs-list.js.map +0 -1
- package/dist/components/workflow-run-tabs/run-tab-count.d.ts +0 -6
- package/dist/components/workflow-run-tabs/run-tab-count.d.ts.map +0 -1
- package/dist/components/workflow-run-tabs/run-tab-count.js +0 -27
- package/dist/components/workflow-run-tabs/run-tab-count.js.map +0 -1
- package/dist/components/workflow-run-tabs/run-tab-strip.d.ts +0 -8
- package/dist/components/workflow-run-tabs/run-tab-strip.d.ts.map +0 -1
- package/dist/components/workflow-run-tabs/run-tab-strip.js +0 -82
- package/dist/components/workflow-run-tabs/run-tab-strip.js.map +0 -1
- package/dist/components/workflow-run-view/agent-config-failure-callout.d.ts.map +0 -1
- package/dist/components/workflow-run-view/agent-config-failure-callout.js.map +0 -1
- package/dist/components/workflow-run-view/job-card.d.ts +0 -17
- package/dist/components/workflow-run-view/job-card.d.ts.map +0 -1
- package/dist/components/workflow-run-view/job-card.js +0 -496
- package/dist/components/workflow-run-view/job-card.js.map +0 -1
- package/dist/components/workflow-run-view/job-execution-switcher.d.ts.map +0 -1
- package/dist/components/workflow-run-view/job-execution-switcher.js.map +0 -1
- package/dist/components/workflow-run-view/job-execution-time-text.d.ts.map +0 -1
- package/dist/components/workflow-run-view/job-execution-time-text.js.map +0 -1
- package/dist/components/workflow-run-view/step-attempt-log-panel.d.ts +0 -9
- package/dist/components/workflow-run-view/step-attempt-log-panel.d.ts.map +0 -1
- package/dist/components/workflow-run-view/step-attempt-log-panel.js.map +0 -1
- package/src/components/workflow-run-tabs/run-jobs-list.tsx +0 -151
- package/src/components/workflow-run-tabs/run-tab-count.tsx +0 -28
- package/src/components/workflow-run-tabs/run-tab-strip.stories.tsx +0 -52
- package/src/components/workflow-run-tabs/run-tab-strip.test.tsx +0 -117
- package/src/components/workflow-run-tabs/run-tab-strip.tsx +0 -97
- package/src/components/workflow-run-view/job-card.stories.tsx +0 -789
- package/src/components/workflow-run-view/job-card.test.tsx +0 -115
- package/src/components/workflow-run-view/job-card.tsx +0 -614
- /package/dist/components/{workflow-run-view → job-detail}/agent-config-failure-callout.d.ts +0 -0
- /package/dist/components/{workflow-run-view → job-detail}/job-execution-switcher.d.ts +0 -0
- /package/dist/components/{workflow-run-view → job-detail}/job-execution-switcher.js +0 -0
- /package/dist/components/{workflow-run-view → job-detail}/job-execution-time-text.d.ts +0 -0
- /package/dist/components/{workflow-run-view → job-detail}/job-execution-time-text.js +0 -0
- /package/src/components/{workflow-run-view → job-detail}/job-execution-switcher.tsx +0 -0
- /package/src/components/{workflow-run-view → job-detail}/job-execution-time-text.tsx +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/job-graph/job-graph-content.tsx"],"sourcesContent":["import {EmptyState} from '@shipfox/react-ui/empty-state';\nimport {TimeTickerProvider} from '@shipfox/react-ui/time-ticker';\nimport {cn} from '@shipfox/react-ui/utils';\nimport type {KeyboardEvent} from 'react';\nimport {useRef, useState} from 'react';\nimport type {WorkflowRunDetail} from '#core/workflow-run.js';\nimport type {JobGraphModel, JobGraphNavigationKey} from './graph-model.js';\nimport {nextJobGraphNodeId} from './graph-model.js';\nimport {JobNode, TriggerNode} from './job-node.js';\nimport type {JobGraphSelectionSource} from './types.js';\n\nconst NODE_WIDTH = 208;\nconst NODE_HEIGHT = 48;\nconst COLUMN_GAP = 64;\nconst ROW_GAP = 20;\nconst TRIGGER_WIDTH = 36;\nconst PADDING = 16;\n\nexport function JobGraphContent({\n model,\n trigger,\n selectedJobId,\n onSelectJob,\n}: {\n model: JobGraphModel;\n trigger: Pick<\n WorkflowRunDetail,\n 'triggerDisplayLabel' | 'triggerLabel' | 'triggerProvider' | 'triggerSource'\n >;\n selectedJobId?: string | undefined;\n onSelectJob: (jobId: string | undefined, source?: JobGraphSelectionSource) => void;\n}) {\n const nodeRefs = useRef(new Map<string, HTMLButtonElement>());\n const [hoveredJobId, setHoveredJobId] = useState<string | undefined>();\n\n if (model.nodes.length === 0) {\n return (\n <EmptyState\n className=\"min-h-160 px-16 py-24\"\n icon=\"componentLine\"\n title=\"No jobs yet\"\n description=\"This run has not materialized jobs.\"\n variant=\"compact\"\n />\n );\n }\n\n const maxRows = Math.max(1, ...model.columns.map((column) => column.length));\n const contentWidth =\n PADDING * 2 +\n TRIGGER_WIDTH +\n COLUMN_GAP +\n model.columns.length * NODE_WIDTH +\n Math.max(0, model.columns.length - 1) * COLUMN_GAP;\n const contentHeight = PADDING * 2 + maxRows * NODE_HEIGHT + Math.max(0, maxRows - 1) * ROW_GAP;\n\n function setNodeRef(jobId: string) {\n return (element: HTMLButtonElement | null) => {\n if (element) {\n nodeRefs.current.set(jobId, element);\n } else {\n nodeRefs.current.delete(jobId);\n }\n };\n }\n\n function handleKeyDown(event: KeyboardEvent<HTMLButtonElement>) {\n const key = navigationKey(event.key);\n if (!key) return;\n\n const currentNodeId = event.currentTarget.dataset.jobId;\n if (!currentNodeId) return;\n\n const nextNodeId = nextJobGraphNodeId({model, currentNodeId, key});\n if (!nextNodeId) return;\n\n event.preventDefault();\n onSelectJob(nextNodeId, 'keyboard');\n nodeRefs.current.get(nextNodeId)?.focus();\n }\n\n return (\n <TimeTickerProvider intervalMs={1000} reducedMotionIntervalMs={10_000}>\n <div className=\"min-h-0 overflow-auto bg-background-neutral-background\">\n <div className=\"relative\" style={{width: contentWidth, minHeight: contentHeight}}>\n <GraphEdges model={model} hoveredJobId={hoveredJobId} />\n <div\n className=\"absolute\"\n style={{left: PADDING, top: PADDING + (NODE_HEIGHT - TRIGGER_WIDTH) / 2}}\n >\n <TriggerNode trigger={trigger} />\n </div>\n {model.columns.map((column, columnIndex) => (\n <div\n key={column.map((node) => node.id).join(':')}\n className=\"absolute flex flex-col gap-20\"\n style={{left: jobLeft(columnIndex), top: PADDING}}\n >\n {column.map((node) => (\n <JobNode\n key={node.id}\n node={node}\n selected={node.id === selectedJobId}\n ref={setNodeRef(node.id)}\n onSelect={() =>\n onSelectJob(node.id === selectedJobId ? undefined : node.id, 'pointer')\n }\n onKeyDown={handleKeyDown}\n onHoverStart={() => setHoveredJobId(node.id)}\n onHoverEnd={() =>\n setHoveredJobId((current) => (current === node.id ? undefined : current))\n }\n />\n ))}\n </div>\n ))}\n </div>\n </div>\n </TimeTickerProvider>\n );\n}\n\nfunction navigationKey(key: string): JobGraphNavigationKey | undefined {\n if (\n key === 'ArrowRight' ||\n key === 'ArrowLeft' ||\n key === 'ArrowDown' ||\n key === 'ArrowUp' ||\n key === 'j' ||\n key === 'k'\n ) {\n return key;\n }\n return undefined;\n}\n\nfunction GraphEdges({\n model,\n hoveredJobId,\n}: {\n model: JobGraphModel;\n hoveredJobId?: string | undefined;\n}) {\n const byId = new Map(model.nodes.map((node) => [node.id, node]));\n const edgeViews = model.edges\n .map((edge) => {\n const fromNode = byId.get(edge.from);\n const toNode = byId.get(edge.to);\n const from = edge.from === 'trigger' ? triggerPoint() : jobRightPoint(fromNode);\n const to = jobLeftPoint(toNode);\n if (!from || !to) return undefined;\n\n return {\n id: edge.id,\n highlighted: edge.from === hoveredJobId || edge.to === hoveredJobId,\n path: edgePath({from, fromNode, to, toNode}),\n };\n })\n .filter((edgeView): edgeView is NonNullable<typeof edgeView> => edgeView !== undefined)\n .sort((left, right) => Number(left.highlighted) - Number(right.highlighted));\n\n return (\n <svg className=\"pointer-events-none absolute inset-0 size-full\" aria-hidden=\"true\">\n {edgeViews.map((edge) => {\n return (\n <g\n key={edge.id}\n className={cn(\n 'text-foreground-neutral-muted transition-colors',\n edge.highlighted && 'text-foreground-neutral-base',\n )}\n >\n <path\n data-edge-id={edge.id}\n d={edge.path}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={edge.highlighted ? 1.5 : 1}\n />\n </g>\n );\n })}\n </svg>\n );\n}\n\nfunction edgePath({\n from,\n fromNode,\n to,\n toNode,\n}: {\n from: {x: number; y: number};\n fromNode: {column: number} | undefined;\n to: {x: number; y: number};\n toNode: {column: number} | undefined;\n}) {\n if (fromNode && toNode && toNode.column - fromNode.column > 1 && from.y !== to.y) {\n const targetLaneX = to.x - COLUMN_GAP / 2;\n return `M ${from.x} ${from.y} H ${targetLaneX} V ${to.y} H ${to.x}`;\n }\n\n const midX = from.x + (to.x - from.x) / 2;\n return `M ${from.x} ${from.y} H ${midX} V ${to.y} H ${to.x}`;\n}\n\nfunction triggerPoint() {\n return {x: PADDING + TRIGGER_WIDTH, y: PADDING + NODE_HEIGHT / 2};\n}\n\nfunction jobLeft(column: number): number {\n // Dependency depths are contiguous, so a node's depth matches its rendered column index.\n return PADDING + TRIGGER_WIDTH + COLUMN_GAP + column * (NODE_WIDTH + COLUMN_GAP);\n}\n\nfunction jobTop(row: number): number {\n return PADDING + row * (NODE_HEIGHT + ROW_GAP);\n}\n\nfunction jobLeftPoint(node: {column: number; row: number} | undefined) {\n if (!node) return undefined;\n return {x: jobLeft(node.column), y: jobTop(node.row) + NODE_HEIGHT / 2};\n}\n\nfunction jobRightPoint(node: {column: number; row: number} | undefined) {\n if (!node) return undefined;\n return {x: jobLeft(node.column) + NODE_WIDTH, y: jobTop(node.row) + NODE_HEIGHT / 2};\n}\n"],"names":["EmptyState","TimeTickerProvider","cn","useRef","useState","nextJobGraphNodeId","JobNode","TriggerNode","NODE_WIDTH","NODE_HEIGHT","COLUMN_GAP","ROW_GAP","TRIGGER_WIDTH","PADDING","JobGraphContent","model","trigger","selectedJobId","onSelectJob","nodeRefs","Map","hoveredJobId","setHoveredJobId","nodes","length","className","icon","title","description","variant","maxRows","Math","max","columns","map","column","contentWidth","contentHeight","setNodeRef","jobId","element","current","set","delete","handleKeyDown","event","key","navigationKey","currentNodeId","currentTarget","dataset","nextNodeId","preventDefault","get","focus","intervalMs","reducedMotionIntervalMs","div","style","width","minHeight","GraphEdges","left","top","columnIndex","jobLeft","node","selected","id","ref","onSelect","undefined","onKeyDown","onHoverStart","onHoverEnd","join","byId","edgeViews","edges","edge","fromNode","from","toNode","to","triggerPoint","jobRightPoint","jobLeftPoint","highlighted","path","edgePath","filter","edgeView","sort","right","Number","svg","aria-hidden","g","data-edge-id","d","fill","stroke","strokeWidth","y","targetLaneX","x","midX","jobTop","row"],"mappings":";AAAA,SAAQA,UAAU,QAAO,gCAAgC;AACzD,SAAQC,kBAAkB,QAAO,gCAAgC;AACjE,SAAQC,EAAE,QAAO,0BAA0B;AAE3C,SAAQC,MAAM,EAAEC,QAAQ,QAAO,QAAQ;AAGvC,SAAQC,kBAAkB,QAAO,mBAAmB;AACpD,SAAQC,OAAO,EAAEC,WAAW,QAAO,gBAAgB;AAGnD,MAAMC,aAAa;AACnB,MAAMC,cAAc;AACpB,MAAMC,aAAa;AACnB,MAAMC,UAAU;AAChB,MAAMC,gBAAgB;AACtB,MAAMC,UAAU;AAEhB,OAAO,SAASC,gBAAgB,EAC9BC,KAAK,EACLC,OAAO,EACPC,aAAa,EACbC,WAAW,EASZ;IACC,MAAMC,WAAWhB,OAAO,IAAIiB;IAC5B,MAAM,CAACC,cAAcC,gBAAgB,GAAGlB;IAExC,IAAIW,MAAMQ,KAAK,CAACC,MAAM,KAAK,GAAG;QAC5B,qBACE,KAACxB;YACCyB,WAAU;YACVC,MAAK;YACLC,OAAM;YACNC,aAAY;YACZC,SAAQ;;IAGd;IAEA,MAAMC,UAAUC,KAAKC,GAAG,CAAC,MAAMjB,MAAMkB,OAAO,CAACC,GAAG,CAAC,CAACC,SAAWA,OAAOX,MAAM;IAC1E,MAAMY,eACJvB,UAAU,IACVD,gBACAF,aACAK,MAAMkB,OAAO,CAACT,MAAM,GAAGhB,aACvBuB,KAAKC,GAAG,CAAC,GAAGjB,MAAMkB,OAAO,CAACT,MAAM,GAAG,KAAKd;IAC1C,MAAM2B,gBAAgBxB,UAAU,IAAIiB,UAAUrB,cAAcsB,KAAKC,GAAG,CAAC,GAAGF,UAAU,KAAKnB;IAEvF,SAAS2B,WAAWC,KAAa;QAC/B,OAAO,CAACC;YACN,IAAIA,SAAS;gBACXrB,SAASsB,OAAO,CAACC,GAAG,CAACH,OAAOC;YAC9B,OAAO;gBACLrB,SAASsB,OAAO,CAACE,MAAM,CAACJ;YAC1B;QACF;IACF;IAEA,SAASK,cAAcC,KAAuC;QAC5D,MAAMC,MAAMC,cAAcF,MAAMC,GAAG;QACnC,IAAI,CAACA,KAAK;QAEV,MAAME,gBAAgBH,MAAMI,aAAa,CAACC,OAAO,CAACX,KAAK;QACvD,IAAI,CAACS,eAAe;QAEpB,MAAMG,aAAa9C,mBAAmB;YAACU;YAAOiC;YAAeF;QAAG;QAChE,IAAI,CAACK,YAAY;QAEjBN,MAAMO,cAAc;QACpBlC,YAAYiC,YAAY;QACxBhC,SAASsB,OAAO,CAACY,GAAG,CAACF,aAAaG;IACpC;IAEA,qBACE,KAACrD;QAAmBsD,YAAY;QAAMC,yBAAyB;kBAC7D,cAAA,KAACC;YAAIhC,WAAU;sBACb,cAAA,MAACgC;gBAAIhC,WAAU;gBAAWiC,OAAO;oBAACC,OAAOvB;oBAAcwB,WAAWvB;gBAAa;;kCAC7E,KAACwB;wBAAW9C,OAAOA;wBAAOM,cAAcA;;kCACxC,KAACoC;wBACChC,WAAU;wBACViC,OAAO;4BAACI,MAAMjD;4BAASkD,KAAKlD,UAAU,AAACJ,CAAAA,cAAcG,aAAY,IAAK;wBAAC;kCAEvE,cAAA,KAACL;4BAAYS,SAASA;;;oBAEvBD,MAAMkB,OAAO,CAACC,GAAG,CAAC,CAACC,QAAQ6B,4BAC1B,KAACP;4BAEChC,WAAU;4BACViC,OAAO;gCAACI,MAAMG,QAAQD;gCAAcD,KAAKlD;4BAAO;sCAE/CsB,OAAOD,GAAG,CAAC,CAACgC,qBACX,KAAC5D;oCAEC4D,MAAMA;oCACNC,UAAUD,KAAKE,EAAE,KAAKnD;oCACtBoD,KAAK/B,WAAW4B,KAAKE,EAAE;oCACvBE,UAAU,IACRpD,YAAYgD,KAAKE,EAAE,KAAKnD,gBAAgBsD,YAAYL,KAAKE,EAAE,EAAE;oCAE/DI,WAAW5B;oCACX6B,cAAc,IAAMnD,gBAAgB4C,KAAKE,EAAE;oCAC3CM,YAAY,IACVpD,gBAAgB,CAACmB,UAAaA,YAAYyB,KAAKE,EAAE,GAAGG,YAAY9B;mCAV7DyB,KAAKE,EAAE;2BANXjC,OAAOD,GAAG,CAAC,CAACgC,OAASA,KAAKE,EAAE,EAAEO,IAAI,CAAC;;;;;AA0BtD;AAEA,SAAS5B,cAAcD,GAAW;IAChC,IACEA,QAAQ,gBACRA,QAAQ,eACRA,QAAQ,eACRA,QAAQ,aACRA,QAAQ,OACRA,QAAQ,KACR;QACA,OAAOA;IACT;IACA,OAAOyB;AACT;AAEA,SAASV,WAAW,EAClB9C,KAAK,EACLM,YAAY,EAIb;IACC,MAAMuD,OAAO,IAAIxD,IAAIL,MAAMQ,KAAK,CAACW,GAAG,CAAC,CAACgC,OAAS;YAACA,KAAKE,EAAE;YAAEF;SAAK;IAC9D,MAAMW,YAAY9D,MAAM+D,KAAK,CAC1B5C,GAAG,CAAC,CAAC6C;QACJ,MAAMC,WAAWJ,KAAKvB,GAAG,CAAC0B,KAAKE,IAAI;QACnC,MAAMC,SAASN,KAAKvB,GAAG,CAAC0B,KAAKI,EAAE;QAC/B,MAAMF,OAAOF,KAAKE,IAAI,KAAK,YAAYG,iBAAiBC,cAAcL;QACtE,MAAMG,KAAKG,aAAaJ;QACxB,IAAI,CAACD,QAAQ,CAACE,IAAI,OAAOZ;QAEzB,OAAO;YACLH,IAAIW,KAAKX,EAAE;YACXmB,aAAaR,KAAKE,IAAI,KAAK5D,gBAAgB0D,KAAKI,EAAE,KAAK9D;YACvDmE,MAAMC,SAAS;gBAACR;gBAAMD;gBAAUG;gBAAID;YAAM;QAC5C;IACF,GACCQ,MAAM,CAAC,CAACC,WAAuDA,aAAapB,WAC5EqB,IAAI,CAAC,CAAC9B,MAAM+B,QAAUC,OAAOhC,KAAKyB,WAAW,IAAIO,OAAOD,MAAMN,WAAW;IAE5E,qBACE,KAACQ;QAAItE,WAAU;QAAiDuE,eAAY;kBACzEnB,UAAU3C,GAAG,CAAC,CAAC6C;YACd,qBACE,KAACkB;gBAECxE,WAAWvB,GACT,mDACA6E,KAAKQ,WAAW,IAAI;0BAGtB,cAAA,KAACC;oBACCU,gBAAcnB,KAAKX,EAAE;oBACrB+B,GAAGpB,KAAKS,IAAI;oBACZY,MAAK;oBACLC,QAAO;oBACPC,aAAavB,KAAKQ,WAAW,GAAG,MAAM;;eAXnCR,KAAKX,EAAE;QAelB;;AAGN;AAEA,SAASqB,SAAS,EAChBR,IAAI,EACJD,QAAQ,EACRG,EAAE,EACFD,MAAM,EAMP;IACC,IAAIF,YAAYE,UAAUA,OAAO/C,MAAM,GAAG6C,SAAS7C,MAAM,GAAG,KAAK8C,KAAKsB,CAAC,KAAKpB,GAAGoB,CAAC,EAAE;QAChF,MAAMC,cAAcrB,GAAGsB,CAAC,GAAG/F,aAAa;QACxC,OAAO,CAAC,EAAE,EAAEuE,KAAKwB,CAAC,CAAC,CAAC,EAAExB,KAAKsB,CAAC,CAAC,GAAG,EAAEC,YAAY,GAAG,EAAErB,GAAGoB,CAAC,CAAC,GAAG,EAAEpB,GAAGsB,CAAC,EAAE;IACrE;IAEA,MAAMC,OAAOzB,KAAKwB,CAAC,GAAG,AAACtB,CAAAA,GAAGsB,CAAC,GAAGxB,KAAKwB,CAAC,AAADA,IAAK;IACxC,OAAO,CAAC,EAAE,EAAExB,KAAKwB,CAAC,CAAC,CAAC,EAAExB,KAAKsB,CAAC,CAAC,GAAG,EAAEG,KAAK,GAAG,EAAEvB,GAAGoB,CAAC,CAAC,GAAG,EAAEpB,GAAGsB,CAAC,EAAE;AAC9D;AAEA,SAASrB;IACP,OAAO;QAACqB,GAAG5F,UAAUD;QAAe2F,GAAG1F,UAAUJ,cAAc;IAAC;AAClE;AAEA,SAASwD,QAAQ9B,MAAc;IAC7B,yFAAyF;IACzF,OAAOtB,UAAUD,gBAAgBF,aAAayB,SAAU3B,CAAAA,aAAaE,UAAS;AAChF;AAEA,SAASiG,OAAOC,GAAW;IACzB,OAAO/F,UAAU+F,MAAOnG,CAAAA,cAAcE,OAAM;AAC9C;AAEA,SAAS2E,aAAapB,IAA+C;IACnE,IAAI,CAACA,MAAM,OAAOK;IAClB,OAAO;QAACkC,GAAGxC,QAAQC,KAAK/B,MAAM;QAAGoE,GAAGI,OAAOzC,KAAK0C,GAAG,IAAInG,cAAc;IAAC;AACxE;AAEA,SAAS4E,cAAcnB,IAA+C;IACpE,IAAI,CAACA,MAAM,OAAOK;IAClB,OAAO;QAACkC,GAAGxC,QAAQC,KAAK/B,MAAM,IAAI3B;QAAY+F,GAAGI,OAAOzC,KAAK0C,GAAG,IAAInG,cAAc;IAAC;AACrF"}
|
|
1
|
+
{"version":3,"sources":["../../../src/components/job-graph/job-graph-content.tsx"],"sourcesContent":["import {EmptyState} from '@shipfox/react-ui/empty-state';\nimport {TimeTickerProvider} from '@shipfox/react-ui/time-ticker';\nimport {cn} from '@shipfox/react-ui/utils';\nimport type {KeyboardEvent} from 'react';\nimport {useRef, useState} from 'react';\nimport type {WorkflowRunDetail} from '#core/workflow-run.js';\nimport type {JobGraphModel, JobGraphNavigationKey} from './graph-model.js';\nimport {nextJobGraphNodeId} from './graph-model.js';\nimport {JobNode, TriggerNode} from './job-node.js';\nimport type {JobGraphSelectionSource} from './types.js';\n\nconst NODE_WIDTH = 208;\nconst NODE_HEIGHT = 48;\nconst COLUMN_GAP = 64;\nconst ROW_GAP = 20;\nconst TRIGGER_WIDTH = 36;\nconst PADDING = 16;\n\nexport function JobGraphContent({\n model,\n trigger,\n selectedJobId,\n onSelectJob,\n}: {\n model: JobGraphModel;\n trigger: Pick<\n WorkflowRunDetail,\n 'triggerDisplayLabel' | 'triggerLabel' | 'triggerProvider' | 'triggerSource'\n >;\n selectedJobId?: string | undefined;\n onSelectJob: (jobId: string | undefined, source?: JobGraphSelectionSource) => void;\n}) {\n const nodeRefs = useRef(new Map<string, HTMLButtonElement>());\n const [hoveredJobId, setHoveredJobId] = useState<string | undefined>();\n\n if (model.nodes.length === 0) {\n return (\n <EmptyState\n className=\"min-h-160 px-16 py-24\"\n icon=\"componentLine\"\n title=\"No jobs yet\"\n description=\"This run has not materialized jobs.\"\n variant=\"compact\"\n />\n );\n }\n\n const maxRows = Math.max(1, ...model.columns.map((column) => column.length));\n const contentWidth =\n PADDING * 2 +\n TRIGGER_WIDTH +\n COLUMN_GAP +\n model.columns.length * NODE_WIDTH +\n Math.max(0, model.columns.length - 1) * COLUMN_GAP;\n const contentHeight = PADDING * 2 + maxRows * NODE_HEIGHT + Math.max(0, maxRows - 1) * ROW_GAP;\n\n function setNodeRef(jobId: string) {\n return (element: HTMLButtonElement | null) => {\n if (element) {\n nodeRefs.current.set(jobId, element);\n } else {\n nodeRefs.current.delete(jobId);\n }\n };\n }\n\n function handleKeyDown(event: KeyboardEvent<HTMLButtonElement>) {\n const key = navigationKey(event.key);\n if (!key) return;\n\n const currentNodeId = event.currentTarget.dataset.jobId;\n if (!currentNodeId) return;\n\n const nextNodeId = nextJobGraphNodeId({model, currentNodeId, key});\n if (!nextNodeId) return;\n\n event.preventDefault();\n onSelectJob(nextNodeId, 'keyboard');\n nodeRefs.current.get(nextNodeId)?.focus();\n }\n\n return (\n <TimeTickerProvider intervalMs={1000} reducedMotionIntervalMs={10_000}>\n <div className=\"min-h-0 overflow-auto bg-transparent\">\n <div className=\"relative\" style={{width: contentWidth, minHeight: contentHeight}}>\n <GraphEdges model={model} hoveredJobId={hoveredJobId} />\n <div\n className=\"absolute\"\n style={{left: PADDING, top: PADDING + (NODE_HEIGHT - TRIGGER_WIDTH) / 2}}\n >\n <TriggerNode trigger={trigger} />\n </div>\n {model.columns.map((column, columnIndex) => (\n <div\n key={column.map((node) => node.id).join(':')}\n className=\"absolute flex flex-col gap-20\"\n style={{left: jobLeft(columnIndex), top: PADDING}}\n >\n {column.map((node) => (\n <JobNode\n key={node.id}\n node={node}\n selected={node.id === selectedJobId}\n ref={setNodeRef(node.id)}\n onSelect={() =>\n onSelectJob(node.id === selectedJobId ? undefined : node.id, 'pointer')\n }\n onKeyDown={handleKeyDown}\n onHoverStart={() => setHoveredJobId(node.id)}\n onHoverEnd={() =>\n setHoveredJobId((current) => (current === node.id ? undefined : current))\n }\n />\n ))}\n </div>\n ))}\n </div>\n </div>\n </TimeTickerProvider>\n );\n}\n\nfunction navigationKey(key: string): JobGraphNavigationKey | undefined {\n if (\n key === 'ArrowRight' ||\n key === 'ArrowLeft' ||\n key === 'ArrowDown' ||\n key === 'ArrowUp' ||\n key === 'j' ||\n key === 'k'\n ) {\n return key;\n }\n return undefined;\n}\n\nfunction GraphEdges({\n model,\n hoveredJobId,\n}: {\n model: JobGraphModel;\n hoveredJobId?: string | undefined;\n}) {\n const byId = new Map(model.nodes.map((node) => [node.id, node]));\n const edgeViews = model.edges\n .map((edge) => {\n const fromNode = byId.get(edge.from);\n const toNode = byId.get(edge.to);\n const from = edge.from === 'trigger' ? triggerPoint() : jobRightPoint(fromNode);\n const to = jobLeftPoint(toNode);\n if (!from || !to) return undefined;\n\n return {\n id: edge.id,\n highlighted: edge.from === hoveredJobId || edge.to === hoveredJobId,\n path: edgePath({from, fromNode, to, toNode}),\n };\n })\n .filter((edgeView): edgeView is NonNullable<typeof edgeView> => edgeView !== undefined)\n .sort((left, right) => Number(left.highlighted) - Number(right.highlighted));\n\n return (\n <svg className=\"pointer-events-none absolute inset-0 size-full\" aria-hidden=\"true\">\n {edgeViews.map((edge) => {\n return (\n <g\n key={edge.id}\n className={cn(\n 'text-foreground-neutral-muted transition-colors',\n edge.highlighted && 'text-foreground-neutral-base',\n )}\n >\n <path\n data-edge-id={edge.id}\n d={edge.path}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={edge.highlighted ? 1.5 : 1}\n />\n </g>\n );\n })}\n </svg>\n );\n}\n\nfunction edgePath({\n from,\n fromNode,\n to,\n toNode,\n}: {\n from: {x: number; y: number};\n fromNode: {column: number} | undefined;\n to: {x: number; y: number};\n toNode: {column: number} | undefined;\n}) {\n if (fromNode && toNode && toNode.column - fromNode.column > 1 && from.y !== to.y) {\n const targetLaneX = to.x - COLUMN_GAP / 2;\n return `M ${from.x} ${from.y} H ${targetLaneX} V ${to.y} H ${to.x}`;\n }\n\n const midX = from.x + (to.x - from.x) / 2;\n return `M ${from.x} ${from.y} H ${midX} V ${to.y} H ${to.x}`;\n}\n\nfunction triggerPoint() {\n return {x: PADDING + TRIGGER_WIDTH, y: PADDING + NODE_HEIGHT / 2};\n}\n\nfunction jobLeft(column: number): number {\n // Dependency depths are contiguous, so a node's depth matches its rendered column index.\n return PADDING + TRIGGER_WIDTH + COLUMN_GAP + column * (NODE_WIDTH + COLUMN_GAP);\n}\n\nfunction jobTop(row: number): number {\n return PADDING + row * (NODE_HEIGHT + ROW_GAP);\n}\n\nfunction jobLeftPoint(node: {column: number; row: number} | undefined) {\n if (!node) return undefined;\n return {x: jobLeft(node.column), y: jobTop(node.row) + NODE_HEIGHT / 2};\n}\n\nfunction jobRightPoint(node: {column: number; row: number} | undefined) {\n if (!node) return undefined;\n return {x: jobLeft(node.column) + NODE_WIDTH, y: jobTop(node.row) + NODE_HEIGHT / 2};\n}\n"],"names":["EmptyState","TimeTickerProvider","cn","useRef","useState","nextJobGraphNodeId","JobNode","TriggerNode","NODE_WIDTH","NODE_HEIGHT","COLUMN_GAP","ROW_GAP","TRIGGER_WIDTH","PADDING","JobGraphContent","model","trigger","selectedJobId","onSelectJob","nodeRefs","Map","hoveredJobId","setHoveredJobId","nodes","length","className","icon","title","description","variant","maxRows","Math","max","columns","map","column","contentWidth","contentHeight","setNodeRef","jobId","element","current","set","delete","handleKeyDown","event","key","navigationKey","currentNodeId","currentTarget","dataset","nextNodeId","preventDefault","get","focus","intervalMs","reducedMotionIntervalMs","div","style","width","minHeight","GraphEdges","left","top","columnIndex","jobLeft","node","selected","id","ref","onSelect","undefined","onKeyDown","onHoverStart","onHoverEnd","join","byId","edgeViews","edges","edge","fromNode","from","toNode","to","triggerPoint","jobRightPoint","jobLeftPoint","highlighted","path","edgePath","filter","edgeView","sort","right","Number","svg","aria-hidden","g","data-edge-id","d","fill","stroke","strokeWidth","y","targetLaneX","x","midX","jobTop","row"],"mappings":"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"}
|
|
@@ -11,7 +11,7 @@ export function buildStepListModel({ job, jobExecution }) {
|
|
|
11
11
|
jobName: job.name ?? job.key,
|
|
12
12
|
jobExecutionId: selectedJobExecution.id,
|
|
13
13
|
stepCount: steps.length,
|
|
14
|
-
activeEntryId:
|
|
14
|
+
activeEntryId: landingEntryId(entries),
|
|
15
15
|
entries
|
|
16
16
|
};
|
|
17
17
|
}
|
|
@@ -121,7 +121,7 @@ function compareAttempts(left, right) {
|
|
|
121
121
|
function compareEntries(left, right) {
|
|
122
122
|
return left.executionOrder - right.executionOrder || left.step.index - right.step.index || left.attempt - right.attempt || left.id.localeCompare(right.id);
|
|
123
123
|
}
|
|
124
|
-
function
|
|
124
|
+
function landingEntryId(entries) {
|
|
125
125
|
for(let index = entries.length - 1; index >= 0; index -= 1){
|
|
126
126
|
const entry = entries[index];
|
|
127
127
|
if (entry?.statusVisual.kind === 'running') return entry.id;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/step-list/step-list-model.ts"],"sourcesContent":["import {\n getWorkflowStatusVisual,\n type WorkflowStatusVisual,\n} from '#components/workflow-status/status-visuals.js';\nimport {\n defaultJobExecution,\n isWorkflowStatus as isKnownWorkflowStatus,\n type Job,\n JobExecution,\n type Step,\n type StepAttempt,\n type StepAttemptDisplayDuration,\n} from '#core/workflow-run.js';\n\nexport interface StepStatusVisual extends Omit<WorkflowStatusVisual, 'kind'> {\n kind: string;\n ripple: boolean;\n}\n\nexport interface StepAttemptModel extends StepAttempt {\n displayDuration: StepAttemptDisplayDuration | null;\n statusVisual: StepStatusVisual;\n carriedOver: boolean;\n}\n\nexport interface StepModel extends Omit<Step, 'attempts'> {\n index: number;\n label: string;\n attempts: StepAttemptModel[];\n}\n\nexport interface StepListEntryModel extends StepAttemptModel {\n step: StepModel;\n}\n\nexport interface StepListModel {\n jobId: string;\n jobName: string;\n jobExecutionId: string;\n stepCount: number;\n activeEntryId: string | undefined;\n entries: StepListEntryModel[];\n}\n\nexport function buildStepListModel({\n job,\n jobExecution,\n}: {\n job: Job;\n jobExecution?: JobExecution | undefined;\n}): StepListModel {\n const selectedJobExecution = jobExecution ?? defaultStepListJobExecution(job);\n const steps = [...selectedJobExecution.steps].sort(compareSteps).map(toStepModel);\n const entries = steps\n .flatMap((step) => toStepEntries(step, job.carriedOver))\n .sort(compareEntries);\n\n return {\n jobId: job.id,\n jobName: job.name ?? job.key,\n jobExecutionId: selectedJobExecution.id,\n stepCount: steps.length,\n activeEntryId: latestRunningEntryId(entries),\n entries,\n };\n}\n\nexport function defaultStepListJobExecution(job: Job): JobExecution {\n return defaultJobExecution(job) ?? emptyJobExecutionForJob(job);\n}\n\nexport function emptyJobExecutionForJob(job: Job): JobExecution {\n return new JobExecution({\n id: `missing:${job.id}`,\n jobId: job.id,\n sequence: 1,\n name: job.name ?? job.key,\n status: job.status === 'skipped' ? 'cancelled' : job.status,\n statusReason: job.statusReason,\n triggerEvents: [],\n queuedAt: null,\n startedAt: null,\n finishedAt: null,\n timedOutAt: null,\n createdAt: job.createdAt,\n updatedAt: job.updatedAt,\n steps: [],\n });\n}\n\nexport function getStepStatusVisual(status: string): StepStatusVisual {\n const normalized = normalizeStatus(status);\n\n if (isKnownWorkflowStatus(normalized)) {\n const visual = getWorkflowStatusVisual(normalized);\n return {...visual, ripple: normalized === 'running'};\n }\n\n return {\n kind: status,\n label: humanizeStatus(status),\n dot: 'neutral',\n badge: 'neutral',\n ripple: false,\n };\n}\n\nexport function humanizeStatus(status: string): string {\n const words = status.trim().replace(/[_-]+/g, ' ').replace(/\\s+/g, ' ').toLowerCase();\n if (!words) return 'Unknown';\n const firstLetter = words.at(0);\n return firstLetter === undefined ? 'Unknown' : firstLetter.toUpperCase() + words.slice(1);\n}\n\nfunction toStepModel(step: Step, index: number): StepModel {\n const attempts = [...step.attempts].sort(compareAttempts).map((attempt) => ({\n ...attempt,\n displayDuration: attempt.displayDuration,\n statusVisual: getStepStatusVisual(attempt.status),\n carriedOver: false,\n }));\n\n return {\n ...step,\n index: index + 1,\n label: stepLabel(step, index),\n attempts,\n };\n}\n\nfunction stepLabel(step: Step, index: number): string {\n const name = step.name.trim();\n if (name) return name;\n\n const key = step.key?.trim();\n if (key) return key;\n\n return `Step ${index + 1}`;\n}\n\nfunction toStepEntries(step: StepModel, carriedOverJob: boolean): StepListEntryModel[] {\n if (step.attempts.length > 0) {\n return step.attempts.map((attempt) => ({\n ...attempt,\n step,\n }));\n }\n\n if (!carriedOverJob) return [];\n\n return [\n {\n id: `carried-over:${step.id}`,\n stepId: step.id,\n jobExecutionId: step.jobExecutionId,\n attempt: step.currentAttempt,\n executionOrder: step.position,\n status: step.status,\n exitCode: null,\n output: null,\n error: null,\n gateResult: null,\n restartFeedback: null,\n startedAt: step.createdAt,\n finishedAt: null,\n displayDuration: null,\n statusVisual: getStepStatusVisual(step.status),\n carriedOver: true,\n step,\n },\n ];\n}\n\nfunction compareSteps(left: Step, right: Step): number {\n return (\n left.position - right.position ||\n (left.name ?? '').localeCompare(right.name ?? '') ||\n left.id.localeCompare(right.id)\n );\n}\n\nfunction compareAttempts(left: Step['attempts'][number], right: Step['attempts'][number]) {\n return left.attempt - right.attempt || left.id.localeCompare(right.id);\n}\n\nfunction compareEntries(left: StepListEntryModel, right: StepListEntryModel): number {\n return (\n left.executionOrder - right.executionOrder ||\n left.step.index - right.step.index ||\n left.attempt - right.attempt ||\n left.id.localeCompare(right.id)\n );\n}\n\nfunction latestRunningEntryId(entries: readonly StepListEntryModel[]): string | undefined {\n for (let index = entries.length - 1; index >= 0; index -= 1) {\n const entry = entries[index];\n if (entry?.statusVisual.kind === 'running') return entry.id;\n }\n return undefined;\n}\n\nfunction normalizeStatus(status: string): string {\n return status.trim().toLowerCase().replace(/-/g, '_');\n}\n"],"names":["getWorkflowStatusVisual","defaultJobExecution","isWorkflowStatus","isKnownWorkflowStatus","JobExecution","buildStepListModel","job","jobExecution","selectedJobExecution","defaultStepListJobExecution","steps","sort","compareSteps","map","toStepModel","entries","flatMap","step","toStepEntries","carriedOver","compareEntries","jobId","id","jobName","name","key","jobExecutionId","stepCount","length","activeEntryId","latestRunningEntryId","emptyJobExecutionForJob","sequence","status","statusReason","triggerEvents","queuedAt","startedAt","finishedAt","timedOutAt","createdAt","updatedAt","getStepStatusVisual","normalized","normalizeStatus","visual","ripple","kind","label","humanizeStatus","dot","badge","words","trim","replace","toLowerCase","firstLetter","at","undefined","toUpperCase","slice","index","attempts","compareAttempts","attempt","displayDuration","statusVisual","stepLabel","carriedOverJob","stepId","currentAttempt","executionOrder","position","exitCode","output","error","gateResult","restartFeedback","left","right","localeCompare","entry"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/components/step-list/step-list-model.ts"],"sourcesContent":["import {\n getWorkflowStatusVisual,\n type WorkflowStatusVisual,\n} from '#components/workflow-status/status-visuals.js';\nimport {\n defaultJobExecution,\n isWorkflowStatus as isKnownWorkflowStatus,\n type Job,\n JobExecution,\n type Step,\n type StepAttempt,\n type StepAttemptDisplayDuration,\n} from '#core/workflow-run.js';\n\nexport interface StepStatusVisual extends Omit<WorkflowStatusVisual, 'kind'> {\n kind: string;\n ripple: boolean;\n}\n\nexport interface StepAttemptModel extends StepAttempt {\n displayDuration: StepAttemptDisplayDuration | null;\n statusVisual: StepStatusVisual;\n carriedOver: boolean;\n}\n\nexport interface StepModel extends Omit<Step, 'attempts'> {\n index: number;\n label: string;\n attempts: StepAttemptModel[];\n}\n\nexport interface StepListEntryModel extends StepAttemptModel {\n step: StepModel;\n}\n\nexport interface StepListModel {\n jobId: string;\n jobName: string;\n jobExecutionId: string;\n stepCount: number;\n activeEntryId: string | undefined;\n entries: StepListEntryModel[];\n}\n\nexport function buildStepListModel({\n job,\n jobExecution,\n}: {\n job: Job;\n jobExecution?: JobExecution | undefined;\n}): StepListModel {\n const selectedJobExecution = jobExecution ?? defaultStepListJobExecution(job);\n const steps = [...selectedJobExecution.steps].sort(compareSteps).map(toStepModel);\n const entries = steps\n .flatMap((step) => toStepEntries(step, job.carriedOver))\n .sort(compareEntries);\n\n return {\n jobId: job.id,\n jobName: job.name ?? job.key,\n jobExecutionId: selectedJobExecution.id,\n stepCount: steps.length,\n activeEntryId: landingEntryId(entries),\n entries,\n };\n}\n\nexport function defaultStepListJobExecution(job: Job): JobExecution {\n return defaultJobExecution(job) ?? emptyJobExecutionForJob(job);\n}\n\nexport function emptyJobExecutionForJob(job: Job): JobExecution {\n return new JobExecution({\n id: `missing:${job.id}`,\n jobId: job.id,\n sequence: 1,\n name: job.name ?? job.key,\n status: job.status === 'skipped' ? 'cancelled' : job.status,\n statusReason: job.statusReason,\n triggerEvents: [],\n queuedAt: null,\n startedAt: null,\n finishedAt: null,\n timedOutAt: null,\n createdAt: job.createdAt,\n updatedAt: job.updatedAt,\n steps: [],\n });\n}\n\nexport function getStepStatusVisual(status: string): StepStatusVisual {\n const normalized = normalizeStatus(status);\n\n if (isKnownWorkflowStatus(normalized)) {\n const visual = getWorkflowStatusVisual(normalized);\n return {...visual, ripple: normalized === 'running'};\n }\n\n return {\n kind: status,\n label: humanizeStatus(status),\n dot: 'neutral',\n badge: 'neutral',\n ripple: false,\n };\n}\n\nexport function humanizeStatus(status: string): string {\n const words = status.trim().replace(/[_-]+/g, ' ').replace(/\\s+/g, ' ').toLowerCase();\n if (!words) return 'Unknown';\n const firstLetter = words.at(0);\n return firstLetter === undefined ? 'Unknown' : firstLetter.toUpperCase() + words.slice(1);\n}\n\nfunction toStepModel(step: Step, index: number): StepModel {\n const attempts = [...step.attempts].sort(compareAttempts).map((attempt) => ({\n ...attempt,\n displayDuration: attempt.displayDuration,\n statusVisual: getStepStatusVisual(attempt.status),\n carriedOver: false,\n }));\n\n return {\n ...step,\n index: index + 1,\n label: stepLabel(step, index),\n attempts,\n };\n}\n\nfunction stepLabel(step: Step, index: number): string {\n const name = step.name.trim();\n if (name) return name;\n\n const key = step.key?.trim();\n if (key) return key;\n\n return `Step ${index + 1}`;\n}\n\nfunction toStepEntries(step: StepModel, carriedOverJob: boolean): StepListEntryModel[] {\n if (step.attempts.length > 0) {\n return step.attempts.map((attempt) => ({\n ...attempt,\n step,\n }));\n }\n\n if (!carriedOverJob) return [];\n\n return [\n {\n id: `carried-over:${step.id}`,\n stepId: step.id,\n jobExecutionId: step.jobExecutionId,\n attempt: step.currentAttempt,\n executionOrder: step.position,\n status: step.status,\n exitCode: null,\n output: null,\n error: null,\n gateResult: null,\n restartFeedback: null,\n startedAt: step.createdAt,\n finishedAt: null,\n displayDuration: null,\n statusVisual: getStepStatusVisual(step.status),\n carriedOver: true,\n step,\n },\n ];\n}\n\nfunction compareSteps(left: Step, right: Step): number {\n return (\n left.position - right.position ||\n (left.name ?? '').localeCompare(right.name ?? '') ||\n left.id.localeCompare(right.id)\n );\n}\n\nfunction compareAttempts(left: Step['attempts'][number], right: Step['attempts'][number]) {\n return left.attempt - right.attempt || left.id.localeCompare(right.id);\n}\n\nfunction compareEntries(left: StepListEntryModel, right: StepListEntryModel): number {\n return (\n left.executionOrder - right.executionOrder ||\n left.step.index - right.step.index ||\n left.attempt - right.attempt ||\n left.id.localeCompare(right.id)\n );\n}\n\nfunction landingEntryId(entries: readonly StepListEntryModel[]): string | undefined {\n for (let index = entries.length - 1; index >= 0; index -= 1) {\n const entry = entries[index];\n if (entry?.statusVisual.kind === 'running') return entry.id;\n }\n return undefined;\n}\n\nfunction normalizeStatus(status: string): string {\n return status.trim().toLowerCase().replace(/-/g, '_');\n}\n"],"names":["getWorkflowStatusVisual","defaultJobExecution","isWorkflowStatus","isKnownWorkflowStatus","JobExecution","buildStepListModel","job","jobExecution","selectedJobExecution","defaultStepListJobExecution","steps","sort","compareSteps","map","toStepModel","entries","flatMap","step","toStepEntries","carriedOver","compareEntries","jobId","id","jobName","name","key","jobExecutionId","stepCount","length","activeEntryId","landingEntryId","emptyJobExecutionForJob","sequence","status","statusReason","triggerEvents","queuedAt","startedAt","finishedAt","timedOutAt","createdAt","updatedAt","getStepStatusVisual","normalized","normalizeStatus","visual","ripple","kind","label","humanizeStatus","dot","badge","words","trim","replace","toLowerCase","firstLetter","at","undefined","toUpperCase","slice","index","attempts","compareAttempts","attempt","displayDuration","statusVisual","stepLabel","carriedOverJob","stepId","currentAttempt","executionOrder","position","exitCode","output","error","gateResult","restartFeedback","left","right","localeCompare","entry"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"step-list.d.ts","sourceRoot":"","sources":["../../../src/components/step-list/step-list.tsx"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,OAAO,CAAC;AAGrC,OAAO,EAEL,KAAK,GAAG,EACR,KAAK,gBAAgB,EACrB,KAAK,YAAY,EACjB,KAAK,IAAI,EACT,KAAK,kBAAkB,EACxB,MAAM,uBAAuB,CAAC;AAW/B,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,IAAI,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,kBAAkB,GAAG,IAAI,CAAC;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC7C,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,gBAAgB,GAAG,SAAS,CAAC;CACvC;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,GAAG,CAAC;IACT,YAAY,CAAC,EAAE,YAAY,GAAG,SAAS,CAAC;IACxC,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAC9C,wBAAwB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9C,uBAAuB,CAAC,EAAE,CAAC,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IAChF,uBAAuB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9C,UAAU,CAAC,EAAE,kBAAkB,GAAG,SAAS,CAAC;IAC5C,kBAAkB,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,mBAAmB,KAAK,SAAS,CAAC,GAAG,SAAS,CAAC;IAC/E,UAAU,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC;AAED,wBAAgB,QAAQ,CAAC,EACvB,GAAG,EACH,YAAY,EACZ,iBAAiB,EACjB,wBAAwB,EACxB,uBAAuB,EACvB,uBAA+B,EAC/B,UAAU,EACV,kBAAkB,EAClB,UAAiB,EACjB,SAAS,GACV,EAAE,aAAa,+BAqBf"}
|
|
1
|
+
{"version":3,"file":"step-list.d.ts","sourceRoot":"","sources":["../../../src/components/step-list/step-list.tsx"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,OAAO,CAAC;AAGrC,OAAO,EAEL,KAAK,GAAG,EACR,KAAK,gBAAgB,EACrB,KAAK,YAAY,EACjB,KAAK,IAAI,EACT,KAAK,kBAAkB,EACxB,MAAM,uBAAuB,CAAC;AAW/B,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,IAAI,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,kBAAkB,GAAG,IAAI,CAAC;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC7C,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,gBAAgB,GAAG,SAAS,CAAC;CACvC;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,GAAG,CAAC;IACT,YAAY,CAAC,EAAE,YAAY,GAAG,SAAS,CAAC;IACxC,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAC9C,wBAAwB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9C,uBAAuB,CAAC,EAAE,CAAC,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IAChF,uBAAuB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9C,UAAU,CAAC,EAAE,kBAAkB,GAAG,SAAS,CAAC;IAC5C,kBAAkB,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,mBAAmB,KAAK,SAAS,CAAC,GAAG,SAAS,CAAC;IAC/E,UAAU,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC;AAED,wBAAgB,QAAQ,CAAC,EACvB,GAAG,EACH,YAAY,EACZ,iBAAiB,EACjB,wBAAwB,EACxB,uBAAuB,EACvB,uBAA+B,EAC/B,UAAU,EACV,kBAAkB,EAClB,UAAiB,EACjB,SAAS,GACV,EAAE,aAAa,+BAqBf"}
|
|
@@ -43,22 +43,46 @@ function StepListContent({ model, selectedAttemptId, defaultSelectedAttemptId, o
|
|
|
43
43
|
] : []);
|
|
44
44
|
const [userSelectedAttempt, setUserSelectedAttempt] = useState(false);
|
|
45
45
|
const lastNotifiedSelectedAttemptId = useRef(null);
|
|
46
|
+
const autoSelectedAttemptIdRef = useRef(undefined);
|
|
47
|
+
const previousSelectedAttemptIdRef = useRef(selectedAttemptId);
|
|
46
48
|
const shouldUseControlledCollapsedState = selectedAttemptId === null && lastNotifiedSelectedAttemptId.current === null;
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
+
const autoSelectedAttemptId = selectedAttemptId === undefined && autoSelectActiveAttempt && !userSelectedAttempt ? autoSelectedAttemptIdRef.current ?? model.activeEntryId : undefined;
|
|
50
|
+
const autoSelectedAttemptIds = autoSelectedAttemptId ? [
|
|
51
|
+
autoSelectedAttemptId
|
|
49
52
|
] : [];
|
|
50
53
|
const selectedAttemptIds = shouldUseControlledCollapsedState ? [] : localSelectedAttemptIds.length > 0 ? localSelectedAttemptIds : autoSelectedAttemptIds;
|
|
51
54
|
const hasExpandedContent = renderExpandedStep !== undefined;
|
|
52
55
|
useEffect(()=>{
|
|
53
|
-
if (selectedAttemptId !== undefined)
|
|
56
|
+
if (selectedAttemptId !== undefined) {
|
|
57
|
+
previousSelectedAttemptIdRef.current = selectedAttemptId;
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
// A changing landing candidate is normal while a live job polls. Only reset the local
|
|
61
|
+
// accordion when the parent has just cleared a previously controlled URL selection.
|
|
62
|
+
const wasControlled = previousSelectedAttemptIdRef.current !== undefined;
|
|
63
|
+
previousSelectedAttemptIdRef.current = undefined;
|
|
64
|
+
if (!wasControlled) return;
|
|
54
65
|
setLocalSelectedAttemptIds(defaultSelectedAttemptId ? [
|
|
55
66
|
defaultSelectedAttemptId
|
|
56
67
|
] : []);
|
|
57
68
|
setUserSelectedAttempt(false);
|
|
69
|
+
autoSelectedAttemptIdRef.current = undefined;
|
|
58
70
|
}, [
|
|
59
71
|
defaultSelectedAttemptId,
|
|
60
72
|
selectedAttemptId
|
|
61
73
|
]);
|
|
74
|
+
useEffect(()=>{
|
|
75
|
+
if (selectedAttemptId !== undefined || !autoSelectActiveAttempt || userSelectedAttempt || localSelectedAttemptIds.length > 0 || autoSelectedAttemptIdRef.current !== undefined) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (model.activeEntryId) autoSelectedAttemptIdRef.current = model.activeEntryId;
|
|
79
|
+
}, [
|
|
80
|
+
autoSelectActiveAttempt,
|
|
81
|
+
localSelectedAttemptIds.length,
|
|
82
|
+
model.activeEntryId,
|
|
83
|
+
selectedAttemptId,
|
|
84
|
+
userSelectedAttempt
|
|
85
|
+
]);
|
|
62
86
|
useEffect(()=>{
|
|
63
87
|
if (selectedAttemptId === undefined) return;
|
|
64
88
|
const nextSelectedAttemptId = selectedAttemptId ?? null;
|
|
@@ -124,6 +148,7 @@ function StepListContent({ model, selectedAttemptId, defaultSelectedAttemptId, o
|
|
|
124
148
|
sourceLocation: entry.step.sourceLocation,
|
|
125
149
|
attempt: entry.attempt,
|
|
126
150
|
attemptId: entry.id,
|
|
151
|
+
attemptStartedAt: entry.startedAt,
|
|
127
152
|
attemptError: entry.error,
|
|
128
153
|
attemptStatus: entry.statusVisual.kind,
|
|
129
154
|
carriedOver: entry.carriedOver
|
|
@@ -362,7 +387,7 @@ function StepAttemptChip({ attempt }) {
|
|
|
362
387
|
className: "flex shrink-0 items-center gap-4",
|
|
363
388
|
"aria-hidden": "true",
|
|
364
389
|
children: /*#__PURE__*/ _jsxs("span", {
|
|
365
|
-
className: cn('inline-flex h-18 min-w-24 items-center justify-center rounded-4 border px-
|
|
390
|
+
className: cn('inline-flex h-18 min-w-24 items-center justify-center rounded-4 border px-6 font-code text-xs leading-16 text-foreground-neutral-base', attemptChipClasses[attempt.statusVisual.badge ?? 'neutral']),
|
|
366
391
|
children: [
|
|
367
392
|
"#",
|
|
368
393
|
attempt.attempt
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/step-list/step-list.tsx"],"sourcesContent":["import {\n Accordion,\n AccordionContent,\n AccordionItem,\n AccordionTrigger,\n} from '@shipfox/react-ui/accordion';\nimport {Badge, type BadgeVariant} from '@shipfox/react-ui/badge';\nimport {Dot} from '@shipfox/react-ui/dot';\nimport {EmptyState} from '@shipfox/react-ui/empty-state';\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {TimeTickerProvider, useTimeTick} from '@shipfox/react-ui/time-ticker';\nimport {Tooltip, TooltipContent, TooltipTrigger} from '@shipfox/react-ui/tooltip';\nimport {Code, Text} from '@shipfox/react-ui/typography';\nimport {cn, humanDuration} from '@shipfox/react-ui/utils';\nimport type {ReactNode} from 'react';\nimport {useEffect, useId, useMemo, useRef, useState} from 'react';\nimport {WorkflowStatusIcon} from '#components/workflow-status/workflow-status-icon.js';\nimport {\n isWorkflowStatus,\n type Job,\n type JobDisplayStatus,\n type JobExecution,\n type Step,\n type StepSourceLocation,\n} from '#core/workflow-run.js';\nimport {formatJobExecutionTimeLabel} from '../job-graph/job-duration-format.js';\nimport {\n buildStepListModel,\n defaultStepListJobExecution,\n humanizeStatus,\n type StepAttemptModel,\n type StepListEntryModel,\n type StepListModel,\n} from './step-list-model.js';\n\nexport interface StepExpandedContext {\n step: Step;\n stepId: string;\n stepLabel: string;\n sourceLocation: StepSourceLocation | null;\n attempt: number;\n attemptId: string;\n attemptError: Record<string, unknown> | null;\n attemptStatus: string;\n carriedOver: boolean;\n}\n\nexport interface StepListEmptyState {\n title: string;\n description: string;\n status?: JobDisplayStatus | undefined;\n}\n\nexport interface StepListProps {\n job: Job;\n jobExecution?: JobExecution | undefined;\n selectedAttemptId?: string | null | undefined;\n defaultSelectedAttemptId?: string | undefined;\n onSelectedAttemptChange?: ((attemptId: string | undefined) => void) | undefined;\n autoSelectActiveAttempt?: boolean | undefined;\n emptyState?: StepListEmptyState | undefined;\n renderExpandedStep?: ((context: StepExpandedContext) => ReactNode) | undefined;\n showHeader?: boolean | undefined;\n className?: string | undefined;\n}\n\nexport function StepList({\n job,\n jobExecution,\n selectedAttemptId,\n defaultSelectedAttemptId,\n onSelectedAttemptChange,\n autoSelectActiveAttempt = false,\n emptyState,\n renderExpandedStep,\n showHeader = true,\n className,\n}: StepListProps) {\n const selectedJobExecution = jobExecution ?? defaultStepListJobExecution(job);\n const model = useMemo(\n () => buildStepListModel({job, jobExecution: selectedJobExecution}),\n [job, selectedJobExecution],\n );\n\n return (\n <StepListContent\n key={model.jobExecutionId}\n model={model}\n selectedAttemptId={selectedAttemptId}\n defaultSelectedAttemptId={defaultSelectedAttemptId}\n onSelectedAttemptChange={onSelectedAttemptChange}\n autoSelectActiveAttempt={autoSelectActiveAttempt}\n emptyState={emptyState}\n renderExpandedStep={renderExpandedStep}\n showHeader={showHeader}\n className={className}\n />\n );\n}\n\nfunction StepListContent({\n model,\n selectedAttemptId,\n defaultSelectedAttemptId,\n onSelectedAttemptChange,\n autoSelectActiveAttempt,\n emptyState,\n renderExpandedStep,\n showHeader,\n className,\n}: Omit<StepListProps, 'job' | 'jobExecution'> & {model: StepListModel}) {\n const titleId = useId();\n const [localSelectedAttemptIds, setLocalSelectedAttemptIds] = useState<string[]>(() =>\n selectedAttemptId\n ? [selectedAttemptId]\n : defaultSelectedAttemptId\n ? [defaultSelectedAttemptId]\n : [],\n );\n const [userSelectedAttempt, setUserSelectedAttempt] = useState(false);\n const lastNotifiedSelectedAttemptId = useRef<string | null>(null);\n const shouldUseControlledCollapsedState =\n selectedAttemptId === null && lastNotifiedSelectedAttemptId.current === null;\n const autoSelectedAttemptIds =\n selectedAttemptId === undefined &&\n autoSelectActiveAttempt &&\n !userSelectedAttempt &&\n model.activeEntryId\n ? [model.activeEntryId]\n : [];\n const selectedAttemptIds = shouldUseControlledCollapsedState\n ? []\n : localSelectedAttemptIds.length > 0\n ? localSelectedAttemptIds\n : autoSelectedAttemptIds;\n const hasExpandedContent = renderExpandedStep !== undefined;\n\n useEffect(() => {\n if (selectedAttemptId !== undefined) return;\n\n setLocalSelectedAttemptIds(defaultSelectedAttemptId ? [defaultSelectedAttemptId] : []);\n setUserSelectedAttempt(false);\n }, [defaultSelectedAttemptId, selectedAttemptId]);\n\n useEffect(() => {\n if (selectedAttemptId === undefined) return;\n\n const nextSelectedAttemptId = selectedAttemptId ?? null;\n if (lastNotifiedSelectedAttemptId.current === nextSelectedAttemptId) {\n lastNotifiedSelectedAttemptId.current = null;\n return;\n }\n\n setLocalSelectedAttemptIds(selectedAttemptId ? [selectedAttemptId] : []);\n setUserSelectedAttempt(true);\n }, [selectedAttemptId]);\n\n function selectAttempt(nextAttemptIds: string[]) {\n const nextAttemptId = nextSelectedAttemptId(selectedAttemptIds, nextAttemptIds);\n setUserSelectedAttempt(true);\n setLocalSelectedAttemptIds(nextAttemptIds);\n lastNotifiedSelectedAttemptId.current = nextAttemptId ?? null;\n onSelectedAttemptChange?.(nextAttemptId);\n }\n\n return (\n <TimeTickerProvider intervalMs={1000} reducedMotionIntervalMs={10_000}>\n <section\n aria-labelledby={showHeader ? titleId : undefined}\n className={cn(\n 'flex min-h-0 flex-col rounded-8 border border-border-neutral-base bg-background-components-base',\n className,\n )}\n >\n {showHeader ? (\n <div className=\"flex min-h-40 items-center border-b border-border-neutral-base px-16 py-8\">\n <Text as=\"h2\" id={titleId} size=\"sm\" bold className=\"text-foreground-neutral-base\">\n {model.jobName}\n </Text>\n </div>\n ) : null}\n\n {model.entries.length === 0 ? (\n <StepListEmptyStateView emptyState={emptyState} />\n ) : (\n <Accordion\n type=\"multiple\"\n value={selectedAttemptIds}\n onValueChange={selectAttempt}\n asChild\n >\n <ol>\n {model.entries.map((entry) => {\n const selected = selectedAttemptIds.includes(entry.id);\n return (\n <StepRow\n key={entry.id}\n entry={entry}\n selected={selected}\n hasExpandedContent={hasExpandedContent}\n onSelect={() => {\n selectAttempt(\n hasExpandedContent\n ? toggleAttemptId(selectedAttemptIds, entry.id)\n : selected\n ? []\n : [entry.id],\n );\n }}\n expandedContent={\n selected\n ? renderExpandedStep?.({\n step: entry.step,\n stepId: entry.step.id,\n stepLabel: entry.step.label,\n sourceLocation: entry.step.sourceLocation,\n attempt: entry.attempt,\n attemptId: entry.id,\n attemptError: entry.error,\n attemptStatus: entry.statusVisual.kind,\n carriedOver: entry.carriedOver,\n })\n : null\n }\n />\n );\n })}\n </ol>\n </Accordion>\n )}\n </section>\n </TimeTickerProvider>\n );\n}\n\nfunction StepListEmptyStateView({\n emptyState = {\n title: 'No steps recorded',\n description: 'This job has not recorded any steps.',\n },\n}: {\n emptyState?: StepListEmptyState | undefined;\n}) {\n if (!emptyState.status) {\n return (\n <EmptyState\n className=\"min-h-120 px-16 py-20\"\n icon=\"componentLine\"\n title={emptyState.title}\n description={emptyState.description}\n variant=\"compact\"\n />\n );\n }\n\n return (\n <div className=\"flex min-h-120 flex-col items-center justify-center gap-10 px-16 py-20\">\n <StepListEmptyStateIcon status={emptyState.status} />\n <div className=\"text-center\">\n <Text size=\"sm\" className=\"text-foreground-neutral-subtle\">\n {emptyState.title}\n </Text>\n <Text size=\"xs\" className=\"text-foreground-neutral-muted\">\n {emptyState.description}\n </Text>\n </div>\n </div>\n );\n}\n\nfunction StepListEmptyStateIcon({status}: {status: JobDisplayStatus}) {\n if (status !== 'running') {\n return (\n <div className=\"flex size-32 items-center justify-center rounded-6 border border-border-neutral-strong bg-background-neutral-base p-8\">\n <WorkflowStatusIcon status={status} size={20} tooltip={false} />\n </div>\n );\n }\n\n return (\n <div className=\"flex size-32 items-center justify-center rounded-6 border border-border-neutral-strong bg-background-neutral-base p-8 text-foreground-neutral-muted\">\n <Icon name=\"timerLine\" size={18} aria-hidden=\"true\" />\n </div>\n );\n}\n\nfunction StepRow({\n entry,\n selected,\n hasExpandedContent,\n onSelect,\n expandedContent,\n}: {\n entry: StepListEntryModel;\n selected: boolean;\n hasExpandedContent: boolean;\n onSelect: () => void;\n expandedContent: ReactNode;\n}) {\n const shouldShowLabelTooltip = entry.step.label.length > 32;\n const rowContent = (\n <>\n <Icon\n name=\"chevronRight\"\n aria-hidden=\"true\"\n className={cn(\n 'size-14 shrink-0 text-foreground-neutral-muted transition-transform',\n selected && 'rotate-90',\n )}\n />\n <StepStatusIcon entry={entry} />\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex min-w-0 items-center gap-8\">\n <Text size=\"sm\" bold className=\"truncate text-foreground-neutral-base\">\n {entry.step.label}\n </Text>\n {entry.step.attempts.length > 1 ? <StepAttemptChip attempt={entry} /> : null}\n {entry.carriedOver ? <CarriedOverBadge /> : null}\n </div>\n </div>\n <StepAttemptDurationLabel attempt={entry} />\n </>\n );\n const rowClasses = cn(\n 'group grid min-h-44 w-full grid-cols-[14px_14px_minmax(0,1fr)_auto] items-center gap-x-8 px-12 py-6 text-left transition-colors hover:bg-background-components-hover focus-visible:shadow-border-interactive-with-active focus-visible:outline-none',\n selected && 'bg-background-components-hover',\n entry.carriedOver && 'opacity-[0.55]',\n );\n const button = hasExpandedContent ? (\n <AccordionTrigger\n showIcon={false}\n aria-label={entryAccessibleLabel(entry)}\n className={rowClasses}\n >\n {rowContent}\n </AccordionTrigger>\n ) : (\n <button\n type=\"button\"\n aria-expanded={false}\n aria-label={entryAccessibleLabel(entry)}\n onClick={onSelect}\n className={rowClasses}\n >\n {rowContent}\n </button>\n );\n const triggerNode = shouldShowLabelTooltip ? (\n <Tooltip>\n <TooltipTrigger asChild>{button}</TooltipTrigger>\n <TooltipContent>\n <span className=\"block max-w-320 break-words\">{entry.step.label}</span>\n </TooltipContent>\n </Tooltip>\n ) : (\n button\n );\n const row = (\n <>\n {triggerNode}\n {selected && expandedContent ? (\n <AccordionContent className=\"border-t border-border-neutral-base bg-background-neutral-base px-12 py-12\">\n <div className=\"grid grid-cols-[14px_14px_minmax(0,1fr)_auto] gap-x-8\">\n <span aria-hidden=\"true\" />\n <span aria-hidden=\"true\" />\n <div className=\"min-w-0\">{expandedContent}</div>\n <span aria-hidden=\"true\" />\n </div>\n </AccordionContent>\n ) : null}\n </>\n );\n\n if (hasExpandedContent) {\n return (\n <AccordionItem value={entry.id} asChild>\n <li>{row}</li>\n </AccordionItem>\n );\n }\n\n return <li className=\"border-b border-border-neutral-base last:border-b-0\">{row}</li>;\n}\n\nfunction CarriedOverBadge() {\n return (\n <Tooltip>\n <TooltipTrigger asChild>\n <span className=\"shrink-0\">\n <Badge variant=\"neutral\" size=\"2xs\">\n reused\n </Badge>\n </span>\n </TooltipTrigger>\n <TooltipContent>\n Carried over from a previous attempt; did not run in this attempt.\n </TooltipContent>\n </Tooltip>\n );\n}\n\nfunction toggleAttemptId(selectedAttemptIds: readonly string[], attemptId: string): string[] {\n if (selectedAttemptIds.includes(attemptId)) {\n return selectedAttemptIds.filter((selectedAttemptId) => selectedAttemptId !== attemptId);\n }\n return [...selectedAttemptIds, attemptId];\n}\n\nfunction nextSelectedAttemptId(\n selectedAttemptIds: readonly string[],\n nextAttemptIds: readonly string[],\n): string | undefined {\n if (nextAttemptIds.length === 0) return undefined;\n\n const openedAttemptId = nextAttemptIds.find(\n (attemptId) => !selectedAttemptIds.includes(attemptId),\n );\n return openedAttemptId ?? nextAttemptIds.at(-1);\n}\n\nfunction StepStatusIcon({entry}: {entry: StepListEntryModel}) {\n if (isWorkflowStatus(entry.statusVisual.kind)) {\n return (\n <WorkflowStatusIcon\n status={entry.statusVisual.kind}\n ripple={entry.statusVisual.ripple}\n size={14}\n />\n );\n }\n\n return (\n <span role=\"img\" aria-label={entry.statusVisual.label} className=\"inline-flex shrink-0\">\n <Dot\n variant={entry.statusVisual.dot}\n ripple={entry.statusVisual.ripple}\n className=\"size-12 shrink-0\"\n />\n </span>\n );\n}\n\nconst attemptChipClasses: Record<NonNullable<BadgeVariant>, string> = {\n neutral: 'bg-tag-neutral-bg border-tag-neutral-border',\n info: 'bg-tag-blue-bg border-tag-blue-border',\n feature: 'bg-tag-purple-bg border-tag-purple-border',\n success: 'bg-tag-success-bg border-tag-success-border',\n warning: 'bg-tag-warning-bg border-tag-warning-border',\n error: 'bg-tag-error-bg border-tag-error-border',\n};\n\nfunction StepAttemptChip({attempt}: {attempt: StepAttemptModel}) {\n return (\n <div className=\"flex shrink-0 items-center gap-4\" aria-hidden=\"true\">\n <span\n className={cn(\n 'inline-flex h-18 min-w-24 items-center justify-center rounded-4 border px-5 font-code text-xs leading-16 text-foreground-neutral-base',\n attemptChipClasses[attempt.statusVisual.badge ?? 'neutral'],\n )}\n >\n #{attempt.attempt}\n </span>\n </div>\n );\n}\n\nfunction entryAccessibleLabel(entry: StepListEntryModel): string {\n const parts = [entry.step.label, entry.statusVisual.label, `attempt ${entry.attempt}`];\n if (entry.step.error?.category) parts.push(humanizeStatus(entry.step.error.category));\n return parts.join(', ');\n}\n\nfunction StepAttemptDurationLabel({attempt}: {attempt: StepAttemptModel}) {\n const duration = attempt.displayDuration;\n if (duration === null) return null;\n\n if (duration.state === 'live') {\n return <LiveDurationText fromIso={duration.fromIso} />;\n }\n\n return <DurationText>{formatJobExecutionTimeLabel(duration)}</DurationText>;\n}\n\nfunction LiveDurationText({fromIso}: {fromIso: string}) {\n useTimeTick();\n return <DurationText>{humanDuration(fromIso)}</DurationText>;\n}\n\nfunction DurationText({children}: {children: string}) {\n return (\n <Code as=\"span\" variant=\"label\" className=\"shrink-0 tabular-nums text-foreground-neutral-muted\">\n {children}\n </Code>\n );\n}\n"],"names":["Accordion","AccordionContent","AccordionItem","AccordionTrigger","Badge","Dot","EmptyState","Icon","TimeTickerProvider","useTimeTick","Tooltip","TooltipContent","TooltipTrigger","Code","Text","cn","humanDuration","useEffect","useId","useMemo","useRef","useState","WorkflowStatusIcon","isWorkflowStatus","formatJobExecutionTimeLabel","buildStepListModel","defaultStepListJobExecution","humanizeStatus","StepList","job","jobExecution","selectedAttemptId","defaultSelectedAttemptId","onSelectedAttemptChange","autoSelectActiveAttempt","emptyState","renderExpandedStep","showHeader","className","selectedJobExecution","model","StepListContent","jobExecutionId","titleId","localSelectedAttemptIds","setLocalSelectedAttemptIds","userSelectedAttempt","setUserSelectedAttempt","lastNotifiedSelectedAttemptId","shouldUseControlledCollapsedState","current","autoSelectedAttemptIds","undefined","activeEntryId","selectedAttemptIds","length","hasExpandedContent","nextSelectedAttemptId","selectAttempt","nextAttemptIds","nextAttemptId","intervalMs","reducedMotionIntervalMs","section","aria-labelledby","div","as","id","size","bold","jobName","entries","StepListEmptyStateView","type","value","onValueChange","asChild","ol","map","entry","selected","includes","StepRow","onSelect","toggleAttemptId","expandedContent","step","stepId","stepLabel","label","sourceLocation","attempt","attemptId","attemptError","error","attemptStatus","statusVisual","kind","carriedOver","title","description","status","icon","variant","StepListEmptyStateIcon","tooltip","name","aria-hidden","shouldShowLabelTooltip","rowContent","StepStatusIcon","attempts","StepAttemptChip","CarriedOverBadge","StepAttemptDurationLabel","rowClasses","button","showIcon","aria-label","entryAccessibleLabel","aria-expanded","onClick","triggerNode","span","row","li","filter","openedAttemptId","find","at","ripple","role","dot","attemptChipClasses","neutral","info","feature","success","warning","badge","parts","category","push","join","duration","displayDuration","state","LiveDurationText","fromIso","DurationText","children"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/components/step-list/step-list.tsx"],"sourcesContent":["import {\n Accordion,\n AccordionContent,\n AccordionItem,\n AccordionTrigger,\n} from '@shipfox/react-ui/accordion';\nimport {Badge, type BadgeVariant} from '@shipfox/react-ui/badge';\nimport {Dot} from '@shipfox/react-ui/dot';\nimport {EmptyState} from '@shipfox/react-ui/empty-state';\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {TimeTickerProvider, useTimeTick} from '@shipfox/react-ui/time-ticker';\nimport {Tooltip, TooltipContent, TooltipTrigger} from '@shipfox/react-ui/tooltip';\nimport {Code, Text} from '@shipfox/react-ui/typography';\nimport {cn, humanDuration} from '@shipfox/react-ui/utils';\nimport type {ReactNode} from 'react';\nimport {useEffect, useId, useMemo, useRef, useState} from 'react';\nimport {WorkflowStatusIcon} from '#components/workflow-status/workflow-status-icon.js';\nimport {\n isWorkflowStatus,\n type Job,\n type JobDisplayStatus,\n type JobExecution,\n type Step,\n type StepSourceLocation,\n} from '#core/workflow-run.js';\nimport {formatJobExecutionTimeLabel} from '../job-graph/job-duration-format.js';\nimport {\n buildStepListModel,\n defaultStepListJobExecution,\n humanizeStatus,\n type StepAttemptModel,\n type StepListEntryModel,\n type StepListModel,\n} from './step-list-model.js';\n\nexport interface StepExpandedContext {\n step: Step;\n stepId: string;\n stepLabel: string;\n sourceLocation: StepSourceLocation | null;\n attempt: number;\n attemptId: string;\n attemptStartedAt: string;\n attemptError: Record<string, unknown> | null;\n attemptStatus: string;\n carriedOver: boolean;\n}\n\nexport interface StepListEmptyState {\n title: string;\n description: string;\n status?: JobDisplayStatus | undefined;\n}\n\nexport interface StepListProps {\n job: Job;\n jobExecution?: JobExecution | undefined;\n selectedAttemptId?: string | null | undefined;\n defaultSelectedAttemptId?: string | undefined;\n onSelectedAttemptChange?: ((attemptId: string | undefined) => void) | undefined;\n autoSelectActiveAttempt?: boolean | undefined;\n emptyState?: StepListEmptyState | undefined;\n renderExpandedStep?: ((context: StepExpandedContext) => ReactNode) | undefined;\n showHeader?: boolean | undefined;\n className?: string | undefined;\n}\n\nexport function StepList({\n job,\n jobExecution,\n selectedAttemptId,\n defaultSelectedAttemptId,\n onSelectedAttemptChange,\n autoSelectActiveAttempt = false,\n emptyState,\n renderExpandedStep,\n showHeader = true,\n className,\n}: StepListProps) {\n const selectedJobExecution = jobExecution ?? defaultStepListJobExecution(job);\n const model = useMemo(\n () => buildStepListModel({job, jobExecution: selectedJobExecution}),\n [job, selectedJobExecution],\n );\n\n return (\n <StepListContent\n key={model.jobExecutionId}\n model={model}\n selectedAttemptId={selectedAttemptId}\n defaultSelectedAttemptId={defaultSelectedAttemptId}\n onSelectedAttemptChange={onSelectedAttemptChange}\n autoSelectActiveAttempt={autoSelectActiveAttempt}\n emptyState={emptyState}\n renderExpandedStep={renderExpandedStep}\n showHeader={showHeader}\n className={className}\n />\n );\n}\n\nfunction StepListContent({\n model,\n selectedAttemptId,\n defaultSelectedAttemptId,\n onSelectedAttemptChange,\n autoSelectActiveAttempt,\n emptyState,\n renderExpandedStep,\n showHeader,\n className,\n}: Omit<StepListProps, 'job' | 'jobExecution'> & {model: StepListModel}) {\n const titleId = useId();\n const [localSelectedAttemptIds, setLocalSelectedAttemptIds] = useState<string[]>(() =>\n selectedAttemptId\n ? [selectedAttemptId]\n : defaultSelectedAttemptId\n ? [defaultSelectedAttemptId]\n : [],\n );\n const [userSelectedAttempt, setUserSelectedAttempt] = useState(false);\n const lastNotifiedSelectedAttemptId = useRef<string | null>(null);\n const autoSelectedAttemptIdRef = useRef<string | undefined>(undefined);\n const previousSelectedAttemptIdRef = useRef<string | null | undefined>(selectedAttemptId);\n const shouldUseControlledCollapsedState =\n selectedAttemptId === null && lastNotifiedSelectedAttemptId.current === null;\n const autoSelectedAttemptId =\n selectedAttemptId === undefined && autoSelectActiveAttempt && !userSelectedAttempt\n ? (autoSelectedAttemptIdRef.current ?? model.activeEntryId)\n : undefined;\n const autoSelectedAttemptIds = autoSelectedAttemptId ? [autoSelectedAttemptId] : [];\n const selectedAttemptIds = shouldUseControlledCollapsedState\n ? []\n : localSelectedAttemptIds.length > 0\n ? localSelectedAttemptIds\n : autoSelectedAttemptIds;\n const hasExpandedContent = renderExpandedStep !== undefined;\n\n useEffect(() => {\n if (selectedAttemptId !== undefined) {\n previousSelectedAttemptIdRef.current = selectedAttemptId;\n return;\n }\n\n // A changing landing candidate is normal while a live job polls. Only reset the local\n // accordion when the parent has just cleared a previously controlled URL selection.\n const wasControlled = previousSelectedAttemptIdRef.current !== undefined;\n previousSelectedAttemptIdRef.current = undefined;\n if (!wasControlled) return;\n\n setLocalSelectedAttemptIds(defaultSelectedAttemptId ? [defaultSelectedAttemptId] : []);\n setUserSelectedAttempt(false);\n autoSelectedAttemptIdRef.current = undefined;\n }, [defaultSelectedAttemptId, selectedAttemptId]);\n\n useEffect(() => {\n if (\n selectedAttemptId !== undefined ||\n !autoSelectActiveAttempt ||\n userSelectedAttempt ||\n localSelectedAttemptIds.length > 0 ||\n autoSelectedAttemptIdRef.current !== undefined\n ) {\n return;\n }\n if (model.activeEntryId) autoSelectedAttemptIdRef.current = model.activeEntryId;\n }, [\n autoSelectActiveAttempt,\n localSelectedAttemptIds.length,\n model.activeEntryId,\n selectedAttemptId,\n userSelectedAttempt,\n ]);\n\n useEffect(() => {\n if (selectedAttemptId === undefined) return;\n\n const nextSelectedAttemptId = selectedAttemptId ?? null;\n if (lastNotifiedSelectedAttemptId.current === nextSelectedAttemptId) {\n lastNotifiedSelectedAttemptId.current = null;\n return;\n }\n\n setLocalSelectedAttemptIds(selectedAttemptId ? [selectedAttemptId] : []);\n setUserSelectedAttempt(true);\n }, [selectedAttemptId]);\n\n function selectAttempt(nextAttemptIds: string[]) {\n const nextAttemptId = nextSelectedAttemptId(selectedAttemptIds, nextAttemptIds);\n setUserSelectedAttempt(true);\n setLocalSelectedAttemptIds(nextAttemptIds);\n lastNotifiedSelectedAttemptId.current = nextAttemptId ?? null;\n onSelectedAttemptChange?.(nextAttemptId);\n }\n\n return (\n <TimeTickerProvider intervalMs={1000} reducedMotionIntervalMs={10_000}>\n <section\n aria-labelledby={showHeader ? titleId : undefined}\n className={cn(\n 'flex min-h-0 flex-col rounded-8 border border-border-neutral-base bg-background-components-base',\n className,\n )}\n >\n {showHeader ? (\n <div className=\"flex min-h-40 items-center border-b border-border-neutral-base px-16 py-8\">\n <Text as=\"h2\" id={titleId} size=\"sm\" bold className=\"text-foreground-neutral-base\">\n {model.jobName}\n </Text>\n </div>\n ) : null}\n\n {model.entries.length === 0 ? (\n <StepListEmptyStateView emptyState={emptyState} />\n ) : (\n <Accordion\n type=\"multiple\"\n value={selectedAttemptIds}\n onValueChange={selectAttempt}\n asChild\n >\n <ol>\n {model.entries.map((entry) => {\n const selected = selectedAttemptIds.includes(entry.id);\n return (\n <StepRow\n key={entry.id}\n entry={entry}\n selected={selected}\n hasExpandedContent={hasExpandedContent}\n onSelect={() => {\n selectAttempt(\n hasExpandedContent\n ? toggleAttemptId(selectedAttemptIds, entry.id)\n : selected\n ? []\n : [entry.id],\n );\n }}\n expandedContent={\n selected\n ? renderExpandedStep?.({\n step: entry.step,\n stepId: entry.step.id,\n stepLabel: entry.step.label,\n sourceLocation: entry.step.sourceLocation,\n attempt: entry.attempt,\n attemptId: entry.id,\n attemptStartedAt: entry.startedAt,\n attemptError: entry.error,\n attemptStatus: entry.statusVisual.kind,\n carriedOver: entry.carriedOver,\n })\n : null\n }\n />\n );\n })}\n </ol>\n </Accordion>\n )}\n </section>\n </TimeTickerProvider>\n );\n}\n\nfunction StepListEmptyStateView({\n emptyState = {\n title: 'No steps recorded',\n description: 'This job has not recorded any steps.',\n },\n}: {\n emptyState?: StepListEmptyState | undefined;\n}) {\n if (!emptyState.status) {\n return (\n <EmptyState\n className=\"min-h-120 px-16 py-20\"\n icon=\"componentLine\"\n title={emptyState.title}\n description={emptyState.description}\n variant=\"compact\"\n />\n );\n }\n\n return (\n <div className=\"flex min-h-120 flex-col items-center justify-center gap-10 px-16 py-20\">\n <StepListEmptyStateIcon status={emptyState.status} />\n <div className=\"text-center\">\n <Text size=\"sm\" className=\"text-foreground-neutral-subtle\">\n {emptyState.title}\n </Text>\n <Text size=\"xs\" className=\"text-foreground-neutral-muted\">\n {emptyState.description}\n </Text>\n </div>\n </div>\n );\n}\n\nfunction StepListEmptyStateIcon({status}: {status: JobDisplayStatus}) {\n if (status !== 'running') {\n return (\n <div className=\"flex size-32 items-center justify-center rounded-6 border border-border-neutral-strong bg-background-neutral-base p-8\">\n <WorkflowStatusIcon status={status} size={20} tooltip={false} />\n </div>\n );\n }\n\n return (\n <div className=\"flex size-32 items-center justify-center rounded-6 border border-border-neutral-strong bg-background-neutral-base p-8 text-foreground-neutral-muted\">\n <Icon name=\"timerLine\" size={18} aria-hidden=\"true\" />\n </div>\n );\n}\n\nfunction StepRow({\n entry,\n selected,\n hasExpandedContent,\n onSelect,\n expandedContent,\n}: {\n entry: StepListEntryModel;\n selected: boolean;\n hasExpandedContent: boolean;\n onSelect: () => void;\n expandedContent: ReactNode;\n}) {\n const shouldShowLabelTooltip = entry.step.label.length > 32;\n const rowContent = (\n <>\n <Icon\n name=\"chevronRight\"\n aria-hidden=\"true\"\n className={cn(\n 'size-14 shrink-0 text-foreground-neutral-muted transition-transform',\n selected && 'rotate-90',\n )}\n />\n <StepStatusIcon entry={entry} />\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex min-w-0 items-center gap-8\">\n <Text size=\"sm\" bold className=\"truncate text-foreground-neutral-base\">\n {entry.step.label}\n </Text>\n {entry.step.attempts.length > 1 ? <StepAttemptChip attempt={entry} /> : null}\n {entry.carriedOver ? <CarriedOverBadge /> : null}\n </div>\n </div>\n <StepAttemptDurationLabel attempt={entry} />\n </>\n );\n const rowClasses = cn(\n 'group grid min-h-44 w-full grid-cols-[14px_14px_minmax(0,1fr)_auto] items-center gap-x-8 px-12 py-6 text-left transition-colors hover:bg-background-components-hover focus-visible:shadow-border-interactive-with-active focus-visible:outline-none',\n selected && 'bg-background-components-hover',\n entry.carriedOver && 'opacity-[0.55]',\n );\n const button = hasExpandedContent ? (\n <AccordionTrigger\n showIcon={false}\n aria-label={entryAccessibleLabel(entry)}\n className={rowClasses}\n >\n {rowContent}\n </AccordionTrigger>\n ) : (\n <button\n type=\"button\"\n aria-expanded={false}\n aria-label={entryAccessibleLabel(entry)}\n onClick={onSelect}\n className={rowClasses}\n >\n {rowContent}\n </button>\n );\n const triggerNode = shouldShowLabelTooltip ? (\n <Tooltip>\n <TooltipTrigger asChild>{button}</TooltipTrigger>\n <TooltipContent>\n <span className=\"block max-w-320 break-words\">{entry.step.label}</span>\n </TooltipContent>\n </Tooltip>\n ) : (\n button\n );\n const row = (\n <>\n {triggerNode}\n {selected && expandedContent ? (\n <AccordionContent className=\"border-t border-border-neutral-base bg-background-neutral-base px-12 py-12\">\n <div className=\"grid grid-cols-[14px_14px_minmax(0,1fr)_auto] gap-x-8\">\n <span aria-hidden=\"true\" />\n <span aria-hidden=\"true\" />\n <div className=\"min-w-0\">{expandedContent}</div>\n <span aria-hidden=\"true\" />\n </div>\n </AccordionContent>\n ) : null}\n </>\n );\n\n if (hasExpandedContent) {\n return (\n <AccordionItem value={entry.id} asChild>\n <li>{row}</li>\n </AccordionItem>\n );\n }\n\n return <li className=\"border-b border-border-neutral-base last:border-b-0\">{row}</li>;\n}\n\nfunction CarriedOverBadge() {\n return (\n <Tooltip>\n <TooltipTrigger asChild>\n <span className=\"shrink-0\">\n <Badge variant=\"neutral\" size=\"2xs\">\n reused\n </Badge>\n </span>\n </TooltipTrigger>\n <TooltipContent>\n Carried over from a previous attempt; did not run in this attempt.\n </TooltipContent>\n </Tooltip>\n );\n}\n\nfunction toggleAttemptId(selectedAttemptIds: readonly string[], attemptId: string): string[] {\n if (selectedAttemptIds.includes(attemptId)) {\n return selectedAttemptIds.filter((selectedAttemptId) => selectedAttemptId !== attemptId);\n }\n return [...selectedAttemptIds, attemptId];\n}\n\nfunction nextSelectedAttemptId(\n selectedAttemptIds: readonly string[],\n nextAttemptIds: readonly string[],\n): string | undefined {\n if (nextAttemptIds.length === 0) return undefined;\n\n const openedAttemptId = nextAttemptIds.find(\n (attemptId) => !selectedAttemptIds.includes(attemptId),\n );\n return openedAttemptId ?? nextAttemptIds.at(-1);\n}\n\nfunction StepStatusIcon({entry}: {entry: StepListEntryModel}) {\n if (isWorkflowStatus(entry.statusVisual.kind)) {\n return (\n <WorkflowStatusIcon\n status={entry.statusVisual.kind}\n ripple={entry.statusVisual.ripple}\n size={14}\n />\n );\n }\n\n return (\n <span role=\"img\" aria-label={entry.statusVisual.label} className=\"inline-flex shrink-0\">\n <Dot\n variant={entry.statusVisual.dot}\n ripple={entry.statusVisual.ripple}\n className=\"size-12 shrink-0\"\n />\n </span>\n );\n}\n\nconst attemptChipClasses: Record<NonNullable<BadgeVariant>, string> = {\n neutral: 'bg-tag-neutral-bg border-tag-neutral-border',\n info: 'bg-tag-blue-bg border-tag-blue-border',\n feature: 'bg-tag-purple-bg border-tag-purple-border',\n success: 'bg-tag-success-bg border-tag-success-border',\n warning: 'bg-tag-warning-bg border-tag-warning-border',\n error: 'bg-tag-error-bg border-tag-error-border',\n};\n\nfunction StepAttemptChip({attempt}: {attempt: StepAttemptModel}) {\n return (\n <div className=\"flex shrink-0 items-center gap-4\" aria-hidden=\"true\">\n <span\n className={cn(\n 'inline-flex h-18 min-w-24 items-center justify-center rounded-4 border px-6 font-code text-xs leading-16 text-foreground-neutral-base',\n attemptChipClasses[attempt.statusVisual.badge ?? 'neutral'],\n )}\n >\n #{attempt.attempt}\n </span>\n </div>\n );\n}\n\nfunction entryAccessibleLabel(entry: StepListEntryModel): string {\n const parts = [entry.step.label, entry.statusVisual.label, `attempt ${entry.attempt}`];\n if (entry.step.error?.category) parts.push(humanizeStatus(entry.step.error.category));\n return parts.join(', ');\n}\n\nfunction StepAttemptDurationLabel({attempt}: {attempt: StepAttemptModel}) {\n const duration = attempt.displayDuration;\n if (duration === null) return null;\n\n if (duration.state === 'live') {\n return <LiveDurationText fromIso={duration.fromIso} />;\n }\n\n return <DurationText>{formatJobExecutionTimeLabel(duration)}</DurationText>;\n}\n\nfunction LiveDurationText({fromIso}: {fromIso: string}) {\n useTimeTick();\n return <DurationText>{humanDuration(fromIso)}</DurationText>;\n}\n\nfunction DurationText({children}: {children: string}) {\n return (\n <Code as=\"span\" variant=\"label\" className=\"shrink-0 tabular-nums text-foreground-neutral-muted\">\n {children}\n </Code>\n );\n}\n"],"names":["Accordion","AccordionContent","AccordionItem","AccordionTrigger","Badge","Dot","EmptyState","Icon","TimeTickerProvider","useTimeTick","Tooltip","TooltipContent","TooltipTrigger","Code","Text","cn","humanDuration","useEffect","useId","useMemo","useRef","useState","WorkflowStatusIcon","isWorkflowStatus","formatJobExecutionTimeLabel","buildStepListModel","defaultStepListJobExecution","humanizeStatus","StepList","job","jobExecution","selectedAttemptId","defaultSelectedAttemptId","onSelectedAttemptChange","autoSelectActiveAttempt","emptyState","renderExpandedStep","showHeader","className","selectedJobExecution","model","StepListContent","jobExecutionId","titleId","localSelectedAttemptIds","setLocalSelectedAttemptIds","userSelectedAttempt","setUserSelectedAttempt","lastNotifiedSelectedAttemptId","autoSelectedAttemptIdRef","undefined","previousSelectedAttemptIdRef","shouldUseControlledCollapsedState","current","autoSelectedAttemptId","activeEntryId","autoSelectedAttemptIds","selectedAttemptIds","length","hasExpandedContent","wasControlled","nextSelectedAttemptId","selectAttempt","nextAttemptIds","nextAttemptId","intervalMs","reducedMotionIntervalMs","section","aria-labelledby","div","as","id","size","bold","jobName","entries","StepListEmptyStateView","type","value","onValueChange","asChild","ol","map","entry","selected","includes","StepRow","onSelect","toggleAttemptId","expandedContent","step","stepId","stepLabel","label","sourceLocation","attempt","attemptId","attemptStartedAt","startedAt","attemptError","error","attemptStatus","statusVisual","kind","carriedOver","title","description","status","icon","variant","StepListEmptyStateIcon","tooltip","name","aria-hidden","shouldShowLabelTooltip","rowContent","StepStatusIcon","attempts","StepAttemptChip","CarriedOverBadge","StepAttemptDurationLabel","rowClasses","button","showIcon","aria-label","entryAccessibleLabel","aria-expanded","onClick","triggerNode","span","row","li","filter","openedAttemptId","find","at","ripple","role","dot","attemptChipClasses","neutral","info","feature","success","warning","badge","parts","category","push","join","duration","displayDuration","state","LiveDurationText","fromIso","DurationText","children"],"mappings":"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"}
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
import type { WorkflowRunAttemptDisplayDuration } from '#core/workflow-run.js';
|
|
1
|
+
import type { WorkflowRunAttemptDisplayDuration, WorkflowRunDisplayDuration } from '#core/workflow-run.js';
|
|
2
|
+
/**
|
|
3
|
+
* The run list reads an attempt's own marks and cannot tell queue from run, so an unkinded
|
|
4
|
+
* duration still renders as run time. Only surfaces holding the run's jobs pass a kind.
|
|
5
|
+
*/
|
|
6
|
+
export type WorkflowRunDurationDisplay = WorkflowRunAttemptDisplayDuration | WorkflowRunDisplayDuration;
|
|
2
7
|
export declare function WorkflowRunDurationLabel({ duration, className, }: {
|
|
3
|
-
duration:
|
|
8
|
+
duration: WorkflowRunDurationDisplay | null;
|
|
4
9
|
className?: string | undefined;
|
|
5
10
|
}): import("react").JSX.Element | null;
|
|
6
|
-
export declare function useWorkflowRunDurationAccessibleLabel(duration:
|
|
7
|
-
export declare function workflowRunDurationAccessibleLabel(duration:
|
|
11
|
+
export declare function useWorkflowRunDurationAccessibleLabel(duration: WorkflowRunDurationDisplay | null): string | undefined;
|
|
12
|
+
export declare function workflowRunDurationAccessibleLabel(duration: WorkflowRunDurationDisplay | null): string | undefined;
|
|
8
13
|
//# sourceMappingURL=workflow-run-duration-label.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workflow-run-duration-label.d.ts","sourceRoot":"","sources":["../../src/components/workflow-run-duration-label.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"workflow-run-duration-label.d.ts","sourceRoot":"","sources":["../../src/components/workflow-run-duration-label.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,iCAAiC,EACjC,0BAA0B,EAC3B,MAAM,uBAAuB,CAAC;AAE/B;;;GAGG;AACH,MAAM,MAAM,0BAA0B,GAClC,iCAAiC,GACjC,0BAA0B,CAAC;AAQ/B,wBAAgB,wBAAwB,CAAC,EACvC,QAAQ,EACR,SAAS,GACV,EAAE;IACD,QAAQ,EAAE,0BAA0B,GAAG,IAAI,CAAC;IAC5C,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC,sCAwBA;AAED,wBAAgB,qCAAqC,CACnD,QAAQ,EAAE,0BAA0B,GAAG,IAAI,GAC1C,MAAM,GAAG,SAAS,CAGpB;AAED,wBAAgB,kCAAkC,CAChD,QAAQ,EAAE,0BAA0B,GAAG,IAAI,GAC1C,MAAM,GAAG,SAAS,CAcpB"}
|
|
@@ -3,21 +3,36 @@ import { Icon } from '@shipfox/react-ui/icon';
|
|
|
3
3
|
import { useTimeTick } from '@shipfox/react-ui/time-ticker';
|
|
4
4
|
import { Code } from '@shipfox/react-ui/typography';
|
|
5
5
|
import { cn, humanDuration } from '@shipfox/react-ui/utils';
|
|
6
|
+
const GLYPH_BY_KIND = {
|
|
7
|
+
queue: 'hourglassLine',
|
|
8
|
+
run: 'timerLine'
|
|
9
|
+
};
|
|
10
|
+
const LIVE_VERB_BY_KIND = {
|
|
11
|
+
queue: 'queued',
|
|
12
|
+
run: 'running'
|
|
13
|
+
};
|
|
14
|
+
const FIXED_VERB_BY_KIND = {
|
|
15
|
+
queue: 'queued',
|
|
16
|
+
run: 'ran'
|
|
17
|
+
};
|
|
6
18
|
export function WorkflowRunDurationLabel({ duration, className }) {
|
|
7
19
|
if (duration === null) return null;
|
|
20
|
+
const kind = durationKind(duration);
|
|
8
21
|
switch(duration.state){
|
|
9
22
|
case 'fixed':
|
|
10
23
|
{
|
|
11
24
|
const display = formatFixedDurationLabel(duration.elapsed);
|
|
12
25
|
return /*#__PURE__*/ _jsx(DurationText, {
|
|
13
26
|
className: className,
|
|
14
|
-
|
|
27
|
+
kind: kind,
|
|
28
|
+
ariaLabel: `${FIXED_VERB_BY_KIND[kind]} ${display}`,
|
|
15
29
|
children: display
|
|
16
30
|
});
|
|
17
31
|
}
|
|
18
32
|
case 'live':
|
|
19
33
|
return /*#__PURE__*/ _jsx(LiveDurationText, {
|
|
20
34
|
duration: duration,
|
|
35
|
+
kind: kind,
|
|
21
36
|
className: className
|
|
22
37
|
});
|
|
23
38
|
default:
|
|
@@ -33,11 +48,12 @@ export function useWorkflowRunDurationAccessibleLabel(duration) {
|
|
|
33
48
|
}
|
|
34
49
|
export function workflowRunDurationAccessibleLabel(duration) {
|
|
35
50
|
if (duration === null) return undefined;
|
|
51
|
+
const kind = durationKind(duration);
|
|
36
52
|
switch(duration.state){
|
|
37
53
|
case 'live':
|
|
38
|
-
return
|
|
54
|
+
return `${LIVE_VERB_BY_KIND[kind]} ${humanDuration(duration.fromIso)}`;
|
|
39
55
|
case 'fixed':
|
|
40
|
-
return
|
|
56
|
+
return `${FIXED_VERB_BY_KIND[kind]} ${formatFixedDurationLabel(duration.elapsed)}`;
|
|
41
57
|
default:
|
|
42
58
|
{
|
|
43
59
|
const exhaustive = duration;
|
|
@@ -45,16 +61,20 @@ export function workflowRunDurationAccessibleLabel(duration) {
|
|
|
45
61
|
}
|
|
46
62
|
}
|
|
47
63
|
}
|
|
48
|
-
function
|
|
64
|
+
function durationKind(duration) {
|
|
65
|
+
return 'kind' in duration ? duration.kind : 'run';
|
|
66
|
+
}
|
|
67
|
+
function LiveDurationText({ duration, kind, className }) {
|
|
49
68
|
useTimeTick();
|
|
50
69
|
const display = humanDuration(duration.fromIso);
|
|
51
70
|
return /*#__PURE__*/ _jsx(DurationText, {
|
|
52
71
|
className: className,
|
|
53
|
-
|
|
72
|
+
kind: kind,
|
|
73
|
+
ariaLabel: `${LIVE_VERB_BY_KIND[kind]} ${display}`,
|
|
54
74
|
children: display
|
|
55
75
|
});
|
|
56
76
|
}
|
|
57
|
-
function DurationText({ children, className, ariaLabel }) {
|
|
77
|
+
function DurationText({ children, className, kind, ariaLabel }) {
|
|
58
78
|
return /*#__PURE__*/ _jsxs(Code, {
|
|
59
79
|
as: "span",
|
|
60
80
|
variant: "label",
|
|
@@ -62,7 +82,7 @@ function DurationText({ children, className, ariaLabel }) {
|
|
|
62
82
|
className: cn('inline-flex shrink-0 items-center gap-4 tabular-nums text-foreground-neutral-subtle', className),
|
|
63
83
|
children: [
|
|
64
84
|
/*#__PURE__*/ _jsx(Icon, {
|
|
65
|
-
name:
|
|
85
|
+
name: GLYPH_BY_KIND[kind],
|
|
66
86
|
className: "size-12 shrink-0",
|
|
67
87
|
"aria-hidden": "true"
|
|
68
88
|
}),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/workflow-run-duration-label.tsx"],"sourcesContent":["import {Icon} from '@shipfox/react-ui/icon';\nimport {useTimeTick} from '@shipfox/react-ui/time-ticker';\nimport {Code} from '@shipfox/react-ui/typography';\nimport {cn, humanDuration} from '@shipfox/react-ui/utils';\nimport type {WorkflowRunAttemptDisplayDuration} from '#core/workflow-run.js';\n\nexport function WorkflowRunDurationLabel({\n duration,\n className,\n}: {\n duration:
|
|
1
|
+
{"version":3,"sources":["../../src/components/workflow-run-duration-label.tsx"],"sourcesContent":["import {Icon} from '@shipfox/react-ui/icon';\nimport {useTimeTick} from '@shipfox/react-ui/time-ticker';\nimport {Code} from '@shipfox/react-ui/typography';\nimport {cn, humanDuration} from '@shipfox/react-ui/utils';\nimport type {\n WorkflowRunAttemptDisplayDuration,\n WorkflowRunDisplayDuration,\n} from '#core/workflow-run.js';\n\n/**\n * The run list reads an attempt's own marks and cannot tell queue from run, so an unkinded\n * duration still renders as run time. Only surfaces holding the run's jobs pass a kind.\n */\nexport type WorkflowRunDurationDisplay =\n | WorkflowRunAttemptDisplayDuration\n | WorkflowRunDisplayDuration;\n\ntype DurationKind = 'queue' | 'run';\n\nconst GLYPH_BY_KIND = {queue: 'hourglassLine', run: 'timerLine'} as const;\nconst LIVE_VERB_BY_KIND = {queue: 'queued', run: 'running'} as const;\nconst FIXED_VERB_BY_KIND = {queue: 'queued', run: 'ran'} as const;\n\nexport function WorkflowRunDurationLabel({\n duration,\n className,\n}: {\n duration: WorkflowRunDurationDisplay | null;\n className?: string | undefined;\n}) {\n if (duration === null) return null;\n const kind = durationKind(duration);\n\n switch (duration.state) {\n case 'fixed': {\n const display = formatFixedDurationLabel(duration.elapsed);\n return (\n <DurationText\n className={className}\n kind={kind}\n ariaLabel={`${FIXED_VERB_BY_KIND[kind]} ${display}`}\n >\n {display}\n </DurationText>\n );\n }\n case 'live':\n return <LiveDurationText duration={duration} kind={kind} className={className} />;\n default: {\n const exhaustive: never = duration;\n return exhaustive;\n }\n }\n}\n\nexport function useWorkflowRunDurationAccessibleLabel(\n duration: WorkflowRunDurationDisplay | null,\n): string | undefined {\n useTimeTick();\n return workflowRunDurationAccessibleLabel(duration);\n}\n\nexport function workflowRunDurationAccessibleLabel(\n duration: WorkflowRunDurationDisplay | null,\n): string | undefined {\n if (duration === null) return undefined;\n const kind = durationKind(duration);\n\n switch (duration.state) {\n case 'live':\n return `${LIVE_VERB_BY_KIND[kind]} ${humanDuration(duration.fromIso)}`;\n case 'fixed':\n return `${FIXED_VERB_BY_KIND[kind]} ${formatFixedDurationLabel(duration.elapsed)}`;\n default: {\n const exhaustive: never = duration;\n return exhaustive;\n }\n }\n}\n\nfunction durationKind(duration: WorkflowRunDurationDisplay): DurationKind {\n return 'kind' in duration ? duration.kind : 'run';\n}\n\nfunction LiveDurationText({\n duration,\n kind,\n className,\n}: {\n duration: Extract<WorkflowRunDurationDisplay, {state: 'live'}>;\n kind: DurationKind;\n className?: string | undefined;\n}) {\n useTimeTick();\n const display = humanDuration(duration.fromIso);\n return (\n <DurationText\n className={className}\n kind={kind}\n ariaLabel={`${LIVE_VERB_BY_KIND[kind]} ${display}`}\n >\n {display}\n </DurationText>\n );\n}\n\nfunction DurationText({\n children,\n className,\n kind,\n ariaLabel,\n}: {\n children: string;\n className?: string | undefined;\n kind: DurationKind;\n ariaLabel?: string | undefined;\n}) {\n return (\n <Code\n as=\"span\"\n variant=\"label\"\n aria-label={ariaLabel}\n className={cn(\n 'inline-flex shrink-0 items-center gap-4 tabular-nums text-foreground-neutral-subtle',\n className,\n )}\n >\n {/* The glyph is the only visible mark of queue vs run: the status badge already writes\n \"Queued\", and the numeric columns are fixed width. The verb rides the accessible\n name instead. */}\n <Icon name={GLYPH_BY_KIND[kind]} className=\"size-12 shrink-0\" aria-hidden=\"true\" />\n {children}\n </Code>\n );\n}\n\nfunction formatFixedDurationLabel({\n years = 0,\n months = 0,\n weeks = 0,\n days = 0,\n hours = 0,\n minutes = 0,\n seconds = 0,\n}: Extract<WorkflowRunDurationDisplay, {state: 'fixed'}>['elapsed']): string {\n const totalDays = years * 365 + months * 30 + weeks * 7 + days;\n const totalHours = totalDays * 24 + hours;\n\n if (totalHours > 0) return `${totalHours}h ${pad2(minutes)}m`;\n if (minutes > 0) return `${minutes}m ${pad2(seconds)}s`;\n return `${seconds}s`;\n}\n\nfunction pad2(value: number): string {\n return value.toString().padStart(2, '0');\n}\n"],"names":["Icon","useTimeTick","Code","cn","humanDuration","GLYPH_BY_KIND","queue","run","LIVE_VERB_BY_KIND","FIXED_VERB_BY_KIND","WorkflowRunDurationLabel","duration","className","kind","durationKind","state","display","formatFixedDurationLabel","elapsed","DurationText","ariaLabel","LiveDurationText","exhaustive","useWorkflowRunDurationAccessibleLabel","workflowRunDurationAccessibleLabel","undefined","fromIso","children","as","variant","aria-label","name","aria-hidden","years","months","weeks","days","hours","minutes","seconds","totalDays","totalHours","pad2","value","toString","padStart"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workflow-run-row.d.ts","sourceRoot":"","sources":["../../../src/components/workflow-run-list/workflow-run-row.tsx"],"names":[],"mappings":"AAiBA,OAAO,EACL,KAAK,mBAAmB,
|
|
1
|
+
{"version":3,"file":"workflow-run-row.d.ts","sourceRoot":"","sources":["../../../src/components/workflow-run-list/workflow-run-row.tsx"],"names":[],"mappings":"AAiBA,OAAO,EACL,KAAK,mBAAmB,EAKzB,MAAM,uBAAuB,CAAC;AAI/B,wBAAgB,kBAAkB,CAAC,EACjC,IAAI,EACJ,aAAa,EACb,WAAW,GACZ,EAAE;IACD,IAAI,EAAE,mBAAmB,EAAE,CAAC;IAC5B,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAClC,+BAaA;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,EAC7B,GAAG,EACH,aAAa,EACb,WAAW,GACZ,EAAE;IACD,GAAG,EAAE,mBAAmB,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAClC,+BA0FA"}
|
|
@@ -10,7 +10,7 @@ import { useWorkflowRunDurationAccessibleLabel, WorkflowRunDurationLabel } from
|
|
|
10
10
|
import { formatWorkflowRunNumberLabel, WorkflowRunNumberLabel } from '#components/workflow-run-number-label.js';
|
|
11
11
|
import { getWorkflowStatusVisual } from '#components/workflow-status/status-visuals.js';
|
|
12
12
|
import { WorkflowStatusIcon } from '#components/workflow-status/workflow-status-icon.js';
|
|
13
|
-
import { workflowRunActor, workflowRunBranchLabel, workflowRunCommitLabel } from '#core/workflow-run.js';
|
|
13
|
+
import { workflowRunActor, workflowRunBranchLabel, workflowRunCommitLabel, workflowRunListItemDisplay } from '#core/workflow-run.js';
|
|
14
14
|
import { withoutWorkflowRunSelectionSearch } from '#core/workflow-run-url-state.js';
|
|
15
15
|
import { JobStatusStrip, jobStatusSummary } from './job-status-strip.js';
|
|
16
16
|
export function WorkflowRunRowList({ runs, workspaceSlug, projectSlug }) {
|
|
@@ -40,8 +40,9 @@ export function WorkflowRunRowList({ runs, workspaceSlug, projectSlug }) {
|
|
|
40
40
|
* column capped at 1120px with 24px of page padding on each side: a 1280px viewport leaves
|
|
41
41
|
* the row 1072px, and a 1024px viewport leaves it 976px.
|
|
42
42
|
*/ export function WorkflowRunRow({ run, workspaceSlug, projectSlug }) {
|
|
43
|
-
const
|
|
44
|
-
const
|
|
43
|
+
const display = workflowRunListItemDisplay(run);
|
|
44
|
+
const durationLabel = useWorkflowRunDurationAccessibleLabel(display.duration);
|
|
45
|
+
const statusLabel = getWorkflowStatusVisual(display.status).label;
|
|
45
46
|
const runNumberLabel = formatWorkflowRunNumberLabel(run);
|
|
46
47
|
const branch = workflowRunBranchLabel(run);
|
|
47
48
|
const commit = workflowRunCommitLabel(run);
|
|
@@ -49,7 +50,7 @@ export function WorkflowRunRowList({ runs, workspaceSlug, projectSlug }) {
|
|
|
49
50
|
const body = /*#__PURE__*/ _jsxs(_Fragment, {
|
|
50
51
|
children: [
|
|
51
52
|
/*#__PURE__*/ _jsx(WorkflowStatusIcon, {
|
|
52
|
-
status:
|
|
53
|
+
status: display.status,
|
|
53
54
|
size: 14,
|
|
54
55
|
className: "shrink-0"
|
|
55
56
|
}),
|
|
@@ -102,7 +103,7 @@ export function WorkflowRunRowList({ runs, workspaceSlug, projectSlug }) {
|
|
|
102
103
|
/*#__PURE__*/ _jsx("span", {
|
|
103
104
|
className: "flex w-64 justify-end",
|
|
104
105
|
children: /*#__PURE__*/ _jsx(WorkflowRunDurationLabel, {
|
|
105
|
-
duration:
|
|
106
|
+
duration: display.duration
|
|
106
107
|
})
|
|
107
108
|
}),
|
|
108
109
|
/*#__PURE__*/ _jsx(Code, {
|