Skip to content

Commit 2b28f83

Browse files
Merge pull request #806 from zenml-io/staging
Release
2 parents 23e7f55 + ea8c53d commit 2b28f83

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+3046
-775
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@
2525
"dependencies": {
2626
"@fontsource/inter": "^5.2.5",
2727
"@hookform/resolvers": "^3.10.0",
28+
"@radix-ui/react-collapsible": "^1.1.12",
2829
"@radix-ui/react-tabs": "^1.1.3",
2930
"@tanstack/react-query": "^5.72.0",
3031
"@tanstack/react-table": "^8.21.2",
32+
"@tanstack/react-virtual": "^3.13.12",
3133
"@zenml-io/react-component-library": "^0.23.1",
3234
"awesome-debounce-promise": "^2.1.0",
3335
"class-variance-authority": "^0.7.1",

pnpm-lock.yaml

Lines changed: 197 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/app/runs/[id]/_Tabs/Configuration/index.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ export function ConfigurationTab() {
4141
)}
4242
<CodeCollapsible runId={runId} />
4343
<EnvironmentCollapsible run={data} />
44+
<NestedCollapsible
45+
isInitialOpen
46+
title="Cache Policy"
47+
sortKeysAlphabetically={false}
48+
data={data.metadata?.config.cache_policy ?? undefined}
49+
/>
4450
<NestedCollapsible
4551
isInitialOpen
4652
title="Resources"

src/app/runs/[id]/_Tabs/LogTab/logs.tsx

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ import { EnhancedLogsViewer } from "@/components/logs/enhanced-log-viewer";
44
import { LoadingLogs } from "@/components/logs/loading-logs";
55
import { usePipelineRun } from "@/data/pipeline-runs/pipeline-run-detail-query";
66
import { useRunLogs } from "@/data/pipeline-runs/run-logs";
7-
import { parseLogString } from "@/lib/logs";
87
import { Skeleton } from "@zenml-io/react-component-library/components/server";
98
import { useMemo, useState } from "react";
109
import { useParams } from "react-router-dom";
1110
import { LogCombobox } from "./combobox";
11+
import { buildInternalLogEntries } from "@/lib/logs";
1212

