@shipfox/client-workflows 14.0.1 → 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 +21 -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 +3 -3
- 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 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/workflow-run-view/step-attempt-log-panel.tsx"],"sourcesContent":["import {\n isMissingStepLogStreamError,\n LogView,\n LogViewSkeleton,\n useStepAttemptLogsQuery,\n} from '@shipfox/client-logs';\nimport {Button} from '@shipfox/react-ui/button';\nimport {Callout} from '@shipfox/react-ui/callout';\nimport {Text} from '@shipfox/react-ui/typography';\nimport {type UIEvent, useEffect, useRef} from 'react';\n\nconst TAIL_FOLLOW_THRESHOLD_PX = 24;\nconst INITIAL_LOG_ERROR_RETRY_COUNT = 5;\nconst INITIAL_LOG_ERROR_RETRY_DELAY_MS = 1_500;\nconst logSurfaceClasses = 'max-h-[40vh] rounded-8 md:max-h-[280px]';\n\nexport interface StepAttemptLogPanelProps {\n stepId: string;\n attempt: number;\n attemptStatus: string;\n initialErrorRetryCount?: number | undefined;\n initialErrorRetryDelayMs?: number | undefined;\n}\n\nexport function StepAttemptLogPanel({\n stepId,\n attempt,\n attemptStatus,\n initialErrorRetryCount = INITIAL_LOG_ERROR_RETRY_COUNT,\n initialErrorRetryDelayMs = INITIAL_LOG_ERROR_RETRY_DELAY_MS,\n}: StepAttemptLogPanelProps) {\n const panelRef = useRef<HTMLDivElement>(null);\n const shouldFollowTailRef = useRef(true);\n const missingStreamRetryCount = attemptStatus === 'running' ? undefined : initialErrorRetryCount;\n const retryMissingStream = attemptStatus === 'running' || isTerminalAttemptStatus(attemptStatus);\n const query = useStepAttemptLogsQuery(stepId, attempt, {\n retryMissingStream,\n missingStreamRetryCount,\n missingStreamRetryDelayMs: initialErrorRetryDelayMs,\n initialErrorRetryCount,\n initialErrorRetryDelayMs,\n });\n const records = query.data?.records ?? [];\n const recordCount = records.length;\n const anchorToFailure = attemptStatus === 'failed';\n const missingActiveStream =\n retryMissingStream && query.data === undefined && isMissingStepLogStreamError(query.error);\n const initialError = query.isError && query.data === undefined && !missingActiveStream;\n const staleError = query.isError && query.data !== undefined;\n\n useEffect(() => {\n if (recordCount === 0) return undefined;\n if (anchorToFailure) return undefined;\n if (!shouldFollowTailRef.current) return undefined;\n\n const frame = scheduleAnimationFrame(() => {\n const scrollElement = panelRef.current?.querySelector<HTMLElement>('[data-slot=\"log-rows\"]');\n if (!scrollElement) return;\n scrollElement.scrollTop = scrollElement.scrollHeight;\n });\n\n return () => {\n cancelScheduledFrame(frame);\n };\n }, [anchorToFailure, recordCount]);\n\n function handleLogScroll(event: UIEvent<HTMLDivElement>) {\n const element = event.currentTarget;\n const distanceFromBottom = element.scrollHeight - element.scrollTop - element.clientHeight;\n shouldFollowTailRef.current = distanceFromBottom <= TAIL_FOLLOW_THRESHOLD_PX;\n }\n\n if (query.isPending) {\n return <StepLogsLoadingSurface label=\"Loading logs\" />;\n }\n\n if (missingActiveStream) {\n return <StepLogsLoadingSurface label=\"Waiting for logs\" />;\n }\n\n if (initialError) {\n return <StepLogsError retrying={query.isFetching} onRetry={() => void query.refetch()} />;\n }\n\n return (\n <div ref={panelRef} className=\"flex min-w-0 flex-col gap-8\">\n {staleError ? (\n <Callout role=\"alert\" type=\"warning\" className=\"px-10 py-8\">\n <div className=\"flex min-w-0 flex-1 items-center justify-between gap-8\">\n <Text size=\"xs\">Could not refresh logs.</Text>\n <Button\n type=\"button\"\n size=\"2xs\"\n variant=\"secondary\"\n isLoading={query.isFetching}\n onClick={() => void query.refetch()}\n >\n Retry\n </Button>\n </div>\n </Callout>\n ) : null}\n <LogView\n records={records}\n emptyState={query.data?.complete ? 'complete' : 'pending'}\n anchorToFailure={anchorToFailure}\n className={logSurfaceClasses}\n onScroll={handleLogScroll}\n />\n </div>\n );\n}\n\nfunction StepLogsLoadingSurface({label}: {label: string}) {\n return (\n <div role=\"status\" aria-label={label}>\n <LogViewSkeleton className={logSurfaceClasses} />\n </div>\n );\n}\n\nfunction scheduleAnimationFrame(callback: FrameRequestCallback): number {\n if (typeof globalThis.requestAnimationFrame === 'function') {\n return globalThis.requestAnimationFrame(callback);\n }\n return window.setTimeout(() => callback(Date.now()), 0);\n}\n\nfunction cancelScheduledFrame(frame: number) {\n if (typeof globalThis.cancelAnimationFrame === 'function') {\n globalThis.cancelAnimationFrame(frame);\n return;\n }\n window.clearTimeout(frame);\n}\n\nfunction isTerminalAttemptStatus(status: string): boolean {\n return status === 'succeeded' || status === 'failed' || status === 'cancelled';\n}\n\nfunction StepLogsError({retrying, onRetry}: {retrying: boolean; onRetry: () => void}) {\n return (\n <Callout role=\"alert\" type=\"error\" className=\"px-10 py-8\">\n <div className=\"flex min-w-0 flex-1 items-center justify-between gap-8\">\n <Text size=\"xs\">Could not load logs.</Text>\n <Button type=\"button\" size=\"2xs\" variant=\"secondary\" isLoading={retrying} onClick={onRetry}>\n Retry\n </Button>\n </div>\n </Callout>\n );\n}\n"],"names":["isMissingStepLogStreamError","LogView","LogViewSkeleton","useStepAttemptLogsQuery","Button","Callout","Text","useEffect","useRef","TAIL_FOLLOW_THRESHOLD_PX","INITIAL_LOG_ERROR_RETRY_COUNT","INITIAL_LOG_ERROR_RETRY_DELAY_MS","logSurfaceClasses","StepAttemptLogPanel","stepId","attempt","attemptStatus","initialErrorRetryCount","initialErrorRetryDelayMs","panelRef","shouldFollowTailRef","missingStreamRetryCount","undefined","retryMissingStream","isTerminalAttemptStatus","query","missingStreamRetryDelayMs","records","data","recordCount","length","anchorToFailure","missingActiveStream","error","initialError","isError","staleError","current","frame","scheduleAnimationFrame","scrollElement","querySelector","scrollTop","scrollHeight","cancelScheduledFrame","handleLogScroll","event","element","currentTarget","distanceFromBottom","clientHeight","isPending","StepLogsLoadingSurface","label","StepLogsError","retrying","isFetching","onRetry","refetch","div","ref","className","role","type","size","variant","isLoading","onClick","emptyState","complete","onScroll","aria-label","callback","globalThis","requestAnimationFrame","window","setTimeout","Date","now","cancelAnimationFrame","clearTimeout","status"],"mappings":"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"}
|
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
import {EmptyState} from '@shipfox/react-ui/empty-state';
|
|
2
|
-
import {useTimeTick} from '@shipfox/react-ui/time-ticker';
|
|
3
|
-
import {cn} from '@shipfox/react-ui/utils';
|
|
4
|
-
import {
|
|
5
|
-
defaultJobExecution,
|
|
6
|
-
deriveJobDisplayStatus,
|
|
7
|
-
deriveJobExecutionDisplayStatus,
|
|
8
|
-
type Job,
|
|
9
|
-
} from '#core/workflow-run.js';
|
|
10
|
-
import {formatJobDurationAccessibleLabel} from '../job-graph/job-duration-format.js';
|
|
11
|
-
import {JobDurationLabel} from '../job-graph/job-duration-label.js';
|
|
12
|
-
import {JobCard} from '../workflow-run-view/job-card.js';
|
|
13
|
-
import {WorkflowStatusIcon} from '../workflow-status/workflow-status-icon.js';
|
|
14
|
-
|
|
15
|
-
export interface RunJobsListProps {
|
|
16
|
-
jobs: Job[];
|
|
17
|
-
selectedJobId?: string | undefined;
|
|
18
|
-
onSelectedJobChange: (jobId: string | undefined) => void;
|
|
19
|
-
workspaceSlug?: string | undefined;
|
|
20
|
-
selectedJobExecution: ReturnType<typeof defaultJobExecution>;
|
|
21
|
-
selectedAttemptId: string | null | undefined;
|
|
22
|
-
onSelectedJobExecutionChange: ((jobExecutionId: string | undefined) => void) | undefined;
|
|
23
|
-
onSelectedAttemptChange: ((attemptId: string | undefined) => void) | undefined;
|
|
24
|
-
sourcePanelId?: string | undefined;
|
|
25
|
-
sourceAvailable?: boolean | undefined;
|
|
26
|
-
focusedSourceStepId?: string | null | undefined;
|
|
27
|
-
onOpenStepSource?: Parameters<typeof JobCard>[0]['onOpenStepSource'];
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function RunJobsList({
|
|
31
|
-
jobs,
|
|
32
|
-
selectedJobId,
|
|
33
|
-
onSelectedJobChange,
|
|
34
|
-
workspaceSlug,
|
|
35
|
-
selectedJobExecution,
|
|
36
|
-
selectedAttemptId,
|
|
37
|
-
onSelectedJobExecutionChange,
|
|
38
|
-
onSelectedAttemptChange,
|
|
39
|
-
sourcePanelId,
|
|
40
|
-
sourceAvailable,
|
|
41
|
-
focusedSourceStepId,
|
|
42
|
-
onOpenStepSource,
|
|
43
|
-
}: RunJobsListProps) {
|
|
44
|
-
useTimeTick();
|
|
45
|
-
|
|
46
|
-
if (jobs.length === 0) {
|
|
47
|
-
return (
|
|
48
|
-
<section
|
|
49
|
-
aria-label="Workflow jobs"
|
|
50
|
-
className="rounded-8 border border-border-neutral-base bg-background-components-base"
|
|
51
|
-
>
|
|
52
|
-
<div className="min-h-160">
|
|
53
|
-
<RunJobsEmptyState />
|
|
54
|
-
</div>
|
|
55
|
-
</section>
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const orderedJobs = [...jobs].sort(compareJobs);
|
|
60
|
-
|
|
61
|
-
return (
|
|
62
|
-
<section aria-label="Workflow jobs" className="flex min-w-0 flex-col gap-8">
|
|
63
|
-
<ul aria-label="Run jobs" className="m-0 flex min-w-0 list-none flex-col gap-8 p-0">
|
|
64
|
-
{orderedJobs.map((job) => {
|
|
65
|
-
const selected = job.id === selectedJobId;
|
|
66
|
-
const status = deriveJobDisplayStatus(job);
|
|
67
|
-
const execution = defaultJobExecution(job);
|
|
68
|
-
const durationLabel = formatJobDurationAccessibleLabel(
|
|
69
|
-
job.displayDuration,
|
|
70
|
-
execution ? deriveJobExecutionDisplayStatus(execution) : undefined,
|
|
71
|
-
);
|
|
72
|
-
const rowLabel = [
|
|
73
|
-
job.displayName,
|
|
74
|
-
statusLabel(status),
|
|
75
|
-
durationLabel,
|
|
76
|
-
job.carriedOver ? 'reused' : undefined,
|
|
77
|
-
]
|
|
78
|
-
.filter((part): part is string => Boolean(part))
|
|
79
|
-
.join(', ');
|
|
80
|
-
|
|
81
|
-
return (
|
|
82
|
-
<li key={job.id} className="min-w-0">
|
|
83
|
-
<button
|
|
84
|
-
type="button"
|
|
85
|
-
aria-expanded={selected}
|
|
86
|
-
aria-controls={selected ? `job-card-${job.id}` : undefined}
|
|
87
|
-
aria-label={rowLabel}
|
|
88
|
-
onClick={() => onSelectedJobChange(selected ? undefined : job.id)}
|
|
89
|
-
className={cn(
|
|
90
|
-
'relative flex min-h-44 w-full min-w-0 items-center gap-8 rounded-8 bg-background-neutral-base px-12 text-left shadow-button-neutral outline-none transition-colors hover:bg-background-components-hover focus-visible:shadow-button-neutral-focus',
|
|
91
|
-
selected &&
|
|
92
|
-
'bg-background-components-hover hover:bg-background-components-pressed',
|
|
93
|
-
)}
|
|
94
|
-
>
|
|
95
|
-
{selected ? (
|
|
96
|
-
<span
|
|
97
|
-
aria-hidden="true"
|
|
98
|
-
className="absolute inset-y-6 left-0 w-px rounded-full bg-border-highlights-interactive"
|
|
99
|
-
/>
|
|
100
|
-
) : null}
|
|
101
|
-
<WorkflowStatusIcon status={status} size={14} tooltip={false} />
|
|
102
|
-
<span className="min-w-0 flex-1 truncate text-sm font-medium text-foreground-neutral-base">
|
|
103
|
-
{job.displayName}
|
|
104
|
-
</span>
|
|
105
|
-
<JobDurationLabel duration={job.displayDuration} />
|
|
106
|
-
</button>
|
|
107
|
-
{selected ? (
|
|
108
|
-
<div id={`job-card-${job.id}`} className="mt-8">
|
|
109
|
-
<JobCard
|
|
110
|
-
workspaceSlug={workspaceSlug}
|
|
111
|
-
job={job}
|
|
112
|
-
selectedJobExecution={selectedJobExecution}
|
|
113
|
-
selectedAttemptId={job.carriedOver ? undefined : selectedAttemptId}
|
|
114
|
-
onSelectedJobExecutionChange={onSelectedJobExecutionChange}
|
|
115
|
-
onSelectedAttemptChange={onSelectedAttemptChange}
|
|
116
|
-
sourcePanelId={sourcePanelId}
|
|
117
|
-
sourceAvailable={sourceAvailable}
|
|
118
|
-
focusedSourceStepId={focusedSourceStepId}
|
|
119
|
-
onOpenStepSource={onOpenStepSource}
|
|
120
|
-
/>
|
|
121
|
-
</div>
|
|
122
|
-
) : null}
|
|
123
|
-
</li>
|
|
124
|
-
);
|
|
125
|
-
})}
|
|
126
|
-
</ul>
|
|
127
|
-
</section>
|
|
128
|
-
);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function RunJobsEmptyState() {
|
|
132
|
-
return (
|
|
133
|
-
<EmptyState
|
|
134
|
-
icon="componentLine"
|
|
135
|
-
title="No jobs yet"
|
|
136
|
-
description="This run has not materialized jobs."
|
|
137
|
-
/>
|
|
138
|
-
);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function compareJobs(left: Job, right: Job): number {
|
|
142
|
-
return (
|
|
143
|
-
left.position - right.position ||
|
|
144
|
-
left.displayName.localeCompare(right.displayName) ||
|
|
145
|
-
left.id.localeCompare(right.id)
|
|
146
|
-
);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
function statusLabel(status: ReturnType<typeof deriveJobDisplayStatus>): string {
|
|
150
|
-
return status === 'listening' ? 'Listening' : status.charAt(0).toUpperCase() + status.slice(1);
|
|
151
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import {Badge} from '@shipfox/react-ui/badge';
|
|
2
|
-
|
|
3
|
-
export function RunTabCount({
|
|
4
|
-
count,
|
|
5
|
-
alertCount = 0,
|
|
6
|
-
alertLabel,
|
|
7
|
-
}: {
|
|
8
|
-
count: number | undefined;
|
|
9
|
-
alertCount?: number | undefined;
|
|
10
|
-
alertLabel: string;
|
|
11
|
-
}) {
|
|
12
|
-
if (count === undefined && alertCount <= 0) return null;
|
|
13
|
-
|
|
14
|
-
return (
|
|
15
|
-
<>
|
|
16
|
-
{count === undefined ? null : (
|
|
17
|
-
<Badge size="2xs" variant="neutral" aria-hidden="true">
|
|
18
|
-
{count}
|
|
19
|
-
</Badge>
|
|
20
|
-
)}
|
|
21
|
-
{alertCount > 0 ? (
|
|
22
|
-
<Badge size="2xs" variant="error" aria-hidden="true">
|
|
23
|
-
{alertCount} {alertLabel}
|
|
24
|
-
</Badge>
|
|
25
|
-
) : null}
|
|
26
|
-
</>
|
|
27
|
-
);
|
|
28
|
-
}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import {Tabs} from '@shipfox/react-ui/tabs';
|
|
2
|
-
import type {Meta, StoryObj} from '@storybook/react';
|
|
3
|
-
import type {RunAnnotationSummary} from '#core/workflow-run-tabs.js';
|
|
4
|
-
import {RunTabStrip} from './run-tab-strip.js';
|
|
5
|
-
|
|
6
|
-
const ANNOTATION_SUMMARY: RunAnnotationSummary = {
|
|
7
|
-
total: 8,
|
|
8
|
-
error: 2,
|
|
9
|
-
warning: 3,
|
|
10
|
-
info: 2,
|
|
11
|
-
success: 1,
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
const meta = {
|
|
15
|
-
title: 'Workflows/RunTabStrip',
|
|
16
|
-
component: RunTabStrip,
|
|
17
|
-
parameters: {layout: 'fullscreen'},
|
|
18
|
-
render: (args) => (
|
|
19
|
-
<div className="bg-background-neutral-background">
|
|
20
|
-
<Tabs defaultValue="summary">
|
|
21
|
-
<RunTabStrip {...args} />
|
|
22
|
-
</Tabs>
|
|
23
|
-
</div>
|
|
24
|
-
),
|
|
25
|
-
args: {
|
|
26
|
-
jobCount: 6,
|
|
27
|
-
jobsFailed: 1,
|
|
28
|
-
annotationSummary: ANNOTATION_SUMMARY,
|
|
29
|
-
},
|
|
30
|
-
} satisfies Meta<typeof RunTabStrip>;
|
|
31
|
-
|
|
32
|
-
export default meta;
|
|
33
|
-
type Story = StoryObj<typeof meta>;
|
|
34
|
-
|
|
35
|
-
export const Playground: Story = {};
|
|
36
|
-
|
|
37
|
-
export const NoCounts: Story = {
|
|
38
|
-
args: {
|
|
39
|
-
jobCount: undefined,
|
|
40
|
-
annotationSummary: undefined,
|
|
41
|
-
},
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
export const NarrowLayout: Story = {
|
|
45
|
-
decorators: [
|
|
46
|
-
(Story) => (
|
|
47
|
-
<div className="w-[360px] max-w-full overflow-hidden border">
|
|
48
|
-
<Story />
|
|
49
|
-
</div>
|
|
50
|
-
),
|
|
51
|
-
],
|
|
52
|
-
};
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import {Tabs} from '@shipfox/react-ui/tabs';
|
|
2
|
-
import {
|
|
3
|
-
createMemoryHistory,
|
|
4
|
-
createRootRoute,
|
|
5
|
-
createRoute,
|
|
6
|
-
createRouter,
|
|
7
|
-
Outlet,
|
|
8
|
-
RouterProvider,
|
|
9
|
-
} from '@tanstack/react-router';
|
|
10
|
-
import {act, render, screen, within} from '@testing-library/react';
|
|
11
|
-
import userEvent from '@testing-library/user-event';
|
|
12
|
-
import type {RunAnnotationSummary} from '#core/workflow-run-tabs.js';
|
|
13
|
-
import {RunTabStrip} from './run-tab-strip.js';
|
|
14
|
-
|
|
15
|
-
const ANNOTATION_SUMMARY: RunAnnotationSummary = {
|
|
16
|
-
total: 5,
|
|
17
|
-
error: 2,
|
|
18
|
-
warning: 1,
|
|
19
|
-
info: 2,
|
|
20
|
-
success: 0,
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
describe('RunTabStrip', () => {
|
|
24
|
-
test('exposes tab counts and keyboard navigation', async () => {
|
|
25
|
-
const user = userEvent.setup();
|
|
26
|
-
|
|
27
|
-
await renderStrip({annotationSummary: ANNOTATION_SUMMARY, jobCount: 3, jobsFailed: 1});
|
|
28
|
-
|
|
29
|
-
const jobsTab = screen.getByRole('tab', {name: 'Jobs, 3 jobs, 1 failed'});
|
|
30
|
-
const annotationsTab = screen.getByRole('tab', {
|
|
31
|
-
name: 'Annotations, 5 annotations, 2 errors',
|
|
32
|
-
});
|
|
33
|
-
expect(within(jobsTab).getByText('3')).toHaveClass('bg-tag-neutral-bg');
|
|
34
|
-
expect(within(jobsTab).getByText('1 failed')).toHaveClass('bg-tag-error-bg');
|
|
35
|
-
expect(within(annotationsTab).getByText('5')).toHaveClass('bg-tag-neutral-bg');
|
|
36
|
-
expect(within(annotationsTab).getByText('2 errors')).toHaveClass('bg-tag-error-bg');
|
|
37
|
-
expect(screen.queryByRole('link', {name: '2 errors'})).not.toBeInTheDocument();
|
|
38
|
-
expect(screen.getByRole('tab', {name: 'Source'})).toHaveClass(
|
|
39
|
-
'data-[state=inactive]:text-foreground-neutral-subtle',
|
|
40
|
-
'data-[state=inactive]:hover:text-foreground-neutral-base',
|
|
41
|
-
);
|
|
42
|
-
|
|
43
|
-
const summary = screen.getByRole('tab', {name: 'Summary'});
|
|
44
|
-
expect(screen.getByRole('tablist', {name: 'Run sections'})).toHaveClass('min-w-full', 'px-16');
|
|
45
|
-
expect(summary.parentElement?.parentElement).toHaveClass('bg-background-neutral-background');
|
|
46
|
-
summary.focus();
|
|
47
|
-
await user.keyboard('{ArrowRight}');
|
|
48
|
-
|
|
49
|
-
expect(screen.getByRole('tab', {name: 'Jobs, 3 jobs, 1 failed'})).toHaveAttribute(
|
|
50
|
-
'aria-selected',
|
|
51
|
-
'true',
|
|
52
|
-
);
|
|
53
|
-
expect(screen.getByRole('tab', {name: 'Summary'})).toHaveAttribute('tabindex', '-1');
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
test('does not reserve count space when data is absent', async () => {
|
|
57
|
-
await renderStrip({annotationSummary: undefined, jobCount: undefined});
|
|
58
|
-
|
|
59
|
-
const jobsTab = screen.getByRole('tab', {name: 'Jobs'});
|
|
60
|
-
const annotationsTab = screen.getByRole('tab', {name: 'Annotations'});
|
|
61
|
-
|
|
62
|
-
expect(jobsTab).toBeInTheDocument();
|
|
63
|
-
expect(annotationsTab).toBeInTheDocument();
|
|
64
|
-
expect(jobsTab.querySelector('[data-slot="badge"]')).toBeNull();
|
|
65
|
-
expect(annotationsTab.querySelector('[data-slot="badge"]')).toBeNull();
|
|
66
|
-
expect(screen.queryByText('5 annotations')).not.toBeInTheDocument();
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
test('singularizes accessible count labels', async () => {
|
|
70
|
-
await renderStrip({
|
|
71
|
-
annotationSummary: {total: 1, error: 1, warning: 0, info: 0, success: 0},
|
|
72
|
-
jobCount: 1,
|
|
73
|
-
jobsFailed: 1,
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
expect(screen.getByRole('tab', {name: 'Jobs, 1 job, 1 failed'})).toBeInTheDocument();
|
|
77
|
-
expect(
|
|
78
|
-
screen.getByRole('tab', {name: 'Annotations, 1 annotation, 1 error'}),
|
|
79
|
-
).toBeInTheDocument();
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
async function renderStrip({
|
|
84
|
-
annotationSummary,
|
|
85
|
-
jobCount,
|
|
86
|
-
jobsFailed = 0,
|
|
87
|
-
}: {
|
|
88
|
-
annotationSummary?: RunAnnotationSummary | undefined;
|
|
89
|
-
jobCount?: number | undefined;
|
|
90
|
-
jobsFailed?: number | undefined;
|
|
91
|
-
} = {}) {
|
|
92
|
-
const rootRoute = createRootRoute({component: Outlet});
|
|
93
|
-
const runRoute = createRoute({
|
|
94
|
-
getParentRoute: () => rootRoute,
|
|
95
|
-
path: '/w/$workspaceSlug/p/$projectSlug/runs/$workflowRunId',
|
|
96
|
-
component: () => (
|
|
97
|
-
<Tabs defaultValue="summary">
|
|
98
|
-
<RunTabStrip
|
|
99
|
-
jobCount={jobCount}
|
|
100
|
-
jobsFailed={jobsFailed}
|
|
101
|
-
annotationSummary={annotationSummary}
|
|
102
|
-
/>
|
|
103
|
-
</Tabs>
|
|
104
|
-
),
|
|
105
|
-
});
|
|
106
|
-
const router = createRouter({
|
|
107
|
-
history: createMemoryHistory({initialEntries: ['/w/acme/p/project/runs/run-1']}),
|
|
108
|
-
routeTree: rootRoute.addChildren([runRoute]),
|
|
109
|
-
});
|
|
110
|
-
await router.load();
|
|
111
|
-
let result: ReturnType<typeof render> | undefined;
|
|
112
|
-
await act(() => {
|
|
113
|
-
result = render(<RouterProvider router={router} />);
|
|
114
|
-
});
|
|
115
|
-
if (!result) throw new Error('Run tab strip did not render.');
|
|
116
|
-
return result;
|
|
117
|
-
}
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import {TabsList, TabsTrigger} from '@shipfox/react-ui/tabs';
|
|
2
|
-
import type {RunAnnotationSummary} from '#core/workflow-run-tabs.js';
|
|
3
|
-
import {RunTabCount} from './run-tab-count.js';
|
|
4
|
-
|
|
5
|
-
export interface RunTabStripProps {
|
|
6
|
-
jobCount?: number | undefined;
|
|
7
|
-
jobsFailed?: number | undefined;
|
|
8
|
-
annotationSummary?: RunAnnotationSummary | undefined;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function RunTabStrip({jobCount, jobsFailed = 0, annotationSummary}: RunTabStripProps) {
|
|
12
|
-
const annotationCount = annotationSummary?.total;
|
|
13
|
-
const annotationErrors = annotationSummary?.error ?? 0;
|
|
14
|
-
|
|
15
|
-
return (
|
|
16
|
-
<div className="min-w-0 overflow-x-auto border-b border-border-neutral-base bg-background-neutral-background [scrollbar-width:none] [&::-webkit-scrollbar]:hidden">
|
|
17
|
-
<TabsList
|
|
18
|
-
aria-label="Run sections"
|
|
19
|
-
className="h-40 min-h-40 w-max min-w-full gap-16 px-16 [@media(pointer:coarse)]:h-44 [@media(pointer:coarse)]:min-h-44"
|
|
20
|
-
>
|
|
21
|
-
<TabsTrigger
|
|
22
|
-
value="summary"
|
|
23
|
-
className="h-40 min-h-40 py-0 text-xs data-[state=inactive]:text-foreground-neutral-subtle data-[state=inactive]:hover:text-foreground-neutral-base [@media(pointer:coarse)]:h-44 [@media(pointer:coarse)]:min-h-44"
|
|
24
|
-
>
|
|
25
|
-
Summary
|
|
26
|
-
</TabsTrigger>
|
|
27
|
-
<TabsTrigger
|
|
28
|
-
value="jobs"
|
|
29
|
-
aria-label={runTabAccessibleName({
|
|
30
|
-
label: 'Jobs',
|
|
31
|
-
total: jobCount,
|
|
32
|
-
totalSingular: 'job',
|
|
33
|
-
totalPlural: 'jobs',
|
|
34
|
-
alertCount: jobsFailed,
|
|
35
|
-
alertSingular: 'failed',
|
|
36
|
-
alertPlural: 'failed',
|
|
37
|
-
})}
|
|
38
|
-
className="h-40 min-h-40 gap-6 py-0 text-xs data-[state=inactive]:text-foreground-neutral-subtle data-[state=inactive]:hover:text-foreground-neutral-base [@media(pointer:coarse)]:h-44 [@media(pointer:coarse)]:min-h-44"
|
|
39
|
-
>
|
|
40
|
-
<span>Jobs</span>
|
|
41
|
-
<RunTabCount count={jobCount} alertCount={jobsFailed} alertLabel="failed" />
|
|
42
|
-
</TabsTrigger>
|
|
43
|
-
<TabsTrigger
|
|
44
|
-
value="annotations"
|
|
45
|
-
aria-label={runTabAccessibleName({
|
|
46
|
-
label: 'Annotations',
|
|
47
|
-
total: annotationCount,
|
|
48
|
-
totalSingular: 'annotation',
|
|
49
|
-
totalPlural: 'annotations',
|
|
50
|
-
alertCount: annotationErrors,
|
|
51
|
-
alertSingular: 'error',
|
|
52
|
-
alertPlural: 'errors',
|
|
53
|
-
})}
|
|
54
|
-
className="h-40 min-h-40 gap-6 py-0 text-xs data-[state=inactive]:text-foreground-neutral-subtle data-[state=inactive]:hover:text-foreground-neutral-base [@media(pointer:coarse)]:h-44 [@media(pointer:coarse)]:min-h-44"
|
|
55
|
-
>
|
|
56
|
-
<span>Annotations</span>
|
|
57
|
-
<RunTabCount
|
|
58
|
-
count={annotationCount}
|
|
59
|
-
alertCount={annotationErrors}
|
|
60
|
-
alertLabel={annotationErrors === 1 ? 'error' : 'errors'}
|
|
61
|
-
/>
|
|
62
|
-
</TabsTrigger>
|
|
63
|
-
<TabsTrigger
|
|
64
|
-
value="source"
|
|
65
|
-
className="h-40 min-h-40 py-0 text-xs data-[state=inactive]:text-foreground-neutral-subtle data-[state=inactive]:hover:text-foreground-neutral-base [@media(pointer:coarse)]:h-44 [@media(pointer:coarse)]:min-h-44"
|
|
66
|
-
>
|
|
67
|
-
Source
|
|
68
|
-
</TabsTrigger>
|
|
69
|
-
</TabsList>
|
|
70
|
-
</div>
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function runTabAccessibleName({
|
|
75
|
-
label,
|
|
76
|
-
total,
|
|
77
|
-
totalSingular,
|
|
78
|
-
totalPlural,
|
|
79
|
-
alertCount,
|
|
80
|
-
alertSingular,
|
|
81
|
-
alertPlural,
|
|
82
|
-
}: {
|
|
83
|
-
label: string;
|
|
84
|
-
total: number | undefined;
|
|
85
|
-
totalSingular: string;
|
|
86
|
-
totalPlural: string;
|
|
87
|
-
alertCount: number;
|
|
88
|
-
alertSingular: string;
|
|
89
|
-
alertPlural: string;
|
|
90
|
-
}): string {
|
|
91
|
-
const parts = [label];
|
|
92
|
-
|
|
93
|
-
if (total !== undefined) parts.push(`${total} ${total === 1 ? totalSingular : totalPlural}`);
|
|
94
|
-
if (alertCount > 0) parts.push(`${alertCount} ${alertCount === 1 ? alertSingular : alertPlural}`);
|
|
95
|
-
|
|
96
|
-
return parts.join(', ');
|
|
97
|
-
}
|