Skip to content

Commit 191446f

Browse files
Merge pull request #688 from zenml-io/staging
Release
2 parents 8b60b0d + 96a5640 commit 191446f

File tree

16 files changed

+325
-49
lines changed

16 files changed

+325
-49
lines changed
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import Refresh from "@/assets/icons/refresh.svg?react";
2+
3+
import { componentQueries } from "@/data/components";
4+
import { useQuery } from "@tanstack/react-query";
5+
import { Button, Skeleton } from "@zenml-io/react-component-library/components/server";
6+
import { getComponentList } from "./columns";
7+
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";
11+
12+
export function StackComponentList() {
13+
const queryParams = useComponentlistQueryParams();
14+
15+
const componentList = useQuery({
16+
...componentQueries.componentList({
17+
...queryParams,
18+
sort_by: "desc:updated"
19+
}),
20+
throwOnError: true
21+
});
22+
const columns = getComponentList();
23+
if (componentList.isError) return null;
24+
const { data, refetch } = componentList;
25+
26+
return (
27+
<section>
28+
<div className="flex flex-col gap-5">
29+
<div className="flex flex-wrap items-center justify-between gap-y-4">
30+
<div className="flex items-center gap-2">
31+
<SearchField searchParams={queryParams} />
32+
</div>
33+
34+
<div className="flex items-center justify-between gap-2">
35+
<Button intent="primary" emphasis="subtle" size="md" onClick={() => refetch()}>
36+
<Refresh className="h-5 w-5 fill-theme-text-brand" />
37+
Refresh
38+
</Button>
39+
</div>
40+
</div>
41+
42+
<div className="flex flex-col items-center gap-5">
43+
<div className="w-full">
44+
{data ? (
45+
<DataTable columns={columns} data={data.items} />
46+
) : (
47+
<Skeleton className="h-[500px] w-full" />
48+
)}
49+
</div>
50+
{data ? (
51+
data.total_pages > 1 && <Pagination searchParams={queryParams} paginate={data} />
52+
) : (
53+
<Skeleton className="h-[36px] w-[300px]" />
54+
)}
55+
</div>
56+
</div>
57+
</section>
58+
);
59+
}

