Skip to content

Commit b2e5af7

Browse files
feat: snapshot creation (#856)
1 parent 821b139 commit b2e5af7

File tree

25 files changed

+598
-28
lines changed

25 files changed

+598
-28
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export function RunsDetailHeader() {
3838
<div className="flex items-center gap-1">
3939
<RunRefreshGroup runId={runId} />
4040
<RunStopGroup runId={runId} />
41-
<RunActionsMenu />
41+
<RunActionsMenu runId={runId} />
4242
</div>
4343
</PageHeader>
4444
);

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

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import HorizontalDots from "@/assets/icons/dots-horizontal.svg?react";
22
import Trash from "@/assets/icons/trash.svg?react";
3+
import Plus from "@/assets/icons/plus.svg?react";
34
import {
45
Button,
56
DropdownMenu,
@@ -9,8 +10,14 @@ import {
910
} from "@zenml-io/react-component-library";
1011
import { useState } from "react";
1112
import { DeleteRunAlert } from "./DeleteRunAlert";
13+
import { Link } from "react-router-dom";
14+
import { routes } from "@/router/routes";
1215

13-
export function RunActionsMenu() {
16+
type Props = {
17+
runId: string;
18+
};
19+
20+
export function RunActionsMenu({ runId }: Props) {
1421
const [deleteOpen, setDeleteOpen] = useState(false);
1522

1623
const [dropdownOpen, setDropdownOpen] = useState(false);
@@ -32,6 +39,12 @@ export function RunActionsMenu() {
3239
</Button>
3340
</DropdownMenuTrigger>
3441
<DropdownMenuContent className="z-10" align="end" sideOffset={1}>
42+
<DropdownMenuItem asChild className="space-x-2">
43+
<Link to={routes.projects.runs.createSnapshot(runId)}>
44+
<Plus className="h-3 w-3 fill-neutral-400" />
45+
<p>New Snapshot</p>
46+
</Link>
47+
</DropdownMenuItem>
3548
<DropdownMenuItem onClick={() => setDeleteOpen(true)} className="space-x-2">
3649
<Trash className="h-3 w-3 fill-neutral-400" />
3750
<p>Delete</p>
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { pipelineBreadcrumb, runBreadcrumb } from "@/components/breadcrumbs/library";
2+
import { useBreadcrumbsContext } from "@/layouts/AuthenticatedLayout/BreadcrumbsContext";
3+
import { routes } from "@/router/routes";
4+
import { PipelineRun } from "@/types/pipeline-runs";
5+
6+
import { useEffect } from "react";
7+
8+
export function useCreateSnapshotFromRunBreadcrumbs(run?: PipelineRun) {
9+
const { setBreadcrumbs } = useBreadcrumbsContext();
10+
11+
useEffect(() => {
12+
if (run) {
13+
setBreadcrumbs([
14+
...(run.resources?.pipeline
15+
? [
16+
pipelineBreadcrumb,
17+
{
18+
label: run.resources.pipeline.name || "",
19+
href: routes.projects.pipelines.detail.runs(run.resources.pipeline.id)
20+
}
21+
]
22+
: [runBreadcrumb]),
23+
{
24+
label: run.name || "",
25+
href: routes.projects.runs.detail(run.id)
26+
},
27+
{
28+
label: "Create Snapshot",
29+
href: routes.projects.runs.createSnapshot(run.id)
30+
}
31+
]);
32+
}
33+
}, [setBreadcrumbs, run]);
34+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { CreateSnapshotForm } from "@/components/pipeline-snapshots/create/form";
2+
import { usePipelineRun } from "@/data/pipeline-runs/pipeline-run-detail-query";
3+
import { useParams } from "react-router-dom";
4+
import { useCreateSnapshotFromRunBreadcrumbs } from "./breadcrumb";
5+
6+
export default function CreateSnapshotFromRunPage() {
7+
const { runId } = useParams() as { runId: string };
8+
const runQuery = usePipelineRun({ runId });
9+
10+
useCreateSnapshotFromRunBreadcrumbs(runQuery.data);
11+
12+
if (runQuery.isPending) return <p>Loading...</p>;
13+
if (runQuery.isError) return <p>Error</p>;
14+
15+
return (
16+
<div className="mx-auto w-full max-w-4xl p-5">
17+
<CreateSnapshotForm run={runQuery.data} />
18+
</div>
19+
);
20+
}

src/app/runs/columns.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export const runsColumns: ColumnDef<PipelineRun>[] = [
6262
}>();
6363
return (
6464
<div className="group/copybutton flex items-center gap-2">
65-
<RunIcon className={`h-5 w-5 ${getExecutionStatusColor(status)}`} />
65+
<RunIcon className={`h-5 w-5 shrink-0 ${getExecutionStatusColor(status)}`} />
6666
<div>
6767
<div className="flex items-center gap-1">
6868
<Link to={routes.projects.runs.detail(id)} className="flex items-center gap-1">

src/app/snapshots/[snapshotId]/_layout/header/info.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ function SnapshotDetailInfoContent({ snapshot }: { snapshot: PipelineSnapshot })
4747
</div>
4848

4949
<div className="flex items-center gap-1">
50-
<SnapshotIcon className={`h-5 w-5 fill-primary-400`} />
50+
<SnapshotIcon className={`h-5 w-5 shrink-0 fill-primary-400`} />
5151
<h2 className="text-text-lg font-semibold text-theme-text-primary">{name}</h2>
5252
</div>
5353

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { snapshotBreadcrumb } from "@/components/breadcrumbs/library";
2+
import { useBreadcrumbsContext } from "@/layouts/AuthenticatedLayout/BreadcrumbsContext";
3+
import { routes } from "@/router/routes";
4+
5+
import { useEffect } from "react";
6+
7+
export function useCreateSnapshotBreadcrumbs() {
8+
const { setBreadcrumbs } = useBreadcrumbsContext();
9+
10+
useEffect(() => {
11+
setBreadcrumbs([
12+
snapshotBreadcrumb,
13+
{
14+
label: "Create",
15+
href: routes.projects.snapshots.create
16+
}
17+
]);
18+
}, [setBreadcrumbs]);
19+
}

src/app/snapshots/create/page.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { CreateSnapshotForm } from "@/components/pipeline-snapshots/create/form";
2+
import { useCreateSnapshotBreadcrumbs } from "./breadcrumbs";
3+
4+
export default function CreateSnapshotPage() {
5+
useCreateSnapshotBreadcrumbs();
6+
return (
7+
<div className="mx-auto w-full max-w-4xl p-5">
8+
<CreateSnapshotForm run={undefined} />
9+
</div>
10+
);
11+
}

src/app/snapshots/page.content.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export function GlobalSnapshotsContent() {
1515

1616
return (
1717
<>
18-
<SnapshotTableToolbar params={searchParams} />
18+
<SnapshotTableToolbar displayCreateButton params={searchParams} />
1919
<PipelineSnapshotsTable params={searchParams} columns={columns} />
2020
</>
2121
);
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { Button } from "@zenml-io/react-component-library";
2+
import { useNavigate } from "react-router-dom";
3+
4+
type Props = {
5+
isPending: boolean;
6+
};
7+
8+
export function CreateSnapshotFormFooter({ isPending }: Props) {
9+
const navigate = useNavigate();
10+
11+
return (
12+
<div className="flex items-center justify-end gap-2 p-5">
13+
<Button type="button" onClick={() => navigate(-1)} intent="secondary" size="md">
14+
Cancel
15+
</Button>
16+
<Button type="submit" size="md" disabled={isPending}>
17+
{isPending && (
18+
<div className="h-4 w-4 animate-spin rounded-rounded border-2 border-theme-text-negative border-b-theme-text-brand" />
19+
)}
20+
Create Snapshot
21+
</Button>
22+
</div>
23+
);
24+
}

0 commit comments

Comments
 (0)