1- import { ChevronsUpDown , ExternalLink } from "lucide-react" ;
2-
31import type { GetContainerExecutionStateResponse } from "@/api/types.gen" ;
4- import { Button } from "@/components/ui/button" ;
5- import {
6- Collapsible ,
7- CollapsibleContent ,
8- CollapsibleTrigger ,
9- } from "@/components/ui/collapsible" ;
2+ import { BlockStack , InlineStack } from "@/components/ui/layout" ;
103import { Skeleton } from "@/components/ui/skeleton" ;
4+ import { Paragraph } from "@/components/ui/typography" ;
115import { useBackend } from "@/providers/BackendProvider" ;
126import { useFetchContainerExecutionState } from "@/services/executionService" ;
137import {
@@ -18,6 +12,8 @@ import { EXIT_CODE_OOM } from "@/utils/constants";
1812import { formatDate , formatDuration } from "@/utils/date" ;
1913
2014import { InfoBox } from "../InfoBox" ;
15+ import { ContentBlock } from "../TaskDetails/Blocks/ContentBlock" ;
16+ import { URLBlock } from "../TaskDetails/Blocks/LinkBlock" ;
2117
2218interface ExecutionDetailsProps {
2319 executionId : string ;
@@ -37,156 +33,126 @@ export const ExecutionDetails = ({
3733 data : containerState ,
3834 isLoading : isLoadingContainerState ,
3935 error : containerStateError ,
40- } = useFetchContainerExecutionState ( executionId || "" , backendUrl ) ;
41-
42- // Don't render if no execution data is available
43- const hasExecutionData =
44- executionId ||
45- containerState ||
46- isLoadingContainerState ||
47- containerStateError ;
48- if ( ! hasExecutionData ) {
49- return null ;
50- }
36+ } = useFetchContainerExecutionState ( executionId , backendUrl ) ;
37+
38+ const loadingMarkup = (
39+ < BlockStack gap = "2" >
40+ < InlineStack gap = "2" blockAlign = "center" >
41+ < Skeleton className = "h-3 w-12" />
42+ < Skeleton className = "h-3 w-32" />
43+ </ InlineStack >
44+ < InlineStack gap = "2" blockAlign = "center" >
45+ < Skeleton className = "h-3 w-20" />
46+ < Skeleton className = "h-3 w-24" />
47+ < Skeleton className = "h-3 w-40" />
48+ </ InlineStack >
49+ </ BlockStack >
50+ ) ;
5151
5252 const podName = executionPodName ( containerState ) ;
5353 const executionJobLinks = getExecutionJobLinks ( containerState ) ;
5454
5555 return (
56- < div className = "flex flex-col px-3 py-2" >
57- < Collapsible defaultOpen >
58- < div className = "font-medium text-sm text-foreground flex items-center gap-1" >
59- Execution Details
60- < CollapsibleTrigger asChild >
61- < Button variant = "ghost" size = "sm" >
62- < ChevronsUpDown className = "h-4 w-4" />
63- < span className = "sr-only" > Toggle</ span >
64- </ Button >
65- </ CollapsibleTrigger >
66- </ div >
67-
68- < CollapsibleContent className = "mt-2 space-y-2" >
69- < div className = "flex items-center gap-2" >
70- < span className = "font-medium text-xs text-foreground min-w-fit" >
71- Execution ID:
72- </ span >
73- < span className = "text-xs text-muted-foreground font-mono truncate" >
74- { executionId }
75- </ span >
76- </ div >
56+ < ContentBlock
57+ title = "Execution Details"
58+ collapsible
59+ defaultOpen
60+ content = {
61+ < BlockStack gap = "2" >
62+ < ExecutionItem name = "Execution ID" value = { executionId } />
7763
7864 { ! isSubgraph && (
7965 < >
80- { isLoadingContainerState && (
81- < div className = "space-y-2" >
82- < div className = "flex items-center gap-2" >
83- < Skeleton className = "h-3 w-12" />
84- < Skeleton className = "h-3 w-32" />
85- </ div >
86- < div className = "flex items-center gap-2" >
87- < Skeleton className = "h-3 w-20" />
88- < Skeleton className = "h-3 w-24" />
89- < Skeleton className = "h-3 w-40" />
90- </ div >
91- </ div >
92- ) }
93-
94- { containerStateError && (
95- < InfoBox title = "Failed to load container state" variant = "error" >
96- { containerStateError . message }
97- </ InfoBox >
98- ) }
66+ { isLoadingContainerState && loadingMarkup }
9967
10068 { ! ! containerState ?. exit_code && (
101- < div className = "flex flex-wrap items-center gap-1" >
102- < span className = "font-medium text-xs text-destructive" >
103- Exit Code:
104- </ span >
105- < span className = "text-xs text-destructive" >
106- { containerState . exit_code }
107- </ span >
108- { containerState . exit_code === EXIT_CODE_OOM && (
109- < span className = "text-xs text-destructive" >
110- (Out of Memory)
111- </ span >
112- ) }
113- </ div >
69+ < ExecutionItem
70+ name = "Exit Code"
71+ critical
72+ value = {
73+ containerState . exit_code === EXIT_CODE_OOM
74+ ? `${ containerState . exit_code } (Out of Memory)`
75+ : `${ containerState . exit_code } `
76+ }
77+ />
11478 ) }
11579
11680 { containerState ?. started_at && (
117- < div className = "flex items-center gap-2" >
118- < span className = "font-medium text-xs text-foreground min-w-fit" >
119- Started:
120- </ span >
121- < span className = "text-xs text-muted-foreground" >
122- { formatDate ( containerState . started_at ) }
123- </ span >
124- </ div >
81+ < ExecutionItem
82+ name = "Started"
83+ value = { formatDate ( containerState . started_at ) }
84+ />
12585 ) }
12686
12787 { containerState ?. ended_at && containerState ?. started_at && (
128- < div className = "flex items-center gap-2" >
129- < span className = "font-medium text-xs text-foreground min-w-fit" >
130- Completed in:
131- </ span >
132- < span className = "text-xs text-muted-foreground" >
133- { formatDuration (
134- containerState . started_at ,
135- containerState . ended_at ,
136- ) }
137- </ span >
138- < span className = "text-xs text-muted-foreground" >
139- ({ formatDate ( containerState . ended_at ) } )
140- </ span >
141- </ div >
88+ < ExecutionItem
89+ name = "Completed in"
90+ value = { `${ formatDuration (
91+ containerState . started_at ,
92+ containerState . ended_at ,
93+ ) } (${ formatDate ( containerState . ended_at ) } )`}
94+ />
14295 ) }
14396
144- { podName && (
145- < div className = "flex items-center gap-2" >
146- < span className = "font-medium text-xs text-foreground min-w-fit" >
147- Pod Name:
148- </ span >
149- < span className = "text-xs text-muted-foreground" >
150- { podName }
151- </ span >
152- </ div >
153- ) }
154- { executionJobLinks && (
155- < >
156- { executionJobLinks . map ( ( linkInfo ) => (
157- < div
158- key = { linkInfo . name }
159- className = "flex text-xs items-center gap-2"
160- >
161- < span className = "font-medium text-foreground min-w-fit" >
162- { linkInfo . name } :
163- </ span >
164- < a
165- href = { linkInfo . url }
166- className = "text-sky-500 hover:underline flex items-center gap-1"
167- target = "_blank"
168- rel = "noopener noreferrer"
169- >
170- { linkInfo . value }
171- < ExternalLink className = "size-3 shrink-0" />
172- </ a >
173- </ div >
174- ) ) }
175- </ >
176- ) }
97+ { podName && < ExecutionItem name = "Pod Name" value = { podName } /> }
98+
99+ { executionJobLinks ?. map ( ( linkInfo ) => (
100+ < ExecutionItem
101+ name = { linkInfo . name }
102+ value = { linkInfo }
103+ key = { linkInfo . name }
104+ />
105+ ) ) }
177106
178- { ! isLoadingContainerState &&
179- ! containerState &&
180- ! containerStateError && (
181- < div className = "text-xs text-muted-foreground" >
182- Container state not available
183- </ div >
184- ) }
107+ { containerStateError && (
108+ < InfoBox
109+ title = "Failed to load container state"
110+ variant = "error"
111+ className = "wrap-anywhere"
112+ >
113+ { containerStateError . message }
114+ </ InfoBox >
115+ ) }
185116 </ >
186117 ) }
187- </ CollapsibleContent >
188- </ Collapsible >
189- </ div >
118+ </ BlockStack >
119+ }
120+ />
121+ ) ;
122+ } ;
123+
124+ const ExecutionItem = ( {
125+ name,
126+ value,
127+ critical,
128+ } : {
129+ name : string ;
130+ value : string | ExecutionLinkItem ;
131+ critical ?: boolean ;
132+ } ) => {
133+ const isLinkItem = (
134+ val : string | ExecutionLinkItem ,
135+ ) : val is ExecutionLinkItem => {
136+ return ( val as ExecutionLinkItem ) . value !== undefined ;
137+ } ;
138+
139+ return (
140+ < InlineStack gap = "2" blockAlign = "center" wrap = "nowrap" >
141+ < Paragraph size = "xs" tone = { critical ? "critical" : "inherit" } >
142+ { name } :
143+ </ Paragraph >
144+ { isLinkItem ( value ) ? (
145+ < URLBlock href = { value . url } text = { value . value } />
146+ ) : (
147+ < Paragraph
148+ size = "xs"
149+ tone = { critical ? "critical" : "subdued" }
150+ className = "truncate"
151+ >
152+ { value }
153+ </ Paragraph >
154+ ) }
155+ </ InlineStack >
190156 ) ;
191157} ;
192158
0 commit comments