src/app/components/columns.tsx

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { CopyButton } from "@/components/CopyButton";
2+
import { DisplayDate } from "@/components/DisplayDate";
3+
import { InlineAvatar } from "@/components/InlineAvatar";
4+
import { ComponentBadge } from "@/components/stack-components/ComponentBadge";
5+
import { ComponentFallbackDialog } from "@/components/stack-components/ComponentFallbackDialog";
6+
import { snakeCaseToTitleCase } from "@/lib/strings";
7+
import { sanitizeUrl } from "@/lib/url";
8+
import { getUsername } from "@/lib/user";
9+
import { StackComponent } from "@/types/components";
10+
import { ColumnDef } from "@tanstack/react-table";
11+
import { Tag } from "@zenml-io/react-component-library/components/server";
12+
13+
export function getComponentList(): ColumnDef<StackComponent>[] {
14+
return [
15+
{
16+
id: "name",
17+
header: "Component",
18+
accessorKey: "name",
19+
cell: ({ row }) => {
20+
const id = row.original.id;
21+
const name = row.original.name;
22+
return (
23+
<div className="group/copybutton flex items-center gap-2">
24+
<img
25+
width={32}
26+
height={32}
27+
src={sanitizeUrl(row.original.body?.logo_url || "")}
28+
alt="Flavor Icon"
29+
/>
30+
<div>
31+
<div className="flex items-center gap-1">
32+
<ComponentFallbackDialog
33+
name={name}
34+
type={row.original.body?.type || "orchestrator"}
35+
>
36+
<button>
37+
<h2 className="text-text-md font-semibold">{name}</h2>
38+
</button>
39+
</ComponentFallbackDialog>
40+
<CopyButton copyText={name} />
41+
</div>
42+
<div className="flex items-center gap-1">
43+
<p className="text-text-xs text-theme-text-secondary">{id.split("-")[0]}</p>
44+
<CopyButton copyText={id} />
45+
</div>
46+
</div>
47+
</div>
48+
);
49+
}
50+
},
51+
{
52+
id: "type",
53+
header: "Component Type",
54+
accessorFn: (row) => row.body?.type,
55+
cell: ({ row }) => {
56+
const type = row.original.body?.type || "orchestrator";
57+
return <ComponentBadge type={type}>{snakeCaseToTitleCase(type)}</ComponentBadge>;
58+
}
59+
},
60+
{
61+
id: "flavor",
62+
header: "Flavor",
63+
accessorFn: (row) => row.body?.flavor,
64+
cell: ({ row }) => {
65+
const flavor = row.original.body?.flavor;
66+
return (
67+
<Tag
68+
rounded={false}
69+
className="w-fit gap-1 text-theme-text-primary"
70+
color="grey"
71+
emphasis="minimal"
72+
>
73+
<img
74+
width={20}
75+
height={20}
76+
src={sanitizeUrl(row.original.body?.logo_url || "")}
77+
alt="Flavor Icon of Component"
78+
/>
79+
<p>{flavor}</p>
80+
</Tag>
81+
);
82+
}
83+
},
84+
{
85+
id: "author",
86+
header: "Author",
87+
accessorFn: (row) => row.body?.user?.name,
88+
cell: ({ row }) => {
89+
const author = row.original.body?.user;
90+
if (!author) return null;
91+
return <InlineAvatar username={getUsername(author)} />;
92+
}
93+
},
94+
{
95+
id: "created",
96+
header: "Created at",
97+
accessorFn: (row) => row.body?.created,
98+
cell: ({ row }) => (
99+
<p className="text-text-sm text-theme-text-secondary">
100+
<DisplayDate dateString={row.original.body?.created || ""} />
101+
</p>
102+
)
103+
}
104+
];
105+
}

src/app/components/page.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { StackComponentList } from "./StackComponentList";
2+
3+
export default function ComponentsPage() {
4+
return <StackComponentList />;
5+
}

src/app/components/service.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { z } from "zod";
2+
import { StackComponentListParams } from "@/types/components";
3+
import { useSearchParams } from "react-router-dom";
4+
5+
const DEFAULT_PAGE = 1;
6+
7+
const filterParamsSchema = z.object({
8+
page: z.coerce.number().min(DEFAULT_PAGE).optional().default(DEFAULT_PAGE).catch(DEFAULT_PAGE),
9+
name: z.string().optional(),
10+
operator: z.enum(["and", "or"]).optional()
11+
});
12+
13+
export function useComponentlistQueryParams(): StackComponentListParams {
14+
const [searchParams] = useSearchParams();
15+
16+
const { page, name, operator } = filterParamsSchema.parse({
17+
page: searchParams.get("page") || undefined,
18+
name: searchParams.get("name") || undefined
19+
});
20+
21+
return { page, name, logical_operator: operator };
22+
}

src/app/stacks/StackList.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export function StackList() {
2727
});
2828

2929
return (
30-
<section className="p-5">
30+
<section>
3131
<div className="flex flex-col gap-5">
3232
<div className="flex flex-wrap items-center justify-between gap-y-4">
3333
<SearchField searchParams={queryParams} />

src/app/stacks/page.tsx

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
11
import { useTourContext } from "@/components/tour/TourContext";
22
import { useEffect } from "react";
33
import { StackList } from "./StackList";
4-
import { useBreadcrumbsContext } from "@/layouts/AuthenticatedLayout/BreadcrumbsContext";
5-
import { PageHeader } from "@/components/PageHeader";
64

75
export default function StacksPage() {
8-
const { setCurrentBreadcrumbData } = useBreadcrumbsContext();
9-
106
const {
117
setTourState,
128
tourState: { tourActive }
@@ -18,22 +14,5 @@ export default function StacksPage() {
1814
}
1915
}, [tourActive]);
2016

21-
useEffect(() => {
22-
setCurrentBreadcrumbData({ segment: "stacks", data: null });
23-
}, []);
24-
25-
return (
26-
<div>
27-
<StacksHeader />
28-
<StackList />
29-
</div>
30-
);
31-
}
32-
33-
function StacksHeader() {
34-
return (
35-
<PageHeader>
36-
<h1 className="text-display-xs font-semibold">Stacks</h1>
37-
</PageHeader>
38-
);
17+
return <StackList />;
3918
}