1313
export function LogTab() {
1414
const { runId } = useParams() as { runId: string };
@@ -39,14 +39,18 @@ function LogTabContent({ sources, runId }: { sources: string[]; runId: string })
3939
const [selectedSource, setSelectedSource] = useState<string>(sources[0]);
4040
return (
4141
<section className="space-y-5">
42-
{sources.length > 1 && (
42+
{sources.length > 0 && (
4343
<div className="flex items-center gap-2">
4444
<span className="text-theme-text-secondary">Logs source:</span>
45-
<LogCombobox
46-
sources={sources}
47-
selectedSource={selectedSource}
48-
setSelectedSource={setSelectedSource}
49-
/>
45+
{sources.length > 1 ? (
46+
<LogCombobox
47+
sources={sources}
48+
selectedSource={selectedSource}
49+
setSelectedSource={setSelectedSource}
50+
/>
51+
) : (
52+
<span className="font-semibold capitalize">{selectedSource}</span>
53+
)}
5054
</div>
5155
)}
5256
<LogDisplay selectedSource={selectedSource} runId={runId} />
@@ -63,7 +67,7 @@ function LogDisplay({ selectedSource, runId }: LogTabContentProps) {
6367

6468
const parsedLogs = useMemo(() => {
6569
if (!runLogs.data) return [];
66-
return parseLogString(runLogs.data);
70+
return buildInternalLogEntries(runLogs.data);
6771
}, [runLogs.data]);
6872

6973
if (runLogs.isPending) return <LoadingLogs />;

src/app/runs/[id]/_Tabs/Overview/Details.tsx

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { Key, Value } from "@/components/KeyValue";
1010
import { RepoBadge } from "@/components/repositories/RepoBadge";
1111
import { usePipelineRun } from "@/data/pipeline-runs/pipeline-run-detail-query";
1212
import { calculateTimeDifference } from "@/lib/dates";
13+
import { snakeCaseToTitleCase } from "@/lib/strings";
1314
import { routes } from "@/router/routes";
1415
import {
1516
CollapsibleContent,
@@ -48,6 +49,9 @@ export function Details() {
4849
if (isError) return null;
4950
if (isPending) return <Skeleton className="h-[200px] w-full" />;
5051

52+
const statusReason = data.body?.status_reason;
53+
const executionMode = data.metadata?.config.execution_mode;
54+
5155
return (
5256
<CollapsiblePanel open={open} onOpenChange={setOpen}>
5357
<CollapsibleHeader className="flex items-center gap-[10px]">
@@ -71,15 +75,31 @@ export function Details() {
7175
</Value>
7276
<Key>Status</Key>
7377
<Value>
74-
<Tag
75-
className="inline-flex items-center gap-0.5"
76-
emphasis="subtle"
77-
color={getExecutionStatusTagColor(data.body?.status)}
78-
>
79-
<ExecutionStatusIcon className="fill-current" status={data.body?.status} />
80-
{data.body?.status}
81-
</Tag>
78+
{(() => {
79+
const statusTag = (
80+
<Tag
81+
className="inline-flex items-center gap-0.5"
82+
emphasis="subtle"
83+
color={getExecutionStatusTagColor(data.body?.status)}
84+
>
85+
<ExecutionStatusIcon className="fill-current" status={data.body?.status} />
86+
{statusReason ?? data.body?.status}
87+
</Tag>
88+
);
89+
90+
return statusReason ? (
91+
<TooltipProvider>
92+
<Tooltip>
93+
<TooltipTrigger>{statusTag}</TooltipTrigger>
94+
<TooltipContent>{data.body?.status}</TooltipContent>
95+
</Tooltip>
96+
</TooltipProvider>
97+
) : (
98+
statusTag
99+
);
100+
})()}
82101
</Value>
102+
83103
<Key>Pipeline</Key>
84104
<Value>
85105
{data.body?.pipeline ? (
@@ -102,6 +122,8 @@ export function Details() {
102122
"Not available"
103123
)}
104124
</Value>
125+
<Key>Execution Mode</Key>
126+
<Value>{executionMode ? snakeCaseToTitleCase(executionMode) : "Not available"}</Value>
105127
<Key>
106128
<div className="flex items-center space-x-0.5 truncate">
107129
<span className="truncate">Repository/Commit</span>
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import Expand from "@/assets/icons/expand.svg?react";
2+
import { Button } from "@zenml-io/react-component-library";
3+
import { Dispatch, ReactNode, SetStateAction } from "react";
4+
5+
export function GlobalDagControls({ children }: { children: ReactNode }) {
6+
return <div className="absolute right-4 top-4 flex items-center gap-2">{children}</div>;
7+
}
8+
9+
type ExpandPanelProps = {
10+
isPanelOpen: boolean;
11+
setIsPanelOpen: Dispatch<SetStateAction<boolean>>;
12+
};
13+
export function ExpandPanelButton({ isPanelOpen, setIsPanelOpen }: ExpandPanelProps) {
14+
return (
15+
<Button
16+
intent="secondary"
17+
className={`h-7 w-7 items-center justify-center border border-neutral-300 bg-theme-surface-primary p-0.5 ${
18+
isPanelOpen ? "hidden" : "flex"
19+
}`}
20+
onClick={() => setIsPanelOpen(true)}
21+
>
22+
<Expand className="h-5 w-5 fill-theme-text-primary" />
23+
</Button>
24+
);
25+
}

src/app/runs/[id]/page.tsx

Lines changed: 10 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1-
import Expand from "@/assets/icons/expand.svg?react";
21
import { useServerInfo } from "@/data/server/info-query";
32
import { checkIsLocalServer } from "@/lib/server";
4-
import { Button, ScrollArea } from "@zenml-io/react-component-library";
5-
import { Dispatch, SetStateAction, useState } from "react";
6-
import { DAG } from "./Dag";
3+
import { ScrollArea } from "@zenml-io/react-component-library";
4+
import { useState } from "react";
5+
import { useParams } from "react-router-dom";
76
import { RunsDetailHeader } from "./Header";
87
import { RunsDetailTabs, TabsHeader } from "./_Tabs";
8+
import { ExpandPanelButton, GlobalDagControls } from "./expand-panel-button";
9+
import { PipelineVisualization } from "./pipeline-viz";
910

1011
export default function RunDetailPage() {
12+
const { runId } = useParams() as { runId: string };
1113
const serverInfo = useServerInfo();
1214
const isLocal = checkIsLocalServer(serverInfo.data?.deployment_type || "other");
1315
const [isPanelOpen, setIsPanelOpen] = useState(true);
@@ -22,8 +24,10 @@ export default function RunDetailPage() {
2224
isPanelOpen ? "w-1/2" : "w-full"
2325
}`}
2426
>
25-
<DAG />
26-
<ExpandPanelButton isPanelOpen={isPanelOpen} setIsPanelOpen={setIsPanelOpen} />
27+
<PipelineVisualization runId={runId} />
28+
<GlobalDagControls>
29+
<ExpandPanelButton isPanelOpen={isPanelOpen} setIsPanelOpen={setIsPanelOpen} />
30+
</GlobalDagControls>
2731
</div>
2832
<div
2933
aria-hidden={!isPanelOpen}
@@ -40,21 +44,3 @@ export default function RunDetailPage() {
4044
</div>
4145
);
4246
}
43-
44-
type ExpandPanelProps = {
45-
isPanelOpen: boolean;
46-
setIsPanelOpen: Dispatch<SetStateAction<boolean>>;
47-
};
48-
function ExpandPanelButton({ isPanelOpen, setIsPanelOpen }: ExpandPanelProps) {
49-
return (
50-
<Button
51-
intent="secondary"
52-
className={`absolute right-4 top-4 h-7 w-7 items-center justify-center border border-neutral-300 bg-theme-surface-primary p-0.5 ${
53-
isPanelOpen ? "hidden" : "flex"
54-
}`}
55-
onClick={() => setIsPanelOpen(true)}
56-
>
57-
<Expand className="h-5 w-5 fill-theme-text-primary" />
58-
</Button>
59-
);
60-
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import {
2+
AlertDialog,
3+
AlertDialogAction,
4+
AlertDialogCancel,
5+
AlertDialogContent,
6+
AlertDialogDescription,
7+
AlertDialogFooter,
8+
AlertDialogHeader,
9+
AlertDialogTitle
10+
} from "@zenml-io/react-component-library";
11+
import { Button } from "@zenml-io/react-component-library/components/server";
12+
13+
type Props = {
14+
open: boolean;
15+
onOpenChange: (open: boolean) => void;
16+
nodeCount: number;
17+
threshold: number;
18+
onConfirm: () => void;
19+
};
20+
21+
export function DagViewConfirmationDialog({
22+
open,
23+
onOpenChange,
24+
nodeCount,
25+
threshold,
26+
onConfirm
27+
}: Props) {
28+
const handleConfirm = () => {
29+
onConfirm();
30+
onOpenChange(false);
31+
};
32+
33+
return (
34+
<AlertDialog open={open} onOpenChange={onOpenChange}>
35+
<AlertDialogContent className="p-0">
36+
<AlertDialogHeader className="py-2 pl-5 pr-3">
37+
<AlertDialogTitle className="text-text-lg font-semibold">
38+
Switch to DAG View
39+
</AlertDialogTitle>
40+
</AlertDialogHeader>
41+
<div className="border-y border-theme-border-moderate px-5 py-5">
42+
<AlertDialogDescription className="space-y-2">
43+
<p>
44+
This pipeline has <strong>{nodeCount} nodes</strong>, which exceeds the recommended
45+
threshold of {threshold} nodes for DAG visualization.
46+
</p>
47+
<p>
48+
Rendering a large DAG may impact performance and take longer to load. Do you want to
49+
continue?
50+
</p>
51+
</AlertDialogDescription>
52+
</div>
53+
<AlertDialogFooter className="flex justify-end gap-3 px-5 py-3">
54+
<AlertDialogCancel asChild>
55+
<Button intent="secondary">Cancel</Button>
56+
</AlertDialogCancel>
57+
<AlertDialogAction asChild>
58+
<Button onClick={handleConfirm} intent="primary">
59+
Continue
60+
</Button>
61+
</AlertDialogAction>
62+
</AlertDialogFooter>
63+
</AlertDialogContent>
64+
</AlertDialog>
65+
);
66+
}

src/app/runs/[id]/Dag.tsx renamed to src/app/runs/[id]/pipeline-viz/dag/index.tsx

Lines changed: 20 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import AlertCircle from "@/assets/icons/alert-circle.svg?react";
21
import { ArtifactNode } from "@/components/dag-visualizer/ArtifactNode";
32
import { DagControls } from "@/components/dag-visualizer/Controls";
43
import { ElkEdge } from "@/components/dag-visualizer/elk-edge";
@@ -7,11 +6,18 @@ import { PreviewArtifactNode } from "@/components/dag-visualizer/PreviewArtifact
76
import { PreviewStepNode } from "@/components/dag-visualizer/PreviewStep";
87
import { SheetProvider } from "@/components/dag-visualizer/sheet-context";
98
import { StepNode } from "@/components/dag-visualizer/StepNode";
10-
import { EmptyState } from "@/components/EmptyState";
9+
import { Dag } from "@/types/dag-visualizer";
1110
import { Spinner } from "@zenml-io/react-component-library/components/server";
12-
import { useMemo } from "react";
1311
import ReactFlow, { NodeTypes } from "reactflow";
1412
import { useDag } from "./useDag";
13+
import { ViewSwitcher } from "../view-switcher";
14+
import { PiplineRunVisualizationView } from "../types";
15+
16+
type Props = {
17+
dagData: Dag;
18+
refetchHandler: () => void;
19+
setActiveView?: (view: PiplineRunVisualizationView) => void;
20+
};
1521

1622
const customEdge = {
1723
elk: ElkEdge
@@ -24,37 +30,16 @@ const customNodes: NodeTypes = {
2430
previewArtifact: PreviewArtifactNode
2531
};
2632

27-
export function DAG() {
28-
const {
29-
dagQuery,
30-
nodes,
31-
edges,
32-
onNodesChange,
33-
onEdgesChange,
34-
initialRender,
35-
topMostNode,
36-
shouldFitView
37-
} = useDag();
38-
39-
// Memoize refetch function to prevent DagControls re-render
40-
const handleRefetch = useMemo(() => {
41-
return () => dagQuery.refetch();
42-
}, [dagQuery]);
43-
44-
if (dagQuery.isError) {
45-
return (
46-
<EmptyState icon={<AlertCircle className="h-[120px] w-[120px] fill-neutral-300" />}>
47-
<p className="text-center">There was an error loading the DAG visualization.</p>
48-
</EmptyState>
49-
);
50-
}
33+
export function DAG({ dagData, refetchHandler, setActiveView }: Props) {
34+
const { nodes, edges, onNodesChange, onEdgesChange, initialRender, topMostNode, shouldFitView } =
35+
useDag(dagData);
5136

52-
if (dagQuery.isPending || initialRender)
37+
if (initialRender)
5338
return (
5439
<div className="flex h-full flex-col items-center justify-center">
5540
<Spinner />
5641
<div className="mt-4 flex flex-col items-center">
57-
<p className="mb-5 text-display-xs">Loading DAG Visualization</p>
42+
<p className="mb-5 text-display-xs">Calculating DAG Visualization</p>
5843
</div>
5944
</div>
6045
);
@@ -87,7 +72,12 @@ export function DAG() {
8772
: undefined
8873
}
8974
>
90-
<DagControls refetch={handleRefetch} />
75+
<DagControls
76+
refetch={refetchHandler}
77+
viewSwitcher={
78+
setActiveView ? <ViewSwitcher activeView={"dag"} setActiveView={setActiveView} /> : null
79+
}
80+
/>
9181
</ReactFlow>
9282
<GlobalSheets />
9383
</SheetProvider>

0 commit comments

Comments
 (0)