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;
+}