src/components/breadcrumbs/Breadcrumbs.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export function Breadcrumbs() {
2727
useEffect(() => {
2828
let matchedData: BreadcrumbData = {};
2929
const pathSegments = pathname.split("/").filter((segment: string) => segment !== "");
30-
const segmentsToCheck: string[] = ["pipelines", "runs", "stacks", "secrets"];
30+
const segmentsToCheck: string[] = ["pipelines", "runs", "stacks", "secrets", "components"];
3131
const mainPaths = segmentsToCheck.some((segment) => pathSegments.includes(segment));
3232
if (!mainPaths) {
3333
const currentSegment =

src/components/breadcrumbs/SegmentsBreadcrumbs.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ export const matchSegmentWithRequest = ({ segment, data }: { segment: string; da
2525
stacks: { name: "Stacks" },
2626
create: { name: "New Stack" }
2727
},
28+
components: {
29+
components: { name: "Components" }
30+
},
2831
secrets: {
2932
secrets: { name: "Secrets" }
3033
},
@@ -92,7 +95,9 @@ export const matchSegmentWithURL = (segment: string, id: string) => {
9295
stacks: routes.stacks.overview,
9396
createStack: routes.stacks.create.index,
9497
//Secrets
95-
secrets: routes.settings.secrets.overview
98+
secrets: routes.settings.secrets.overview,
99+
//components
100+
components: routes.components.overview
96101
};
97102

98103
return routeMap[segment] || "#";

src/components/stack-components/ComponentFallbackDialog.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ function Info({ type }: InfoProps) {
6666
<div className="flex w-full flex-wrap justify-between gap-2">
6767
<div className="min-w-0">
6868
<p className="truncate text-text-sm font-semibold">
69-
We are working on the new Stacks experience.
69+
We are working on the new Stack Components experience.
7070
</p>
7171
<p className="truncate text-text-sm">
7272
Meanwhile you can use the CLI to manage your {snakeCaseToLowerCase(type)}.

src/data/components/index.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { infiniteQueryOptions } from "@tanstack/react-query";
1+
import { infiniteQueryOptions, queryOptions } from "@tanstack/react-query";
22
import { fetchComponents } from "./components-list";
33
import { StackComponentListParams } from "@/types/components";
44

@@ -11,13 +11,10 @@ export const componentQueries = {
1111
getNextPageParam: (lastPage) =>
1212
lastPage.index < lastPage.total_pages ? lastPage.index + 1 : null,
1313
initialPageParam: 1
14+
}),
15+
componentList: (queryParams: StackComponentListParams) =>
16+
queryOptions({
17+
queryKey: [...componentQueries.all, queryParams],
18+
queryFn: async () => fetchComponents(queryParams)
1419
})
15-
16-
// This is not used for now, in case we need the infinite query, and the regular one, the queryKeys should not be the same
17-
18-
// componentList: (backendUrl: string, queryParams: StackComponentListParams) =>
19-
// queryOptions({
20-
// queryKey: [backendUrl, ...componentQueries.all, queryParams],
21-
// queryFn: async () => fetchComponents(backendUrl, queryParams)
22-
// })
2320
};

0 commit comments

Comments
 (0)