1- import { ChevronsUpDown , ExternalLink } from "lucide- react" ;
1+ import { useMemo } from "react" ;
22
33import 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" ;
10- import { Link } from "@/components/ui/link" ;
4+ import { BlockStack , InlineStack } from "@/components/ui/layout" ;
115import { Skeleton } from "@/components/ui/skeleton" ;
126import { useBackend } from "@/providers/BackendProvider" ;
137import { useFetchContainerExecutionState } from "@/services/executionService" ;
@@ -18,16 +12,23 @@ import {
1812import { EXIT_CODE_OOM } from "@/utils/constants" ;
1913import { formatDate , formatDuration } from "@/utils/date" ;
2014
15+ import { ContentBlock } from "../ContextPanel/Blocks/ContentBlock" ;
16+ import {
17+ ListBlock ,
18+ type ListBlockItemProps ,
19+ } from "../ContextPanel/Blocks/ListBlock" ;
2120import { InfoBox } from "../InfoBox" ;
2221
2322interface ExecutionDetailsProps {
2423 executionId : string ;
2524 componentSpec ?: ComponentSpec ;
25+ className ?: string ;
2626}
2727
2828export const ExecutionDetails = ( {
2929 executionId,
3030 componentSpec,
31+ className,
3132} : ExecutionDetailsProps ) => {
3233 const { backendUrl } = useBackend ( ) ;
3334
@@ -38,155 +39,106 @@ export const ExecutionDetails = ({
3839 data : containerState ,
3940 isLoading : isLoadingContainerState ,
4041 error : containerStateError ,
41- } = useFetchContainerExecutionState ( executionId || "" , backendUrl ) ;
42-
43- // Don't render if no execution data is available
44- const hasExecutionData =
45- executionId ||
46- containerState ||
47- isLoadingContainerState ||
48- containerStateError ;
49- if ( ! hasExecutionData ) {
50- return null ;
51- }
42+ } = useFetchContainerExecutionState ( executionId , backendUrl ) ;
43+
44+ const loadingMarkup = (
45+ < BlockStack gap = "2" >
46+ < InlineStack gap = "2" blockAlign = "center" >
47+ < Skeleton className = "h-3 w-12" />
48+ < Skeleton className = "h-3 w-32" />
49+ </ InlineStack >
50+ < InlineStack gap = "2" blockAlign = "center" >
51+ < Skeleton className = "h-3 w-20" />
52+ < Skeleton className = "h-3 w-24" />
53+ < Skeleton className = "h-3 w-40" />
54+ </ InlineStack >
55+ </ BlockStack >
56+ ) ;
5257
53- const podName = executionPodName ( containerState ) ;
54- const executionJobLinks = getExecutionJobLinks ( containerState ) ;
58+ const executionItems = useMemo ( ( ) => {
59+ const items : ListBlockItemProps [ ] = [
60+ { name : "Execution ID" , value : executionId } ,
61+ ] ;
62+
63+ if ( isSubgraph ) {
64+ return items ;
65+ }
66+
67+ if ( containerState ?. exit_code && containerState . exit_code > 0 ) {
68+ const exitCodeValue =
69+ containerState . exit_code === EXIT_CODE_OOM
70+ ? `${ containerState . exit_code } (Out of Memory)`
71+ : `${ containerState . exit_code } ` ;
72+
73+ items . push ( {
74+ name : "Exit Code" ,
75+ value : exitCodeValue ,
76+ critical : true ,
77+ } ) ;
78+ }
79+
80+ if ( containerState ?. started_at ) {
81+ items . push ( {
82+ name : "Started" ,
83+ value : formatDate ( containerState . started_at ) ,
84+ } ) ;
85+ }
86+
87+ if ( containerState ?. ended_at && containerState ?. started_at ) {
88+ items . push ( {
89+ name : "Completed in" ,
90+ value : `${ formatDuration (
91+ containerState . started_at ,
92+ containerState . ended_at ,
93+ ) } (${ formatDate ( containerState . ended_at ) } )`,
94+ } ) ;
95+ }
96+
97+ const podName = executionPodName ( containerState ) ;
98+ if ( podName ) {
99+ items . push ( { name : "Pod Name" , value : podName } ) ;
100+ }
101+
102+ const executionJobLinks = getExecutionJobLinks ( containerState ) ;
103+ if ( executionJobLinks ) {
104+ executionJobLinks . forEach ( ( linkInfo ) => {
105+ if ( ! linkInfo . url ) {
106+ return ;
107+ }
108+
109+ items . push ( {
110+ name : linkInfo . name ,
111+ value : { text : linkInfo . value , href : linkInfo . url } ,
112+ } ) ;
113+ } ) ;
114+ }
115+
116+ return items ;
117+ } , [ executionId , isSubgraph , containerState ] ) ;
55118
56119 return (
57- < div className = "flex flex-col px-3 py-2" >
58- < Collapsible defaultOpen >
59- < div className = "font-medium text-sm text-foreground flex items-center gap-1" >
60- Execution Details
61- < CollapsibleTrigger asChild >
62- < Button variant = "ghost" size = "sm" >
63- < ChevronsUpDown className = "h-4 w-4" />
64- < span className = "sr-only" > Toggle</ span >
65- </ Button >
66- </ CollapsibleTrigger >
67- </ div >
68-
69- < CollapsibleContent className = "mt-2 space-y-2" >
70- < div className = "flex items-center gap-2" >
71- < span className = "font-medium text-xs text-foreground min-w-fit" >
72- Execution ID:
73- </ span >
74- < span className = "text-xs text-muted-foreground font-mono truncate" >
75- { executionId }
76- </ span >
77- </ div >
78-
79- { ! isSubgraph && (
80- < >
81- { isLoadingContainerState && (
82- < div className = "space-y-2" >
83- < div className = "flex items-center gap-2" >
84- < Skeleton className = "h-3 w-12" />
85- < Skeleton className = "h-3 w-32" />
86- </ div >
87- < div className = "flex items-center gap-2" >
88- < Skeleton className = "h-3 w-20" />
89- < Skeleton className = "h-3 w-24" />
90- < Skeleton className = "h-3 w-40" />
91- </ div >
92- </ div >
93- ) }
94-
95- { containerStateError && (
96- < InfoBox title = "Failed to load container state" variant = "error" >
97- { containerStateError . message }
98- </ InfoBox >
99- ) }
100-
101- { ! ! containerState ?. exit_code && (
102- < div className = "flex flex-wrap items-center gap-1" >
103- < span className = "font-medium text-xs text-destructive" >
104- Exit Code:
105- </ span >
106- < span className = "text-xs text-destructive" >
107- { containerState . exit_code }
108- </ span >
109- { containerState . exit_code === EXIT_CODE_OOM && (
110- < span className = "text-xs text-destructive" >
111- (Out of Memory)
112- </ span >
113- ) }
114- </ div >
115- ) }
116-
117- { containerState ?. started_at && (
118- < div className = "flex items-center gap-2" >
119- < span className = "font-medium text-xs text-foreground min-w-fit" >
120- Started:
121- </ span >
122- < span className = "text-xs text-muted-foreground" >
123- { formatDate ( containerState . started_at ) }
124- </ span >
125- </ div >
126- ) }
127-
128- { containerState ?. ended_at && containerState ?. started_at && (
129- < div className = "flex items-center gap-2" >
130- < span className = "font-medium text-xs text-foreground min-w-fit" >
131- Completed in:
132- </ span >
133- < span className = "text-xs text-muted-foreground" >
134- { formatDuration (
135- containerState . started_at ,
136- containerState . ended_at ,
137- ) }
138- </ span >
139- < span className = "text-xs text-muted-foreground" >
140- ({ formatDate ( containerState . ended_at ) } )
141- </ span >
142- </ div >
143- ) }
144-
145- { podName && (
146- < div className = "flex items-center gap-2" >
147- < span className = "font-medium text-xs text-foreground min-w-fit" >
148- Pod Name:
149- </ span >
150- < span className = "text-xs text-muted-foreground" >
151- { podName }
152- </ span >
153- </ div >
154- ) }
155- { executionJobLinks && (
156- < >
157- { executionJobLinks . map ( ( linkInfo ) => (
158- < div
159- key = { linkInfo . name }
160- className = "flex text-xs items-center gap-2"
161- >
162- < span className = "font-medium text-foreground min-w-fit" >
163- { linkInfo . name } :
164- </ span >
165- < Link
166- href = { linkInfo . url }
167- target = "_blank"
168- rel = "noopener noreferrer"
169- >
170- { linkInfo . value }
171- < ExternalLink className = "size-3 shrink-0" />
172- </ Link >
173- </ div >
174- ) ) }
175- </ >
176- ) }
177-
178- { ! isLoadingContainerState &&
179- ! containerState &&
180- ! containerStateError && (
181- < div className = "text-xs text-muted-foreground" >
182- Container state not available
183- </ div >
184- ) }
185- </ >
186- ) }
187- </ CollapsibleContent >
188- </ Collapsible >
189- </ div >
120+ < ContentBlock
121+ title = "Execution Details"
122+ collapsible
123+ defaultOpen
124+ className = { className }
125+ >
126+ < BlockStack gap = "2" >
127+ < ListBlock items = { executionItems } marker = "none" />
128+
129+ { ! isSubgraph && isLoadingContainerState && loadingMarkup }
130+
131+ { ! isSubgraph && containerStateError && (
132+ < InfoBox
133+ title = "Failed to load container state"
134+ variant = "error"
135+ className = "wrap-anywhere"
136+ >
137+ { containerStateError . message }
138+ </ InfoBox >
139+ ) }
140+ </ BlockStack >
141+ </ ContentBlock >
190142 ) ;
191143} ;
192144
0 commit comments