Skip to content

Commit 3b10819

Browse files
feat: updates to client env & deep refresh (#737)
* feat: updates for client env * feat: init deep refresh (#738)
1 parent 3bc2631 commit 3b10819

File tree

15 files changed

+353
-77
lines changed

15 files changed

+353
-77
lines changed

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { Skeleton } from "@zenml-io/react-component-library";
77
import { useEffect } from "react";
88
import { useParams } from "react-router-dom";
99
import { RunActionsMenu } from "./RunActionMenu";
10+
import { RunRefreshGroup } from "@/components/runs/refresh-group";
1011

1112
export function RunsDetailHeader() {
1213
const { runId } = useParams() as { runId: string };
@@ -42,7 +43,10 @@ export function RunsDetailHeader() {
4243
</>
4344
)}
4445
</div>
45-
<RunActionsMenu />
46+
<div className="flex items-center gap-1">
47+
<RunRefreshGroup runId={runId} />
48+
<RunActionsMenu />
49+
</div>
4650
</PageHeader>
4751
);
4852
}

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

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import HorizontalDots from "@/assets/icons/dots-horizontal.svg?react";
22
import Trash from "@/assets/icons/trash.svg?react";
33
import {
4+
Button,
45
DropdownMenu,
56
DropdownMenuContent,
67
DropdownMenuItem,
@@ -19,9 +20,16 @@ export function RunActionsMenu() {
1920
<DeleteRunAlert setOpen={setDeleteOpen} open={deleteOpen} />
2021
<DropdownMenu modal={dropdownOpen} open={dropdownOpen} onOpenChange={setDropdownOpen}>
2122
<DropdownMenu>
22-
<DropdownMenuTrigger className="z-10">
23-
<HorizontalDots className="h-5 w-5 shrink-0 fill-theme-text-secondary" />
24-
<p className="sr-only">Run Actions</p>
23+
<DropdownMenuTrigger asChild>
24+
<Button
25+
intent="secondary"
26+
className="flex aspect-square items-center justify-center p-0"
27+
emphasis="minimal"
28+
size="md"
29+
>
30+
<HorizontalDots className="h-5 w-5 shrink-0 fill-theme-text-secondary" />
31+
<p className="sr-only">Run Actions</p>
32+
</Button>
2533
</DropdownMenuTrigger>
2634
<DropdownMenuContent className="z-10" align="end" sideOffset={1}>
2735
<DropdownMenuItem onClick={() => setDeleteOpen(true)} className="space-x-2">

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

Lines changed: 66 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,29 @@ import {
55
CollapsibleContent,
66
CollapsibleHeader,
77
CollapsiblePanel,
8-
CollapsibleTrigger
8+
CollapsibleTrigger,
9+
Tooltip,
10+
TooltipContent,
11+
TooltipProvider,
12+
TooltipTrigger
913
} from "@zenml-io/react-component-library";
1014
import { useState } from "react";
1115

1216
type Props = {
1317
run: PipelineRun;
1418
};
1519

20+
const PYTHON_PACKAGES_KEY = "python_packages";
21+
1622
export function EnvironmentCollapsible({ run }: Props) {
1723
const [open, setOpen] = useState(true);
1824

25+
const executionEnvironment = run.metadata?.orchestrator_environment;
26+
const clientEnvironment = run.metadata?.client_environment;
27+
28+
const normalizedExecutionEnvironment = normalizePythonPackages(executionEnvironment ?? {});
29+
const normalizedClientEnvironment = normalizePythonPackages(clientEnvironment ?? {});
30+
1931
return (
2032
<CollapsiblePanel open={open} onOpenChange={setOpen}>
2133
<CollapsibleHeader intent="primary" className="flex items-center gap-[10px]">
@@ -32,16 +44,65 @@ export function EnvironmentCollapsible({ run }: Props) {
3244
<NestedCollapsible
3345
isInitialOpen
3446
intent="secondary"
35-
title="Client Environment"
36-
data={run.metadata?.client_environment}
47+
title={
48+
<TooltipProvider>
49+
<Tooltip>
50+
<TooltipTrigger asChild>
51+
<span>Client Environment</span>
52+
</TooltipTrigger>
53+
<TooltipContent className="max-w-[200px] whitespace-normal xl:max-w-[300px]">
54+
Environment where you start your pipeline run by calling the pipeline function.{" "}
55+
<a
56+
rel="noopener noreferrer"
57+
className="link"
58+
href="https://docs.zenml.io/user-guides/best-practices/configure-python-environments#client-environment-or-the-runner-environment"
59+
target="_blank"
60+
>
61+
Learn more about client environments
62+
</a>
63+
</TooltipContent>
64+
</Tooltip>
65+
</TooltipProvider>
66+
}
67+
data={normalizedClientEnvironment}
3768
/>
3869
<NestedCollapsible
3970
isInitialOpen
4071
intent="secondary"
41-
title="Orchestrator Environment"
42-
data={run.metadata?.orchestrator_environment}
72+
title={
73+
<TooltipProvider>
74+
<Tooltip>
75+
<TooltipTrigger asChild>
76+
<span>Execution Environment</span>
77+
</TooltipTrigger>
78+
<TooltipContent className="max-w-[200px] whitespace-normal xl:max-w-[300px]">
79+
Environment where your ZenML steps get executed.{" "}
80+
<a
81+
rel="noopener noreferrer"
82+
className="link"
83+
href="https://docs.zenml.io/user-guides/best-practices/configure-python-environments#execution-environments"
84+
target="_blank"
85+
>
86+
Learn more about exectuion environments
87+
</a>
88+
</TooltipContent>
89+
</Tooltip>
90+
</TooltipProvider>
91+
}
92+
data={normalizedExecutionEnvironment}
4393
/>
4494
</CollapsibleContent>
4595
</CollapsiblePanel>
4696
);
4797
}
98+
99+
function normalizePythonPackages(env: Record<string, unknown>) {
100+
const pythonPackages = env[PYTHON_PACKAGES_KEY];
101+
if (typeof pythonPackages === "string") {
102+
return {
103+
...env,
104+
[PYTHON_PACKAGES_KEY]: pythonPackages.split("\n").map((pkg) => pkg.trim())
105+
};
106+
}
107+
return env;
108+
}

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ import { PipelineParamsCollapsible } from "./ParameterCollapsible";
1111

1212
export function ConfigurationTab() {
1313
const { runId } = useParams() as { runId: string };
14-
const { data, isError, isPending } = usePipelineRun({ runId: runId }, { throwOnError: true });
14+
const { data, isError, isPending } = usePipelineRun(
15+
{ runId: runId, queryParams: { include_python_packages: true } },
16+
{ throwOnError: true }
17+
);
1518
const { data: buildData } = usePipelineBuild(
1619
{
1720
buildId: data?.body?.build?.id as string

src/components/CollapsibleCard.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ type CollapsibleCardProps = {
1616
className?: string;
1717
contentClassName?: string;
1818
intent?: CollapsibleHeaderProps["intent"];
19+
headerClassName?: string;
20+
headerChildren?: React.ReactNode;
1921
};
2022

2123
export function CollapsibleCard({
@@ -24,12 +26,14 @@ export function CollapsibleCard({
2426
initialOpen = false,
2527
className,
2628
contentClassName,
27-
intent = "primary"
29+
intent = "primary",
30+
headerClassName,
31+
headerChildren
2832
}: CollapsibleCardProps) {
2933
const [open, setOpen] = useState(initialOpen);
3034
return (
3135
<CollapsiblePanel className={className} open={open} onOpenChange={setOpen}>
32-
<CollapsibleHeader intent={intent}>
36+
<CollapsibleHeader intent={intent} className={headerClassName}>
3337
<CollapsibleTrigger className="flex w-full items-center gap-[10px]">
3438
<ChevronDown
3539
className={` ${
@@ -38,6 +42,8 @@ export function CollapsibleCard({
3842
/>
3943
{title}
4044
</CollapsibleTrigger>
45+
46+
{headerChildren}
4147
</CollapsibleHeader>
4248

4349
<CollapsibleContent

src/components/MetadataCards.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { CollapsibleCard } from "./CollapsibleCard";
1111
import { KeyValue } from "./KeyValue";
1212
import { NestedCollapsible } from "./NestedCollapsible";
1313
import { isUrl } from "../lib/url";
14+
import { CopyMetadataButton } from "./copy-metadata-button";
1415

1516
type Props = { metadata: MetadataMap };
1617

@@ -35,9 +36,22 @@ export function UncategorizedCard({ metadata, title }: Props & { title?: string
3536
// sort nonDicts alphabetically by index 0
3637
nonDicts.sort((a, b) => a[0].localeCompare(b[0]));
3738

39+
const nonDictsObject = nonDicts.reduce(
40+
(acc, [key, value]) => {
41+
acc[key] = value;
42+
return acc;
43+
},
44+
{} as Record<string, unknown>
45+
);
46+
3847
return (
3948
<div>
40-
<CollapsibleCard initialOpen title={title || "Uncategorized"}>
49+
<CollapsibleCard
50+
headerClassName="flex items-center gap-2"
51+
headerChildren={<CopyMetadataButton copyText={JSON.stringify(nonDictsObject)} />}
52+
initialOpen
53+
title={title || "Uncategorized"}
54+
>
4155
<dl className="grid grid-cols-1 gap-x-[10px] gap-y-2 md:grid-cols-3 md:gap-y-4">
4256
{nonDicts.map(([name, value], idx) => {
4357
return (

src/components/NestedCollapsible.tsx

Lines changed: 47 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ import { PropsWithChildren, ReactNode } from "react";
1111
import { Codesnippet } from "./CodeSnippet";
1212
import { CollapsibleCard } from "./CollapsibleCard";
1313
import { KeyValue } from "./KeyValue";
14+
import { CopyMetadataButton } from "./copy-metadata-button";
15+
16+
const regex = /^<class\s+'.*'>$/;
1417

1518
type Props = {
1619
intent?: CollapsibleHeaderProps["intent"];
@@ -33,7 +36,6 @@ export function NestedCollapsible({
3336
const objects: { [key: string]: Record<string, unknown> } = {};
3437
const nonObjects: { [key: string]: unknown } = {};
3538
const arrays: { [key: string]: unknown[] } = {};
36-
const regex = /^<class\s+'.*'>$/;
3739

3840
for (const [key, value] of Object.entries(data || {})) {
3941
if (isObject(value)) {
@@ -48,15 +50,19 @@ export function NestedCollapsible({
4850
const values = Object.entries(nonObjects);
4951
values.sort((a, b) => a[0].localeCompare(b[0]));
5052

53+
const hasNoData = Object.keys(data || {}).length === 0;
54+
5155
return (
5256
<CollapsibleCard
5357
contentClassName={contentClassName}
5458
className={className}
5559
initialOpen={isInitialOpen}
5660
intent={intent}
5761
title={title}
62+
headerClassName="flex items-center gap-2"
63+
headerChildren={hasNoData ? null : <CopyMetadataButton copyText={JSON.stringify(data)} />}
5864
>
59-
{Object.keys(data || {}).length === 0 ? (
65+
{hasNoData ? (
6066
<p>Not available</p>
6167
) : (
6268
<div className="flex flex-col gap-3">
@@ -72,28 +78,7 @@ export function NestedCollapsible({
7278
</Tooltip>
7379
</TooltipProvider>
7480
}
75-
value={
76-
<>
77-
{isString(value) && regex.test(value) ? (
78-
<Codesnippet className="py-1" highlightCode code={value} />
79-
) : value === null ? (
80-
<div>null</div>
81-
) : isString(value) && isUrl(value) ? (
82-
<a
83-
className="underline transition-all duration-200 hover:decoration-transparent"
84-
href={value}
85-
target="_blank"
86-
rel="noopener noreferrer"
87-
>
88-
{value}
89-
</a>
90-
) : (
91-
<div className="whitespace-normal">
92-
{isString(value) ? value : JSON.stringify(value)}
93-
</div>
94-
)}
95-
</>
96-
}
81+
value={<RenderSimpleValue value={value} />}
9782
/>
9883
))}
9984
</dl>
@@ -118,43 +103,21 @@ function RenderArray({ title, value }: { title: string; value: unknown[] }) {
118103

119104
return (
120105
<>
121-
<CollapsibleCard intent="secondary" key={title} title={title}>
106+
<CollapsibleCard
107+
headerClassName="flex items-center gap-2"
108+
headerChildren={<CopyMetadataButton copyText={JSON.stringify(value)} />}
109+
intent="secondary"
110+
key={title}
111+
title={title}
112+
>
122113
{simpleValues.length > 0 && (
123-
<dl className="grid grid-cols-1 gap-x-[10px] gap-y-2 truncate md:grid-cols-3 md:gap-y-4">
124-
{Object.entries(simpleValues).map(([key, value]) => (
125-
<KeyValue
126-
key={key}
127-
label={
128-
<TooltipProvider>
129-
<Tooltip>
130-
<TooltipTrigger className="cursor-default truncate">{key}</TooltipTrigger>
131-
<TooltipContent className="max-w-[480px]">{key}</TooltipContent>
132-
</Tooltip>
133-
</TooltipProvider>
134-
}
135-
value={
136-
<div className="py-1">
137-
{value === null ? (
138-
<div>null</div>
139-
) : isString(value) && isUrl(value) ? (
140-
<a
141-
className="underline transition-all duration-200 hover:decoration-transparent"
142-
href={value}
143-
target="_blank"
144-
rel="noopener noreferrer"
145-
>
146-
{value}
147-
</a>
148-
) : (
149-
<div className="whitespace-normal">
150-
{isString(value) ? value : JSON.stringify(value)}
151-
</div>
152-
)}
153-
</div>
154-
}
155-
/>
114+
<ul className="space-y-2 md:space-y-4">
115+
{simpleValues.map((val, index) => (
116+
<li key={index}>
117+
<RenderSimpleValue value={val} />
118+
</li>
156119
))}
157-
</dl>
120+
</ul>
158121
)}
159122
{nestedValues.length > 0 && (
160123
<ul className="space-y-4">
@@ -174,3 +137,28 @@ function RenderArray({ title, value }: { title: string; value: unknown[] }) {
174137
</>
175138
);
176139
}
140+
141+
function RenderSimpleValue({ value }: { value: unknown }) {
142+
if (isString(value) && regex.test(value)) {
143+
return <Codesnippet highlightCode code={value} />;
144+
}
145+
146+
if (value === null) {
147+
return <div>null</div>;
148+
}
149+
150+
if (isString(value) && isUrl(value)) {
151+
return (
152+
<a
153+
className="underline transition-all duration-200 hover:decoration-transparent"
154+
href={value}
155+
target="_blank"
156+
rel="noopener noreferrer"
157+
>
158+
{value}
159+
</a>
160+
);
161+
}
162+
163+
return <div className="whitespace-normal">{isString(value) ? value : JSON.stringify(value)}</div>;
164+
}

0 commit comments

Comments
 (0)