@shipfox/client-workflows 15.0.0 → 16.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.storybook/preview.css +4 -0
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +21 -0
- package/dist/components/job-detail/job-detail-header.d.ts +8 -1
- package/dist/components/job-detail/job-detail-header.d.ts.map +1 -1
- package/dist/components/job-detail/job-detail-header.js +25 -12
- package/dist/components/job-detail/job-detail-header.js.map +1 -1
- package/dist/components/job-detail/job-detail-view.d.ts.map +1 -1
- package/dist/components/job-detail/job-detail-view.js +17 -2
- package/dist/components/job-detail/job-detail-view.js.map +1 -1
- package/dist/components/workflow-run-summary/workflow-run-summary.js +1 -0
- package/dist/components/workflow-run-summary/workflow-run-summary.js.map +1 -1
- package/dist/components/workflow-run-tabs/index.d.ts +3 -0
- package/dist/components/workflow-run-tabs/index.d.ts.map +1 -1
- package/dist/components/workflow-run-tabs/index.js +3 -0
- package/dist/components/workflow-run-tabs/index.js.map +1 -1
- package/dist/components/workflow-run-tabs/run-annotation-count-chip.d.ts +16 -0
- package/dist/components/workflow-run-tabs/run-annotation-count-chip.d.ts.map +1 -0
- package/dist/components/workflow-run-tabs/run-annotation-count-chip.js +50 -0
- package/dist/components/workflow-run-tabs/run-annotation-count-chip.js.map +1 -0
- package/dist/components/workflow-run-tabs/run-annotation-item.d.ts +20 -0
- package/dist/components/workflow-run-tabs/run-annotation-item.d.ts.map +1 -0
- package/dist/components/workflow-run-tabs/run-annotation-item.js +102 -0
- package/dist/components/workflow-run-tabs/run-annotation-item.js.map +1 -0
- package/dist/components/workflow-run-tabs/run-annotation-list.d.ts +32 -0
- package/dist/components/workflow-run-tabs/run-annotation-list.d.ts.map +1 -0
- package/dist/components/workflow-run-tabs/run-annotation-list.js +202 -0
- package/dist/components/workflow-run-tabs/run-annotation-list.js.map +1 -0
- package/dist/components/workflow-run-tabs/run-annotation-summary-line.d.ts +1 -1
- package/dist/components/workflow-run-tabs/run-annotation-summary-line.d.ts.map +1 -1
- package/dist/components/workflow-run-tabs/run-annotation-summary-line.js +87 -22
- package/dist/components/workflow-run-tabs/run-annotation-summary-line.js.map +1 -1
- package/dist/components/workflow-run-tabs/severity-visuals.d.ts +20 -0
- package/dist/components/workflow-run-tabs/severity-visuals.d.ts.map +1 -0
- package/dist/components/workflow-run-tabs/severity-visuals.js +31 -0
- package/dist/components/workflow-run-tabs/severity-visuals.js.map +1 -0
- package/dist/components/workflow-run-view/run-workspace-nav.d.ts +1 -1
- package/dist/components/workflow-run-view/run-workspace-nav.d.ts.map +1 -1
- package/dist/components/workflow-run-view/run-workspace-nav.js +9 -3
- package/dist/components/workflow-run-view/run-workspace-nav.js.map +1 -1
- package/dist/components/workflow-run-view/workflow-run-view.d.ts +1 -3
- package/dist/components/workflow-run-view/workflow-run-view.d.ts.map +1 -1
- package/dist/components/workflow-run-view/workflow-run-view.js +150 -52
- package/dist/components/workflow-run-view/workflow-run-view.js.map +1 -1
- package/dist/core/run-annotation.d.ts +73 -0
- package/dist/core/run-annotation.d.ts.map +1 -0
- package/dist/core/run-annotation.js +125 -0
- package/dist/core/run-annotation.js.map +1 -0
- package/dist/hooks/api/run-annotation-mapper.d.ts +10 -0
- package/dist/hooks/api/run-annotation-mapper.d.ts.map +1 -0
- package/dist/hooks/api/run-annotation-mapper.js +22 -0
- package/dist/hooks/api/run-annotation-mapper.js.map +1 -0
- package/dist/hooks/api/run-annotations.d.ts +25 -0
- package/dist/hooks/api/run-annotations.d.ts.map +1 -0
- package/dist/hooks/api/run-annotations.js +78 -0
- package/dist/hooks/api/run-annotations.js.map +1 -0
- package/dist/routes/inputs.d.ts +7 -2
- package/dist/routes/inputs.d.ts.map +1 -1
- package/dist/routes/inputs.js +5 -6
- package/dist/routes/inputs.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +10 -9
- package/src/components/job-detail/job-detail-header.test.tsx +3 -0
- package/src/components/job-detail/job-detail-header.tsx +30 -5
- package/src/components/job-detail/job-detail-view.stories.tsx +5 -0
- package/src/components/job-detail/job-detail-view.tsx +18 -1
- package/src/components/workflow-run-summary/workflow-run-summary.tsx +1 -1
- package/src/components/workflow-run-tabs/index.ts +3 -0
- package/src/components/workflow-run-tabs/run-annotation-count-chip.tsx +63 -0
- package/src/components/workflow-run-tabs/run-annotation-item.tsx +123 -0
- package/src/components/workflow-run-tabs/run-annotation-list.tsx +258 -0
- package/src/components/workflow-run-tabs/run-annotation-summary-line.test.tsx +77 -3
- package/src/components/workflow-run-tabs/run-annotation-summary-line.tsx +92 -17
- package/src/components/workflow-run-tabs/run-annotations.stories.tsx +293 -0
- package/src/components/workflow-run-tabs/severity-visuals.ts +37 -0
- package/src/components/workflow-run-view/run-workspace-nav.test.tsx +44 -0
- package/src/components/workflow-run-view/run-workspace-nav.tsx +15 -2
- package/src/components/workflow-run-view/workflow-run-view.test.tsx +246 -20
- package/src/components/workflow-run-view/workflow-run-view.tsx +148 -30
- package/src/core/run-annotation.test.ts +315 -0
- package/src/core/run-annotation.ts +238 -0
- package/src/hooks/api/run-annotation-mapper.ts +30 -0
- package/src/hooks/api/run-annotations.ts +137 -0
- package/src/pages/workflow-job-detail-page.test.tsx +66 -0
- package/src/routes/inputs.ts +7 -2
- package/tsconfig.build.tsbuildinfo +1 -1
- package/dist/core/workflow-run-tabs.d.ts +0 -9
- package/dist/core/workflow-run-tabs.d.ts.map +0 -1
- package/dist/core/workflow-run-tabs.js +0 -3
- package/dist/core/workflow-run-tabs.js.map +0 -1
- package/src/core/workflow-run-tabs.ts +0 -8
package/.storybook/preview.css
CHANGED
|
@@ -8,3 +8,7 @@
|
|
|
8
8
|
apps/client/src/styles.css scans every package it renders. */
|
|
9
9
|
@source "../src";
|
|
10
10
|
@source "../../../shared/react/ui/src";
|
|
11
|
+
/* client-ui owns `AnnotationCard`, whose clamp fade and prose measure live nowhere else.
|
|
12
|
+
Without this the classes are silently dropped and every Argos snapshot of a story that
|
|
13
|
+
renders one is a picture of unstyled markup, which is worse than no snapshot. */
|
|
14
|
+
@source "../../ui/src";
|
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
$ shipfox-swc
|
|
2
|
-
Successfully compiled:
|
|
2
|
+
Successfully compiled: 81 files with swc (667.96ms)
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# @shipfox/client-workflows
|
|
2
2
|
|
|
3
|
+
## 16.0.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 80cde6b: Render run annotations in the run workspace. `AnnotationCard` gains an optional context title, provenance, and action, and bounds a long body behind a disclosure. The run's Annotations section is now the only surface that renders an annotation body, ranked by severity and then emission order, with the job page linking to it through a bounded count chip.
|
|
8
|
+
|
|
9
|
+
`Markdown` tables now size to their content instead of stretching to the container, so a two-column table no longer puts a cell the width of the page away from its row header.
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- Updated dependencies [80cde6b]
|
|
14
|
+
- Updated dependencies [ce0984d]
|
|
15
|
+
- @shipfox/react-ui@1.1.0
|
|
16
|
+
- @shipfox/client-ui@16.0.0
|
|
17
|
+
- @shipfox/api-workflows-dto@12.2.0
|
|
18
|
+
- @shipfox/client-projects@16.0.0
|
|
19
|
+
- @shipfox/client-logs@16.0.0
|
|
20
|
+
- @shipfox/client-shell@16.0.0
|
|
21
|
+
- @shipfox/client-triggers@16.0.0
|
|
22
|
+
- @shipfox/api-definitions-dto@12.2.0
|
|
23
|
+
|
|
3
24
|
## 15.0.0
|
|
4
25
|
|
|
5
26
|
### Minor Changes
|
|
@@ -1,8 +1,15 @@
|
|
|
1
|
+
import type { RunAnnotationSummary } from '#core/run-annotation.js';
|
|
1
2
|
import { type Job, type JobExecution } from '#core/workflow-run.js';
|
|
2
3
|
export interface JobDetailHeaderProps {
|
|
3
4
|
job: Job;
|
|
4
5
|
selectedJobExecution: JobExecution | undefined;
|
|
5
6
|
onSelectedJobExecutionChange: (jobExecutionId: string) => void;
|
|
7
|
+
workspaceSlug: string;
|
|
8
|
+
projectSlug: string;
|
|
9
|
+
workflowRunId: string;
|
|
10
|
+
runAttempt?: number | undefined;
|
|
11
|
+
/** Counts for this job only. Renders a link into the run's Annotations section, never a body. */
|
|
12
|
+
annotationSummary?: RunAnnotationSummary | undefined;
|
|
6
13
|
}
|
|
7
|
-
export declare function JobDetailHeader({ job, selectedJobExecution, onSelectedJobExecutionChange, }: JobDetailHeaderProps): import("react").JSX.Element;
|
|
14
|
+
export declare function JobDetailHeader({ job, selectedJobExecution, onSelectedJobExecutionChange, workspaceSlug, projectSlug, workflowRunId, runAttempt, annotationSummary, }: JobDetailHeaderProps): import("react").JSX.Element;
|
|
8
15
|
//# sourceMappingURL=job-detail-header.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"job-detail-header.d.ts","sourceRoot":"","sources":["../../../src/components/job-detail/job-detail-header.tsx"],"names":[],"mappings":"AAMA,OAAO,EAIL,KAAK,GAAG,EACR,KAAK,YAAY,EAClB,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"job-detail-header.d.ts","sourceRoot":"","sources":["../../../src/components/job-detail/job-detail-header.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,yBAAyB,CAAC;AAClE,OAAO,EAIL,KAAK,GAAG,EACR,KAAK,YAAY,EAClB,MAAM,uBAAuB,CAAC;AAK/B,MAAM,WAAW,oBAAoB;IACnC,GAAG,EAAE,GAAG,CAAC;IACT,oBAAoB,EAAE,YAAY,GAAG,SAAS,CAAC;IAC/C,4BAA4B,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/D,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,iGAAiG;IACjG,iBAAiB,CAAC,EAAE,oBAAoB,GAAG,SAAS,CAAC;CACtD;AAED,wBAAgB,eAAe,CAAC,EAC9B,GAAG,EACH,oBAAoB,EACpB,4BAA4B,EAC5B,aAAa,EACb,WAAW,EACX,aAAa,EACb,UAAU,EACV,iBAAiB,GAClB,EAAE,oBAAoB,+BAuDtB"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { Icon } from '@shipfox/react-ui/icon';
|
|
3
3
|
import { Tooltip, TooltipContent, TooltipTrigger } from '@shipfox/react-ui/tooltip';
|
|
4
4
|
import { Code, Text } from '@shipfox/react-ui/typography';
|
|
@@ -6,9 +6,10 @@ import { formatTimestamp } from '@shipfox/react-ui/utils';
|
|
|
6
6
|
import { getWorkflowStatusVisual } from '#components/workflow-status/status-visuals.js';
|
|
7
7
|
import { WorkflowStatusIcon } from '#components/workflow-status/workflow-status-icon.js';
|
|
8
8
|
import { defaultJobExecution, deriveJobDisplayStatus, deriveJobExecutionDisplayStatus } from '#core/workflow-run.js';
|
|
9
|
+
import { RunAnnotationCountChip } from '../workflow-run-tabs/index.js';
|
|
9
10
|
import { JobExecutionSwitcher } from './job-execution-switcher.js';
|
|
10
11
|
import { JobExecutionTimeText } from './job-execution-time-text.js';
|
|
11
|
-
export function JobDetailHeader({ job, selectedJobExecution, onSelectedJobExecutionChange }) {
|
|
12
|
+
export function JobDetailHeader({ job, selectedJobExecution, onSelectedJobExecutionChange, workspaceSlug, projectSlug, workflowRunId, runAttempt, annotationSummary }) {
|
|
12
13
|
const selectedStatus = selectedExecutionStatus(job, selectedJobExecution);
|
|
13
14
|
const jobStatus = getWorkflowStatusVisual(selectedStatus);
|
|
14
15
|
return /*#__PURE__*/ _jsx("header", {
|
|
@@ -26,7 +27,7 @@ export function JobDetailHeader({ job, selectedJobExecution, onSelectedJobExecut
|
|
|
26
27
|
ariaLabel: `Job status: ${jobStatus.label}`
|
|
27
28
|
}),
|
|
28
29
|
/*#__PURE__*/ _jsx(Code, {
|
|
29
|
-
as: "
|
|
30
|
+
as: "h2",
|
|
30
31
|
variant: "paragraph",
|
|
31
32
|
bold: true,
|
|
32
33
|
tabIndex: -1,
|
|
@@ -36,22 +37,34 @@ export function JobDetailHeader({ job, selectedJobExecution, onSelectedJobExecut
|
|
|
36
37
|
})
|
|
37
38
|
]
|
|
38
39
|
}),
|
|
39
|
-
selectedJobExecution ? /*#__PURE__*/ _jsxs("div", {
|
|
40
|
+
selectedJobExecution || annotationSummary?.total ? /*#__PURE__*/ _jsxs("div", {
|
|
40
41
|
className: "flex min-w-0 flex-wrap items-center gap-10 text-foreground-neutral-muted",
|
|
41
42
|
children: [
|
|
42
|
-
job.executionCountVisible ? /*#__PURE__*/ _jsx(JobExecutionSwitcher, {
|
|
43
|
+
selectedJobExecution && job.executionCountVisible ? /*#__PURE__*/ _jsx(JobExecutionSwitcher, {
|
|
43
44
|
job: job,
|
|
44
45
|
selectedJobExecution: selectedJobExecution.id,
|
|
45
46
|
onSelectedJobExecutionChange: onSelectedJobExecutionChange,
|
|
46
47
|
variant: "title"
|
|
47
48
|
}) : null,
|
|
48
|
-
/*#__PURE__*/
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
49
|
+
selectedJobExecution ? /*#__PURE__*/ _jsxs(_Fragment, {
|
|
50
|
+
children: [
|
|
51
|
+
/*#__PURE__*/ _jsx(JobDurationMeta, {
|
|
52
|
+
execution: selectedJobExecution,
|
|
53
|
+
kind: "queue"
|
|
54
|
+
}),
|
|
55
|
+
/*#__PURE__*/ _jsx(JobDurationMeta, {
|
|
56
|
+
execution: selectedJobExecution,
|
|
57
|
+
kind: "run"
|
|
58
|
+
})
|
|
59
|
+
]
|
|
60
|
+
}) : null,
|
|
61
|
+
/*#__PURE__*/ _jsx(RunAnnotationCountChip, {
|
|
62
|
+
summary: annotationSummary,
|
|
63
|
+
workspaceSlug: workspaceSlug,
|
|
64
|
+
projectSlug: projectSlug,
|
|
65
|
+
workflowRunId: workflowRunId,
|
|
66
|
+
runAttempt: runAttempt,
|
|
67
|
+
jobId: job.id
|
|
55
68
|
})
|
|
56
69
|
]
|
|
57
70
|
}) : null
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/job-detail/job-detail-header.tsx"],"sourcesContent":["import {Icon} from '@shipfox/react-ui/icon';\nimport {Tooltip, TooltipContent, TooltipTrigger} from '@shipfox/react-ui/tooltip';\nimport {Code, Text} from '@shipfox/react-ui/typography';\nimport {formatTimestamp} from '@shipfox/react-ui/utils';\nimport {getWorkflowStatusVisual} from '#components/workflow-status/status-visuals.js';\nimport {WorkflowStatusIcon} from '#components/workflow-status/workflow-status-icon.js';\nimport {\n defaultJobExecution,\n deriveJobDisplayStatus,\n deriveJobExecutionDisplayStatus,\n type Job,\n type JobExecution,\n} from '#core/workflow-run.js';\nimport {JobExecutionSwitcher} from './job-execution-switcher.js';\nimport {JobExecutionTimeText} from './job-execution-time-text.js';\n\nexport interface JobDetailHeaderProps {\n job: Job;\n selectedJobExecution: JobExecution | undefined;\n onSelectedJobExecutionChange: (jobExecutionId: string) => void;\n}\n\nexport function JobDetailHeader({\n job,\n selectedJobExecution,\n onSelectedJobExecutionChange,\n}: JobDetailHeaderProps) {\n const selectedStatus = selectedExecutionStatus(job, selectedJobExecution);\n const jobStatus = getWorkflowStatusVisual(selectedStatus);\n\n return (\n <header className=\"border-b border-border-neutral-base px-16 py-12\">\n <div className=\"flex min-w-0 flex-col gap-8\">\n <div className=\"flex min-w-0 items-center gap-8\">\n <WorkflowStatusIcon\n status={selectedStatus}\n size={14}\n tooltip\n ariaLabel={`Job status: ${jobStatus.label}`}\n />\n <Code\n as=\"
|
|
1
|
+
{"version":3,"sources":["../../../src/components/job-detail/job-detail-header.tsx"],"sourcesContent":["import {Icon} from '@shipfox/react-ui/icon';\nimport {Tooltip, TooltipContent, TooltipTrigger} from '@shipfox/react-ui/tooltip';\nimport {Code, Text} from '@shipfox/react-ui/typography';\nimport {formatTimestamp} from '@shipfox/react-ui/utils';\nimport {getWorkflowStatusVisual} from '#components/workflow-status/status-visuals.js';\nimport {WorkflowStatusIcon} from '#components/workflow-status/workflow-status-icon.js';\nimport type {RunAnnotationSummary} from '#core/run-annotation.js';\nimport {\n defaultJobExecution,\n deriveJobDisplayStatus,\n deriveJobExecutionDisplayStatus,\n type Job,\n type JobExecution,\n} from '#core/workflow-run.js';\nimport {RunAnnotationCountChip} from '../workflow-run-tabs/index.js';\nimport {JobExecutionSwitcher} from './job-execution-switcher.js';\nimport {JobExecutionTimeText} from './job-execution-time-text.js';\n\nexport interface JobDetailHeaderProps {\n job: Job;\n selectedJobExecution: JobExecution | undefined;\n onSelectedJobExecutionChange: (jobExecutionId: string) => void;\n workspaceSlug: string;\n projectSlug: string;\n workflowRunId: string;\n runAttempt?: number | undefined;\n /** Counts for this job only. Renders a link into the run's Annotations section, never a body. */\n annotationSummary?: RunAnnotationSummary | undefined;\n}\n\nexport function JobDetailHeader({\n job,\n selectedJobExecution,\n onSelectedJobExecutionChange,\n workspaceSlug,\n projectSlug,\n workflowRunId,\n runAttempt,\n annotationSummary,\n}: JobDetailHeaderProps) {\n const selectedStatus = selectedExecutionStatus(job, selectedJobExecution);\n const jobStatus = getWorkflowStatusVisual(selectedStatus);\n\n return (\n <header className=\"border-b border-border-neutral-base px-16 py-12\">\n <div className=\"flex min-w-0 flex-col gap-8\">\n <div className=\"flex min-w-0 items-center gap-8\">\n <WorkflowStatusIcon\n status={selectedStatus}\n size={14}\n tooltip\n ariaLabel={`Job status: ${jobStatus.label}`}\n />\n <Code\n as=\"h2\"\n variant=\"paragraph\"\n bold\n tabIndex={-1}\n className=\"min-w-0 truncate text-lg leading-24 text-foreground-neutral-base outline-none\"\n data-job-heading\n >\n {job.displayName}\n </Code>\n </div>\n\n {selectedJobExecution || annotationSummary?.total ? (\n <div className=\"flex min-w-0 flex-wrap items-center gap-10 text-foreground-neutral-muted\">\n {selectedJobExecution && job.executionCountVisible ? (\n <JobExecutionSwitcher\n job={job}\n selectedJobExecution={selectedJobExecution.id}\n onSelectedJobExecutionChange={onSelectedJobExecutionChange}\n variant=\"title\"\n />\n ) : null}\n {selectedJobExecution ? (\n <>\n <JobDurationMeta execution={selectedJobExecution} kind=\"queue\" />\n <JobDurationMeta execution={selectedJobExecution} kind=\"run\" />\n </>\n ) : null}\n <RunAnnotationCountChip\n summary={annotationSummary}\n workspaceSlug={workspaceSlug}\n projectSlug={projectSlug}\n workflowRunId={workflowRunId}\n runAttempt={runAttempt}\n jobId={job.id}\n />\n </div>\n ) : null}\n </div>\n </header>\n );\n}\n\nfunction selectedExecutionStatus(job: Job, execution: JobExecution | undefined) {\n if (!execution) return deriveJobDisplayStatus(job);\n const defaultExecution = defaultJobExecution(job);\n return execution.id === defaultExecution?.id\n ? deriveJobDisplayStatus(job)\n : deriveJobExecutionDisplayStatus(execution);\n}\n\nfunction JobDurationMeta({execution, kind}: {execution: JobExecution; kind: 'queue' | 'run'}) {\n const time = kind === 'queue' ? execution.queueTime : execution.runTime;\n const from = kind === 'queue' ? execution.queuedAt : execution.startedAt;\n if (!time || !from) return null;\n\n const to = kind === 'queue' ? execution.startedAt : execution.finishedAt;\n const live = time.state === 'live';\n const label = kind === 'queue' ? 'queued' : live ? 'running' : 'ran';\n const tooltipLabel = kind === 'queue' ? 'Queued' : live ? 'Running' : 'Ran';\n const tooltip = `${tooltipLabel} ${formatTimestamp(from)}${to ? ` – ${formatTimestamp(to)}` : ' – now'}`;\n\n return (\n <Tooltip>\n <TooltipTrigger asChild>\n <span className=\"inline-flex items-center gap-4 whitespace-nowrap font-code text-xs leading-20 tabular-nums\">\n <Icon\n name={kind === 'queue' ? 'hourglassLine' : 'timerLine'}\n size={12}\n aria-hidden=\"true\"\n />\n <span>{label} </span>\n <JobExecutionTimeText time={time} />\n </span>\n </TooltipTrigger>\n <TooltipContent>\n <Text as=\"span\" size=\"xs\">\n {tooltip}\n </Text>\n </TooltipContent>\n </Tooltip>\n );\n}\n"],"names":["Icon","Tooltip","TooltipContent","TooltipTrigger","Code","Text","formatTimestamp","getWorkflowStatusVisual","WorkflowStatusIcon","defaultJobExecution","deriveJobDisplayStatus","deriveJobExecutionDisplayStatus","RunAnnotationCountChip","JobExecutionSwitcher","JobExecutionTimeText","JobDetailHeader","job","selectedJobExecution","onSelectedJobExecutionChange","workspaceSlug","projectSlug","workflowRunId","runAttempt","annotationSummary","selectedStatus","selectedExecutionStatus","jobStatus","header","className","div","status","size","tooltip","ariaLabel","label","as","variant","bold","tabIndex","data-job-heading","displayName","total","executionCountVisible","id","JobDurationMeta","execution","kind","summary","jobId","defaultExecution","time","queueTime","runTime","from","queuedAt","startedAt","to","finishedAt","live","state","tooltipLabel","asChild","span","name","aria-hidden"],"mappings":";AAAA,SAAQA,IAAI,QAAO,yBAAyB;AAC5C,SAAQC,OAAO,EAAEC,cAAc,EAAEC,cAAc,QAAO,4BAA4B;AAClF,SAAQC,IAAI,EAAEC,IAAI,QAAO,+BAA+B;AACxD,SAAQC,eAAe,QAAO,0BAA0B;AACxD,SAAQC,uBAAuB,QAAO,gDAAgD;AACtF,SAAQC,kBAAkB,QAAO,sDAAsD;AAEvF,SACEC,mBAAmB,EACnBC,sBAAsB,EACtBC,+BAA+B,QAG1B,wBAAwB;AAC/B,SAAQC,sBAAsB,QAAO,gCAAgC;AACrE,SAAQC,oBAAoB,QAAO,8BAA8B;AACjE,SAAQC,oBAAoB,QAAO,+BAA+B;AAclE,OAAO,SAASC,gBAAgB,EAC9BC,GAAG,EACHC,oBAAoB,EACpBC,4BAA4B,EAC5BC,aAAa,EACbC,WAAW,EACXC,aAAa,EACbC,UAAU,EACVC,iBAAiB,EACI;IACrB,MAAMC,iBAAiBC,wBAAwBT,KAAKC;IACpD,MAAMS,YAAYnB,wBAAwBiB;IAE1C,qBACE,KAACG;QAAOC,WAAU;kBAChB,cAAA,MAACC;YAAID,WAAU;;8BACb,MAACC;oBAAID,WAAU;;sCACb,KAACpB;4BACCsB,QAAQN;4BACRO,MAAM;4BACNC,OAAO;4BACPC,WAAW,CAAC,YAAY,EAAEP,UAAUQ,KAAK,EAAE;;sCAE7C,KAAC9B;4BACC+B,IAAG;4BACHC,SAAQ;4BACRC,IAAI;4BACJC,UAAU,CAAC;4BACXV,WAAU;4BACVW,kBAAgB;sCAEfvB,IAAIwB,WAAW;;;;gBAInBvB,wBAAwBM,mBAAmBkB,sBAC1C,MAACZ;oBAAID,WAAU;;wBACZX,wBAAwBD,IAAI0B,qBAAqB,iBAChD,KAAC7B;4BACCG,KAAKA;4BACLC,sBAAsBA,qBAAqB0B,EAAE;4BAC7CzB,8BAA8BA;4BAC9BkB,SAAQ;6BAER;wBACHnB,qCACC;;8CACE,KAAC2B;oCAAgBC,WAAW5B;oCAAsB6B,MAAK;;8CACvD,KAACF;oCAAgBC,WAAW5B;oCAAsB6B,MAAK;;;6BAEvD;sCACJ,KAAClC;4BACCmC,SAASxB;4BACTJ,eAAeA;4BACfC,aAAaA;4BACbC,eAAeA;4BACfC,YAAYA;4BACZ0B,OAAOhC,IAAI2B,EAAE;;;qBAGf;;;;AAIZ;AAEA,SAASlB,wBAAwBT,GAAQ,EAAE6B,SAAmC;IAC5E,IAAI,CAACA,WAAW,OAAOnC,uBAAuBM;IAC9C,MAAMiC,mBAAmBxC,oBAAoBO;IAC7C,OAAO6B,UAAUF,EAAE,KAAKM,kBAAkBN,KACtCjC,uBAAuBM,OACvBL,gCAAgCkC;AACtC;AAEA,SAASD,gBAAgB,EAACC,SAAS,EAAEC,IAAI,EAAmD;IAC1F,MAAMI,OAAOJ,SAAS,UAAUD,UAAUM,SAAS,GAAGN,UAAUO,OAAO;IACvE,MAAMC,OAAOP,SAAS,UAAUD,UAAUS,QAAQ,GAAGT,UAAUU,SAAS;IACxE,IAAI,CAACL,QAAQ,CAACG,MAAM,OAAO;IAE3B,MAAMG,KAAKV,SAAS,UAAUD,UAAUU,SAAS,GAAGV,UAAUY,UAAU;IACxE,MAAMC,OAAOR,KAAKS,KAAK,KAAK;IAC5B,MAAMzB,QAAQY,SAAS,UAAU,WAAWY,OAAO,YAAY;IAC/D,MAAME,eAAed,SAAS,UAAU,WAAWY,OAAO,YAAY;IACtE,MAAM1B,UAAU,GAAG4B,aAAa,CAAC,EAAEtD,gBAAgB+C,QAAQG,KAAK,CAAC,GAAG,EAAElD,gBAAgBkD,KAAK,GAAG,UAAU;IAExG,qBACE,MAACvD;;0BACC,KAACE;gBAAe0D,OAAO;0BACrB,cAAA,MAACC;oBAAKlC,WAAU;;sCACd,KAAC5B;4BACC+D,MAAMjB,SAAS,UAAU,kBAAkB;4BAC3Cf,MAAM;4BACNiC,eAAY;;sCAEd,MAACF;;gCAAM5B;gCAAM;;;sCACb,KAACpB;4BAAqBoC,MAAMA;;;;;0BAGhC,KAAChD;0BACC,cAAA,KAACG;oBAAK8B,IAAG;oBAAOJ,MAAK;8BAClBC;;;;;AAKX"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"job-detail-view.d.ts","sourceRoot":"","sources":["../../../src/components/job-detail/job-detail-view.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"job-detail-view.d.ts","sourceRoot":"","sources":["../../../src/components/job-detail/job-detail-view.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAC,GAAG,EAAe,MAAM,uBAAuB,CAAC;AAE7D,OAAO,KAAK,EAAC,0BAA0B,EAAC,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EACL,KAAK,iBAAiB,EAGvB,MAAM,mBAAmB,CAAC;AAuB3B,MAAM,WAAW,kBAAkB;IACjC,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,iBAAiB,CAAC;IAC1B,KAAK,EAAE,UAAU,CAAC,OAAO,0BAA0B,CAAC,CAAC;IACrD,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,QAAQ,CAAC,EAAE,GAAG,GAAG,SAAS,CAAC;IAC3B,iBAAiB,EAAE,CAAC,SAAS,EAAE,iBAAiB,KAAK,IAAI,CAAC;CAC3D;AAED,wBAAgB,aAAa,CAAC,EAC5B,aAAa,EACb,WAAW,EACX,aAAa,EACb,KAAK,EACL,MAAM,EACN,KAAK,EACL,YAAY,EACZ,QAAQ,EACR,iBAAiB,GAClB,EAAE,kBAAkB,+BA2NpB"}
|
|
@@ -10,6 +10,8 @@ import { TimeTickerProvider } from '@shipfox/react-ui/time-ticker';
|
|
|
10
10
|
import { Text } from '@shipfox/react-ui/typography';
|
|
11
11
|
import { Link } from '@tanstack/react-router';
|
|
12
12
|
import { useEffect, useRef } from 'react';
|
|
13
|
+
import { summarizeJobAnnotations } from '#core/run-annotation.js';
|
|
14
|
+
import { useRunAnnotationsQuery } from '#hooks/api/run-annotations.js';
|
|
13
15
|
import { workflowJobSearchParams, workflowRunSearchParams } from '#routes/inputs.js';
|
|
14
16
|
import { StepList } from '../step-list/index.js';
|
|
15
17
|
import { WorkflowRunNotFound, WorkflowRunStaleError } from '../workflow-run-view/workflow-run-states.js';
|
|
@@ -23,6 +25,14 @@ export function JobDetailView({ workspaceSlug, projectSlug, workflowRunId, jobId
|
|
|
23
25
|
const pageScrollRef = useRef(null);
|
|
24
26
|
const landingSelectionRef = useRef(undefined);
|
|
25
27
|
const hasLoadedData = query.data !== undefined;
|
|
28
|
+
// Same query key as the run workspace, so this reads the cache instead of fetching again.
|
|
29
|
+
const annotations = useRunAnnotationsQuery({
|
|
30
|
+
workflowRunId,
|
|
31
|
+
runAttempt: query.data?.runAttempt.attempt
|
|
32
|
+
});
|
|
33
|
+
const jobAnnotationSummary = annotations.annotations ? summarizeJobAnnotations(annotations.annotations, jobId, {
|
|
34
|
+
truncated: annotations.summary?.truncated ?? false
|
|
35
|
+
}) : undefined;
|
|
26
36
|
useEffect(()=>{
|
|
27
37
|
if (!jobId || !hasLoadedData) return;
|
|
28
38
|
const heading = rootRef.current?.querySelector('[data-job-heading]');
|
|
@@ -157,10 +167,15 @@ export function JobDetailView({ workspaceSlug, projectSlug, workflowRunId, jobId
|
|
|
157
167
|
/*#__PURE__*/ _jsx(JobDetailHeader, {
|
|
158
168
|
job: job,
|
|
159
169
|
selectedJobExecution: selectedJobExecution,
|
|
160
|
-
onSelectedJobExecutionChange: selectExecution
|
|
170
|
+
onSelectedJobExecutionChange: selectExecution,
|
|
171
|
+
workspaceSlug: workspaceSlug,
|
|
172
|
+
projectSlug: projectSlug,
|
|
173
|
+
workflowRunId: workflowRunId,
|
|
174
|
+
runAttempt: run.runAttempt.attempt,
|
|
175
|
+
annotationSummary: jobAnnotationSummary
|
|
161
176
|
}),
|
|
162
177
|
/*#__PURE__*/ _jsx(Text, {
|
|
163
|
-
as: "
|
|
178
|
+
as: "h3",
|
|
164
179
|
className: "sr-only",
|
|
165
180
|
children: "Logs"
|
|
166
181
|
}),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/job-detail/job-detail-view.tsx"],"sourcesContent":["// biome-ignore-all lint/a11y/noRedundantRoles: the job log region keeps an explicit role for the public accessibility contract.\n// biome-ignore-all lint/a11y/noNoninteractiveTabindex: the job log region is intentionally keyboard focusable.\n\nimport {ApiError} from '@shipfox/client-api';\nimport {QueryLoadError} from '@shipfox/client-ui';\nimport {EmptyState} from '@shipfox/react-ui/empty-state';\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {Skeleton} from '@shipfox/react-ui/skeleton';\nimport {TimeTickerProvider} from '@shipfox/react-ui/time-ticker';\nimport {Text} from '@shipfox/react-ui/typography';\nimport {Link} from '@tanstack/react-router';\nimport {type RefObject, useEffect, useRef} from 'react';\nimport type {Job, JobExecution} from '#core/workflow-run.js';\nimport type {useWorkflowRunAttemptQuery} from '#hooks/api/workflow-runs.js';\nimport {\n type WorkflowJobSearch,\n workflowJobSearchParams,\n workflowRunSearchParams,\n} from '#routes/inputs.js';\nimport {type StepExpandedContext, StepList} from '../step-list/index.js';\nimport {\n WorkflowRunNotFound,\n WorkflowRunStaleError,\n} from '../workflow-run-view/workflow-run-states.js';\nimport {AgentConfigFailureCallout} from './agent-config-failure-callout.js';\nimport {JobDetailHeader} from './job-detail-header.js';\nimport {\n CarriedOverStepPanel,\n emptyStateForJob,\n emptyStateForMissingExecution,\n isAgentConfigFailure,\n jobSucceededSummary,\n toSelectedAttemptError,\n} from './job-empty-states.js';\nimport {\n resolveWorkflowJobSelection,\n type WorkflowJobLandingSelection,\n workflowJobLandingSelection,\n} from './job-selection.js';\nimport {StepAttemptLogPanel} from './step-attempt-log-panel.js';\n\nexport interface JobDetailViewProps {\n workspaceSlug: string;\n projectSlug: string;\n workflowRunId: string;\n jobId: string;\n search: WorkflowJobSearch;\n query: ReturnType<typeof useWorkflowRunAttemptQuery>;\n newerAttempt?: number | undefined;\n newerJob?: Job | undefined;\n onSelectionChange: (selection: WorkflowJobSearch) => void;\n}\n\nexport function JobDetailView({\n workspaceSlug,\n projectSlug,\n workflowRunId,\n jobId,\n search,\n query,\n newerAttempt,\n newerJob,\n onSelectionChange,\n}: JobDetailViewProps) {\n const rootRef = useRef<HTMLDivElement>(null);\n const pageScrollRef = useRef<HTMLDivElement>(null);\n const landingSelectionRef = useRef<FrozenLandingSelection | undefined>(undefined);\n const hasLoadedData = query.data !== undefined;\n\n useEffect(() => {\n if (!jobId || !hasLoadedData) return;\n const heading = rootRef.current?.querySelector<HTMLElement>('[data-job-heading]');\n // The heading is intentionally the route-change focus target: it announces the new job\n // after rail navigation without putting focus on a decorative status element.\n heading?.focus({preventScroll: true});\n }, [hasLoadedData, jobId]);\n\n if (query.isPending) return <JobDetailSkeleton />;\n\n if (query.isError && query.data === undefined) {\n if (query.error instanceof ApiError && query.error.status === 404) {\n return <WorkflowRunNotFound />;\n }\n return <QueryLoadError query={query} subject=\"workflow run\" icon=\"pulseLine\" />;\n }\n\n if (query.data === undefined) return <JobDetailSkeleton />;\n\n const run = query.data;\n const job = run.jobs.find((candidate) => candidate.id === jobId);\n if (!job) {\n return (\n <JobNotFoundState\n workspaceSlug={workspaceSlug}\n projectSlug={projectSlug}\n workflowRunId={workflowRunId}\n search={search}\n />\n );\n }\n\n const resolvedSelection = resolveWorkflowJobSelection({job, selection: search});\n const selectedJobExecution = resolvedSelection.jobExecution;\n const hasExplicitStep = Boolean(search.stepId && resolvedSelection.step);\n const currentLandingSelection = workflowJobLandingSelection(selectedJobExecution);\n if (selectedJobExecution) {\n const frozenLanding = landingSelectionRef.current;\n if (\n !frozenLanding ||\n frozenLanding.jobId !== job.id ||\n frozenLanding.jobExecutionId !== selectedJobExecution.id\n ) {\n landingSelectionRef.current = {\n jobId: job.id,\n jobExecutionId: selectedJobExecution.id,\n selection: currentLandingSelection,\n hasSelection: currentLandingSelection !== undefined,\n };\n } else if (!frozenLanding.hasSelection && currentLandingSelection) {\n landingSelectionRef.current = {\n ...frozenLanding,\n selection: currentLandingSelection,\n hasSelection: true,\n };\n }\n } else {\n landingSelectionRef.current = undefined;\n }\n const landingSelection = landingSelectionRef.current?.selection;\n const selectedAttemptId = hasExplicitStep ? resolvedSelection.selectedAttemptId : undefined;\n const runningSelection = runningStepSelection(selectedJobExecution);\n const selectedStepId = resolvedSelection.step?.id ?? landingSelection?.stepId;\n const selectedAttemptForNotice = hasExplicitStep\n ? resolvedSelection.selectedAttemptId\n : landingSelection?.attemptId;\n const showRetargetNotice =\n runningSelection !== undefined &&\n (selectedStepId !== runningSelection.stepId ||\n selectedAttemptForNotice !== runningSelection.attemptId);\n const succeededSummary =\n !hasExplicitStep && landingSelection === undefined && selectedJobExecution\n ? jobSucceededSummary(job, selectedJobExecution)\n : undefined;\n\n function selectExecution(jobExecutionId: string) {\n onSelectionChange({\n ...search,\n jobExecutionId,\n stepId: undefined,\n stepAttemptId: undefined,\n });\n }\n\n function selectAttempt(attemptId: string | undefined) {\n if (!selectedJobExecution) return;\n if (!attemptId) {\n onSelectionChange({\n ...search,\n jobExecutionId: selectedJobExecution.id,\n stepId: undefined,\n stepAttemptId: undefined,\n });\n return;\n }\n\n const match = findAttempt(selectedJobExecution, attemptId);\n if (!match) return;\n onSelectionChange({\n ...search,\n jobExecutionId: selectedJobExecution.id,\n stepId: match.step.id,\n stepAttemptId: match.attemptId,\n });\n }\n\n function retargetToRunningStep() {\n if (!runningSelection || !selectedJobExecution) return;\n onSelectionChange({\n ...search,\n jobExecutionId: selectedJobExecution.id,\n stepId: runningSelection.stepId,\n stepAttemptId: runningSelection.attemptId,\n });\n }\n\n return (\n <TimeTickerProvider intervalMs={1000} reducedMotionIntervalMs={10_000}>\n <div ref={rootRef} className=\"flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden\">\n {query.isError ? <WorkflowRunStaleError query={query} /> : null}\n {newerJob && newerAttempt && newerAttempt > run.runAttempt.attempt ? (\n <NewerAttemptNotice\n workspaceSlug={workspaceSlug}\n projectSlug={projectSlug}\n runId={run.id}\n jobId={newerJob.id}\n attempt={newerAttempt}\n />\n ) : null}\n <div ref={pageScrollRef} className=\"@container min-h-0 flex-1 overflow-auto pb-24\">\n <section\n aria-label={`${job.displayName} logs`}\n className=\"mx-auto flex w-full max-w-[1120px] flex-col gap-8 px-24\"\n >\n <section className=\"min-w-0 overflow-hidden\">\n <JobDetailHeader\n job={job}\n selectedJobExecution={selectedJobExecution}\n onSelectedJobExecutionChange={selectExecution}\n />\n <Text as=\"h2\" className=\"sr-only\">\n Logs\n </Text>\n {selectedJobExecution ? (\n <StepList\n job={job}\n jobExecution={selectedJobExecution}\n selectedAttemptId={selectedAttemptId}\n defaultSelectedAttemptId={landingSelection?.attemptId}\n onSelectedAttemptChange={selectAttempt}\n autoSelectActiveAttempt\n emptyState={emptyStateForJob(job, selectedJobExecution)}\n showHeader={false}\n className=\"rounded-none border-0\"\n renderExpandedStep={(context) => (\n <ExpandedStep\n context={context}\n workspaceSlug={workspaceSlug}\n pageScrollRef={pageScrollRef}\n />\n )}\n />\n ) : (\n <EmptyStateForMissingExecution job={job} />\n )}\n </section>\n {showRetargetNotice && runningSelection ? (\n <div\n role=\"status\"\n aria-live=\"polite\"\n className=\"flex min-w-0 items-center justify-between gap-8 rounded-8 border border-border-neutral-base bg-background-components-base px-10 py-8\"\n >\n <Text size=\"xs\" className=\"min-w-0 text-foreground-neutral-muted\">\n Run moved on to{' '}\n <span className=\"font-code text-foreground-neutral-base\">\n {runningSelection.stepLabel}\n </span>\n .\n </Text>\n <button\n type=\"button\"\n className=\"shrink-0 rounded-4 px-6 py-4 text-xs font-medium text-foreground-highlight-interactive outline-none focus-visible:shadow-border-interactive-with-active\"\n onClick={retargetToRunningStep}\n >\n Jump to it\n </button>\n </div>\n ) : null}\n {succeededSummary ? (\n <Text size=\"xs\" className=\"px-8 text-foreground-neutral-muted\">\n {succeededSummary}\n </Text>\n ) : null}\n </section>\n </div>\n </div>\n </TimeTickerProvider>\n );\n}\n\nfunction ExpandedStep({\n context,\n workspaceSlug,\n pageScrollRef,\n}: {\n context: StepExpandedContext;\n workspaceSlug: string;\n pageScrollRef: RefObject<HTMLDivElement | null>;\n}) {\n if (context.carriedOver) return <CarriedOverStepPanel />;\n\n const selectedAttemptError =\n toSelectedAttemptError(context.step, context.attemptError) ?? context.step.error;\n\n return (\n <section\n role=\"region\"\n tabIndex={0}\n aria-label={`${context.stepLabel} output, attempt ${context.attempt}`}\n className=\"flex min-w-0 flex-col gap-10 border-t border-border-neutral-base bg-background-neutral-subtle px-16 py-12 outline-none focus-visible:shadow-border-interactive-with-active\"\n >\n {isAgentConfigFailure(context.step, selectedAttemptError) ? (\n <AgentConfigFailureCallout\n workspaceSlug={workspaceSlug}\n config={context.step.agentConfig}\n error={selectedAttemptError}\n />\n ) : null}\n <StepAttemptLogPanel\n stepId={context.stepId}\n attempt={context.attempt}\n attemptStatus={context.attemptStatus}\n attemptStartedAt={context.attemptStartedAt}\n pageScrollRef={pageScrollRef}\n surfaceClassName=\"rounded-8\"\n />\n </section>\n );\n}\n\nfunction EmptyStateForMissingExecution({job}: {job: Job}) {\n const emptyState = emptyStateForMissingExecution(job);\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\nfunction JobNotFoundState({\n workspaceSlug,\n projectSlug,\n workflowRunId,\n search,\n}: {\n workspaceSlug: string;\n projectSlug: string;\n workflowRunId: string;\n search: WorkflowJobSearch;\n}) {\n return (\n <div className=\"flex min-h-0 min-w-0 flex-1 flex-col items-center justify-center gap-12 p-24\">\n <EmptyState\n icon=\"componentLine\"\n title=\"Job not found\"\n description=\"This job is not part of the selected workflow run.\"\n />\n <Link\n to=\"/w/$workspaceSlug/p/$projectSlug/runs/$workflowRunId\"\n params={{workspaceSlug, projectSlug, workflowRunId}}\n search={\n workflowRunSearchParams(\n {runAttempt: search.runAttempt},\n {runAttempt: search.runAttempt},\n ) as never\n }\n className=\"inline-flex items-center gap-6 rounded-6 px-8 py-6 text-sm text-foreground-highlight-interactive outline-none focus-visible:shadow-border-interactive-with-active\"\n >\n <Icon name=\"arrowLeftLine\" size={14} aria-hidden=\"true\" />\n Back to run summary\n </Link>\n </div>\n );\n}\n\nfunction NewerAttemptNotice({\n workspaceSlug,\n projectSlug,\n runId,\n jobId,\n attempt,\n}: {\n workspaceSlug: string;\n projectSlug: string;\n runId: string;\n jobId: string;\n attempt: number;\n}) {\n return (\n <div role=\"status\" className=\"border-b border-border-neutral-base px-16 py-8\">\n <div className=\"mx-auto flex max-w-[1120px] items-center justify-between gap-8 rounded-8 border border-border-neutral-base bg-background-components-base px-10 py-8\">\n <Text size=\"xs\" className=\"text-foreground-neutral-muted\">\n A newer run attempt is available.\n </Text>\n <Link\n to=\"/w/$workspaceSlug/p/$projectSlug/runs/$workflowRunId/jobs/$jobId\"\n params={{workspaceSlug, projectSlug, workflowRunId: runId, jobId}}\n search={workflowJobSearchParams({runAttempt: attempt}) as never}\n className=\"shrink-0 rounded-4 px-6 py-4 text-xs font-medium text-foreground-highlight-interactive outline-none focus-visible:shadow-border-interactive-with-active\"\n >\n View attempt #{attempt}\n </Link>\n </div>\n </div>\n );\n}\n\nfunction runningStepSelection(jobExecution: JobExecution | undefined) {\n if (!jobExecution) return undefined;\n const steps = [...jobExecution.steps].sort(\n (left, right) => left.position - right.position || left.id.localeCompare(right.id),\n );\n for (let stepIndex = steps.length - 1; stepIndex >= 0; stepIndex -= 1) {\n const step = steps[stepIndex];\n if (!step) continue;\n const attempt = [...step.attempts]\n .sort((left, right) => left.attempt - right.attempt || left.id.localeCompare(right.id))\n .reverse()\n .find((candidate) => candidate.status === 'running');\n if (attempt)\n return {stepId: step.id, attemptId: attempt.id, stepLabel: step.name || step.key || step.id};\n }\n return undefined;\n}\n\nfunction findAttempt(jobExecution: JobExecution, attemptId: string) {\n for (const step of jobExecution.steps) {\n const attempt = step.attempts.find((candidate) => candidate.id === attemptId);\n if (attempt) return {step, attemptId: attempt.id};\n }\n return undefined;\n}\n\nfunction JobDetailSkeleton() {\n return (\n <div className=\"min-h-0 min-w-0 flex-1 overflow-auto px-24 pb-24\">\n <div className=\"mx-auto max-w-[1120px] overflow-hidden\">\n <header className=\"flex items-center gap-12 border-b border-border-neutral-base px-16 py-12\">\n <Skeleton className=\"size-20 rounded-full\" />\n <Skeleton className=\"h-20 w-160 rounded-4\" />\n <Skeleton className=\"h-24 w-72 rounded-6\" />\n <Skeleton className=\"h-24 w-180 rounded-4\" />\n </header>\n <div className=\"p-16\">\n {JOB_DETAIL_SKELETON_ROWS.map((row) => (\n <div\n key={row}\n className=\"flex min-h-44 items-center gap-8 border-b border-border-neutral-base last:border-b-0\"\n >\n <Skeleton className=\"size-14 rounded-full\" />\n <Skeleton className=\"h-16 w-180 rounded-4\" />\n </div>\n ))}\n </div>\n </div>\n </div>\n );\n}\n\nconst JOB_DETAIL_SKELETON_ROWS = ['step-1', 'step-2', 'step-3', 'step-4', 'step-5'];\n\ninterface FrozenLandingSelection {\n jobId: string;\n jobExecutionId: string;\n selection: WorkflowJobLandingSelection | undefined;\n hasSelection: boolean;\n}\n"],"names":["ApiError","QueryLoadError","EmptyState","Icon","Skeleton","TimeTickerProvider","Text","Link","useEffect","useRef","workflowJobSearchParams","workflowRunSearchParams","StepList","WorkflowRunNotFound","WorkflowRunStaleError","AgentConfigFailureCallout","JobDetailHeader","CarriedOverStepPanel","emptyStateForJob","emptyStateForMissingExecution","isAgentConfigFailure","jobSucceededSummary","toSelectedAttemptError","resolveWorkflowJobSelection","workflowJobLandingSelection","StepAttemptLogPanel","JobDetailView","workspaceSlug","projectSlug","workflowRunId","jobId","search","query","newerAttempt","newerJob","onSelectionChange","rootRef","pageScrollRef","landingSelectionRef","undefined","hasLoadedData","data","heading","current","querySelector","focus","preventScroll","isPending","JobDetailSkeleton","isError","error","status","subject","icon","run","job","jobs","find","candidate","id","JobNotFoundState","resolvedSelection","selection","selectedJobExecution","jobExecution","hasExplicitStep","Boolean","stepId","step","currentLandingSelection","frozenLanding","jobExecutionId","hasSelection","landingSelection","selectedAttemptId","runningSelection","runningStepSelection","selectedStepId","selectedAttemptForNotice","attemptId","showRetargetNotice","succeededSummary","selectExecution","stepAttemptId","selectAttempt","match","findAttempt","retargetToRunningStep","intervalMs","reducedMotionIntervalMs","div","ref","className","runAttempt","attempt","NewerAttemptNotice","runId","section","aria-label","displayName","onSelectedJobExecutionChange","as","defaultSelectedAttemptId","onSelectedAttemptChange","autoSelectActiveAttempt","emptyState","showHeader","renderExpandedStep","context","ExpandedStep","EmptyStateForMissingExecution","role","aria-live","size","span","stepLabel","button","type","onClick","carriedOver","selectedAttemptError","attemptError","tabIndex","config","agentConfig","attemptStatus","attemptStartedAt","surfaceClassName","title","description","variant","to","params","name","aria-hidden","steps","sort","left","right","position","localeCompare","stepIndex","length","attempts","reverse","key","header","JOB_DETAIL_SKELETON_ROWS","map","row"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/components/job-detail/job-detail-view.tsx"],"sourcesContent":["// biome-ignore-all lint/a11y/noRedundantRoles: the job log region keeps an explicit role for the public accessibility contract.\n// biome-ignore-all lint/a11y/noNoninteractiveTabindex: the job log region is intentionally keyboard focusable.\n\nimport {ApiError} from '@shipfox/client-api';\nimport {QueryLoadError} from '@shipfox/client-ui';\nimport {EmptyState} from '@shipfox/react-ui/empty-state';\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {Skeleton} from '@shipfox/react-ui/skeleton';\nimport {TimeTickerProvider} from '@shipfox/react-ui/time-ticker';\nimport {Text} from '@shipfox/react-ui/typography';\nimport {Link} from '@tanstack/react-router';\nimport {type RefObject, useEffect, useRef} from 'react';\nimport {summarizeJobAnnotations} from '#core/run-annotation.js';\nimport type {Job, JobExecution} from '#core/workflow-run.js';\nimport {useRunAnnotationsQuery} from '#hooks/api/run-annotations.js';\nimport type {useWorkflowRunAttemptQuery} from '#hooks/api/workflow-runs.js';\nimport {\n type WorkflowJobSearch,\n workflowJobSearchParams,\n workflowRunSearchParams,\n} from '#routes/inputs.js';\nimport {type StepExpandedContext, StepList} from '../step-list/index.js';\nimport {\n WorkflowRunNotFound,\n WorkflowRunStaleError,\n} from '../workflow-run-view/workflow-run-states.js';\nimport {AgentConfigFailureCallout} from './agent-config-failure-callout.js';\nimport {JobDetailHeader} from './job-detail-header.js';\nimport {\n CarriedOverStepPanel,\n emptyStateForJob,\n emptyStateForMissingExecution,\n isAgentConfigFailure,\n jobSucceededSummary,\n toSelectedAttemptError,\n} from './job-empty-states.js';\nimport {\n resolveWorkflowJobSelection,\n type WorkflowJobLandingSelection,\n workflowJobLandingSelection,\n} from './job-selection.js';\nimport {StepAttemptLogPanel} from './step-attempt-log-panel.js';\n\nexport interface JobDetailViewProps {\n workspaceSlug: string;\n projectSlug: string;\n workflowRunId: string;\n jobId: string;\n search: WorkflowJobSearch;\n query: ReturnType<typeof useWorkflowRunAttemptQuery>;\n newerAttempt?: number | undefined;\n newerJob?: Job | undefined;\n onSelectionChange: (selection: WorkflowJobSearch) => void;\n}\n\nexport function JobDetailView({\n workspaceSlug,\n projectSlug,\n workflowRunId,\n jobId,\n search,\n query,\n newerAttempt,\n newerJob,\n onSelectionChange,\n}: JobDetailViewProps) {\n const rootRef = useRef<HTMLDivElement>(null);\n const pageScrollRef = useRef<HTMLDivElement>(null);\n const landingSelectionRef = useRef<FrozenLandingSelection | undefined>(undefined);\n const hasLoadedData = query.data !== undefined;\n // Same query key as the run workspace, so this reads the cache instead of fetching again.\n const annotations = useRunAnnotationsQuery({\n workflowRunId,\n runAttempt: query.data?.runAttempt.attempt,\n });\n const jobAnnotationSummary = annotations.annotations\n ? summarizeJobAnnotations(annotations.annotations, jobId, {\n truncated: annotations.summary?.truncated ?? false,\n })\n : undefined;\n\n useEffect(() => {\n if (!jobId || !hasLoadedData) return;\n const heading = rootRef.current?.querySelector<HTMLElement>('[data-job-heading]');\n // The heading is intentionally the route-change focus target: it announces the new job\n // after rail navigation without putting focus on a decorative status element.\n heading?.focus({preventScroll: true});\n }, [hasLoadedData, jobId]);\n\n if (query.isPending) return <JobDetailSkeleton />;\n\n if (query.isError && query.data === undefined) {\n if (query.error instanceof ApiError && query.error.status === 404) {\n return <WorkflowRunNotFound />;\n }\n return <QueryLoadError query={query} subject=\"workflow run\" icon=\"pulseLine\" />;\n }\n\n if (query.data === undefined) return <JobDetailSkeleton />;\n\n const run = query.data;\n const job = run.jobs.find((candidate) => candidate.id === jobId);\n if (!job) {\n return (\n <JobNotFoundState\n workspaceSlug={workspaceSlug}\n projectSlug={projectSlug}\n workflowRunId={workflowRunId}\n search={search}\n />\n );\n }\n\n const resolvedSelection = resolveWorkflowJobSelection({job, selection: search});\n const selectedJobExecution = resolvedSelection.jobExecution;\n const hasExplicitStep = Boolean(search.stepId && resolvedSelection.step);\n const currentLandingSelection = workflowJobLandingSelection(selectedJobExecution);\n if (selectedJobExecution) {\n const frozenLanding = landingSelectionRef.current;\n if (\n !frozenLanding ||\n frozenLanding.jobId !== job.id ||\n frozenLanding.jobExecutionId !== selectedJobExecution.id\n ) {\n landingSelectionRef.current = {\n jobId: job.id,\n jobExecutionId: selectedJobExecution.id,\n selection: currentLandingSelection,\n hasSelection: currentLandingSelection !== undefined,\n };\n } else if (!frozenLanding.hasSelection && currentLandingSelection) {\n landingSelectionRef.current = {\n ...frozenLanding,\n selection: currentLandingSelection,\n hasSelection: true,\n };\n }\n } else {\n landingSelectionRef.current = undefined;\n }\n const landingSelection = landingSelectionRef.current?.selection;\n const selectedAttemptId = hasExplicitStep ? resolvedSelection.selectedAttemptId : undefined;\n const runningSelection = runningStepSelection(selectedJobExecution);\n const selectedStepId = resolvedSelection.step?.id ?? landingSelection?.stepId;\n const selectedAttemptForNotice = hasExplicitStep\n ? resolvedSelection.selectedAttemptId\n : landingSelection?.attemptId;\n const showRetargetNotice =\n runningSelection !== undefined &&\n (selectedStepId !== runningSelection.stepId ||\n selectedAttemptForNotice !== runningSelection.attemptId);\n const succeededSummary =\n !hasExplicitStep && landingSelection === undefined && selectedJobExecution\n ? jobSucceededSummary(job, selectedJobExecution)\n : undefined;\n\n function selectExecution(jobExecutionId: string) {\n onSelectionChange({\n ...search,\n jobExecutionId,\n stepId: undefined,\n stepAttemptId: undefined,\n });\n }\n\n function selectAttempt(attemptId: string | undefined) {\n if (!selectedJobExecution) return;\n if (!attemptId) {\n onSelectionChange({\n ...search,\n jobExecutionId: selectedJobExecution.id,\n stepId: undefined,\n stepAttemptId: undefined,\n });\n return;\n }\n\n const match = findAttempt(selectedJobExecution, attemptId);\n if (!match) return;\n onSelectionChange({\n ...search,\n jobExecutionId: selectedJobExecution.id,\n stepId: match.step.id,\n stepAttemptId: match.attemptId,\n });\n }\n\n function retargetToRunningStep() {\n if (!runningSelection || !selectedJobExecution) return;\n onSelectionChange({\n ...search,\n jobExecutionId: selectedJobExecution.id,\n stepId: runningSelection.stepId,\n stepAttemptId: runningSelection.attemptId,\n });\n }\n\n return (\n <TimeTickerProvider intervalMs={1000} reducedMotionIntervalMs={10_000}>\n <div ref={rootRef} className=\"flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden\">\n {query.isError ? <WorkflowRunStaleError query={query} /> : null}\n {newerJob && newerAttempt && newerAttempt > run.runAttempt.attempt ? (\n <NewerAttemptNotice\n workspaceSlug={workspaceSlug}\n projectSlug={projectSlug}\n runId={run.id}\n jobId={newerJob.id}\n attempt={newerAttempt}\n />\n ) : null}\n <div ref={pageScrollRef} className=\"@container min-h-0 flex-1 overflow-auto pb-24\">\n <section\n aria-label={`${job.displayName} logs`}\n className=\"mx-auto flex w-full max-w-[1120px] flex-col gap-8 px-24\"\n >\n <section className=\"min-w-0 overflow-hidden\">\n <JobDetailHeader\n job={job}\n selectedJobExecution={selectedJobExecution}\n onSelectedJobExecutionChange={selectExecution}\n workspaceSlug={workspaceSlug}\n projectSlug={projectSlug}\n workflowRunId={workflowRunId}\n runAttempt={run.runAttempt.attempt}\n annotationSummary={jobAnnotationSummary}\n />\n <Text as=\"h3\" className=\"sr-only\">\n Logs\n </Text>\n {selectedJobExecution ? (\n <StepList\n job={job}\n jobExecution={selectedJobExecution}\n selectedAttemptId={selectedAttemptId}\n defaultSelectedAttemptId={landingSelection?.attemptId}\n onSelectedAttemptChange={selectAttempt}\n autoSelectActiveAttempt\n emptyState={emptyStateForJob(job, selectedJobExecution)}\n showHeader={false}\n className=\"rounded-none border-0\"\n renderExpandedStep={(context) => (\n <ExpandedStep\n context={context}\n workspaceSlug={workspaceSlug}\n pageScrollRef={pageScrollRef}\n />\n )}\n />\n ) : (\n <EmptyStateForMissingExecution job={job} />\n )}\n </section>\n {showRetargetNotice && runningSelection ? (\n <div\n role=\"status\"\n aria-live=\"polite\"\n className=\"flex min-w-0 items-center justify-between gap-8 rounded-8 border border-border-neutral-base bg-background-components-base px-10 py-8\"\n >\n <Text size=\"xs\" className=\"min-w-0 text-foreground-neutral-muted\">\n Run moved on to{' '}\n <span className=\"font-code text-foreground-neutral-base\">\n {runningSelection.stepLabel}\n </span>\n .\n </Text>\n <button\n type=\"button\"\n className=\"shrink-0 rounded-4 px-6 py-4 text-xs font-medium text-foreground-highlight-interactive outline-none focus-visible:shadow-border-interactive-with-active\"\n onClick={retargetToRunningStep}\n >\n Jump to it\n </button>\n </div>\n ) : null}\n {succeededSummary ? (\n <Text size=\"xs\" className=\"px-8 text-foreground-neutral-muted\">\n {succeededSummary}\n </Text>\n ) : null}\n </section>\n </div>\n </div>\n </TimeTickerProvider>\n );\n}\n\nfunction ExpandedStep({\n context,\n workspaceSlug,\n pageScrollRef,\n}: {\n context: StepExpandedContext;\n workspaceSlug: string;\n pageScrollRef: RefObject<HTMLDivElement | null>;\n}) {\n if (context.carriedOver) return <CarriedOverStepPanel />;\n\n const selectedAttemptError =\n toSelectedAttemptError(context.step, context.attemptError) ?? context.step.error;\n\n return (\n <section\n role=\"region\"\n tabIndex={0}\n aria-label={`${context.stepLabel} output, attempt ${context.attempt}`}\n className=\"flex min-w-0 flex-col gap-10 border-t border-border-neutral-base bg-background-neutral-subtle px-16 py-12 outline-none focus-visible:shadow-border-interactive-with-active\"\n >\n {isAgentConfigFailure(context.step, selectedAttemptError) ? (\n <AgentConfigFailureCallout\n workspaceSlug={workspaceSlug}\n config={context.step.agentConfig}\n error={selectedAttemptError}\n />\n ) : null}\n <StepAttemptLogPanel\n stepId={context.stepId}\n attempt={context.attempt}\n attemptStatus={context.attemptStatus}\n attemptStartedAt={context.attemptStartedAt}\n pageScrollRef={pageScrollRef}\n surfaceClassName=\"rounded-8\"\n />\n </section>\n );\n}\n\nfunction EmptyStateForMissingExecution({job}: {job: Job}) {\n const emptyState = emptyStateForMissingExecution(job);\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\nfunction JobNotFoundState({\n workspaceSlug,\n projectSlug,\n workflowRunId,\n search,\n}: {\n workspaceSlug: string;\n projectSlug: string;\n workflowRunId: string;\n search: WorkflowJobSearch;\n}) {\n return (\n <div className=\"flex min-h-0 min-w-0 flex-1 flex-col items-center justify-center gap-12 p-24\">\n <EmptyState\n icon=\"componentLine\"\n title=\"Job not found\"\n description=\"This job is not part of the selected workflow run.\"\n />\n <Link\n to=\"/w/$workspaceSlug/p/$projectSlug/runs/$workflowRunId\"\n params={{workspaceSlug, projectSlug, workflowRunId}}\n search={\n workflowRunSearchParams(\n {runAttempt: search.runAttempt},\n {runAttempt: search.runAttempt},\n ) as never\n }\n className=\"inline-flex items-center gap-6 rounded-6 px-8 py-6 text-sm text-foreground-highlight-interactive outline-none focus-visible:shadow-border-interactive-with-active\"\n >\n <Icon name=\"arrowLeftLine\" size={14} aria-hidden=\"true\" />\n Back to run summary\n </Link>\n </div>\n );\n}\n\nfunction NewerAttemptNotice({\n workspaceSlug,\n projectSlug,\n runId,\n jobId,\n attempt,\n}: {\n workspaceSlug: string;\n projectSlug: string;\n runId: string;\n jobId: string;\n attempt: number;\n}) {\n return (\n <div role=\"status\" className=\"border-b border-border-neutral-base px-16 py-8\">\n <div className=\"mx-auto flex max-w-[1120px] items-center justify-between gap-8 rounded-8 border border-border-neutral-base bg-background-components-base px-10 py-8\">\n <Text size=\"xs\" className=\"text-foreground-neutral-muted\">\n A newer run attempt is available.\n </Text>\n <Link\n to=\"/w/$workspaceSlug/p/$projectSlug/runs/$workflowRunId/jobs/$jobId\"\n params={{workspaceSlug, projectSlug, workflowRunId: runId, jobId}}\n search={workflowJobSearchParams({runAttempt: attempt}) as never}\n className=\"shrink-0 rounded-4 px-6 py-4 text-xs font-medium text-foreground-highlight-interactive outline-none focus-visible:shadow-border-interactive-with-active\"\n >\n View attempt #{attempt}\n </Link>\n </div>\n </div>\n );\n}\n\nfunction runningStepSelection(jobExecution: JobExecution | undefined) {\n if (!jobExecution) return undefined;\n const steps = [...jobExecution.steps].sort(\n (left, right) => left.position - right.position || left.id.localeCompare(right.id),\n );\n for (let stepIndex = steps.length - 1; stepIndex >= 0; stepIndex -= 1) {\n const step = steps[stepIndex];\n if (!step) continue;\n const attempt = [...step.attempts]\n .sort((left, right) => left.attempt - right.attempt || left.id.localeCompare(right.id))\n .reverse()\n .find((candidate) => candidate.status === 'running');\n if (attempt)\n return {stepId: step.id, attemptId: attempt.id, stepLabel: step.name || step.key || step.id};\n }\n return undefined;\n}\n\nfunction findAttempt(jobExecution: JobExecution, attemptId: string) {\n for (const step of jobExecution.steps) {\n const attempt = step.attempts.find((candidate) => candidate.id === attemptId);\n if (attempt) return {step, attemptId: attempt.id};\n }\n return undefined;\n}\n\nfunction JobDetailSkeleton() {\n return (\n <div className=\"min-h-0 min-w-0 flex-1 overflow-auto px-24 pb-24\">\n <div className=\"mx-auto max-w-[1120px] overflow-hidden\">\n <header className=\"flex items-center gap-12 border-b border-border-neutral-base px-16 py-12\">\n <Skeleton className=\"size-20 rounded-full\" />\n <Skeleton className=\"h-20 w-160 rounded-4\" />\n <Skeleton className=\"h-24 w-72 rounded-6\" />\n <Skeleton className=\"h-24 w-180 rounded-4\" />\n </header>\n <div className=\"p-16\">\n {JOB_DETAIL_SKELETON_ROWS.map((row) => (\n <div\n key={row}\n className=\"flex min-h-44 items-center gap-8 border-b border-border-neutral-base last:border-b-0\"\n >\n <Skeleton className=\"size-14 rounded-full\" />\n <Skeleton className=\"h-16 w-180 rounded-4\" />\n </div>\n ))}\n </div>\n </div>\n </div>\n );\n}\n\nconst JOB_DETAIL_SKELETON_ROWS = ['step-1', 'step-2', 'step-3', 'step-4', 'step-5'];\n\ninterface FrozenLandingSelection {\n jobId: string;\n jobExecutionId: string;\n selection: WorkflowJobLandingSelection | undefined;\n hasSelection: boolean;\n}\n"],"names":["ApiError","QueryLoadError","EmptyState","Icon","Skeleton","TimeTickerProvider","Text","Link","useEffect","useRef","summarizeJobAnnotations","useRunAnnotationsQuery","workflowJobSearchParams","workflowRunSearchParams","StepList","WorkflowRunNotFound","WorkflowRunStaleError","AgentConfigFailureCallout","JobDetailHeader","CarriedOverStepPanel","emptyStateForJob","emptyStateForMissingExecution","isAgentConfigFailure","jobSucceededSummary","toSelectedAttemptError","resolveWorkflowJobSelection","workflowJobLandingSelection","StepAttemptLogPanel","JobDetailView","workspaceSlug","projectSlug","workflowRunId","jobId","search","query","newerAttempt","newerJob","onSelectionChange","rootRef","pageScrollRef","landingSelectionRef","undefined","hasLoadedData","data","annotations","runAttempt","attempt","jobAnnotationSummary","truncated","summary","heading","current","querySelector","focus","preventScroll","isPending","JobDetailSkeleton","isError","error","status","subject","icon","run","job","jobs","find","candidate","id","JobNotFoundState","resolvedSelection","selection","selectedJobExecution","jobExecution","hasExplicitStep","Boolean","stepId","step","currentLandingSelection","frozenLanding","jobExecutionId","hasSelection","landingSelection","selectedAttemptId","runningSelection","runningStepSelection","selectedStepId","selectedAttemptForNotice","attemptId","showRetargetNotice","succeededSummary","selectExecution","stepAttemptId","selectAttempt","match","findAttempt","retargetToRunningStep","intervalMs","reducedMotionIntervalMs","div","ref","className","NewerAttemptNotice","runId","section","aria-label","displayName","onSelectedJobExecutionChange","annotationSummary","as","defaultSelectedAttemptId","onSelectedAttemptChange","autoSelectActiveAttempt","emptyState","showHeader","renderExpandedStep","context","ExpandedStep","EmptyStateForMissingExecution","role","aria-live","size","span","stepLabel","button","type","onClick","carriedOver","selectedAttemptError","attemptError","tabIndex","config","agentConfig","attemptStatus","attemptStartedAt","surfaceClassName","title","description","variant","to","params","name","aria-hidden","steps","sort","left","right","position","localeCompare","stepIndex","length","attempts","reverse","key","header","JOB_DETAIL_SKELETON_ROWS","map","row"],"mappings":"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"}
|
|
@@ -65,6 +65,7 @@ export function WorkflowRunSummary({ workspaceSlug, projectSlug, run, cancelling
|
|
|
65
65
|
/*#__PURE__*/ _jsx(TooltipTrigger, {
|
|
66
66
|
asChild: true,
|
|
67
67
|
children: /*#__PURE__*/ _jsx(Header, {
|
|
68
|
+
as: "h1",
|
|
68
69
|
id: headingId,
|
|
69
70
|
variant: "h3",
|
|
70
71
|
className: "min-w-0 truncate",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/workflow-run-summary/workflow-run-summary.tsx"],"sourcesContent":["import {TriggerSourceIcon} from '@shipfox/client-triggers';\nimport {Badge} from '@shipfox/react-ui/badge';\nimport {Button} from '@shipfox/react-ui/button';\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from '@shipfox/react-ui/dropdown-menu';\nimport {useIsTextTruncated} from '@shipfox/react-ui/hooks';\nimport {RelativeTime} from '@shipfox/react-ui/relative-time';\nimport {TimeTickerProvider} from '@shipfox/react-ui/time-ticker';\nimport {Tooltip, TooltipContent, TooltipTrigger} from '@shipfox/react-ui/tooltip';\nimport {Code, Header, Text} from '@shipfox/react-ui/typography';\nimport {useId} from 'react';\nimport {WorkflowRunNumberLabel} from '#components/workflow-run-number-label.js';\nimport {\n isWorkflowRunTerminal,\n type Job,\n WORKFLOW_RUN_STATUSES,\n type WorkflowRunDetail,\n type WorkflowRunRerunMode,\n workflowRunBlockingJob,\n workflowRunDetailDisplay,\n} from '#core/workflow-run.js';\nimport {WorkflowRunDurationLabel} from '../workflow-run-duration-label.js';\nimport {getWorkflowStatusVisual} from '../workflow-status/status-visuals.js';\nimport {WorkflowRunAttemptSwitcher} from './workflow-run-attempt-switcher.js';\n\nconst STATUS_BADGE_LABEL_WIDTH_CH = Math.max(\n ...[...WORKFLOW_RUN_STATUSES, 'queued' as const].map(\n (status) => getWorkflowStatusVisual(status).label.length,\n ),\n);\nconst RERUN_BUTTON_SURFACE_CLASS_NAME =\n 'bg-background-neutral-base hover:bg-background-neutral-hover active:bg-background-neutral-pressed';\n\ntype WorkflowRunAction = 'cancel' | 'rerun-all' | 'rerun-menu' | 'none';\n\nexport interface WorkflowRunSummaryProps {\n workspaceSlug?: string | undefined;\n projectSlug?: string | undefined;\n run: WorkflowRunDetail;\n cancelling?: boolean | undefined;\n onCancel?: (() => void) | undefined;\n rerunPending?: boolean | undefined;\n onRerun?: ((mode: WorkflowRunRerunMode) => void) | undefined;\n latestAttempt?: number | undefined;\n}\n\nexport function WorkflowRunSummary({\n workspaceSlug,\n projectSlug,\n run,\n cancelling = false,\n onCancel,\n rerunPending = false,\n onRerun,\n latestAttempt,\n}: WorkflowRunSummaryProps) {\n const headingId = useId();\n const display = workflowRunDetailDisplay(run);\n const status = getWorkflowStatusVisual(display.status);\n const action = workflowRunActionForRun(run);\n const hasAction = canRenderWorkflowRunAction(action, onCancel, onRerun);\n const attemptSwitcher =\n latestAttempt && latestAttempt > 1 && workspaceSlug && projectSlug\n ? {workspaceSlug, projectSlug, latestAttempt}\n : null;\n const displayDuration = display.duration;\n // Named only while the run is waiting and has more than one job: with a single job the rail\n // and the graph already say which one it is.\n const blockingJob =\n display.status === 'queued' && run.jobs.length > 1 ? workflowRunBlockingJob(run.jobs) : null;\n const {ref: headingTextRef, isTruncated: isHeadingTruncated} =\n useIsTextTruncated<HTMLSpanElement>(run.name);\n\n return (\n <TimeTickerProvider intervalMs={1000} reducedMotionIntervalMs={10_000}>\n <section aria-labelledby={headingId} className=\"bg-background-neutral-background px-16 py-12\">\n <div className=\"grid min-w-0 grid-cols-[minmax(0,1fr)_auto] items-center gap-x-12 gap-y-8 overflow-hidden max-[480px]:grid-cols-1\">\n <div className=\"col-start-1 row-start-1 min-w-0 max-[480px]:col-start-auto max-[480px]:row-start-auto\">\n <div className=\"flex min-w-0 items-center gap-8\">\n <Badge variant={status.badge} size=\"xs\">\n <span className=\"text-center\" style={{width: `${STATUS_BADGE_LABEL_WIDTH_CH}ch`}}>\n {status.label}\n </span>\n </Badge>\n\n <Tooltip>\n <TooltipTrigger asChild>\n <Header id={headingId} variant=\"h3\" className=\"min-w-0 truncate\">\n <span ref={headingTextRef} className=\"block min-w-0 truncate\">\n {run.name}\n </span>\n </Header>\n </TooltipTrigger>\n {isHeadingTruncated ? (\n <TooltipContent>\n <Text as=\"span\" size=\"xs\" className=\"max-w-[360px] break-words\">\n {run.name}\n </Text>\n </TooltipContent>\n ) : null}\n </Tooltip>\n </div>\n </div>\n\n {hasAction ? (\n <div className=\"col-start-2 row-start-1 flex min-w-max items-center gap-6 justify-self-end max-[480px]:col-start-auto max-[480px]:row-start-auto max-[480px]:justify-self-start\">\n <WorkflowRunActionSlot\n action={action}\n cancelling={cancelling}\n onCancel={onCancel}\n rerunPending={rerunPending}\n onRerun={onRerun}\n />\n </div>\n ) : null}\n\n <div className=\"col-span-2 row-start-2 flex min-w-0 items-center gap-12 overflow-hidden text-foreground-neutral-subtle max-[480px]:col-span-1 max-[480px]:row-start-auto\">\n {run.number !== null ? (\n <>\n <WorkflowRunNumberLabel run={run} />\n {attemptSwitcher || run.triggerDisplayLabel ? <MetadataSeparator /> : null}\n </>\n ) : null}\n\n {attemptSwitcher ? (\n <WorkflowRunAttemptSwitcher\n workspaceSlug={attemptSwitcher.workspaceSlug}\n projectSlug={attemptSwitcher.projectSlug}\n run={run}\n latestAttempt={attemptSwitcher.latestAttempt}\n />\n ) : null}\n\n {run.triggerDisplayLabel ? (\n <>\n {attemptSwitcher ? <MetadataSeparator /> : null}\n <span className=\"min-w-0\">\n <Tooltip>\n <TooltipTrigger asChild>\n <button\n type=\"button\"\n aria-label={run.triggerLabel}\n className=\"inline-flex max-w-full min-w-0 cursor-help items-center gap-4 rounded-6 border-0 bg-transparent p-0 text-left text-foreground-neutral-subtle outline-none focus-visible:shadow-button-neutral-focus\"\n >\n <TriggerSourceIcon\n provider={run.triggerProvider}\n source={run.triggerSource}\n aria-hidden=\"true\"\n className=\"size-12 shrink-0\"\n />\n <Text as=\"span\" size=\"xs\" className=\"min-w-0 truncate\">\n {run.triggerDisplayLabel}\n </Text>\n </button>\n </TooltipTrigger>\n <TooltipContent>\n <Text as=\"span\" size=\"xs\" className=\"block max-w-[360px] break-words\">\n {run.triggerLabel}\n </Text>\n </TooltipContent>\n </Tooltip>\n </span>\n </>\n ) : null}\n\n {run.number !== null || attemptSwitcher || run.triggerDisplayLabel ? (\n <MetadataSeparator />\n ) : null}\n <RelativeTime\n value={run.runAttempt.createdAt}\n className=\"shrink-0 whitespace-nowrap text-xs leading-20 text-foreground-neutral-subtle\"\n />\n\n {displayDuration ? (\n <>\n <MetadataSeparator />\n <WorkflowRunDurationLabel\n duration={displayDuration}\n className=\"text-foreground-neutral-subtle\"\n />\n </>\n ) : null}\n\n {blockingJob ? (\n <>\n <MetadataSeparator />\n <span className=\"flex min-w-0 items-center gap-4\">\n <Text as=\"span\" size=\"xs\" className=\"shrink-0\">\n waiting on\n </Text>\n <Code as=\"span\" variant=\"label\" className=\"min-w-0 truncate\">\n {blockingJob.displayName}\n </Code>\n </span>\n </>\n ) : null}\n </div>\n </div>\n </section>\n </TimeTickerProvider>\n );\n}\n\nfunction WorkflowRunActionSlot({\n action,\n cancelling,\n onCancel,\n rerunPending,\n onRerun,\n}: {\n action: WorkflowRunAction;\n cancelling: boolean;\n onCancel?: (() => void) | undefined;\n rerunPending: boolean;\n onRerun?: ((mode: WorkflowRunRerunMode) => void) | undefined;\n}) {\n if (action === 'none') return null;\n\n if (action === 'cancel') {\n if (!onCancel) return null;\n\n return (\n <Button\n type=\"button\"\n variant=\"danger\"\n size=\"xs\"\n isLoading={cancelling}\n disabled={cancelling}\n onClick={onCancel}\n >\n Cancel workflow\n </Button>\n );\n }\n\n if (action === 'rerun-all') {\n if (!onRerun) return null;\n\n return (\n <Button\n type=\"button\"\n variant=\"secondary\"\n size=\"xs\"\n className={RERUN_BUTTON_SURFACE_CLASS_NAME}\n isLoading={rerunPending}\n disabled={rerunPending}\n onClick={() => onRerun('all')}\n >\n Re-run workflow\n </Button>\n );\n }\n\n if (!onRerun) return null;\n\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n type=\"button\"\n variant=\"secondary\"\n size=\"xs\"\n className={RERUN_BUTTON_SURFACE_CLASS_NAME}\n iconRight=\"arrowDownSLine\"\n isLoading={rerunPending}\n disabled={rerunPending}\n >\n Re-run jobs\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuItem disabled={rerunPending} onSelect={() => onRerun('all')}>\n Re-run all jobs\n </DropdownMenuItem>\n <DropdownMenuItem disabled={rerunPending} onSelect={() => onRerun('failed')}>\n Re-run failed jobs\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n\nfunction workflowRunActionForRun(run: WorkflowRunDetail): WorkflowRunAction {\n if (run.runAttempt.attempt !== run.currentAttempt) return 'none';\n if (!isWorkflowRunTerminal(run.runAttempt.status)) return 'cancel';\n if (run.runAttempt.status === 'succeeded' || !hasFailedOrCancelledJobs(run)) return 'rerun-all';\n return 'rerun-menu';\n}\n\nfunction canRenderWorkflowRunAction(\n action: WorkflowRunAction,\n onCancel: (() => void) | undefined,\n onRerun: ((mode: WorkflowRunRerunMode) => void) | undefined,\n): boolean {\n if (action === 'cancel') return onCancel !== undefined;\n if (action === 'rerun-all' || action === 'rerun-menu') return onRerun !== undefined;\n return false;\n}\n\nfunction hasFailedOrCancelledJobs(run: WorkflowRunDetail): boolean {\n if (!workflowRunHasJobs(run)) return false;\n\n return run.jobs.some((job) => job.status === 'failed' || job.status === 'cancelled');\n}\n\nfunction workflowRunHasJobs(run: WorkflowRunDetail): run is WorkflowRunDetail & {jobs: Job[]} {\n return 'jobs' in run && Array.isArray(run.jobs);\n}\n\nexport function MetadataSeparator() {\n return <span aria-hidden=\"true\" className=\"h-12 w-px shrink-0 bg-border-neutral-base\" />;\n}\n"],"names":["TriggerSourceIcon","Badge","Button","DropdownMenu","DropdownMenuContent","DropdownMenuItem","DropdownMenuTrigger","useIsTextTruncated","RelativeTime","TimeTickerProvider","Tooltip","TooltipContent","TooltipTrigger","Code","Header","Text","useId","WorkflowRunNumberLabel","isWorkflowRunTerminal","WORKFLOW_RUN_STATUSES","workflowRunBlockingJob","workflowRunDetailDisplay","WorkflowRunDurationLabel","getWorkflowStatusVisual","WorkflowRunAttemptSwitcher","STATUS_BADGE_LABEL_WIDTH_CH","Math","max","map","status","label","length","RERUN_BUTTON_SURFACE_CLASS_NAME","WorkflowRunSummary","workspaceSlug","projectSlug","run","cancelling","onCancel","rerunPending","onRerun","latestAttempt","headingId","display","action","workflowRunActionForRun","hasAction","canRenderWorkflowRunAction","attemptSwitcher","displayDuration","duration","blockingJob","jobs","ref","headingTextRef","isTruncated","isHeadingTruncated","name","intervalMs","reducedMotionIntervalMs","section","aria-labelledby","className","div","variant","badge","size","span","style","width","asChild","id","as","WorkflowRunActionSlot","number","triggerDisplayLabel","MetadataSeparator","button","type","aria-label","triggerLabel","provider","triggerProvider","source","triggerSource","aria-hidden","value","runAttempt","createdAt","displayName","isLoading","disabled","onClick","iconRight","align","onSelect","attempt","currentAttempt","hasFailedOrCancelledJobs","undefined","workflowRunHasJobs","some","job","Array","isArray"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/components/workflow-run-summary/workflow-run-summary.tsx"],"sourcesContent":["import {TriggerSourceIcon} from '@shipfox/client-triggers';\nimport {Badge} from '@shipfox/react-ui/badge';\nimport {Button} from '@shipfox/react-ui/button';\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from '@shipfox/react-ui/dropdown-menu';\nimport {useIsTextTruncated} from '@shipfox/react-ui/hooks';\nimport {RelativeTime} from '@shipfox/react-ui/relative-time';\nimport {TimeTickerProvider} from '@shipfox/react-ui/time-ticker';\nimport {Tooltip, TooltipContent, TooltipTrigger} from '@shipfox/react-ui/tooltip';\nimport {Code, Header, Text} from '@shipfox/react-ui/typography';\nimport {useId} from 'react';\nimport {WorkflowRunNumberLabel} from '#components/workflow-run-number-label.js';\nimport {\n isWorkflowRunTerminal,\n type Job,\n WORKFLOW_RUN_STATUSES,\n type WorkflowRunDetail,\n type WorkflowRunRerunMode,\n workflowRunBlockingJob,\n workflowRunDetailDisplay,\n} from '#core/workflow-run.js';\nimport {WorkflowRunDurationLabel} from '../workflow-run-duration-label.js';\nimport {getWorkflowStatusVisual} from '../workflow-status/status-visuals.js';\nimport {WorkflowRunAttemptSwitcher} from './workflow-run-attempt-switcher.js';\n\nconst STATUS_BADGE_LABEL_WIDTH_CH = Math.max(\n ...[...WORKFLOW_RUN_STATUSES, 'queued' as const].map(\n (status) => getWorkflowStatusVisual(status).label.length,\n ),\n);\nconst RERUN_BUTTON_SURFACE_CLASS_NAME =\n 'bg-background-neutral-base hover:bg-background-neutral-hover active:bg-background-neutral-pressed';\n\ntype WorkflowRunAction = 'cancel' | 'rerun-all' | 'rerun-menu' | 'none';\n\nexport interface WorkflowRunSummaryProps {\n workspaceSlug?: string | undefined;\n projectSlug?: string | undefined;\n run: WorkflowRunDetail;\n cancelling?: boolean | undefined;\n onCancel?: (() => void) | undefined;\n rerunPending?: boolean | undefined;\n onRerun?: ((mode: WorkflowRunRerunMode) => void) | undefined;\n latestAttempt?: number | undefined;\n}\n\nexport function WorkflowRunSummary({\n workspaceSlug,\n projectSlug,\n run,\n cancelling = false,\n onCancel,\n rerunPending = false,\n onRerun,\n latestAttempt,\n}: WorkflowRunSummaryProps) {\n const headingId = useId();\n const display = workflowRunDetailDisplay(run);\n const status = getWorkflowStatusVisual(display.status);\n const action = workflowRunActionForRun(run);\n const hasAction = canRenderWorkflowRunAction(action, onCancel, onRerun);\n const attemptSwitcher =\n latestAttempt && latestAttempt > 1 && workspaceSlug && projectSlug\n ? {workspaceSlug, projectSlug, latestAttempt}\n : null;\n const displayDuration = display.duration;\n // Named only while the run is waiting and has more than one job: with a single job the rail\n // and the graph already say which one it is.\n const blockingJob =\n display.status === 'queued' && run.jobs.length > 1 ? workflowRunBlockingJob(run.jobs) : null;\n const {ref: headingTextRef, isTruncated: isHeadingTruncated} =\n useIsTextTruncated<HTMLSpanElement>(run.name);\n\n return (\n <TimeTickerProvider intervalMs={1000} reducedMotionIntervalMs={10_000}>\n <section aria-labelledby={headingId} className=\"bg-background-neutral-background px-16 py-12\">\n <div className=\"grid min-w-0 grid-cols-[minmax(0,1fr)_auto] items-center gap-x-12 gap-y-8 overflow-hidden max-[480px]:grid-cols-1\">\n <div className=\"col-start-1 row-start-1 min-w-0 max-[480px]:col-start-auto max-[480px]:row-start-auto\">\n <div className=\"flex min-w-0 items-center gap-8\">\n <Badge variant={status.badge} size=\"xs\">\n <span className=\"text-center\" style={{width: `${STATUS_BADGE_LABEL_WIDTH_CH}ch`}}>\n {status.label}\n </span>\n </Badge>\n\n <Tooltip>\n <TooltipTrigger asChild>\n <Header as=\"h1\" id={headingId} variant=\"h3\" className=\"min-w-0 truncate\">\n <span ref={headingTextRef} className=\"block min-w-0 truncate\">\n {run.name}\n </span>\n </Header>\n </TooltipTrigger>\n {isHeadingTruncated ? (\n <TooltipContent>\n <Text as=\"span\" size=\"xs\" className=\"max-w-[360px] break-words\">\n {run.name}\n </Text>\n </TooltipContent>\n ) : null}\n </Tooltip>\n </div>\n </div>\n\n {hasAction ? (\n <div className=\"col-start-2 row-start-1 flex min-w-max items-center gap-6 justify-self-end max-[480px]:col-start-auto max-[480px]:row-start-auto max-[480px]:justify-self-start\">\n <WorkflowRunActionSlot\n action={action}\n cancelling={cancelling}\n onCancel={onCancel}\n rerunPending={rerunPending}\n onRerun={onRerun}\n />\n </div>\n ) : null}\n\n <div className=\"col-span-2 row-start-2 flex min-w-0 items-center gap-12 overflow-hidden text-foreground-neutral-subtle max-[480px]:col-span-1 max-[480px]:row-start-auto\">\n {run.number !== null ? (\n <>\n <WorkflowRunNumberLabel run={run} />\n {attemptSwitcher || run.triggerDisplayLabel ? <MetadataSeparator /> : null}\n </>\n ) : null}\n\n {attemptSwitcher ? (\n <WorkflowRunAttemptSwitcher\n workspaceSlug={attemptSwitcher.workspaceSlug}\n projectSlug={attemptSwitcher.projectSlug}\n run={run}\n latestAttempt={attemptSwitcher.latestAttempt}\n />\n ) : null}\n\n {run.triggerDisplayLabel ? (\n <>\n {attemptSwitcher ? <MetadataSeparator /> : null}\n <span className=\"min-w-0\">\n <Tooltip>\n <TooltipTrigger asChild>\n <button\n type=\"button\"\n aria-label={run.triggerLabel}\n className=\"inline-flex max-w-full min-w-0 cursor-help items-center gap-4 rounded-6 border-0 bg-transparent p-0 text-left text-foreground-neutral-subtle outline-none focus-visible:shadow-button-neutral-focus\"\n >\n <TriggerSourceIcon\n provider={run.triggerProvider}\n source={run.triggerSource}\n aria-hidden=\"true\"\n className=\"size-12 shrink-0\"\n />\n <Text as=\"span\" size=\"xs\" className=\"min-w-0 truncate\">\n {run.triggerDisplayLabel}\n </Text>\n </button>\n </TooltipTrigger>\n <TooltipContent>\n <Text as=\"span\" size=\"xs\" className=\"block max-w-[360px] break-words\">\n {run.triggerLabel}\n </Text>\n </TooltipContent>\n </Tooltip>\n </span>\n </>\n ) : null}\n\n {run.number !== null || attemptSwitcher || run.triggerDisplayLabel ? (\n <MetadataSeparator />\n ) : null}\n <RelativeTime\n value={run.runAttempt.createdAt}\n className=\"shrink-0 whitespace-nowrap text-xs leading-20 text-foreground-neutral-subtle\"\n />\n\n {displayDuration ? (\n <>\n <MetadataSeparator />\n <WorkflowRunDurationLabel\n duration={displayDuration}\n className=\"text-foreground-neutral-subtle\"\n />\n </>\n ) : null}\n\n {blockingJob ? (\n <>\n <MetadataSeparator />\n <span className=\"flex min-w-0 items-center gap-4\">\n <Text as=\"span\" size=\"xs\" className=\"shrink-0\">\n waiting on\n </Text>\n <Code as=\"span\" variant=\"label\" className=\"min-w-0 truncate\">\n {blockingJob.displayName}\n </Code>\n </span>\n </>\n ) : null}\n </div>\n </div>\n </section>\n </TimeTickerProvider>\n );\n}\n\nfunction WorkflowRunActionSlot({\n action,\n cancelling,\n onCancel,\n rerunPending,\n onRerun,\n}: {\n action: WorkflowRunAction;\n cancelling: boolean;\n onCancel?: (() => void) | undefined;\n rerunPending: boolean;\n onRerun?: ((mode: WorkflowRunRerunMode) => void) | undefined;\n}) {\n if (action === 'none') return null;\n\n if (action === 'cancel') {\n if (!onCancel) return null;\n\n return (\n <Button\n type=\"button\"\n variant=\"danger\"\n size=\"xs\"\n isLoading={cancelling}\n disabled={cancelling}\n onClick={onCancel}\n >\n Cancel workflow\n </Button>\n );\n }\n\n if (action === 'rerun-all') {\n if (!onRerun) return null;\n\n return (\n <Button\n type=\"button\"\n variant=\"secondary\"\n size=\"xs\"\n className={RERUN_BUTTON_SURFACE_CLASS_NAME}\n isLoading={rerunPending}\n disabled={rerunPending}\n onClick={() => onRerun('all')}\n >\n Re-run workflow\n </Button>\n );\n }\n\n if (!onRerun) return null;\n\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n type=\"button\"\n variant=\"secondary\"\n size=\"xs\"\n className={RERUN_BUTTON_SURFACE_CLASS_NAME}\n iconRight=\"arrowDownSLine\"\n isLoading={rerunPending}\n disabled={rerunPending}\n >\n Re-run jobs\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuItem disabled={rerunPending} onSelect={() => onRerun('all')}>\n Re-run all jobs\n </DropdownMenuItem>\n <DropdownMenuItem disabled={rerunPending} onSelect={() => onRerun('failed')}>\n Re-run failed jobs\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n\nfunction workflowRunActionForRun(run: WorkflowRunDetail): WorkflowRunAction {\n if (run.runAttempt.attempt !== run.currentAttempt) return 'none';\n if (!isWorkflowRunTerminal(run.runAttempt.status)) return 'cancel';\n if (run.runAttempt.status === 'succeeded' || !hasFailedOrCancelledJobs(run)) return 'rerun-all';\n return 'rerun-menu';\n}\n\nfunction canRenderWorkflowRunAction(\n action: WorkflowRunAction,\n onCancel: (() => void) | undefined,\n onRerun: ((mode: WorkflowRunRerunMode) => void) | undefined,\n): boolean {\n if (action === 'cancel') return onCancel !== undefined;\n if (action === 'rerun-all' || action === 'rerun-menu') return onRerun !== undefined;\n return false;\n}\n\nfunction hasFailedOrCancelledJobs(run: WorkflowRunDetail): boolean {\n if (!workflowRunHasJobs(run)) return false;\n\n return run.jobs.some((job) => job.status === 'failed' || job.status === 'cancelled');\n}\n\nfunction workflowRunHasJobs(run: WorkflowRunDetail): run is WorkflowRunDetail & {jobs: Job[]} {\n return 'jobs' in run && Array.isArray(run.jobs);\n}\n\nexport function MetadataSeparator() {\n return <span aria-hidden=\"true\" className=\"h-12 w-px shrink-0 bg-border-neutral-base\" />;\n}\n"],"names":["TriggerSourceIcon","Badge","Button","DropdownMenu","DropdownMenuContent","DropdownMenuItem","DropdownMenuTrigger","useIsTextTruncated","RelativeTime","TimeTickerProvider","Tooltip","TooltipContent","TooltipTrigger","Code","Header","Text","useId","WorkflowRunNumberLabel","isWorkflowRunTerminal","WORKFLOW_RUN_STATUSES","workflowRunBlockingJob","workflowRunDetailDisplay","WorkflowRunDurationLabel","getWorkflowStatusVisual","WorkflowRunAttemptSwitcher","STATUS_BADGE_LABEL_WIDTH_CH","Math","max","map","status","label","length","RERUN_BUTTON_SURFACE_CLASS_NAME","WorkflowRunSummary","workspaceSlug","projectSlug","run","cancelling","onCancel","rerunPending","onRerun","latestAttempt","headingId","display","action","workflowRunActionForRun","hasAction","canRenderWorkflowRunAction","attemptSwitcher","displayDuration","duration","blockingJob","jobs","ref","headingTextRef","isTruncated","isHeadingTruncated","name","intervalMs","reducedMotionIntervalMs","section","aria-labelledby","className","div","variant","badge","size","span","style","width","asChild","as","id","WorkflowRunActionSlot","number","triggerDisplayLabel","MetadataSeparator","button","type","aria-label","triggerLabel","provider","triggerProvider","source","triggerSource","aria-hidden","value","runAttempt","createdAt","displayName","isLoading","disabled","onClick","iconRight","align","onSelect","attempt","currentAttempt","hasFailedOrCancelledJobs","undefined","workflowRunHasJobs","some","job","Array","isArray"],"mappings":"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"}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
export { RunAnnotationCountChip } from './run-annotation-count-chip.js';
|
|
2
|
+
export { annotationElementId, RunAnnotationItem } from './run-annotation-item.js';
|
|
3
|
+
export { RunAnnotationList, type RunAnnotationListQuery } from './run-annotation-list.js';
|
|
1
4
|
export { RunAnnotationSummaryLine } from './run-annotation-summary-line.js';
|
|
2
5
|
export { RunAnnotationsEmpty } from './run-annotations-empty.js';
|
|
3
6
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/workflow-run-tabs/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,wBAAwB,EAAC,MAAM,kCAAkC,CAAC;AAC1E,OAAO,EAAC,mBAAmB,EAAC,MAAM,4BAA4B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/workflow-run-tabs/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,sBAAsB,EAAC,MAAM,gCAAgC,CAAC;AACtE,OAAO,EAAC,mBAAmB,EAAE,iBAAiB,EAAC,MAAM,0BAA0B,CAAC;AAChF,OAAO,EAAC,iBAAiB,EAAE,KAAK,sBAAsB,EAAC,MAAM,0BAA0B,CAAC;AACxF,OAAO,EAAC,wBAAwB,EAAC,MAAM,kCAAkC,CAAC;AAC1E,OAAO,EAAC,mBAAmB,EAAC,MAAM,4BAA4B,CAAC"}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
export { RunAnnotationCountChip } from './run-annotation-count-chip.js';
|
|
2
|
+
export { annotationElementId, RunAnnotationItem } from './run-annotation-item.js';
|
|
3
|
+
export { RunAnnotationList } from './run-annotation-list.js';
|
|
1
4
|
export { RunAnnotationSummaryLine } from './run-annotation-summary-line.js';
|
|
2
5
|
export { RunAnnotationsEmpty } from './run-annotations-empty.js';
|
|
3
6
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/workflow-run-tabs/index.ts"],"sourcesContent":["export {RunAnnotationSummaryLine} from './run-annotation-summary-line.js';\nexport {RunAnnotationsEmpty} from './run-annotations-empty.js';\n"],"names":["RunAnnotationSummaryLine","RunAnnotationsEmpty"],"mappings":"AAAA,SAAQA,wBAAwB,QAAO,mCAAmC;AAC1E,SAAQC,mBAAmB,QAAO,6BAA6B"}
|
|
1
|
+
{"version":3,"sources":["../../../src/components/workflow-run-tabs/index.ts"],"sourcesContent":["export {RunAnnotationCountChip} from './run-annotation-count-chip.js';\nexport {annotationElementId, RunAnnotationItem} from './run-annotation-item.js';\nexport {RunAnnotationList, type RunAnnotationListQuery} from './run-annotation-list.js';\nexport {RunAnnotationSummaryLine} from './run-annotation-summary-line.js';\nexport {RunAnnotationsEmpty} from './run-annotations-empty.js';\n"],"names":["RunAnnotationCountChip","annotationElementId","RunAnnotationItem","RunAnnotationList","RunAnnotationSummaryLine","RunAnnotationsEmpty"],"mappings":"AAAA,SAAQA,sBAAsB,QAAO,iCAAiC;AACtE,SAAQC,mBAAmB,EAAEC,iBAAiB,QAAO,2BAA2B;AAChF,SAAQC,iBAAiB,QAAoC,2BAA2B;AACxF,SAAQC,wBAAwB,QAAO,mCAAmC;AAC1E,SAAQC,mBAAmB,QAAO,6BAA6B"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type RunAnnotationSummary } from '#core/run-annotation.js';
|
|
2
|
+
export interface RunAnnotationCountChipProps {
|
|
3
|
+
summary: RunAnnotationSummary | undefined;
|
|
4
|
+
workspaceSlug: string;
|
|
5
|
+
projectSlug: string;
|
|
6
|
+
workflowRunId: string;
|
|
7
|
+
runAttempt?: number | undefined;
|
|
8
|
+
/** Scopes the destination list to one job. */
|
|
9
|
+
jobId?: string | undefined;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* A bounded reference into the run's Annotations section: a count, its highest severity, and one
|
|
13
|
+
* link. Never a body, so an annotation is rendered exactly once per page.
|
|
14
|
+
*/
|
|
15
|
+
export declare function RunAnnotationCountChip({ summary, workspaceSlug, projectSlug, workflowRunId, runAttempt, jobId, }: RunAnnotationCountChipProps): import("react").JSX.Element | null;
|
|
16
|
+
//# sourceMappingURL=run-annotation-count-chip.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-annotation-count-chip.d.ts","sourceRoot":"","sources":["../../../src/components/workflow-run-tabs/run-annotation-count-chip.tsx"],"names":[],"mappings":"AAGA,OAAO,EAA+B,KAAK,oBAAoB,EAAC,MAAM,yBAAyB,CAAC;AAIhG,MAAM,WAAW,2BAA2B;IAC1C,OAAO,EAAE,oBAAoB,GAAG,SAAS,CAAC;IAC1C,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,8CAA8C;IAC9C,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5B;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,EACrC,OAAO,EACP,aAAa,EACb,WAAW,EACX,aAAa,EACb,UAAU,EACV,KAAK,GACN,EAAE,2BAA2B,sCAkC7B"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Icon } from '@shipfox/react-ui/icon';
|
|
3
|
+
import { cn } from '@shipfox/react-ui/utils';
|
|
4
|
+
import { Link } from '@tanstack/react-router';
|
|
5
|
+
import { highestRunAnnotationSeverity } from '#core/run-annotation.js';
|
|
6
|
+
import { workflowRunSearchParams } from '#routes/inputs.js';
|
|
7
|
+
import { SEVERITY_CHIP_TONE, SEVERITY_ICON } from './severity-visuals.js';
|
|
8
|
+
/**
|
|
9
|
+
* A bounded reference into the run's Annotations section: a count, its highest severity, and one
|
|
10
|
+
* link. Never a body, so an annotation is rendered exactly once per page.
|
|
11
|
+
*/ export function RunAnnotationCountChip({ summary, workspaceSlug, projectSlug, workflowRunId, runAttempt, jobId }) {
|
|
12
|
+
if (!summary || summary.total === 0) return null;
|
|
13
|
+
const severity = highestRunAnnotationSeverity(summary);
|
|
14
|
+
const count = `${summary.total}${summary.truncated ? '+' : ''}`;
|
|
15
|
+
const label = `${count} ${summary.total === 1 && !summary.truncated ? 'annotation' : 'annotations'}`;
|
|
16
|
+
const accessibleLabel = severity ? `View ${label}, highest severity ${severity}` : `View ${label}`;
|
|
17
|
+
const search = {
|
|
18
|
+
tab: 'annotations',
|
|
19
|
+
...runAttempt ? {
|
|
20
|
+
runAttempt
|
|
21
|
+
} : {}
|
|
22
|
+
};
|
|
23
|
+
if (jobId) search.jobId = jobId;
|
|
24
|
+
return /*#__PURE__*/ _jsxs(Link, {
|
|
25
|
+
to: "/w/$workspaceSlug/p/$projectSlug/runs/$workflowRunId",
|
|
26
|
+
params: {
|
|
27
|
+
workspaceSlug,
|
|
28
|
+
projectSlug,
|
|
29
|
+
workflowRunId
|
|
30
|
+
},
|
|
31
|
+
search: workflowRunSearchParams(search, search),
|
|
32
|
+
"aria-label": accessibleLabel,
|
|
33
|
+
className: cn('inline-flex h-20 shrink-0 items-center gap-4 rounded-4 border px-6 font-code text-xs leading-16 tabular-nums outline-none transition-colors hover:bg-background-neutral-hover focus-visible:shadow-border-interactive-with-active', // The chip stays 20px optically so it sits level with the duration metadata beside it;
|
|
34
|
+
// the hit area grows to the 44px minimum only where the pointer is coarse.
|
|
35
|
+
'[@media(pointer:coarse)]:h-44 [@media(pointer:coarse)]:px-10', severity ? SEVERITY_CHIP_TONE[severity] : 'border-border-neutral-base'),
|
|
36
|
+
children: [
|
|
37
|
+
/*#__PURE__*/ _jsx(Icon, {
|
|
38
|
+
name: severity ? SEVERITY_ICON[severity] : 'fileTextLine',
|
|
39
|
+
size: 12,
|
|
40
|
+
"aria-hidden": "true"
|
|
41
|
+
}),
|
|
42
|
+
/*#__PURE__*/ _jsx("span", {
|
|
43
|
+
className: "text-foreground-neutral-base",
|
|
44
|
+
children: count
|
|
45
|
+
})
|
|
46
|
+
]
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
//# sourceMappingURL=run-annotation-count-chip.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/workflow-run-tabs/run-annotation-count-chip.tsx"],"sourcesContent":["import {Icon} from '@shipfox/react-ui/icon';\nimport {cn} from '@shipfox/react-ui/utils';\nimport {Link} from '@tanstack/react-router';\nimport {highestRunAnnotationSeverity, type RunAnnotationSummary} from '#core/run-annotation.js';\nimport {type WorkflowRunsSearch, workflowRunSearchParams} from '#routes/inputs.js';\nimport {SEVERITY_CHIP_TONE, SEVERITY_ICON} from './severity-visuals.js';\n\nexport interface RunAnnotationCountChipProps {\n summary: RunAnnotationSummary | undefined;\n workspaceSlug: string;\n projectSlug: string;\n workflowRunId: string;\n runAttempt?: number | undefined;\n /** Scopes the destination list to one job. */\n jobId?: string | undefined;\n}\n\n/**\n * A bounded reference into the run's Annotations section: a count, its highest severity, and one\n * link. Never a body, so an annotation is rendered exactly once per page.\n */\nexport function RunAnnotationCountChip({\n summary,\n workspaceSlug,\n projectSlug,\n workflowRunId,\n runAttempt,\n jobId,\n}: RunAnnotationCountChipProps) {\n if (!summary || summary.total === 0) return null;\n\n const severity = highestRunAnnotationSeverity(summary);\n const count = `${summary.total}${summary.truncated ? '+' : ''}`;\n const label = `${count} ${summary.total === 1 && !summary.truncated ? 'annotation' : 'annotations'}`;\n const accessibleLabel = severity\n ? `View ${label}, highest severity ${severity}`\n : `View ${label}`;\n const search: WorkflowRunsSearch = {tab: 'annotations', ...(runAttempt ? {runAttempt} : {})};\n if (jobId) search.jobId = jobId;\n\n return (\n <Link\n to=\"/w/$workspaceSlug/p/$projectSlug/runs/$workflowRunId\"\n params={{workspaceSlug, projectSlug, workflowRunId}}\n search={workflowRunSearchParams(search, search) as never}\n aria-label={accessibleLabel}\n className={cn(\n 'inline-flex h-20 shrink-0 items-center gap-4 rounded-4 border px-6 font-code text-xs leading-16 tabular-nums outline-none transition-colors hover:bg-background-neutral-hover focus-visible:shadow-border-interactive-with-active',\n // The chip stays 20px optically so it sits level with the duration metadata beside it;\n // the hit area grows to the 44px minimum only where the pointer is coarse.\n '[@media(pointer:coarse)]:h-44 [@media(pointer:coarse)]:px-10',\n severity ? SEVERITY_CHIP_TONE[severity] : 'border-border-neutral-base',\n )}\n >\n <Icon\n name={severity ? SEVERITY_ICON[severity] : 'fileTextLine'}\n size={12}\n aria-hidden=\"true\"\n />\n <span className=\"text-foreground-neutral-base\">{count}</span>\n </Link>\n );\n}\n"],"names":["Icon","cn","Link","highestRunAnnotationSeverity","workflowRunSearchParams","SEVERITY_CHIP_TONE","SEVERITY_ICON","RunAnnotationCountChip","summary","workspaceSlug","projectSlug","workflowRunId","runAttempt","jobId","total","severity","count","truncated","label","accessibleLabel","search","tab","to","params","aria-label","className","name","size","aria-hidden","span"],"mappings":";AAAA,SAAQA,IAAI,QAAO,yBAAyB;AAC5C,SAAQC,EAAE,QAAO,0BAA0B;AAC3C,SAAQC,IAAI,QAAO,yBAAyB;AAC5C,SAAQC,4BAA4B,QAAkC,0BAA0B;AAChG,SAAiCC,uBAAuB,QAAO,oBAAoB;AACnF,SAAQC,kBAAkB,EAAEC,aAAa,QAAO,wBAAwB;AAYxE;;;CAGC,GACD,OAAO,SAASC,uBAAuB,EACrCC,OAAO,EACPC,aAAa,EACbC,WAAW,EACXC,aAAa,EACbC,UAAU,EACVC,KAAK,EACuB;IAC5B,IAAI,CAACL,WAAWA,QAAQM,KAAK,KAAK,GAAG,OAAO;IAE5C,MAAMC,WAAWZ,6BAA6BK;IAC9C,MAAMQ,QAAQ,GAAGR,QAAQM,KAAK,GAAGN,QAAQS,SAAS,GAAG,MAAM,IAAI;IAC/D,MAAMC,QAAQ,GAAGF,MAAM,CAAC,EAAER,QAAQM,KAAK,KAAK,KAAK,CAACN,QAAQS,SAAS,GAAG,eAAe,eAAe;IACpG,MAAME,kBAAkBJ,WACpB,CAAC,KAAK,EAAEG,MAAM,mBAAmB,EAAEH,UAAU,GAC7C,CAAC,KAAK,EAAEG,OAAO;IACnB,MAAME,SAA6B;QAACC,KAAK;QAAe,GAAIT,aAAa;YAACA;QAAU,IAAI,CAAC,CAAC;IAAC;IAC3F,IAAIC,OAAOO,OAAOP,KAAK,GAAGA;IAE1B,qBACE,MAACX;QACCoB,IAAG;QACHC,QAAQ;YAACd;YAAeC;YAAaC;QAAa;QAClDS,QAAQhB,wBAAwBgB,QAAQA;QACxCI,cAAYL;QACZM,WAAWxB,GACT,qOACA,uFAAuF;QACvF,2EAA2E;QAC3E,gEACAc,WAAWV,kBAAkB,CAACU,SAAS,GAAG;;0BAG5C,KAACf;gBACC0B,MAAMX,WAAWT,aAAa,CAACS,SAAS,GAAG;gBAC3CY,MAAM;gBACNC,eAAY;;0BAEd,KAACC;gBAAKJ,WAAU;0BAAgCT;;;;AAGtD"}
|