From 9c9af856bac42ef585ff8875ee53952f25c96f74 Mon Sep 17 00:00:00 2001 From: Camiel van Schoonhoven Date: Thu, 4 Dec 2025 13:58:27 -0800 Subject: [PATCH] Cleanup ExecutionDetails --- .../ExecutionDetails/ExecutionDetails.tsx | 252 ------------------ src/components/shared/TaskDetails/Details.tsx | 3 +- .../shared/TaskDetails/ExecutionDetails.tsx | 208 +++++++++++++++ 3 files changed, 210 insertions(+), 253 deletions(-) delete mode 100644 src/components/shared/ExecutionDetails/ExecutionDetails.tsx create mode 100644 src/components/shared/TaskDetails/ExecutionDetails.tsx diff --git a/src/components/shared/ExecutionDetails/ExecutionDetails.tsx b/src/components/shared/ExecutionDetails/ExecutionDetails.tsx deleted file mode 100644 index 55eb03b32..000000000 --- a/src/components/shared/ExecutionDetails/ExecutionDetails.tsx +++ /dev/null @@ -1,252 +0,0 @@ -import { ChevronsUpDown, ExternalLink } from "lucide-react"; - -import type { GetContainerExecutionStateResponse } from "@/api/types.gen"; -import { Button } from "@/components/ui/button"; -import { - Collapsible, - CollapsibleContent, - CollapsibleTrigger, -} from "@/components/ui/collapsible"; -import { Link } from "@/components/ui/link"; -import { Skeleton } from "@/components/ui/skeleton"; -import { useBackend } from "@/providers/BackendProvider"; -import { useFetchContainerExecutionState } from "@/services/executionService"; -import { - type ComponentSpec, - isGraphImplementation, -} from "@/utils/componentSpec"; -import { EXIT_CODE_OOM } from "@/utils/constants"; -import { formatDate, formatDuration } from "@/utils/date"; - -import { InfoBox } from "../InfoBox"; - -interface ExecutionDetailsProps { - executionId: string; - componentSpec?: ComponentSpec; -} - -export const ExecutionDetails = ({ - executionId, - componentSpec, -}: ExecutionDetailsProps) => { - const { backendUrl } = useBackend(); - - const isSubgraph = - componentSpec && isGraphImplementation(componentSpec.implementation); - - const { - data: containerState, - isLoading: isLoadingContainerState, - error: containerStateError, - } = useFetchContainerExecutionState(executionId || "", backendUrl); - - // Don't render if no execution data is available - const hasExecutionData = - executionId || - containerState || - isLoadingContainerState || - containerStateError; - if (!hasExecutionData) { - return null; - } - - const podName = executionPodName(containerState); - const executionJobLinks = getExecutionJobLinks(containerState); - - return ( -
- -
- Execution Details - - - -
- - -
- - Execution ID: - - - {executionId} - -
- - {!isSubgraph && ( - <> - {isLoadingContainerState && ( -
-
- - -
-
- - - -
-
- )} - - {containerStateError && ( - - {containerStateError.message} - - )} - - {!!containerState?.exit_code && ( -
- - Exit Code: - - - {containerState.exit_code} - - {containerState.exit_code === EXIT_CODE_OOM && ( - - (Out of Memory) - - )} -
- )} - - {containerState?.started_at && ( -
- - Started: - - - {formatDate(containerState.started_at)} - -
- )} - - {containerState?.ended_at && containerState?.started_at && ( -
- - Completed in: - - - {formatDuration( - containerState.started_at, - containerState.ended_at, - )} - - - ({formatDate(containerState.ended_at)}) - -
- )} - - {podName && ( -
- - Pod Name: - - - {podName} - -
- )} - {executionJobLinks && ( - <> - {executionJobLinks.map((linkInfo) => ( -
- - {linkInfo.name}: - - - {linkInfo.value} - - -
- ))} - - )} - - {!isLoadingContainerState && - !containerState && - !containerStateError && ( -
- Container state not available -
- )} - - )} -
-
-
- ); -}; - -function executionPodName( - containerState?: GetContainerExecutionStateResponse, -): string | null { - if (!containerState || !("debug_info" in containerState)) { - return null; - } - - const debugInfo = containerState.debug_info as Record; - - if ("pod_name" in debugInfo && typeof debugInfo.pod_name === "string") { - return debugInfo.pod_name; - } - - if ( - "kubernetes" in debugInfo && - debugInfo.kubernetes && - typeof debugInfo.kubernetes === "object" && - "pod_name" in debugInfo.kubernetes && - typeof debugInfo.kubernetes.pod_name === "string" - ) { - return debugInfo.kubernetes.pod_name; - } - - return null; -} - -interface ExecutionLinkItem { - name: string; - value: string; - url?: string; -} - -function getExecutionJobLinks( - containerState?: GetContainerExecutionStateResponse, -): Array | null { - if (!containerState || !("debug_info" in containerState)) { - return null; - } - - const debugInfo = containerState.debug_info as Record; - - const result = Array(); - - const huggingfaceJob = debugInfo.huggingface_job as Record; - if ( - huggingfaceJob && - typeof huggingfaceJob === "object" && - typeof huggingfaceJob.id === "string" && - typeof huggingfaceJob.namespace === "string" - ) { - const url = `https://huggingface.co/jobs/${huggingfaceJob.namespace}/${huggingfaceJob.id}`; - result.push({ - name: "HuggingFace Job", - value: huggingfaceJob.id, - url: url, - }); - } - - return result; -} diff --git a/src/components/shared/TaskDetails/Details.tsx b/src/components/shared/TaskDetails/Details.tsx index 3a134ae01..83d0ff692 100644 --- a/src/components/shared/TaskDetails/Details.tsx +++ b/src/components/shared/TaskDetails/Details.tsx @@ -30,7 +30,7 @@ import { } from "@/utils/URL"; import copyToYaml from "@/utils/yaml"; -import { ExecutionDetails } from "../ExecutionDetails/ExecutionDetails"; +import { ExecutionDetails } from "./ExecutionDetails"; interface TaskDetailsProps { displayName: string; @@ -169,6 +169,7 @@ const TaskDetails = ({ )} diff --git a/src/components/shared/TaskDetails/ExecutionDetails.tsx b/src/components/shared/TaskDetails/ExecutionDetails.tsx new file mode 100644 index 000000000..db2d6d474 --- /dev/null +++ b/src/components/shared/TaskDetails/ExecutionDetails.tsx @@ -0,0 +1,208 @@ +import type { GetContainerExecutionStateResponse } from "@/api/types.gen"; +import { BlockStack, InlineStack } from "@/components/ui/layout"; +import { Skeleton } from "@/components/ui/skeleton"; +import { useBackend } from "@/providers/BackendProvider"; +import { useFetchContainerExecutionState } from "@/services/executionService"; +import { + type ComponentSpec, + isGraphImplementation, +} from "@/utils/componentSpec"; +import { EXIT_CODE_OOM } from "@/utils/constants"; +import { formatDate, formatDuration } from "@/utils/date"; + +import type { AttributeProps } from "../ContextPanel/Blocks/Attribute"; +import { ContentBlock } from "../ContextPanel/Blocks/ContentBlock"; +import { ListBlock } from "../ContextPanel/Blocks/ListBlock"; +import { InfoBox } from "../InfoBox"; + +interface ExecutionDetailsProps { + executionId: string; + componentSpec?: ComponentSpec; + className?: string; +} + +export const ExecutionDetails = ({ + executionId, + componentSpec, + className, +}: ExecutionDetailsProps) => { + const { backendUrl } = useBackend(); + + const isSubgraph = + componentSpec && isGraphImplementation(componentSpec.implementation); + + const { + data: containerState, + isLoading: isLoadingContainerState, + error: containerStateError, + } = useFetchContainerExecutionState(executionId, backendUrl); + + const getExecutionItems = () => { + const items: AttributeProps[] = [ + { label: "Execution ID", value: executionId }, + ]; + + if (isSubgraph) { + return items; + } + + if (containerState?.exit_code && containerState.exit_code > 0) { + const exitCodeValue = + containerState.exit_code === EXIT_CODE_OOM + ? `${containerState.exit_code} (Out of Memory)` + : `${containerState.exit_code}`; + + items.push({ + label: "Exit Code", + value: exitCodeValue, + critical: true, + }); + } + + if (containerState?.started_at) { + items.push({ + label: "Started", + value: formatDate(containerState.started_at), + }); + } + + if (containerState?.ended_at && containerState?.started_at) { + items.push({ + label: "Completed in", + value: `${formatDuration( + containerState.started_at, + containerState.ended_at, + )} (${formatDate(containerState.ended_at)})`, + }); + } + + const podName = executionPodName(containerState); + if (podName) { + items.push({ label: "Pod Name", value: podName }); + } + + const executionJobLinks = getExecutionJobLinks(containerState); + if (executionJobLinks) { + executionJobLinks.forEach((linkInfo) => { + if (!linkInfo.url) { + return; + } + + items.push({ + label: linkInfo.name, + value: { text: linkInfo.value, href: linkInfo.url }, + }); + }); + } + + return items; + }; + + const executionItems = getExecutionItems(); + + return ( + + + + + {!isSubgraph && isLoadingContainerState && ( + + + + + + + + + + + + )} + + {!isSubgraph && containerStateError && ( + + {containerStateError.message} + + )} + + + ); +}; + +function executionPodName( + containerState?: GetContainerExecutionStateResponse, +): string | null { + if (!containerState || !("debug_info" in containerState)) { + return null; + } + + const debugInfo = containerState.debug_info; + + if (!isRecord(debugInfo)) { + return null; + } + + if (typeof debugInfo.pod_name === "string") { + return debugInfo.pod_name; + } + + if ( + isRecord(debugInfo.kubernetes) && + typeof debugInfo.kubernetes.pod_name === "string" + ) { + return debugInfo.kubernetes.pod_name; + } + + return null; +} + +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null; +} + +interface ExecutionLinkItem { + name: string; + value: string; + url?: string; +} + +function getExecutionJobLinks( + containerState?: GetContainerExecutionStateResponse, +): Array | null { + if (!containerState || !("debug_info" in containerState)) { + return null; + } + + const debugInfo = containerState.debug_info; + + if (!isRecord(debugInfo)) { + return null; + } + + const result = Array(); + + const huggingfaceJob = debugInfo.huggingface_job; + if ( + isRecord(huggingfaceJob) && + typeof huggingfaceJob.id === "string" && + typeof huggingfaceJob.namespace === "string" + ) { + const url = `https://huggingface.co/jobs/${huggingfaceJob.namespace}/${huggingfaceJob.id}`; + result.push({ + name: "HuggingFace Job", + value: huggingfaceJob.id, + url: url, + }); + } + + return result; +}