@shipfox/client-workflows 16.0.0 → 17.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/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-type.log +1 -0
- package/CHANGELOG.md +20 -0
- package/dist/components/identifier/identifier.js +2 -2
- package/dist/components/identifier/identifier.js.map +1 -1
- package/dist/components/job-detail/agent-config-failure-callout.d.ts.map +1 -1
- package/dist/components/job-detail/agent-config-failure-callout.js +1 -1
- package/dist/components/job-detail/agent-config-failure-callout.js.map +1 -1
- package/dist/components/job-detail/job-context-panel.d.ts +6 -0
- package/dist/components/job-detail/job-context-panel.d.ts.map +1 -0
- package/dist/components/job-detail/job-context-panel.js +224 -0
- package/dist/components/job-detail/job-context-panel.js.map +1 -0
- package/dist/components/job-detail/job-detail-header.d.ts +3 -1
- package/dist/components/job-detail/job-detail-header.d.ts.map +1 -1
- package/dist/components/job-detail/job-detail-header.js +54 -48
- package/dist/components/job-detail/job-detail-header.js.map +1 -1
- package/dist/components/job-detail/job-detail-view.d.ts +1 -1
- package/dist/components/job-detail/job-detail-view.d.ts.map +1 -1
- package/dist/components/job-detail/job-detail-view.js +80 -44
- package/dist/components/job-detail/job-detail-view.js.map +1 -1
- package/dist/components/job-detail/job-empty-states.d.ts.map +1 -1
- package/dist/components/job-detail/job-empty-states.js +19 -10
- package/dist/components/job-detail/job-empty-states.js.map +1 -1
- package/dist/components/job-detail/job-execution-switcher.js +4 -4
- package/dist/components/job-detail/job-execution-switcher.js.map +1 -1
- package/dist/components/job-detail/json-code.d.ts +14 -0
- package/dist/components/job-detail/json-code.d.ts.map +1 -0
- package/dist/components/job-detail/json-code.js +93 -0
- package/dist/components/job-detail/json-code.js.map +1 -0
- package/dist/components/job-detail/step-attempt-log-panel.d.ts.map +1 -1
- package/dist/components/job-detail/step-attempt-log-panel.js +9 -6
- package/dist/components/job-detail/step-attempt-log-panel.js.map +1 -1
- package/dist/components/job-detail/step-troubleshooting.d.ts +18 -0
- package/dist/components/job-detail/step-troubleshooting.d.ts.map +1 -0
- package/dist/components/job-detail/step-troubleshooting.js +493 -0
- package/dist/components/job-detail/step-troubleshooting.js.map +1 -0
- package/dist/components/job-graph/job-graph-content.d.ts.map +1 -1
- package/dist/components/job-graph/job-graph-content.js +5 -4
- package/dist/components/job-graph/job-graph-content.js.map +1 -1
- package/dist/components/job-graph/job-node.js +3 -3
- package/dist/components/job-graph/job-node.js.map +1 -1
- package/dist/components/step-list/index.d.ts +1 -0
- package/dist/components/step-list/index.d.ts.map +1 -1
- package/dist/components/step-list/index.js.map +1 -1
- package/dist/components/step-list/step-list-model.d.ts.map +1 -1
- package/dist/components/step-list/step-list-model.js +5 -0
- package/dist/components/step-list/step-list-model.js.map +1 -1
- package/dist/components/step-list/step-list.d.ts +5 -1
- package/dist/components/step-list/step-list.d.ts.map +1 -1
- package/dist/components/step-list/step-list.js +117 -82
- package/dist/components/step-list/step-list.js.map +1 -1
- package/dist/components/workflow-run-duration-label.js +1 -1
- package/dist/components/workflow-run-duration-label.js.map +1 -1
- package/dist/components/workflow-run-list/job-status-strip.js +6 -6
- package/dist/components/workflow-run-list/job-status-strip.js.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-filter-menu.js +1 -1
- package/dist/components/workflow-run-list/workflow-run-filter-menu.js.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-filters.js +4 -4
- package/dist/components/workflow-run-list/workflow-run-filters.js.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-list-states.js +6 -6
- package/dist/components/workflow-run-list/workflow-run-list-states.js.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-list-view.js +1 -1
- package/dist/components/workflow-run-list/workflow-run-list-view.js.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-row.js +7 -7
- package/dist/components/workflow-run-list/workflow-run-row.js.map +1 -1
- package/dist/components/workflow-run-summary/workflow-run-attempt-switcher.js +1 -1
- package/dist/components/workflow-run-summary/workflow-run-attempt-switcher.js.map +1 -1
- package/dist/components/workflow-run-summary/workflow-run-summary.d.ts +0 -1
- package/dist/components/workflow-run-summary/workflow-run-summary.d.ts.map +1 -1
- package/dist/components/workflow-run-summary/workflow-run-summary.js +8 -13
- package/dist/components/workflow-run-summary/workflow-run-summary.js.map +1 -1
- package/dist/components/workflow-run-tabs/index.d.ts +1 -1
- package/dist/components/workflow-run-tabs/index.d.ts.map +1 -1
- package/dist/components/workflow-run-tabs/index.js.map +1 -1
- package/dist/components/workflow-run-tabs/run-annotation-count-chip.js +2 -2
- package/dist/components/workflow-run-tabs/run-annotation-count-chip.js.map +1 -1
- package/dist/components/workflow-run-tabs/run-annotation-list.d.ts +9 -2
- package/dist/components/workflow-run-tabs/run-annotation-list.d.ts.map +1 -1
- package/dist/components/workflow-run-tabs/run-annotation-list.js +28 -19
- package/dist/components/workflow-run-tabs/run-annotation-list.js.map +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 +5 -5
- package/dist/components/workflow-run-tabs/run-annotation-summary-line.js.map +1 -1
- package/dist/components/workflow-run-view/run-workspace-nav.js +7 -7
- package/dist/components/workflow-run-view/run-workspace-nav.js.map +1 -1
- package/dist/components/workflow-run-view/workflow-run-states.js +6 -6
- package/dist/components/workflow-run-view/workflow-run-states.js.map +1 -1
- package/dist/components/workflow-run-view/workflow-run-view.d.ts.map +1 -1
- package/dist/components/workflow-run-view/workflow-run-view.js +72 -12
- package/dist/components/workflow-run-view/workflow-run-view.js.map +1 -1
- package/dist/core/entities/job-execution.d.ts +7 -0
- package/dist/core/entities/job-execution.d.ts.map +1 -1
- package/dist/core/entities/job-execution.js.map +1 -1
- package/dist/core/entities/job.d.ts +9 -0
- package/dist/core/entities/job.d.ts.map +1 -1
- package/dist/core/entities/job.js.map +1 -1
- package/dist/core/entities/step-attempt.d.ts +29 -0
- package/dist/core/entities/step-attempt.d.ts.map +1 -1
- package/dist/core/entities/step-attempt.js.map +1 -1
- package/dist/core/entities/step.d.ts +3 -1
- package/dist/core/entities/step.d.ts.map +1 -1
- package/dist/core/entities/step.js.map +1 -1
- package/dist/core/run-annotation.d.ts +6 -0
- package/dist/core/run-annotation.d.ts.map +1 -1
- package/dist/core/run-annotation.js.map +1 -1
- package/dist/core/workflow-run.d.ts +1 -1
- package/dist/core/workflow-run.d.ts.map +1 -1
- package/dist/core/workflow-run.js.map +1 -1
- package/dist/hooks/api/annotations.d.ts +63 -0
- package/dist/hooks/api/annotations.d.ts.map +1 -0
- package/dist/hooks/api/annotations.js +145 -0
- package/dist/hooks/api/annotations.js.map +1 -0
- package/dist/hooks/api/step-attempt-detail.d.ts +14 -0
- package/dist/hooks/api/step-attempt-detail.d.ts.map +1 -0
- package/dist/hooks/api/step-attempt-detail.js +43 -0
- package/dist/hooks/api/step-attempt-detail.js.map +1 -0
- package/dist/hooks/api/workflow-run-mapper.d.ts +9 -2
- package/dist/hooks/api/workflow-run-mapper.d.ts.map +1 -1
- package/dist/hooks/api/workflow-run-mapper.js +47 -0
- package/dist/hooks/api/workflow-run-mapper.js.map +1 -1
- package/dist/pages/project-workflows-page.js +19 -19
- package/dist/pages/project-workflows-page.js.map +1 -1
- package/dist/pages/workflow-run-list-page.js +1 -1
- package/dist/pages/workflow-run-list-page.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +10 -10
- package/src/components/identifier/identifier.tsx +2 -2
- package/src/components/job-detail/agent-config-failure-callout.tsx +5 -1
- package/src/components/job-detail/job-context-panel.test.tsx +72 -0
- package/src/components/job-detail/job-context-panel.tsx +237 -0
- package/src/components/job-detail/job-detail-header.tsx +52 -46
- package/src/components/job-detail/job-detail-view.stories.tsx +555 -53
- package/src/components/job-detail/job-detail-view.tsx +87 -45
- package/src/components/job-detail/job-empty-states.tsx +23 -10
- package/src/components/job-detail/job-execution-switcher.tsx +5 -5
- package/src/components/job-detail/json-code.tsx +120 -0
- package/src/components/job-detail/step-attempt-log-panel.test.tsx +1 -0
- package/src/components/job-detail/step-attempt-log-panel.tsx +17 -6
- package/src/components/job-detail/step-troubleshooting.stories.tsx +120 -0
- package/src/components/job-detail/step-troubleshooting.test.tsx +337 -0
- package/src/components/job-detail/step-troubleshooting.tsx +537 -0
- package/src/components/job-graph/job-graph-content.tsx +4 -3
- package/src/components/job-graph/job-node.tsx +3 -3
- package/src/components/step-list/index.ts +1 -0
- package/src/components/step-list/step-list-model.ts +5 -0
- package/src/components/step-list/step-list.test.tsx +75 -2
- package/src/components/step-list/step-list.tsx +73 -20
- package/src/components/workflow-run-duration-label.tsx +1 -1
- package/src/components/workflow-run-list/job-status-strip.tsx +6 -6
- package/src/components/workflow-run-list/workflow-run-filter-menu.tsx +1 -1
- package/src/components/workflow-run-list/workflow-run-filters.tsx +4 -4
- package/src/components/workflow-run-list/workflow-run-list-states.tsx +6 -6
- package/src/components/workflow-run-list/workflow-run-list-view.tsx +1 -1
- package/src/components/workflow-run-list/workflow-run-row.tsx +7 -7
- package/src/components/workflow-run-summary/workflow-run-attempt-switcher.tsx +1 -1
- package/src/components/workflow-run-summary/workflow-run-summary.test.tsx +1 -1
- package/src/components/workflow-run-summary/workflow-run-summary.tsx +11 -11
- package/src/components/workflow-run-tabs/index.ts +5 -1
- package/src/components/workflow-run-tabs/run-annotation-count-chip.tsx +2 -2
- package/src/components/workflow-run-tabs/run-annotation-list.tsx +25 -10
- package/src/components/workflow-run-tabs/run-annotation-summary-line.tsx +5 -5
- package/src/components/workflow-run-view/run-workspace-nav.tsx +7 -7
- package/src/components/workflow-run-view/workflow-run-states.tsx +6 -6
- package/src/components/workflow-run-view/workflow-run-view.tsx +114 -14
- package/src/core/entities/job-execution.ts +7 -0
- package/src/core/entities/job.ts +9 -0
- package/src/core/entities/step-attempt.ts +33 -0
- package/src/core/entities/step.ts +3 -1
- package/src/core/run-annotation.ts +6 -0
- package/src/core/workflow-run.ts +4 -0
- package/src/hooks/api/annotations.test.ts +84 -0
- package/src/hooks/api/annotations.ts +240 -0
- package/src/hooks/api/step-attempt-detail.ts +67 -0
- package/src/hooks/api/workflow-run-mapper.ts +46 -0
- package/src/hooks/api/workflow-runs.test.tsx +61 -1
- package/src/pages/project-workflows-page.tsx +19 -19
- package/src/pages/workflow-job-detail-page.test.tsx +52 -1
- package/src/pages/workflow-run-list-page.tsx +1 -1
- package/test/fixtures/workflow-run.ts +7 -0
- package/tsconfig.build.tsbuildinfo +1 -1
|
@@ -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 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-
|
|
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 type {ReactNode} from 'react';\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 jobContext?: ReactNode;\n}\n\nexport function JobDetailHeader({\n job,\n selectedJobExecution,\n onSelectedJobExecutionChange,\n workspaceSlug,\n projectSlug,\n workflowRunId,\n runAttempt,\n annotationSummary,\n jobContext,\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-row py-row\">\n <div className=\"flex min-w-0 items-start justify-between gap-cluster\">\n <div className=\"flex min-w-0 flex-col gap-inline\">\n <div className=\"flex min-w-0 items-center gap-inline\">\n <WorkflowStatusIcon\n status={selectedStatus}\n size={14}\n tooltip\n ariaLabel={`Job status: ${jobStatus.label}`}\n />\n <Code\n as=\"h1\"\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-inline 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 {jobContext}\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-tight 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","jobContext","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":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Job } from '#core/workflow-run.js';
|
|
2
2
|
import type { useWorkflowRunAttemptQuery } from '#hooks/api/workflow-runs.js';
|
|
3
3
|
import { type WorkflowJobSearch } from '#routes/inputs.js';
|
|
4
4
|
export interface JobDetailViewProps {
|
|
@@ -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":"AAcA,OAAO,EAAwB,KAAK,GAAG,EAAoB,MAAM,uBAAuB,CAAC;AAGzF,OAAO,KAAK,EAAC,0BAA0B,EAAC,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EACL,KAAK,iBAAiB,EAGvB,MAAM,mBAAmB,CAAC;AAwB3B,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,+BAqQpB"}
|
|
@@ -9,23 +9,27 @@ import { Skeleton } from '@shipfox/react-ui/skeleton';
|
|
|
9
9
|
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
|
-
import { useEffect, useRef } from 'react';
|
|
12
|
+
import { useEffect, useRef, useState } from 'react';
|
|
13
13
|
import { summarizeJobAnnotations } from '#core/run-annotation.js';
|
|
14
|
+
import { isWorkflowRunTerminal } from '#core/workflow-run.js';
|
|
15
|
+
import { useWorkflowRunAnnotationSummaryQuery } from '#hooks/api/annotations.js';
|
|
14
16
|
import { useRunAnnotationsQuery } from '#hooks/api/run-annotations.js';
|
|
15
17
|
import { workflowJobSearchParams, workflowRunSearchParams } from '#routes/inputs.js';
|
|
16
18
|
import { StepList } from '../step-list/index.js';
|
|
17
19
|
import { WorkflowRunNotFound, WorkflowRunStaleError } from '../workflow-run-view/workflow-run-states.js';
|
|
18
|
-
import {
|
|
20
|
+
import { JobContextPanel } from './job-context-panel.js';
|
|
19
21
|
import { JobDetailHeader } from './job-detail-header.js';
|
|
20
|
-
import { CarriedOverStepPanel, emptyStateForJob, emptyStateForMissingExecution,
|
|
22
|
+
import { CarriedOverStepPanel, emptyStateForJob, emptyStateForMissingExecution, jobSucceededSummary } from './job-empty-states.js';
|
|
21
23
|
import { resolveWorkflowJobSelection, workflowJobLandingSelection } from './job-selection.js';
|
|
22
24
|
import { StepAttemptLogPanel } from './step-attempt-log-panel.js';
|
|
25
|
+
import { StepInspectorSheet } from './step-troubleshooting.js';
|
|
23
26
|
export function JobDetailView({ workspaceSlug, projectSlug, workflowRunId, jobId, search, query, newerAttempt, newerJob, onSelectionChange }) {
|
|
24
27
|
const rootRef = useRef(null);
|
|
25
28
|
const pageScrollRef = useRef(null);
|
|
26
29
|
const landingSelectionRef = useRef(undefined);
|
|
27
30
|
const hasLoadedData = query.data !== undefined;
|
|
28
|
-
//
|
|
31
|
+
// Reuse the run workspace's bounded annotation read for the job header chip. The separate
|
|
32
|
+
// summary query below stays counts-only and is scoped to the inspector's selected execution.
|
|
29
33
|
const annotations = useRunAnnotationsQuery({
|
|
30
34
|
workflowRunId,
|
|
31
35
|
runAttempt: query.data?.runAttempt.attempt
|
|
@@ -33,6 +37,21 @@ export function JobDetailView({ workspaceSlug, projectSlug, workflowRunId, jobId
|
|
|
33
37
|
const jobAnnotationSummary = annotations.annotations ? summarizeJobAnnotations(annotations.annotations, jobId, {
|
|
34
38
|
truncated: annotations.summary?.truncated ?? false
|
|
35
39
|
}) : undefined;
|
|
40
|
+
const inspectorResetKey = `${jobId}:${search.jobExecutionId ?? ''}`;
|
|
41
|
+
const [inspectorState, setInspectorState] = useState(()=>({
|
|
42
|
+
key: inspectorResetKey,
|
|
43
|
+
attemptId: null
|
|
44
|
+
}));
|
|
45
|
+
const inspectorOpenAttemptId = inspectorState.key === inspectorResetKey ? inspectorState.attemptId : null;
|
|
46
|
+
const annotationJob = query.data?.jobs.find((candidate)=>candidate.id === jobId);
|
|
47
|
+
const annotationExecutionId = annotationJob ? resolveWorkflowJobSelection({
|
|
48
|
+
job: annotationJob,
|
|
49
|
+
selection: search
|
|
50
|
+
}).jobExecution?.id : search.jobExecutionId;
|
|
51
|
+
const annotationPolling = query.data ? !isWorkflowRunTerminal(query.data.runAttempt.status) : true;
|
|
52
|
+
const annotationSummaryQuery = useWorkflowRunAnnotationSummaryQuery(query.data?.id, query.data?.runAttempt.attempt, annotationExecutionId, {
|
|
53
|
+
polling: annotationPolling
|
|
54
|
+
});
|
|
36
55
|
useEffect(()=>{
|
|
37
56
|
if (!jobId || !hasLoadedData) return;
|
|
38
57
|
const heading = rootRef.current?.querySelector('[data-job-heading]');
|
|
@@ -128,6 +147,12 @@ export function JobDetailView({ workspaceSlug, projectSlug, workflowRunId, jobId
|
|
|
128
147
|
stepAttemptId: match.attemptId
|
|
129
148
|
});
|
|
130
149
|
}
|
|
150
|
+
function onInspectorOpenChange(attemptId) {
|
|
151
|
+
setInspectorState({
|
|
152
|
+
key: inspectorResetKey,
|
|
153
|
+
attemptId
|
|
154
|
+
});
|
|
155
|
+
}
|
|
131
156
|
function retargetToRunningStep() {
|
|
132
157
|
if (!runningSelection || !selectedJobExecution) return;
|
|
133
158
|
onSelectionChange({
|
|
@@ -156,10 +181,10 @@ export function JobDetailView({ workspaceSlug, projectSlug, workflowRunId, jobId
|
|
|
156
181
|
}) : null,
|
|
157
182
|
/*#__PURE__*/ _jsx("div", {
|
|
158
183
|
ref: pageScrollRef,
|
|
159
|
-
className: "@container min-h-0 flex-1 overflow-auto pb-
|
|
184
|
+
className: "@container min-h-0 flex-1 overflow-auto pb-panel",
|
|
160
185
|
children: /*#__PURE__*/ _jsxs("section", {
|
|
161
186
|
"aria-label": `${job.displayName} logs`,
|
|
162
|
-
className: "
|
|
187
|
+
className: "flex w-full flex-col",
|
|
163
188
|
children: [
|
|
164
189
|
/*#__PURE__*/ _jsxs("section", {
|
|
165
190
|
className: "min-w-0 overflow-hidden",
|
|
@@ -170,12 +195,16 @@ export function JobDetailView({ workspaceSlug, projectSlug, workflowRunId, jobId
|
|
|
170
195
|
onSelectedJobExecutionChange: selectExecution,
|
|
171
196
|
workspaceSlug: workspaceSlug,
|
|
172
197
|
projectSlug: projectSlug,
|
|
173
|
-
workflowRunId:
|
|
198
|
+
workflowRunId: run.id,
|
|
174
199
|
runAttempt: run.runAttempt.attempt,
|
|
175
|
-
annotationSummary: jobAnnotationSummary
|
|
200
|
+
annotationSummary: jobAnnotationSummary,
|
|
201
|
+
jobContext: selectedJobExecution ? /*#__PURE__*/ _jsx(JobContextPanel, {
|
|
202
|
+
job: job,
|
|
203
|
+
execution: selectedJobExecution
|
|
204
|
+
}) : undefined
|
|
176
205
|
}),
|
|
177
206
|
/*#__PURE__*/ _jsx(Text, {
|
|
178
|
-
as: "
|
|
207
|
+
as: "h2",
|
|
179
208
|
className: "sr-only",
|
|
180
209
|
children: "Logs"
|
|
181
210
|
}),
|
|
@@ -185,14 +214,26 @@ export function JobDetailView({ workspaceSlug, projectSlug, workflowRunId, jobId
|
|
|
185
214
|
selectedAttemptId: selectedAttemptId,
|
|
186
215
|
defaultSelectedAttemptId: landingSelection?.attemptId,
|
|
187
216
|
onSelectedAttemptChange: selectAttempt,
|
|
217
|
+
inspectorOpenAttemptId: inspectorOpenAttemptId,
|
|
218
|
+
onInspectorOpenChange: onInspectorOpenChange,
|
|
188
219
|
autoSelectActiveAttempt: true,
|
|
189
220
|
emptyState: emptyStateForJob(job, selectedJobExecution),
|
|
190
221
|
showHeader: false,
|
|
191
|
-
className: "rounded-none border-0",
|
|
222
|
+
className: "rounded-none border-0 bg-transparent",
|
|
192
223
|
renderExpandedStep: (context)=>/*#__PURE__*/ _jsx(ExpandedStep, {
|
|
193
224
|
context: context,
|
|
194
|
-
workspaceSlug: workspaceSlug,
|
|
195
225
|
pageScrollRef: pageScrollRef
|
|
226
|
+
}),
|
|
227
|
+
renderInspector: (entry)=>/*#__PURE__*/ _jsx(StepInspectorSheet, {
|
|
228
|
+
entry: entry,
|
|
229
|
+
open: true,
|
|
230
|
+
onOpenChange: (open)=>onInspectorOpenChange(open ? entry.id : null),
|
|
231
|
+
workspaceSlug: workspaceSlug,
|
|
232
|
+
projectSlug: projectSlug,
|
|
233
|
+
workflowRunId: run.id,
|
|
234
|
+
runAttempt: run.runAttempt.attempt,
|
|
235
|
+
jobId: job.id,
|
|
236
|
+
annotationCount: annotationCountForStep(annotationSummaryQuery.data, entry.step.id, entry.attempt)
|
|
196
237
|
})
|
|
197
238
|
}) : /*#__PURE__*/ _jsx(EmptyStateForMissingExecution, {
|
|
198
239
|
job: job
|
|
@@ -202,7 +243,7 @@ export function JobDetailView({ workspaceSlug, projectSlug, workflowRunId, jobId
|
|
|
202
243
|
showRetargetNotice && runningSelection ? /*#__PURE__*/ _jsxs("div", {
|
|
203
244
|
role: "status",
|
|
204
245
|
"aria-live": "polite",
|
|
205
|
-
className: "flex min-w-0 items-center justify-between gap-
|
|
246
|
+
className: "flex min-w-0 items-center justify-between gap-inline border-t border-border-neutral-base px-row py-row",
|
|
206
247
|
children: [
|
|
207
248
|
/*#__PURE__*/ _jsxs(Text, {
|
|
208
249
|
size: "xs",
|
|
@@ -219,7 +260,7 @@ export function JobDetailView({ workspaceSlug, projectSlug, workflowRunId, jobId
|
|
|
219
260
|
}),
|
|
220
261
|
/*#__PURE__*/ _jsx("button", {
|
|
221
262
|
type: "button",
|
|
222
|
-
className: "shrink-0 rounded-4 px-
|
|
263
|
+
className: "shrink-0 rounded-4 px-tight py-[4px] text-xs font-medium text-foreground-highlight-interactive outline-none focus-visible:shadow-border-interactive-with-active",
|
|
223
264
|
onClick: retargetToRunningStep,
|
|
224
265
|
children: "Jump to it"
|
|
225
266
|
})
|
|
@@ -227,7 +268,7 @@ export function JobDetailView({ workspaceSlug, projectSlug, workflowRunId, jobId
|
|
|
227
268
|
}) : null,
|
|
228
269
|
succeededSummary ? /*#__PURE__*/ _jsx(Text, {
|
|
229
270
|
size: "xs",
|
|
230
|
-
className: "px-
|
|
271
|
+
className: "px-row py-row text-foreground-neutral-muted",
|
|
231
272
|
children: succeededSummary
|
|
232
273
|
}) : null
|
|
233
274
|
]
|
|
@@ -237,35 +278,27 @@ export function JobDetailView({ workspaceSlug, projectSlug, workflowRunId, jobId
|
|
|
237
278
|
})
|
|
238
279
|
});
|
|
239
280
|
}
|
|
240
|
-
function ExpandedStep({ context,
|
|
281
|
+
function ExpandedStep({ context, pageScrollRef }) {
|
|
241
282
|
if (context.carriedOver) return /*#__PURE__*/ _jsx(CarriedOverStepPanel, {});
|
|
242
|
-
|
|
243
|
-
return /*#__PURE__*/ _jsxs("section", {
|
|
283
|
+
return /*#__PURE__*/ _jsx("section", {
|
|
244
284
|
role: "region",
|
|
245
285
|
tabIndex: 0,
|
|
246
286
|
"aria-label": `${context.stepLabel} output, attempt ${context.attempt}`,
|
|
247
|
-
className: "flex min-w-0 flex-col
|
|
248
|
-
children:
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
attempt: context.attempt,
|
|
257
|
-
attemptStatus: context.attemptStatus,
|
|
258
|
-
attemptStartedAt: context.attemptStartedAt,
|
|
259
|
-
pageScrollRef: pageScrollRef,
|
|
260
|
-
surfaceClassName: "rounded-8"
|
|
261
|
-
})
|
|
262
|
-
]
|
|
287
|
+
className: "flex min-w-0 flex-col border-t border-border-neutral-base bg-background-neutral-base outline-none focus-visible:shadow-border-interactive-with-active dark:bg-background-contrast-subtle",
|
|
288
|
+
children: /*#__PURE__*/ _jsx(StepAttemptLogPanel, {
|
|
289
|
+
stepId: context.stepId,
|
|
290
|
+
attempt: context.attempt,
|
|
291
|
+
attemptStatus: context.attemptStatus,
|
|
292
|
+
attemptStartedAt: context.attemptStartedAt,
|
|
293
|
+
pageScrollRef: pageScrollRef,
|
|
294
|
+
surfaceClassName: "rounded-none border-0 bg-transparent shadow-none"
|
|
295
|
+
})
|
|
263
296
|
});
|
|
264
297
|
}
|
|
265
298
|
function EmptyStateForMissingExecution({ job }) {
|
|
266
299
|
const emptyState = emptyStateForMissingExecution(job);
|
|
267
300
|
return /*#__PURE__*/ _jsx(EmptyState, {
|
|
268
|
-
className: "min-h-120
|
|
301
|
+
className: "min-h-120 p-panel",
|
|
269
302
|
icon: "componentLine",
|
|
270
303
|
title: emptyState.title,
|
|
271
304
|
description: emptyState.description,
|
|
@@ -274,7 +307,7 @@ function EmptyStateForMissingExecution({ job }) {
|
|
|
274
307
|
}
|
|
275
308
|
function JobNotFoundState({ workspaceSlug, projectSlug, workflowRunId, search }) {
|
|
276
309
|
return /*#__PURE__*/ _jsxs("div", {
|
|
277
|
-
className: "flex min-h-0 min-w-0 flex-1 flex-col items-center justify-center gap-
|
|
310
|
+
className: "flex min-h-0 min-w-0 flex-1 flex-col items-center justify-center gap-cluster p-panel",
|
|
278
311
|
children: [
|
|
279
312
|
/*#__PURE__*/ _jsx(EmptyState, {
|
|
280
313
|
icon: "componentLine",
|
|
@@ -293,7 +326,7 @@ function JobNotFoundState({ workspaceSlug, projectSlug, workflowRunId, search })
|
|
|
293
326
|
}, {
|
|
294
327
|
runAttempt: search.runAttempt
|
|
295
328
|
}),
|
|
296
|
-
className: "inline-flex items-center gap-
|
|
329
|
+
className: "inline-flex items-center gap-inline rounded-6 px-tight py-[6px] text-sm text-foreground-highlight-interactive outline-none focus-visible:shadow-border-interactive-with-active",
|
|
297
330
|
children: [
|
|
298
331
|
/*#__PURE__*/ _jsx(Icon, {
|
|
299
332
|
name: "arrowLeftLine",
|
|
@@ -309,9 +342,9 @@ function JobNotFoundState({ workspaceSlug, projectSlug, workflowRunId, search })
|
|
|
309
342
|
function NewerAttemptNotice({ workspaceSlug, projectSlug, runId, jobId, attempt }) {
|
|
310
343
|
return /*#__PURE__*/ _jsx("div", {
|
|
311
344
|
role: "status",
|
|
312
|
-
className: "border-b border-border-neutral-base px-
|
|
345
|
+
className: "border-b border-border-neutral-base px-row py-row",
|
|
313
346
|
children: /*#__PURE__*/ _jsxs("div", {
|
|
314
|
-
className: "
|
|
347
|
+
className: "flex items-center justify-between gap-inline",
|
|
315
348
|
children: [
|
|
316
349
|
/*#__PURE__*/ _jsx(Text, {
|
|
317
350
|
size: "xs",
|
|
@@ -329,7 +362,7 @@ function NewerAttemptNotice({ workspaceSlug, projectSlug, runId, jobId, attempt
|
|
|
329
362
|
search: workflowJobSearchParams({
|
|
330
363
|
runAttempt: attempt
|
|
331
364
|
}),
|
|
332
|
-
className: "shrink-0 rounded-4 px-
|
|
365
|
+
className: "shrink-0 rounded-4 px-tight py-[4px] text-xs font-medium text-foreground-highlight-interactive outline-none focus-visible:shadow-border-interactive-with-active",
|
|
333
366
|
children: [
|
|
334
367
|
"View attempt #",
|
|
335
368
|
attempt
|
|
@@ -368,14 +401,17 @@ function findAttempt(jobExecution, attemptId) {
|
|
|
368
401
|
}
|
|
369
402
|
return undefined;
|
|
370
403
|
}
|
|
404
|
+
function annotationCountForStep(summary, stepId, attempt) {
|
|
405
|
+
return summary?.stepCounts?.find((entry)=>entry.stepId === stepId && entry.attempt === attempt)?.total;
|
|
406
|
+
}
|
|
371
407
|
function JobDetailSkeleton() {
|
|
372
408
|
return /*#__PURE__*/ _jsx("div", {
|
|
373
|
-
className: "min-h-0 min-w-0 flex-1 overflow-auto
|
|
409
|
+
className: "min-h-0 min-w-0 flex-1 overflow-auto pb-panel",
|
|
374
410
|
children: /*#__PURE__*/ _jsxs("div", {
|
|
375
|
-
className: "
|
|
411
|
+
className: "w-full",
|
|
376
412
|
children: [
|
|
377
413
|
/*#__PURE__*/ _jsxs("header", {
|
|
378
|
-
className: "flex items-center gap-
|
|
414
|
+
className: "flex items-center gap-cluster border-b border-border-neutral-base px-row py-row",
|
|
379
415
|
children: [
|
|
380
416
|
/*#__PURE__*/ _jsx(Skeleton, {
|
|
381
417
|
className: "size-20 rounded-full"
|
|
@@ -392,9 +428,9 @@ function JobDetailSkeleton() {
|
|
|
392
428
|
]
|
|
393
429
|
}),
|
|
394
430
|
/*#__PURE__*/ _jsx("div", {
|
|
395
|
-
className: "p-
|
|
431
|
+
className: "p-panel-compact",
|
|
396
432
|
children: JOB_DETAIL_SKELETON_ROWS.map((row)=>/*#__PURE__*/ _jsxs("div", {
|
|
397
|
-
className: "flex min-h-44 items-center gap-
|
|
433
|
+
className: "flex min-h-44 items-center gap-inline border-b border-border-neutral-base last:border-b-0",
|
|
398
434
|
children: [
|
|
399
435
|
/*#__PURE__*/ _jsx(Skeleton, {
|
|
400
436
|
className: "size-14 rounded-full"
|
|
@@ -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 {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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"}
|
|
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, useState} from 'react';\nimport type {RunAnnotationSummary} from '#core/run-annotation.js';\nimport {summarizeJobAnnotations} from '#core/run-annotation.js';\nimport {isWorkflowRunTerminal, type Job, type JobExecution} from '#core/workflow-run.js';\nimport {useWorkflowRunAnnotationSummaryQuery} from '#hooks/api/annotations.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 {JobContextPanel} from './job-context-panel.js';\nimport {JobDetailHeader} from './job-detail-header.js';\nimport {\n CarriedOverStepPanel,\n emptyStateForJob,\n emptyStateForMissingExecution,\n jobSucceededSummary,\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';\nimport {StepInspectorSheet} from './step-troubleshooting.js';\n\ntype InspectorState = {key: string; attemptId: string | null};\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 // Reuse the run workspace's bounded annotation read for the job header chip. The separate\n // summary query below stays counts-only and is scoped to the inspector's selected execution.\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 const inspectorResetKey = `${jobId}:${search.jobExecutionId ?? ''}`;\n const [inspectorState, setInspectorState] = useState<InspectorState>(() => ({\n key: inspectorResetKey,\n attemptId: null,\n }));\n const inspectorOpenAttemptId =\n inspectorState.key === inspectorResetKey ? inspectorState.attemptId : null;\n const annotationJob = query.data?.jobs.find((candidate) => candidate.id === jobId);\n const annotationExecutionId = annotationJob\n ? resolveWorkflowJobSelection({job: annotationJob, selection: search}).jobExecution?.id\n : search.jobExecutionId;\n const annotationPolling = query.data\n ? !isWorkflowRunTerminal(query.data.runAttempt.status)\n : true;\n const annotationSummaryQuery = useWorkflowRunAnnotationSummaryQuery(\n query.data?.id,\n query.data?.runAttempt.attempt,\n annotationExecutionId,\n {polling: annotationPolling},\n );\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 onInspectorOpenChange(attemptId: string | null) {\n setInspectorState({key: inspectorResetKey, attemptId});\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-panel\">\n <section aria-label={`${job.displayName} logs`} className=\"flex w-full flex-col\">\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={run.id}\n runAttempt={run.runAttempt.attempt}\n annotationSummary={jobAnnotationSummary}\n jobContext={\n selectedJobExecution ? (\n <JobContextPanel job={job} execution={selectedJobExecution} />\n ) : undefined\n }\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 inspectorOpenAttemptId={inspectorOpenAttemptId}\n onInspectorOpenChange={onInspectorOpenChange}\n autoSelectActiveAttempt\n emptyState={emptyStateForJob(job, selectedJobExecution)}\n showHeader={false}\n className=\"rounded-none border-0 bg-transparent\"\n renderExpandedStep={(context) => (\n <ExpandedStep context={context} pageScrollRef={pageScrollRef} />\n )}\n renderInspector={(entry) => (\n <StepInspectorSheet\n entry={entry}\n open\n onOpenChange={(open) => onInspectorOpenChange(open ? entry.id : null)}\n workspaceSlug={workspaceSlug}\n projectSlug={projectSlug}\n workflowRunId={run.id}\n runAttempt={run.runAttempt.attempt}\n jobId={job.id}\n annotationCount={annotationCountForStep(\n annotationSummaryQuery.data,\n entry.step.id,\n entry.attempt,\n )}\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-inline border-t border-border-neutral-base px-row py-row\"\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-tight py-[4px] 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-row py-row 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 pageScrollRef,\n}: {\n context: StepExpandedContext;\n pageScrollRef: RefObject<HTMLDivElement | null>;\n}) {\n if (context.carriedOver) return <CarriedOverStepPanel />;\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 border-t border-border-neutral-base bg-background-neutral-base outline-none focus-visible:shadow-border-interactive-with-active dark:bg-background-contrast-subtle\"\n >\n <StepAttemptLogPanel\n stepId={context.stepId}\n attempt={context.attempt}\n attemptStatus={context.attemptStatus}\n attemptStartedAt={context.attemptStartedAt}\n pageScrollRef={pageScrollRef}\n surfaceClassName=\"rounded-none border-0 bg-transparent shadow-none\"\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 p-panel\"\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-cluster p-panel\">\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-inline rounded-6 px-tight py-[6px] 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-row py-row\">\n <div className=\"flex items-center justify-between gap-inline\">\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-tight py-[4px] 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 annotationCountForStep(\n summary: RunAnnotationSummary | undefined,\n stepId: string,\n attempt: number,\n): number | undefined {\n return summary?.stepCounts?.find((entry) => entry.stepId === stepId && entry.attempt === attempt)\n ?.total;\n}\n\nfunction JobDetailSkeleton() {\n return (\n <div className=\"min-h-0 min-w-0 flex-1 overflow-auto pb-panel\">\n <div className=\"w-full\">\n <header className=\"flex items-center gap-cluster border-b border-border-neutral-base px-row py-row\">\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-panel-compact\">\n {JOB_DETAIL_SKELETON_ROWS.map((row) => (\n <div\n key={row}\n className=\"flex min-h-44 items-center gap-inline 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","useState","summarizeJobAnnotations","isWorkflowRunTerminal","useWorkflowRunAnnotationSummaryQuery","useRunAnnotationsQuery","workflowJobSearchParams","workflowRunSearchParams","StepList","WorkflowRunNotFound","WorkflowRunStaleError","JobContextPanel","JobDetailHeader","CarriedOverStepPanel","emptyStateForJob","emptyStateForMissingExecution","jobSucceededSummary","resolveWorkflowJobSelection","workflowJobLandingSelection","StepAttemptLogPanel","StepInspectorSheet","JobDetailView","workspaceSlug","projectSlug","workflowRunId","jobId","search","query","newerAttempt","newerJob","onSelectionChange","rootRef","pageScrollRef","landingSelectionRef","undefined","hasLoadedData","data","annotations","runAttempt","attempt","jobAnnotationSummary","truncated","summary","inspectorResetKey","jobExecutionId","inspectorState","setInspectorState","key","attemptId","inspectorOpenAttemptId","annotationJob","jobs","find","candidate","id","annotationExecutionId","job","selection","jobExecution","annotationPolling","status","annotationSummaryQuery","polling","heading","current","querySelector","focus","preventScroll","isPending","JobDetailSkeleton","isError","error","subject","icon","run","JobNotFoundState","resolvedSelection","selectedJobExecution","hasExplicitStep","Boolean","stepId","step","currentLandingSelection","frozenLanding","hasSelection","landingSelection","selectedAttemptId","runningSelection","runningStepSelection","selectedStepId","selectedAttemptForNotice","showRetargetNotice","succeededSummary","selectExecution","stepAttemptId","selectAttempt","match","findAttempt","onInspectorOpenChange","retargetToRunningStep","intervalMs","reducedMotionIntervalMs","div","ref","className","NewerAttemptNotice","runId","section","aria-label","displayName","onSelectedJobExecutionChange","annotationSummary","jobContext","execution","as","defaultSelectedAttemptId","onSelectedAttemptChange","autoSelectActiveAttempt","emptyState","showHeader","renderExpandedStep","context","ExpandedStep","renderInspector","entry","open","onOpenChange","annotationCount","annotationCountForStep","EmptyStateForMissingExecution","role","aria-live","size","span","stepLabel","button","type","onClick","carriedOver","tabIndex","attemptStatus","attemptStartedAt","surfaceClassName","title","description","variant","to","params","name","aria-hidden","steps","sort","left","right","position","localeCompare","stepIndex","length","attempts","reverse","stepCounts","total","header","JOB_DETAIL_SKELETON_ROWS","map","row"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"job-empty-states.d.ts","sourceRoot":"","sources":["../../../src/components/job-detail/job-empty-states.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,GAAG,EAAE,YAAY,EAAE,IAAI,EAAE,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAO9E,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,uBAAuB,CAAC;AAG9D,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,GAAG,EACR,YAAY,EAAE,YAAY,GACzB,kBAAkB,GAAG,SAAS,
|
|
1
|
+
{"version":3,"file":"job-empty-states.d.ts","sourceRoot":"","sources":["../../../src/components/job-detail/job-empty-states.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,GAAG,EAAE,YAAY,EAAE,IAAI,EAAE,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAO9E,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,uBAAuB,CAAC;AAG9D,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,GAAG,EACR,YAAY,EAAE,YAAY,GACzB,kBAAkB,GAAG,SAAS,CAkEhC;AAED,wBAAgB,6BAA6B,CAAC,GAAG,EAAE,GAAG,GAAG,kBAAkB,CA8D1E;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,GAAG,CAAC,cAAc,CAAC,GAAG,MAAM,CAoBzE;AAmCD,wBAAgB,oBAAoB,gCAUnC;AAED,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GACpC,SAAS,GAAG,IAAI,CAgClB;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,GAAG,IAAI,GAAG,OAAO,CAEjF;AAED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,GAAG,EAAE,SAAS,EAAE,YAAY,GAAG,MAAM,GAAG,SAAS,CAMzF"}
|
|
@@ -11,10 +11,11 @@ export function emptyStateForJob(job, jobExecution) {
|
|
|
11
11
|
};
|
|
12
12
|
}
|
|
13
13
|
const displayStatus = job.mode === 'listening' ? deriveJobExecutionDisplayStatus(jobExecution) : deriveJobDisplayStatus(job);
|
|
14
|
+
const runner = jobExecution.runner?.length ? jobExecution.runner : job.runner;
|
|
14
15
|
if (jobExecution.status === 'running' && displayStatus === 'pending') {
|
|
15
16
|
return {
|
|
16
|
-
title: 'Runner preparing job',
|
|
17
|
-
description:
|
|
17
|
+
title: runner?.length ? 'Runner preparing job' : 'Waiting for a runner',
|
|
18
|
+
description: runner?.length ? `Runner ${runner.join(', ')} is preparing the job. Steps will appear here when work begins.` : 'No runner has claimed this job yet. Steps will appear here when a runner starts work.',
|
|
18
19
|
status: displayStatus
|
|
19
20
|
};
|
|
20
21
|
}
|
|
@@ -42,7 +43,7 @@ export function emptyStateForJob(job, jobExecution) {
|
|
|
42
43
|
if (displayStatus === 'failed') {
|
|
43
44
|
return {
|
|
44
45
|
title: 'Job failed before its first step started',
|
|
45
|
-
description: preStepFailureDescription(jobExecution.statusReason ?? job.statusReason),
|
|
46
|
+
description: preStepFailureDescription(jobExecution.statusReason ?? job.statusReason, runner),
|
|
46
47
|
status: displayStatus
|
|
47
48
|
};
|
|
48
49
|
}
|
|
@@ -98,6 +99,13 @@ export function emptyStateForMissingExecution(job) {
|
|
|
98
99
|
status: 'cancelled'
|
|
99
100
|
};
|
|
100
101
|
}
|
|
102
|
+
if (job.status === 'failed') {
|
|
103
|
+
return {
|
|
104
|
+
title: 'Job failed before an execution was created',
|
|
105
|
+
description: preStepFailureDescription(job.statusReason, job.runner),
|
|
106
|
+
status: 'failed'
|
|
107
|
+
};
|
|
108
|
+
}
|
|
101
109
|
return {
|
|
102
110
|
title: 'Execution details unavailable',
|
|
103
111
|
description: 'This job finished, but no job execution record is available.',
|
|
@@ -125,17 +133,18 @@ export function skippedJobDescription(reason) {
|
|
|
125
133
|
return 'This job did not start.';
|
|
126
134
|
}
|
|
127
135
|
}
|
|
128
|
-
function preStepFailureDescription(reason) {
|
|
136
|
+
function preStepFailureDescription(reason, runner = null) {
|
|
137
|
+
const runnerCopy = runner?.length ? ` Required runner labels: ${runner.join(', ')}.` : '';
|
|
129
138
|
switch(reason){
|
|
130
139
|
case 'runner_lost':
|
|
131
|
-
return
|
|
140
|
+
return `The runner stopped responding before work began.${runnerCopy} Check runner availability before re-running the workflow.`;
|
|
132
141
|
case 'timed_out':
|
|
133
|
-
return
|
|
142
|
+
return `The job timed out before work began.${runnerCopy} Check runner capacity and timeout configuration before re-running the workflow.`;
|
|
134
143
|
case 'user_cancelled':
|
|
135
144
|
case 'run_cancelled':
|
|
136
145
|
return 'The execution ended while the run was being cancelled.';
|
|
137
146
|
case 'step_failed':
|
|
138
|
-
return
|
|
147
|
+
return `The execution failed before step details were recorded.${runnerCopy} Review run annotations before re-running the workflow.`;
|
|
139
148
|
case 'condition_errored':
|
|
140
149
|
return 'The job condition could not be evaluated. Review run annotations before re-running the workflow.';
|
|
141
150
|
case 'dependency_not_completed':
|
|
@@ -146,9 +155,9 @@ function preStepFailureDescription(reason) {
|
|
|
146
155
|
return 'The job condition did not match, so no work started.';
|
|
147
156
|
case 'unknown':
|
|
148
157
|
case null:
|
|
149
|
-
return
|
|
158
|
+
return `Shipfox did not record a failure reason.${runnerCopy} Re-run only after checking the runner and workflow configuration.`;
|
|
150
159
|
default:
|
|
151
|
-
return `The execution ended because ${humanizeFailureReason(reason)}
|
|
160
|
+
return `The execution ended because ${humanizeFailureReason(reason)}.${runnerCopy} Resolve that condition before re-running the workflow.`;
|
|
152
161
|
}
|
|
153
162
|
}
|
|
154
163
|
function humanizeFailureReason(reason) {
|
|
@@ -156,7 +165,7 @@ function humanizeFailureReason(reason) {
|
|
|
156
165
|
}
|
|
157
166
|
export function CarriedOverStepPanel() {
|
|
158
167
|
return /*#__PURE__*/ _jsx(EmptyState, {
|
|
159
|
-
className: "min-h-120 rounded-8 border border-border-neutral-base bg-background-components-base
|
|
168
|
+
className: "min-h-120 rounded-8 border border-border-neutral-base bg-background-components-base p-panel",
|
|
160
169
|
icon: "componentLine",
|
|
161
170
|
title: "Carried over from a previous attempt",
|
|
162
171
|
description: "Not executed in this run.",
|