Skip to content

Commit ca613bd

Browse files
feat: display integrations (#631)
1 parent c5cfcdb commit ca613bd

File tree

3 files changed

+118
-6
lines changed

3 files changed

+118
-6
lines changed

src/app/stacks/columns.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export function getStackColumns(): ColumnDef<Stack>[] {
2323
</Avatar>
2424
<div>
2525
<div className="flex items-center gap-1">
26-
<StackSheet stackId={id}>
26+
<StackSheet stackName={name} stackId={id}>
2727
<h2 className="text-text-md font-semibold">{name}</h2>
2828
</StackSheet>
2929
</div>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { createContext, Dispatch, SetStateAction, useContext, useState } from "react";
2+
3+
type IntegrationsContextType = {
4+
integrations: string[];
5+
setIntegrations: Dispatch<SetStateAction<string[]>>;
6+
};
7+
8+
export const IntegrationsContext = createContext<IntegrationsContextType | null>(null);
9+
10+
export function IntegrationsContextProvider({ children }: { children: React.ReactNode }) {
11+
const [integrations, setIntegrations] = useState<string[]>([]);
12+
return (
13+
<IntegrationsContext.Provider
14+
value={{
15+
integrations,
16+
setIntegrations
17+
}}
18+
>
19+
{children}
20+
</IntegrationsContext.Provider>
21+
);
22+
}
23+
24+
export function useIntegrationsContext() {
25+
const context = useContext(IntegrationsContext);
26+
if (context === null) {
27+
throw new Error("useIntegrationsContext must be used within an AuthProvider");
28+
}
29+
return context;
30+
}

src/components/stacks/Sheet/index.tsx

Lines changed: 87 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { Codesnippet } from "@/components/CodeSnippet";
2+
import { CollapsibleCard } from "@/components/CollapsibleCard";
13
import { SheetHeader } from "@/components/sheet/SheetHeader";
24
import { flavorQueries } from "@/data/flavors";
35
import { stackQueries } from "@/data/stacks";
@@ -15,23 +17,31 @@ import {
1517
SheetTrigger,
1618
Skeleton
1719
} from "@zenml-io/react-component-library";
18-
import { PropsWithChildren } from "react";
20+
import { PropsWithChildren, useEffect } from "react";
1921
import { CopyButton } from "../../CopyButton";
2022
import { ComponentBadge } from "../../stack-components/ComponentBadge";
2123
import { ComponentFallbackDialog } from "../../stack-components/ComponentFallbackDialog";
24+
import { IntegrationsContextProvider, useIntegrationsContext } from "./IntegrationsContext";
2225

2326
type Props = {
2427
stackId: string;
2528
};
2629

27-
export function StackSheet({ children, stackId }: PropsWithChildren<Props>) {
30+
export function StackSheet({
31+
children,
32+
stackId,
33+
stackName
34+
}: PropsWithChildren<Props & { stackName: string }>) {
2835
return (
2936
<Sheet>
3037
<SheetTrigger>{children}</SheetTrigger>
3138
<SheetContent className="w-[1000px] overflow-y-auto">
32-
<SheetHeader />
33-
<StackHeadline stackId={stackId} />
34-
<ComponentList stackId={stackId} />
39+
<IntegrationsContextProvider>
40+
<SheetHeader />
41+
<StackHeadline stackId={stackId} />
42+
<StackSetCommand name={stackName} />
43+
<ComponentList stackId={stackId} />
44+
</IntegrationsContextProvider>
3545
</SheetContent>
3646
</Sheet>
3747
);
@@ -130,8 +140,22 @@ type FlavorIconProps = {
130140
};
131141

132142
function FlavorIcon({ flavor, type }: FlavorIconProps) {
143+
const { setIntegrations } = useIntegrationsContext();
133144
const flavorQuery = useQuery({ ...flavorQueries.flavorList({ name: flavor, type }) });
134145

146+
useEffect(() => {
147+
if (
148+
flavorQuery.data?.items?.length &&
149+
flavorQuery.data.items[0].body?.integration &&
150+
flavorQuery.data.items[0].body.integration !== "built-in"
151+
) {
152+
setIntegrations((prev) => {
153+
const newIntegration = flavorQuery.data.items[0].body?.integration || "";
154+
return Array.from(new Set([...prev, newIntegration])).filter(Boolean);
155+
});
156+
}
157+
}, [setIntegrations, flavorQuery.data]);
158+
135159
if (flavorQuery.isError) return null;
136160
if (flavorQuery.isPending) return <Skeleton className="h-6 w-6" />;
137161

@@ -144,3 +168,61 @@ function FlavorIcon({ flavor, type }: FlavorIconProps) {
144168
/>
145169
);
146170
}
171+
172+
type StackSetCommandProps = {
173+
name: string;
174+
};
175+
function StackSetCommand({ name }: StackSetCommandProps) {
176+
const { integrations } = useIntegrationsContext();
177+
178+
return (
179+
<section className="px-5 pt-5">
180+
<CollapsibleCard title={<span className="text-text-lg">Set this stack</span>}>
181+
<ul className="space-y-5">
182+
<li className="space-y-2">
183+
<div className="flex items-center gap-2">
184+
<Number>1</Number>
185+
<p className="font-semibold">Set your stack</p>
186+
</div>
187+
<div className="space-y-1">
188+
<p className="text-text-sm text-theme-text-secondary">
189+
Set the stack as active on your client
190+
</p>
191+
<Codesnippet
192+
codeClasses="whitespace-pre-wrap"
193+
wrap
194+
code={`zenml stack set ${name}`}
195+
/>
196+
</div>
197+
</li>
198+
{integrations.length >= 1 && (
199+
<li className="space-y-2">
200+
<div className="flex items-center gap-2">
201+
<Number>2</Number>
202+
<p className="font-semibold">Install the integrations</p>
203+
</div>
204+
<div className="space-y-1">
205+
<p className="text-text-sm text-theme-text-secondary">
206+
Install the required integrations to run pipelines in your stack
207+
</p>
208+
<Codesnippet
209+
codeClasses="whitespace-pre-wrap"
210+
wrap
211+
code={`zenml integration install ${integrations.join(" ")}`}
212+
/>
213+
</div>
214+
</li>
215+
)}
216+
</ul>
217+
</CollapsibleCard>
218+
</section>
219+
);
220+
}
221+
222+
function Number({ children }: PropsWithChildren) {
223+
return (
224+
<div className="flex h-7 w-7 items-center justify-center rounded-sm bg-primary-100 text-text-lg font-semibold text-theme-text-brand">
225+
{children}
226+
</div>
227+
);
228+
}

0 commit comments

Comments
 (0)