Skip to content

Commit d17596f

Browse files
Merge pull request #665 from zenml-io/staging
Release
2 parents 4972558 + 4f9d46c commit d17596f

Some content is hidden

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

50 files changed

+1495
-269
lines changed

src/app/onboarding/ProductionSetup/Items.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,7 @@ export function RunNewPipeline({ active, completed, hasDownstreamStep }: Onboard
5353
</div>
5454
<div className="space-y-1">
5555
<p className="text-text-sm text-theme-text-secondary">Run the pipeline</p>
56-
<Codesnippet
57-
wrap
58-
codeClasses="whitespace-pre-wrap"
59-
code="python run.py --training-pipeline"
60-
/>
56+
<Codesnippet wrap codeClasses="whitespace-pre-wrap" code="python run.py" />
6157
</div>
6258
<div>
6359
<HelpBox link="https://docs.zenml.io/user-guide/production-guide/understand-stacks" />

src/app/onboarding/StarterSetup/Items.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ export function RunFirstPipeline({ active, completed, hasDownstreamStep }: Onboa
6666
Once it is running, your dashboard will show all the details of the associated run,
6767
models, and artifacts.
6868
</p>
69-
<Codesnippet code="python run.py --training-pipeline" />
69+
<Codesnippet code="python run.py" />
7070
</div>
7171
<Box className="flex w-full flex-wrap items-center justify-between gap-y-1 p-2">
7272
<div className="flex items-center gap-[10px]">

src/app/page.tsx

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import CloudSquares from "@/assets/illustrations/cloud-squares.svg";
2-
import { Box } from "@zenml-io/react-component-library";
2+
import { Badge, Box, Button } from "@zenml-io/react-component-library";
33
import { Codesnippet } from "../components/CodeSnippet";
4+
import External from "@/assets/icons/link-external.svg?react";
5+
import Codespaces from "@/assets/images/codespaces.gif";
46
import { OverviewHeader } from "./Header";
57
import { Link } from "react-router-dom";
68
import { routes } from "@/router/routes";
@@ -9,8 +11,9 @@ export default function IndexPage() {
911
return (
1012
<div>
1113
<OverviewHeader />
12-
<div className="layout-container py-5">
14+
<div className="layout-container space-y-5 py-5">
1315
<OverviewContent />
16+
<VsCodeBox />
1417
</div>
1518
</div>
1619
);
@@ -53,3 +56,33 @@ function OverviewContent() {
5356
</Box>
5457
);
5558
}
59+
60+
function VsCodeBox() {
61+
return (
62+
<Box className="flex flex-col-reverse items-center overflow-hidden md:max-h-[200px] md:flex-row">
63+
<div className="w-full space-y-3 px-7 py-5 xl:w-4/5">
64+
<div className="flex items-center space-x-1">
65+
<Badge className="text-text-xs font-semibold" color="green">
66+
NEW
67+
</Badge>
68+
<h2 className="text-text-xl font-semibold">VS Code Quickstart with ZenML</h2>
69+
</div>
70+
<p className="text-theme-text-secondary">
71+
Get started quickly with ZenML using GitHub Codespaces!
72+
<br />
73+
You can run our quickstart guide in a pre-configured environment.
74+
</p>
75+
<Button size="sm" className="w-fit" emphasis="subtle" asChild>
76+
<a target="_blank" rel="noopener noreferrer" href="https://github.com/zenml-io/zenml">
77+
Visit our GitHub repo <External className="h-5 w-5" />
78+
</a>
79+
</Button>
80+
</div>
81+
<img
82+
className="object-contain"
83+
src={Codespaces}
84+
alt="Gif explaining how to setup codespaces"
85+
/>
86+
</Box>
87+
);
88+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { DeletePipelineAlert } from "./DeletePipelineAlert";
2+
import { usePipelinesSelectorContext } from "./PipelineSelectorContext";
3+
4+
export function PipelinesButtonGroup() {
5+
const { selectedPipelines } = usePipelinesSelectorContext();
6+
return (
7+
<div className="flex items-center divide-x divide-theme-border-moderate overflow-hidden rounded-md border border-theme-border-moderate">
8+
<div className="bg-primary-25 px-2 py-1 font-semibold text-theme-text-brand">{`${selectedPipelines?.length} Pipeline${selectedPipelines?.length > 1 ? "s" : ""} selected`}</div>
9+
<DeletePipelineAlert />
10+
</div>
11+
);
12+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import Trash from "@/assets/icons/trash.svg?react";
2+
import { DeleteAlertContent } from "@/components/DeleteAlertDialog";
3+
import { AlertDialog, AlertDialogTrigger, Button } from "@zenml-io/react-component-library";
4+
import { useState } from "react";
5+
import { usePipelinesSelectorContext } from "./PipelineSelectorContext";
6+
7+
export function DeletePipelineAlert() {
8+
const [isOpen, setIsOpen] = useState(false);
9+
const { bulkDeletePipelines, selectedPipelines } = usePipelinesSelectorContext();
10+
11+
async function handleDelete() {
12+
await bulkDeletePipelines(selectedPipelines);
13+
setIsOpen(false);
14+
}
15+
16+
return (
17+
<AlertDialog open={isOpen} onOpenChange={setIsOpen}>
18+
<AlertDialogTrigger>
19+
<Button
20+
className="rounded-sharp border-none bg-white"
21+
size="md"
22+
emphasis="subtle"
23+
intent="secondary"
24+
>
25+
<Trash className="h-5 w-5 shrink-0 gap-1 fill-neutral-400" />
26+
Delete
27+
</Button>
28+
</AlertDialogTrigger>
29+
<DeleteAlertContent
30+
title={`Delete Pipeline${selectedPipelines.length >= 2 ? "s" : ""}`}
31+
handleDelete={handleDelete}
32+
/>
33+
</AlertDialog>
34+
);
35+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import DotsIcon from "@/assets/icons/dots-horizontal.svg?react";
2+
import Trash from "@/assets/icons/trash.svg?react";
3+
import { AlertDialogItem } from "@/components/AlertDialogDropdownItem";
4+
import { DeleteAlertContent } from "@/components/DeleteAlertDialog";
5+
import {
6+
AlertDialogTrigger,
7+
DropdownMenu,
8+
DropdownMenuContent,
9+
DropdownMenuTrigger
10+
} from "@zenml-io/react-component-library";
11+
import { ElementRef, useRef, useState } from "react";
12+
import { usePipelinesSelectorContext } from "./PipelineSelectorContext";
13+
14+
type Props = {
15+
id: string;
16+
};
17+
export function PipelineDropdown({ id }: Props) {
18+
const [hasOpenDialog, setHasOpenDialog] = useState(false);
19+
const [dropdownOpen, setDropdownOpen] = useState(false);
20+
const dropdownTriggerRef = useRef<ElementRef<typeof AlertDialogTrigger> | null>(null);
21+
const focusRef = useRef<HTMLElement | null>(null);
22+
23+
const { bulkDeletePipelines } = usePipelinesSelectorContext();
24+
25+
async function handleDelete() {
26+
await bulkDeletePipelines([id]);
27+
handleDialogItemOpenChange(false);
28+
}
29+
30+
function handleDialogItemSelect() {
31+
focusRef.current = dropdownTriggerRef.current;
32+
}
33+
34+
function handleDialogItemOpenChange(open: boolean) {
35+
if (open === false) {
36+
setDropdownOpen(false);
37+
setTimeout(() => {
38+
setHasOpenDialog(open);
39+
}, 200);
40+
return;
41+
}
42+
setHasOpenDialog(open);
43+
}
44+
45+
return (
46+
<DropdownMenu onOpenChange={setDropdownOpen} open={dropdownOpen}>
47+
<DropdownMenuTrigger ref={dropdownTriggerRef}>
48+
<DotsIcon className="h-4 w-4 fill-theme-text-tertiary" />
49+
</DropdownMenuTrigger>
50+
<DropdownMenuContent
51+
hidden={hasOpenDialog}
52+
onCloseAutoFocus={(event) => {
53+
if (focusRef.current) {
54+
focusRef.current.focus();
55+
focusRef.current = null;
56+
event.preventDefault();
57+
}
58+
}}
59+
align="end"
60+
sideOffset={7}
61+
>
62+
{/* <DropdownMenuItem ></DropdownMenuItem> */}
63+
<AlertDialogItem
64+
onSelect={handleDialogItemSelect}
65+
open={hasOpenDialog}
66+
onOpenChange={handleDialogItemOpenChange}
67+
triggerChildren="Delete"
68+
icon={<Trash fill="red" />}
69+
>
70+
<DeleteAlertContent title="Delete Pipeline" handleDelete={handleDelete} />
71+
</AlertDialogItem>
72+
</DropdownMenuContent>
73+
</DropdownMenu>
74+
);
75+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { pipelineQueries } from "@/data/pipelines";
2+
import { useDeletePipeline } from "@/data/pipelines/delete-pipeline";
3+
import { useQueryClient } from "@tanstack/react-query";
4+
import { useToast } from "@zenml-io/react-component-library";
5+
import { SetStateAction, createContext, useContext, useState } from "react";
6+
7+
type PipelinesSelectorContextProps = {
8+
selectedPipelines: string[];
9+
setSelectedPipelines: (actions: SetStateAction<string[]>) => void;
10+
bulkDeletePipelines: (runIds: string[]) => Promise<void>;
11+
};
12+
13+
const PipelinesSelectorContext = createContext<PipelinesSelectorContextProps | null>(null);
14+
15+
export function PipelinesSelectorProvider({ children }: { children: React.ReactNode }) {
16+
const [selectedPipelines, setSelectedPipelines] = useState<string[]>([]);
17+
const queryClient = useQueryClient();
18+
19+
const { toast } = useToast();
20+
21+
const deleteRunMutation = useDeletePipeline();
22+
23+
const bulkDeletePipelines = async (runIds: string[]) => {
24+
try {
25+
// Use mutateAsync to handle each delete operation
26+
const deletePromises = runIds.map((id) => deleteRunMutation.mutateAsync({ pipelineId: id }));
27+
28+
await Promise.all(deletePromises);
29+
toast({
30+
description: "Deleted successfully.",
31+
status: "success",
32+
emphasis: "subtle",
33+
rounded: true
34+
});
35+
await queryClient.invalidateQueries({ queryKey: pipelineQueries.all });
36+
setSelectedPipelines([]);
37+
} catch (error) {
38+
console.error("Failed to delete some pipelines:", error);
39+
}
40+
};
41+
42+
return (
43+
<PipelinesSelectorContext.Provider
44+
value={{ selectedPipelines, setSelectedPipelines, bulkDeletePipelines }}
45+
>
46+
{children}
47+
</PipelinesSelectorContext.Provider>
48+
);
49+
}
50+
51+
export function usePipelinesSelectorContext() {
52+
const context = useContext(PipelinesSelectorContext);
53+
if (!context)
54+
throw new Error("usePipelinesSelectorContext must be used within a PipelinesSelectorProvider");
55+
return context;
56+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import Refresh from "@/assets/icons/refresh.svg?react";
2+
import Pagination from "@/components/Pagination";
3+
import { SearchField } from "@/components/SearchField";
4+
import { pipelineQueries } from "@/data/pipelines";
5+
import { useQuery } from "@tanstack/react-query";
6+
import { Button, DataTable, Skeleton } from "@zenml-io/react-component-library";
7+
import { PipelinesButtonGroup } from "./ButtonGroup";
8+
import { getPipelineColumns } from "./columns";
9+
import { usePipelinesSelectorContext } from "./PipelineSelectorContext";
10+
import { usePipelineOverviewSearchParams } from "./service";
11+
12+
export function PipelinesBody() {
13+
const queryParams = usePipelineOverviewSearchParams();
14+
const { selectedPipelines } = usePipelinesSelectorContext();
15+
const { data, refetch } = useQuery({
16+
...pipelineQueries.pipelineList({ ...queryParams, sort_by: "desc:latest_run" }),
17+
throwOnError: true
18+
});
19+
20+
return (
21+
<div className="flex flex-col gap-5">
22+
<div className="flex items-center justify-between">
23+
{selectedPipelines.length ? (
24+
<PipelinesButtonGroup />
25+
) : (
26+
<SearchField searchParams={queryParams} />
27+
)}
28+
<div className="flex justify-between">
29+
<Button intent="primary" emphasis="subtle" size="md" onClick={() => refetch()}>
30+
<Refresh className="h-5 w-5 fill-theme-text-brand" />
31+
Refresh
32+
</Button>
33+
</div>
34+
</div>
35+
<div className="flex flex-col items-center gap-5">
36+
<div className="w-full">
37+
{data ? (
38+
<DataTable columns={getPipelineColumns()} data={data.items} />
39+
) : (
40+
<Skeleton className="h-[500px] w-full" />
41+
)}
42+
</div>
43+
{data ? (
44+
data.total_pages > 1 && <Pagination searchParams={queryParams} paginate={data} />
45+
) : (
46+
<Skeleton className="h-[36px] w-[300px]" />
47+
)}
48+
</div>
49+
</div>
50+
);
51+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { Checkbox } from "@zenml-io/react-component-library";
2+
import { usePipelinesSelectorContext } from "./PipelineSelectorContext";
3+
4+
type Props = {
5+
id: string;
6+
};
7+
8+
export const PipelinesSelector = ({ id }: Props) => {
9+
const { selectedPipelines, setSelectedPipelines } = usePipelinesSelectorContext();
10+
11+
const handleCheck = (isChecked: boolean, id: string) => {
12+
setSelectedPipelines((prevSelectedItems: any) => {
13+
return isChecked
14+
? [...prevSelectedItems, id]
15+
: prevSelectedItems.filter((selectedItem: any) => selectedItem !== id);
16+
});
17+
};
18+
19+
return (
20+
<Checkbox
21+
id={id}
22+
onCheckedChange={(e: boolean) => handleCheck(e, id)}
23+
checked={selectedPipelines.includes(id)}
24+
className="h-3 w-3"
25+
/>
26+
);
27+
};

0 commit comments

Comments
 (0)