Skip to content

Commit 3e171ab

Browse files
feat: register & update stack components (#724)
1 parent a873c4d commit 3e171ab

Some content is hidden

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

67 files changed

+3038
-1095
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
"@tanstack/react-query": "^5.65.1",
3131
"@tanstack/react-table": "^8.20.6",
3232
"@tisoap/react-flow-smart-edge": "^3.0.0",
33-
"@zenml-io/react-component-library": "^0.19.2",
33+
"@zenml-io/react-component-library": "^0.22.0",
3434
"awesome-debounce-promise": "^2.1.0",
3535
"class-variance-authority": "^0.7.1",
3636
"clsx": "^2.1.1",

pnpm-lock.yaml

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

src/app/components/StackComponentList.tsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1+
import Plus from "@/assets/icons/plus.svg?react";
12
import Refresh from "@/assets/icons/refresh.svg?react";
2-
3+
import Pagination from "@/components/Pagination";
4+
import { SearchField } from "@/components/SearchField";
35
import { componentQueries } from "@/data/components";
6+
import { routes } from "@/router/routes";
47
import { useQuery } from "@tanstack/react-query";
8+
import { DataTable } from "@zenml-io/react-component-library";
59
import { Button, Skeleton } from "@zenml-io/react-component-library/components/server";
10+
import { Link } from "react-router-dom";
611
import { getComponentList } from "./columns";
712
import { useComponentlistQueryParams } from "./service";
8-
import { SearchField } from "../../components/SearchField";
9-
import { DataTable } from "@zenml-io/react-component-library";
10-
import Pagination from "../../components/Pagination";
1113

1214
export function StackComponentList() {
1315
const queryParams = useComponentlistQueryParams();
@@ -36,6 +38,12 @@ export function StackComponentList() {
3638
<Refresh className="h-5 w-5 fill-theme-text-brand" />
3739
Refresh
3840
</Button>
41+
<Button size="md" asChild>
42+
<Link to={routes.components.create}>
43+
<Plus className="h-5 w-5 shrink-0 fill-white" />
44+
<span>New Component</span>
45+
</Link>
46+
</Button>
3947
</div>
4048
</div>
4149

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
"use client";
2+
3+
import { Button, Skeleton } from "@zenml-io/react-component-library";
4+
import { useId } from "react";
5+
import * as Wizard from "@/components/wizard/Wizard";
6+
import { Link, useNavigate, useParams } from "react-router-dom";
7+
import { componentQueries } from "@/data/components";
8+
import { useQuery } from "@tanstack/react-query";
9+
import { routes } from "@/router/routes";
10+
import { ComponentConfigurationForm } from "@/components/stack-components/create-component/configuration-form";
11+
12+
export function EditComponentConfig() {
13+
const navigate = useNavigate();
14+
const { componentId } = useParams() as { componentId: string };
15+
const component = useQuery({
16+
...componentQueries.componentDetail(componentId),
17+
throwOnError: true
18+
});
19+
20+
function handleSuccess(id: string) {
21+
navigate(routes.components.detail(id));
22+
}
23+
24+
const formId = useId();
25+
if (component.isPending) {
26+
return <Skeleton className="h-[300px] w-full" />;
27+
}
28+
29+
if (component.isError) {
30+
return <div>Error</div>;
31+
}
32+
33+
const flavorId = component.data.resources?.flavor?.id;
34+
35+
if (!flavorId) {
36+
return <div>No flavor found</div>;
37+
}
38+
39+
return (
40+
<>
41+
<Wizard.Body className="p-0">
42+
<ComponentConfigurationForm
43+
component={component.data}
44+
flavorId={flavorId}
45+
formId={formId}
46+
isCreate={false}
47+
successHandler={handleSuccess}
48+
FooterComponent={FooterComponent}
49+
/>
50+
</Wizard.Body>
51+
</>
52+
);
53+
}
54+
55+
function FooterComponent({ formId, isPending }: { formId: string; isPending: boolean }) {
56+
const params = useParams() as { componentId: string };
57+
58+
return (
59+
<Wizard.Footer>
60+
<Button asChild intent="secondary" size="md">
61+
<Link to={routes.components.detail(params.componentId)}>Cancel</Link>
62+
</Button>
63+
<Button size="md" disabled={isPending} type="submit" form={formId}>
64+
{isPending && (
65+
<div
66+
role="alert"
67+
aria-busy="true"
68+
className="full h-[20px] w-[20px] animate-spin rounded-rounded border-2 border-theme-text-negative border-b-theme-text-brand"
69+
></div>
70+
)}
71+
Update Component
72+
</Button>
73+
</Wizard.Footer>
74+
);
75+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Wrapper } from "@/components/wizard/Wizard";
2+
import { EditComponentConfig } from "./form-step";
3+
4+
export default function ComponentEditPage() {
5+
return (
6+
<section className="layout-container mt-5 pb-5">
7+
<Wrapper>
8+
<EditComponentConfig />
9+
</Wrapper>
10+
</section>
11+
);
12+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { StackComponentsDetailHeader } from "@/components/stack-components/component-detail/Header";
2+
import { Outlet, useParams } from "react-router-dom";
3+
4+
export default function ComponentLayout() {
5+
const { componentId } = useParams() as { componentId: string };
6+
return (
7+
<div>
8+
<StackComponentsDetailHeader isPanel={false} componentId={componentId} />
9+
<Outlet />
10+
</div>
11+
);
12+
}

src/app/components/[componentId]/page.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
1-
import { useParams } from "react-router-dom";
2-
import { StackComponentsDetailHeader } from "../../../components/stack-components/component-detail/Header";
31
import { StackComponentTabs } from "@/components/stack-components/component-detail/Tabs";
4-
import { StackList } from "../../stacks/StackList";
2+
import { useParams } from "react-router-dom";
53
import { RunsBody } from "../../pipelines/RunsTab/RunsBody";
64
import { RunsSelectorProvider } from "../../pipelines/RunsTab/RunsSelectorContext";
5+
import { StackList } from "../../stacks/StackList";
76

87
export default function ComponentDetailPage() {
98
const { componentId } = useParams() as { componentId: string };
109

1110
return (
1211
<div className="@container">
13-
<StackComponentsDetailHeader isPanel={false} componentId={componentId} />
1412
<StackComponentTabs
1513
isPanel={false}
1614
stacksTabContent={<StackList fixedQueryParams={{ component_id: componentId }} />}

src/app/components/columns.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { getUsername } from "@/lib/user";
99
import { StackComponent } from "@/types/components";
1010
import { ColumnDef } from "@tanstack/react-table";
1111
import { Tag } from "@zenml-io/react-component-library/components/server";
12+
import { ComponentDropdown } from "./component-dropdown";
1213

1314
export function getComponentList(): ColumnDef<StackComponent>[] {
1415
return [
@@ -101,6 +102,13 @@ export function getComponentList(): ColumnDef<StackComponent>[] {
101102
<DisplayDate dateString={row.original.body?.created || ""} />
102103
</p>
103104
)
105+
},
106+
{
107+
id: "admin_actions",
108+
header: "",
109+
cell: ({ row }) => {
110+
return <ComponentDropdown id={row.original.id} />;
111+
}
104112
}
105113
];
106114
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import DotsIcon from "@/assets/icons/dots-horizontal.svg?react";
2+
import Edit from "@/assets/icons/edit.svg?react";
3+
import { routes } from "@/router/routes";
4+
import {
5+
DropdownMenu,
6+
DropdownMenuContent,
7+
DropdownMenuItem,
8+
DropdownMenuTrigger
9+
} from "@zenml-io/react-component-library/components/client";
10+
import { Button } from "@zenml-io/react-component-library/components/server";
11+
import { useNavigate } from "react-router-dom";
12+
13+
type Props = {
14+
id: string;
15+
};
16+
17+
export function ComponentDropdown({ id }: Props) {
18+
const navigate = useNavigate();
19+
return (
20+
<DropdownMenu>
21+
<DropdownMenuTrigger asChild>
22+
<Button
23+
intent="secondary"
24+
emphasis="minimal"
25+
className="flex aspect-square items-center justify-center p-0"
26+
>
27+
<DotsIcon className="h-4 w-4 shrink-0 fill-theme-text-tertiary" />
28+
</Button>
29+
</DropdownMenuTrigger>
30+
<DropdownMenuContent align="end" sideOffset={7}>
31+
<DropdownMenuItem
32+
onSelect={() => navigate(routes.components.edit(id))}
33+
className="cursor-pointer space-x-2"
34+
>
35+
<Edit className="h-3 w-3 fill-neutral-400" />
36+
<p>Edit</p>
37+
</DropdownMenuItem>
38+
</DropdownMenuContent>
39+
</DropdownMenu>
40+
);
41+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import ArrowLeft from "@/assets/icons/arrow-left.svg?react";
2+
import { ComponentConfigurationForm } from "@/components/stack-components/create-component/configuration-form";
3+
import * as Wizard from "@/components/wizard/Wizard";
4+
import { snakeCaseToTitleCase } from "@/lib/strings";
5+
import { routes } from "@/router/routes";
6+
import { StackComponentType } from "@/types/components";
7+
import { Flavor } from "@/types/flavors";
8+
import { Button } from "@zenml-io/react-component-library/components/server";
9+
import { useId } from "react";
10+
import { Link, useNavigate } from "react-router-dom";
11+
12+
type Props = {
13+
flavor: Flavor;
14+
type: StackComponentType;
15+
handleBack: () => void;
16+
};
17+
18+
export function ConfiguratinStep({ flavor, type, handleBack }: Props) {
19+
const formId = useId();
20+
const navigate = useNavigate();
21+
22+
function handleSuccess(id: string) {
23+
navigate(routes.components.detail(id));
24+
}
25+
26+
return (
27+
<>
28+
<Wizard.Header className="flex items-center gap-2">
29+
<Button
30+
intent="secondary"
31+
emphasis="subtle"
32+
className="flex aspect-square size-6 items-center justify-center"
33+
onClick={() => handleBack()}
34+
>
35+
<ArrowLeft className="size-5 shrink-0" />
36+
<span className="sr-only">Go step back</span>
37+
</Button>
38+
<span>
39+
Configure your {snakeCaseToTitleCase(flavor.name)} {snakeCaseToTitleCase(type)}
40+
</span>
41+
</Wizard.Header>
42+
<Wizard.Body className="p-0">
43+
<ComponentConfigurationForm
44+
flavorId={flavor.id}
45+
formId={formId}
46+
successHandler={handleSuccess}
47+
FooterComponent={FooterComponent}
48+
/>
49+
</Wizard.Body>
50+
</>
51+
);
52+
}
53+
54+
function FooterComponent({ formId, isPending }: { formId: string; isPending: boolean }) {
55+
return (
56+
<Wizard.Footer>
57+
<Button asChild intent="secondary" size="md">
58+
<Link to={routes.components.overview}>Cancel</Link>
59+
</Button>
60+
<Button size="md" disabled={isPending} type="submit" form={formId}>
61+
{isPending && (
62+
<div
63+
role="alert"
64+
aria-busy="true"
65+
className="full h-[20px] w-[20px] animate-spin rounded-rounded border-2 border-theme-text-negative border-b-theme-text-brand"
66+
></div>
67+
)}
68+
Register Component
69+
</Button>
70+
</Wizard.Footer>
71+
);
72+
}

0 commit comments

Comments
 (0)