Run the pipeline
diff --git a/src/app/onboarding/ProductionSetup/index.tsx b/src/app/onboarding/ProductionSetup/index.tsx
index 8c712a00f..3d8c60deb 100644
--- a/src/app/onboarding/ProductionSetup/index.tsx
+++ b/src/app/onboarding/ProductionSetup/index.tsx
@@ -1,9 +1,8 @@
import ChevronDown from "@/assets/icons/chevron-down.svg?react";
import { Tick } from "@/components/Tick";
-import { useServerSettings } from "@/data/server/get-server-settings";
import { useServerInfo } from "@/data/server/info-query";
-import { PRODUCTION_SETUP_ITEMS } from "@/lib/constants";
-import { getOnboardingState, getProgress, getStarterSetupItems } from "@/lib/onboarding";
+import { useOnboarding } from "@/data/server/onboarding-state";
+import { getProductionSetup, getStarterSetup } from "@/lib/onboarding";
import { checkIsLocalServer } from "@/lib/server";
import {
Collapsible,
@@ -14,30 +13,26 @@ import {
cn
} from "@zenml-io/react-component-library";
import { useState } from "react";
-import {
- CreateArtifactStore,
- CreateNewStack,
- CreateServiceConnector,
- RunNewPipeline
-} from "./Items";
+import { CreateNewStack, RunNewPipeline } from "./Items";
export function ProductionSetupChecklist() {
- const { isError, isPending, data } = useServerSettings({ throwOnError: true });
+ const onboarding = useOnboarding({ refetchInterval: 5000 });
const serverInfo = useServerInfo();
const [open, setOpen] = useState(true);
- if (isPending || serverInfo.isPending) return
;
- if (isError || serverInfo.isError) return null;
+ if (onboarding.isPending || serverInfo.isPending)
+ return
;
+ if (onboarding.isError || serverInfo.isError) return null;
- const STARTER_SETUP_ITEMS = getStarterSetupItems(
+ const starterSetup = getStarterSetup(
+ onboarding.data,
checkIsLocalServer(serverInfo.data.deployment_type || "other")
);
- const onboardingState = getOnboardingState(data);
- const isStarterSetupFinished =
- getProgress(onboardingState, STARTER_SETUP_ITEMS) === STARTER_SETUP_ITEMS.length;
- const doneItems = getProgress(onboardingState, PRODUCTION_SETUP_ITEMS);
- const progress = (doneItems / PRODUCTION_SETUP_ITEMS.length) * 100;
+ const { progress, totalItems, itemsDone, getItem } = getProductionSetup(onboarding.data);
+
+ const stackStep = getItem("stack_with_remote_orchestrator_created");
+ const pipelineStep = getItem("pipeline_run_with_remote_orchestrator");
return (
<>
@@ -53,7 +48,7 @@ export function ProductionSetupChecklist() {
) : (
- {doneItems}/{PRODUCTION_SETUP_ITEMS.length}
+ {itemsDone}/{totalItems}
)}
@@ -64,7 +59,7 @@ export function ProductionSetupChecklist() {
(10 min)
- {isStarterSetupFinished ? (
+ {starterSetup.isFinished ? (
"Level up your skills in a production setting."
) : (
@@ -83,23 +78,19 @@ export function ProductionSetupChecklist() {
diff --git a/src/app/onboarding/StarterSetup/Items.tsx b/src/app/onboarding/StarterSetup/Items.tsx
index 8ec294c8f..76b692898 100644
--- a/src/app/onboarding/StarterSetup/Items.tsx
+++ b/src/app/onboarding/StarterSetup/Items.tsx
@@ -1,22 +1,20 @@
-import { ChecklistItem } from "@/components/onboarding/ChecklistItem";
+import Help from "@/assets/icons/help.svg?react";
import { Codesnippet } from "@/components/CodeSnippet";
import { HelpBox } from "@/components/fallback-pages/Helpbox";
-import { Box, Skeleton, buttonVariants } from "@zenml-io/react-component-library";
-import Help from "@/assets/icons/help.svg?react";
-import { OnboardingChecklistItemName, OnboardingState } from "@/types/onboarding";
-import { getOnboardingItem } from "@/lib/onboarding";
+import { ChecklistItem } from "@/components/onboarding/ChecklistItem";
import { useServerInfo } from "@/data/server/info-query";
+import { OnboardingStep } from "@/types/onboarding";
+import { Box, Skeleton, buttonVariants } from "@zenml-io/react-component-library";
-type Props = {
- onboardingState?: OnboardingState;
-};
-export function ConnectZenMLStep({ onboardingState }: Props) {
+export function ConnectZenMLStep({ completed, hasDownstreamStep, active }: OnboardingStep) {
const { data } = useServerInfo({ throwOnError: true });
-
- const itemName = "connect_zenml";
- const item = getOnboardingItem(onboardingState || {}, itemName);
return (
-
+
Install ZenML
@@ -34,11 +32,14 @@ export function ConnectZenMLStep({ onboardingState }: Props) {
);
}
-export function RunFirstPipeline({ onboardingState }: Props) {
- const itemName: OnboardingChecklistItemName = "run_first_pipeline";
- const item = getOnboardingItem(onboardingState || {}, itemName);
+export function RunFirstPipeline({ active, completed, hasDownstreamStep }: OnboardingStep) {
return (
-
+
diff --git a/src/app/onboarding/StarterSetup/index.tsx b/src/app/onboarding/StarterSetup/index.tsx
index 4851664b9..94a143a4d 100644
--- a/src/app/onboarding/StarterSetup/index.tsx
+++ b/src/app/onboarding/StarterSetup/index.tsx
@@ -1,8 +1,8 @@
import ChevronDown from "@/assets/icons/chevron-down.svg?react";
import { Tick } from "@/components/Tick";
-import { useServerSettings } from "@/data/server/get-server-settings";
import { useServerInfo } from "@/data/server/info-query";
-import { getOnboardingState, getProgress, getStarterSetupItems } from "@/lib/onboarding";
+import { useOnboarding } from "@/data/server/onboarding-state";
+import { getStarterSetup } from "@/lib/onboarding";
import { checkIsLocalServer } from "@/lib/server";
import {
Collapsible,
@@ -15,18 +15,21 @@ import { useState } from "react";
import { ConnectZenMLStep, RunFirstPipeline } from "./Items";
export function StarterSetupList() {
- const { isError, isPending, data } = useServerSettings({ throwOnError: true });
+ const onboarding = useOnboarding({ refetchInterval: 5000 });
const serverInfo = useServerInfo();
const [open, setOpen] = useState(true);
- if (isPending || serverInfo.isPending) return ;
- if (isError || serverInfo.isError) return null;
+ if (onboarding.isPending || serverInfo.isPending)
+ return ;
+ if (onboarding.isError || serverInfo.isError) return null;
const isLocalServer = checkIsLocalServer(serverInfo.data.deployment_type || "other");
- const STARTER_SETUP_ITEMS = getStarterSetupItems(isLocalServer);
-
- const doneItems = getProgress(getOnboardingState(data), STARTER_SETUP_ITEMS);
- const progress = (doneItems / STARTER_SETUP_ITEMS.length) * 100;
+ const { progress, itemsDone, totalItems, getItem } = getStarterSetup(
+ onboarding.data,
+ isLocalServer
+ );
+ const connectStep = getItem("device_verified");
+ const pipelineStep = getItem("pipeline_run");
return (
- {doneItems}/{STARTER_SETUP_ITEMS.length}
+ {itemsDone}/{totalItems}
)}
@@ -67,11 +70,19 @@ export function StarterSetupList() {
{!isLocalServer && (
-
+
)}
-
+
diff --git a/src/app/stacks/create/SmartSetup.tsx b/src/app/stacks/create/SmartSetup.tsx
index 744151cd8..37c5de9d2 100644
--- a/src/app/stacks/create/SmartSetup.tsx
+++ b/src/app/stacks/create/SmartSetup.tsx
@@ -4,7 +4,7 @@ import { useServerInfo } from "@/data/server/info-query";
import { checkIsLocalServer } from "@/lib/server";
import { routes } from "@/router/routes";
import { Skeleton } from "@zenml-io/react-component-library";
-import { Link } from "react-router-dom";
+import { Link, useSearchParams } from "react-router-dom";
import { CreateStackOptionCard } from "./OptionCard";
export function SmartSetup() {
@@ -34,10 +34,13 @@ type Props = {
isLocalDeployment: boolean;
};
function NewInfrastructure({ isLocalDeployment }: Props) {
+ const [searchParams] = useSearchParams();
+ const link =
+ routes.stacks.create.newInfra + (searchParams.size >= 1 ? `?${searchParams.toString()}` : "");
return (
{isLocalDeployment &&
}
-
+
) {
- const [open, setOpen] = useState(false);
- const queryClient = useQueryClient();
- const { data } = useServerSettings({ throwOnError: true });
-
- const { mutate } = useUpdateServerSettings({
- onSuccess: () => {
- queryClient.invalidateQueries({ queryKey: getServerSettingsKey() });
- }
- });
-
- function toggleItem(isDone: boolean) {
- const newOnboardingState: OnboardingState = {
- ...data?.metadata?.onboarding_state,
- [itemName]: isDone
- };
- mutate({ onboarding_state: newOnboardingState });
- }
+ const [open, setOpen] = useState(active);
return (
{completed ? (
-
toggleItem(false)}>
-
-
+
+ ) : hasDownstream ? (
+
) : (
)}
-
+
- {!completed && active && (
-
toggleItem(true)}
- className="items-center whitespace-nowrap"
- intent="primary"
- emphasis="subtle"
- >
- Mark as done
-
- )}
{title}
diff --git a/src/components/onboarding/SkippedStep.tsx b/src/components/onboarding/SkippedStep.tsx
new file mode 100644
index 000000000..a60a5c0d5
--- /dev/null
+++ b/src/components/onboarding/SkippedStep.tsx
@@ -0,0 +1,17 @@
+import { cn } from "@zenml-io/react-component-library";
+import { HTMLAttributes } from "react";
+import Forward from "@/assets/icons/chevron-right-double.svg?react";
+
+export function SkippedStep({ className, ...rest }: HTMLAttributes
) {
+ return (
+
+
+
+ );
+}
diff --git a/src/data/api.ts b/src/data/api.ts
index 82c77b8ef..1bbe10d40 100644
--- a/src/data/api.ts
+++ b/src/data/api.ts
@@ -5,6 +5,7 @@ export const apiPaths = {
currentUser: "/current-user",
info: "/info",
settings: "/settings",
+ onboarding: "/onboarding_state",
pipelines: {
namespaces: "/pipeline_namespaces"
},
diff --git a/src/data/server/activate-server-mutation.ts b/src/data/server/activate-server-mutation.ts
index dd08ff008..02959d13d 100644
--- a/src/data/server/activate-server-mutation.ts
+++ b/src/data/server/activate-server-mutation.ts
@@ -20,7 +20,7 @@ export async function updateServerSettings(body: ServerActivationPayload) {
const errorData: string = await res
.json()
.then((data) => {
- if (data.detail instanceof Array) {
+ if (Array.isArray(data.detail)) {
return data.detail[1];
}
return data.detail;
diff --git a/src/data/server/get-server-settings.ts b/src/data/server/get-server-settings.ts
index ce41bbe99..8693543cc 100644
--- a/src/data/server/get-server-settings.ts
+++ b/src/data/server/get-server-settings.ts
@@ -21,7 +21,7 @@ export async function fetchServerSettings(): Promise {
const errorData: string = await res
.json()
.then((data) => {
- if (data.detail instanceof Array) {
+ if (Array.isArray(data.detail)) {
return data.detail[1];
}
return data.detail;
diff --git a/src/data/server/onboarding-state.ts b/src/data/server/onboarding-state.ts
new file mode 100644
index 000000000..353bdcf0d
--- /dev/null
+++ b/src/data/server/onboarding-state.ts
@@ -0,0 +1,49 @@
+import { FetchError } from "@/lib/fetch-error";
+import { OnboardingResponse } from "@/types/onboarding";
+import { UseQueryOptions, useQuery } from "@tanstack/react-query";
+import { apiPaths, createApiPath } from "../api";
+import { fetcher } from "../fetch";
+
+export function getOnboardingKey() {
+ return ["onboarding_state"];
+}
+
+export async function fetchOnboarding() {
+ const url = createApiPath(apiPaths.onboarding);
+ const res = await fetcher(url, {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json"
+ }
+ });
+
+ if (!res.ok) {
+ const errorData: string = await res
+ .json()
+ .then((data) => {
+ if (Array.isArray(data.detail)) {
+ return data.detail[1];
+ }
+ return data.detail;
+ })
+ .catch(() => "Failed to update activate user");
+
+ throw new FetchError({
+ status: res.status,
+ statusText: res.statusText,
+ message: errorData
+ });
+ }
+
+ return res.json();
+}
+
+export function useOnboarding(
+ options?: Omit, "queryKey" | "queryFn">
+) {
+ return useQuery({
+ queryKey: getOnboardingKey(),
+ queryFn: async () => fetchOnboarding(),
+ ...options
+ });
+}
diff --git a/src/data/server/update-server-settings-mutation.ts b/src/data/server/update-server-settings-mutation.ts
index dab0e2849..bc03d9677 100644
--- a/src/data/server/update-server-settings-mutation.ts
+++ b/src/data/server/update-server-settings-mutation.ts
@@ -19,7 +19,7 @@ export async function updateServerSettings(body: ServerSettigsUpdate) {
const errorData: string = await res
.json()
.then((data) => {
- if (data.detail instanceof Array) {
+ if (Array.isArray(data.detail)) {
return data.detail[1];
}
return data.detail;
diff --git a/src/data/users/activate-user-mutation.ts b/src/data/users/activate-user-mutation.ts
index 8a8661cd1..d69dac83f 100644
--- a/src/data/users/activate-user-mutation.ts
+++ b/src/data/users/activate-user-mutation.ts
@@ -24,7 +24,7 @@ export async function activateUser({ userId, body }: ActivateUserArgs) {
const errorData: string = await res
.json()
.then((data) => {
- if (data.detail instanceof Array) {
+ if (Array.isArray(data.detail)) {
return data.detail[1];
}
return data.detail;
diff --git a/src/data/users/update-current-user-mutation.ts b/src/data/users/update-current-user-mutation.ts
index 49162ae0d..fb910a64d 100644
--- a/src/data/users/update-current-user-mutation.ts
+++ b/src/data/users/update-current-user-mutation.ts
@@ -19,7 +19,7 @@ export async function updateUser(body: UpdateUser) {
const errorData: string = await res
.json()
.then((data) => {
- if (data.detail instanceof Array) {
+ if (Array.isArray(data.detail)) {
return data.detail[1];
}
return data.detail;
diff --git a/src/layouts/AuthenticatedLayout/OnboardingItem.tsx b/src/layouts/AuthenticatedLayout/OnboardingItem.tsx
index 538f79710..325d125e3 100644
--- a/src/layouts/AuthenticatedLayout/OnboardingItem.tsx
+++ b/src/layouts/AuthenticatedLayout/OnboardingItem.tsx
@@ -1,19 +1,18 @@
import ChevronRight from "@/assets/icons/chevron-right.svg?react";
-import { useServerSettings } from "@/data/server/get-server-settings";
import { useServerInfo } from "@/data/server/info-query";
-import { PRODUCTION_SETUP_ITEMS } from "@/lib/constants";
-import { getOnboardingState, getProgress, getStarterSetupItems } from "@/lib/onboarding";
+import { useOnboarding } from "@/data/server/onboarding-state";
+import { getProductionSetup, getStarterSetup } from "@/lib/onboarding";
import { checkIsLocalServer } from "@/lib/server";
import { routes } from "@/router/routes";
import { Box, ProgressBar, Skeleton, useSidebarContext } from "@zenml-io/react-component-library";
import { Link } from "react-router-dom";
export function OnboardingItem() {
- const { isPending, isError, data } = useServerSettings({ throwOnError: true });
+ const onboarding = useOnboarding({ refetchInterval: 3600 * 1000 });
const serverInfo = useServerInfo();
const { isOpen } = useSidebarContext();
- if (isError || serverInfo.isError) return null;
- if (isPending || serverInfo.isPending) {
+ if (onboarding.isError || serverInfo.isError) return null;
+ if (onboarding.isPending || serverInfo.isPending) {
return (
@@ -21,32 +20,20 @@ export function OnboardingItem() {
);
}
- const STARTER_SETUP_ITEMS = getStarterSetupItems(
+ const starterSetup = getStarterSetup(
+ onboarding.data,
checkIsLocalServer(serverInfo.data.deployment_type || "other")
);
+ const productionSetup = getProductionSetup(onboarding.data);
- const onboardingState = getOnboardingState(data || {});
- const isStarterSetupFinished =
- getProgress(onboardingState, STARTER_SETUP_ITEMS) === STARTER_SETUP_ITEMS.length;
+ const title = starterSetup.isFinished ? "Production Setup" : "Starter Setup";
+ const completedItems = starterSetup.isFinished
+ ? productionSetup.itemsDone
+ : starterSetup.itemsDone;
+ const totalItems = starterSetup.isFinished ? productionSetup.totalItems : starterSetup.totalItems;
+ const progress = starterSetup.isFinished ? productionSetup.progress : starterSetup.progress;
- const isProductionSetupFinished =
- getProgress(onboardingState, PRODUCTION_SETUP_ITEMS) === PRODUCTION_SETUP_ITEMS.length;
-
- const doneItems = getProgress(
- onboardingState,
- isStarterSetupFinished ? PRODUCTION_SETUP_ITEMS : STARTER_SETUP_ITEMS
- );
-
- const activeFlow = {
- title: isStarterSetupFinished ? "Production Setup" : "Starter Setup",
- doneItems,
- total: isStarterSetupFinished ? PRODUCTION_SETUP_ITEMS.length : STARTER_SETUP_ITEMS.length
- };
-
- if (isProductionSetupFinished && isStarterSetupFinished) {
- return null;
- }
- const progress = (activeFlow.doneItems / activeFlow.total) * 100;
+ if (starterSetup.isFinished && productionSetup.isFinished) return null;
return (
@@ -55,12 +42,12 @@ export function OnboardingItem() {
- {activeFlow.title}
+ {title}
- {activeFlow.doneItems}/{activeFlow.total}
+ {completedItems}/{totalItems}
diff --git a/src/layouts/AuthenticatedLayout/Sidebar.tsx b/src/layouts/AuthenticatedLayout/Sidebar.tsx
index a7f467424..46c15156a 100644
--- a/src/layouts/AuthenticatedLayout/Sidebar.tsx
+++ b/src/layouts/AuthenticatedLayout/Sidebar.tsx
@@ -54,7 +54,7 @@ export function Sidebar() {
- { }
+
{
test("doesnt return connect step for local deployment", () => {
const isLocal = true;
const items = getStarterSetupItems(isLocal);
- expect(items).toEqual(["run_first_pipeline"]);
+ expect(items).toEqual(["pipeline_run"]);
});
test("includes the connect step for non-local deployments", () => {
const isLocal = false;
const items = getStarterSetupItems(isLocal);
- expect(items).toEqual(["connect_zenml", "run_first_pipeline"]);
+ expect(items).toEqual(["device_verified", "pipeline_run"]);
+ });
+});
+
+describe("returns the correct progress, depending on the current state", () => {
+ test("returns 2 if the first two steps are done", () => {
+ const state: OnboardingResponse = ["device_verified", "pipeline_run"];
+ const flow = "starter";
+ const progress = getProgress(state, flow, false);
+ expect(progress).toBe(2);
+ });
+
+ test("returns 0 if no steps are done", () => {
+ const onboardingState: OnboardingChecklistItemName[] = [];
+ const progress = getProgress(onboardingState, "starter", false);
+ expect(progress).toBe(0);
+ });
+
+ test("returns 2 if only the second step is run", () => {
+ const onboardingState: OnboardingChecklistItemName[] = ["pipeline_run"];
+ const progress = getProgress(onboardingState, "starter", false);
+ expect(progress).toBe(2);
+ });
+
+ test("returns 2 if only the finalStep is there", () => {
+ const onboardingState: OnboardingChecklistItemName[] = ["starter_setup_completed"];
+ const progress = getProgress(onboardingState, "starter", false);
+ expect(progress).toBe(2);
+ });
+
+ test("returns correct value if flow is local", () => {
+ const onboardingState: OnboardingChecklistItemName[] = ["device_verified"];
+ const progress = getProgress(onboardingState, "starter", true);
+ expect(progress).toBe(0);
+ });
+});
+
+describe("checks if the item has downstream items", () => {
+ test("returns true if a downstream step is there", () => {
+ const itemName = "device_verified";
+ const onboardingState: OnboardingChecklistItemName[] = [
+ "device_verified",
+ "pipeline_run",
+ "starter_setup_completed"
+ ];
+ const hasDownStreamFinishded = checkDownstreamStep(itemName, onboardingState, "starter", false);
+ expect(hasDownStreamFinishded).toBe(true);
+ });
+
+ test("returns false if there is no downstream step", () => {
+ const itemName = "pipeline_run";
+ const onboardingState: OnboardingChecklistItemName[] = ["pipeline_run"];
+ const hasDownStreamFinishded = checkDownstreamStep(itemName, onboardingState, "starter", false);
+ expect(hasDownStreamFinishded).toBe(false);
+ });
+ test("returns correct value if starter setup is local", () => {
+ const itemName = "device_verified";
+ const onboardingState: OnboardingChecklistItemName[] = ["device_verified", "pipeline_run"];
+ const hasDownStreamFinishded = checkDownstreamStep(itemName, onboardingState, "starter", true);
+ expect(hasDownStreamFinishded).toBe(false);
+ });
+
+ test("only final step is there", () => {
+ const itemName: OnboardingChecklistItemName = "stack_with_remote_orchestrator_created";
+ const onboarding_state: OnboardingChecklistItemName[] = ["production_setup_completed"];
+ const hasDownStreamFinished = checkDownstreamStep(
+ itemName,
+ onboarding_state,
+ "production",
+ false
+ );
+ expect(hasDownStreamFinished).toBe(true);
+ });
+});
+
+describe("checks if the correct length is returned", () => {
+ test("returns correct value for local starter setup", () => {
+ const isLocal = true;
+ const items = getOnboardingLength("starter", isLocal);
+ expect(items).toBe(1);
+ });
+ test("returns correct value for non-local starter setup", () => {
+ const isLocal = false;
+ const items = getOnboardingLength("starter", isLocal);
+ expect(items).toBe(2);
});
});
diff --git a/src/lib/onboarding.ts b/src/lib/onboarding.ts
index 0600bb763..778f479fe 100644
--- a/src/lib/onboarding.ts
+++ b/src/lib/onboarding.ts
@@ -1,27 +1,150 @@
-import { OnboardingChecklistItemName, OnboardingState } from "@/types/onboarding";
-import { ServerSettings } from "@/types/server";
+import { OnboardingChecklistItemName, OnboardingResponse } from "@/types/onboarding";
-export function getOnboardingState(data: ServerSettings) {
- return data.metadata?.onboarding_state as OnboardingState;
+export type Flow = "starter" | "production";
+
+export function getStarterSetupItems(isLocal: boolean): OnboardingChecklistItemName[] {
+ return [...(isLocal ? [] : ["device_verified" as OnboardingChecklistItemName]), "pipeline_run"];
+}
+
+function getProductionSetupItems(): OnboardingChecklistItemName[] {
+ return ["stack_with_remote_orchestrator_created", "pipeline_run_with_remote_orchestrator"];
+}
+
+const finalSteps: {
+ starter: OnboardingChecklistItemName;
+ production: OnboardingChecklistItemName;
+} = {
+ starter: "starter_setup_completed",
+ production: "production_setup_completed"
+};
+
+export function getStarterSetup(data: OnboardingResponse, isLocal: boolean) {
+ const flowType: Flow = "starter";
+ const finalStep = finalSteps[flowType];
+ const itemsDone = getProgress(data, flowType, isLocal);
+ const totalItems = getOnboardingLength(flowType, isLocal);
+ return {
+ itemsDone,
+ totalItems,
+ items: getStarterSetupItems(isLocal),
+ isFinished: data.includes(finalStep),
+ progress: (itemsDone / totalItems) * 100,
+ finalStep: finalSteps.starter,
+ hasItem: (item: OnboardingChecklistItemName) => hasOnboardingItem(item, data),
+ getItem: (item: OnboardingChecklistItemName) => getItem(item, data, flowType)
+ };
+}
+
+export function getProductionSetup(data: OnboardingResponse) {
+ const flowType: Flow = "production";
+ const finalStep = finalSteps[flowType];
+ const itemsDone = getProgress(data, flowType);
+ const totalItems = getOnboardingLength(flowType);
+ return {
+ itemsDone,
+ totalItems,
+ items: getProductionSetupItems(),
+ isFinished: data.includes(finalStep),
+ progress: (itemsDone / totalItems) * 100,
+ finalStep: finalSteps.starter,
+ hasItem: (item: OnboardingChecklistItemName) => hasOnboardingItem(item, data),
+ getItem: (item: OnboardingChecklistItemName) => getItem(item, data, flowType)
+ };
}
-export function getOnboardingItem(
- onboardingState: OnboardingState,
- item: OnboardingChecklistItemName
+function getItem(
+ item: OnboardingChecklistItemName,
+ state: OnboardingResponse,
+ flow: Flow,
+ isLocal?: boolean
) {
- return onboardingState[item];
+ return {
+ isCompleted: state.includes(item),
+ hasDownStreamStep: checkDownstreamStep(item, state, flow, isLocal || false),
+ isActive: isStepActive(item, state, flow, isLocal || false)
+ };
}
-export function getProgress(
- onboardingState: OnboardingState,
- checklistItems: OnboardingChecklistItemName[]
+function hasOnboardingItem(item: OnboardingChecklistItemName, state: OnboardingResponse): boolean {
+ return state.includes(item);
+}
+
+export function checkDownstreamStep(
+ item: OnboardingChecklistItemName,
+ state: OnboardingResponse,
+ flow: Flow,
+ isLocal?: boolean
) {
- return checklistItems.filter((item) => onboardingState[item]).length;
+ const order =
+ flow === "starter" ? getStarterSetupItems(isLocal || false) : getProductionSetupItems();
+ const withFinalStep = [...order, finalSteps[flow]];
+ const currentIndex = withFinalStep.indexOf(item);
+ if (currentIndex === -1) {
+ return false; // If the item is not found in the order array, return false
+ }
+ const downstreamSteps = withFinalStep.slice(currentIndex + 1);
+ return downstreamSteps.some((step) => state.includes(step));
}
-export function getStarterSetupItems(isLocal: boolean): OnboardingChecklistItemName[] {
- return [
- ...(isLocal ? [] : ["connect_zenml" as OnboardingChecklistItemName]),
- "run_first_pipeline"
- ];
+function isStepActive(
+ step: OnboardingChecklistItemName,
+ state: OnboardingResponse,
+ flowType: Flow,
+ isLocal: boolean
+): boolean {
+ const flow = flowType === "starter" ? getStarterSetupItems(isLocal) : getProductionSetupItems();
+ if (flow.length === 0) {
+ return false;
+ }
+
+ const stepIndex = flow.indexOf(step);
+
+ if (stepIndex === -1) {
+ // Step is not in the setup list
+ return false;
+ }
+
+ if (state.includes(step)) {
+ // Step is already done
+ return false;
+ }
+
+ if (stepIndex === 0) {
+ // First step is active if not done
+ return true;
+ }
+
+ // Check if the previous step is done
+ const previousStep = flow[stepIndex - 1];
+ return state.includes(previousStep);
+}
+
+export function getProgress(onboardingState: OnboardingResponse, flow: Flow, isLocal?: boolean) {
+ const items =
+ flow === "starter" ? getStarterSetupItems(isLocal || false) : getProductionSetupItems();
+ const finalStep = finalSteps[flow];
+ // Filter out the finalStep from the checklist items
+ const filteredItems = items.filter((item) => item !== finalStep);
+
+ // If the final step is present in the onboarding state, return the length of the filtered items
+ if (onboardingState.includes(finalStep)) {
+ return filteredItems.length;
+ }
+
+ // Find the highest index of any present item in the onboarding state
+ let highestIndex = -1;
+ filteredItems.forEach((item, index) => {
+ if (onboardingState.includes(item)) {
+ highestIndex = index;
+ }
+ });
+
+ // If any item is found, return the highest index + 1 as the completed count
+ return highestIndex + 1;
+}
+
+export function getOnboardingLength(flow: Flow, isLocal?: boolean) {
+ const items =
+ flow === "starter" ? getStarterSetupItems(isLocal || false) : getProductionSetupItems();
+ return items.length;
}
diff --git a/src/types/core.ts b/src/types/core.ts
index e7b1d2502..a1cb73fb6 100644
--- a/src/types/core.ts
+++ b/src/types/core.ts
@@ -1301,6 +1301,16 @@ export type paths = {
*/
get: operations["server_info_api_v1_info_get"];
};
+ "/api/v1/onboarding_state": {
+ /**
+ * Get Onboarding State
+ * @description Get the onboarding state of the server.
+ *
+ * Returns:
+ * The onboarding state of the server.
+ */
+ get: operations["get_onboarding_state_api_v1_onboarding_state_get"];
+ };
"/api/v1/settings": {
/**
* Get Settings
@@ -7005,10 +7015,6 @@ export type components = {
display_announcements?: boolean | null;
/** Whether to display notifications about ZenML updates in the dashboard. */
display_updates?: boolean | null;
- /** The server's onboarding state. */
- onboarding_state?: {
- [key: string]: unknown;
- } | null;
/** The username of the default admin account to create. Leave empty to skip creating the default admin account. */
admin_username?: string | null;
/** The password of the default admin account to create. Leave empty to skip creating the default admin account. */
@@ -7148,15 +7154,7 @@ export type components = {
* ServerSettingsResponseMetadata
* @description Response metadata for server settings.
*/
- ServerSettingsResponseMetadata: {
- /**
- * The server's onboarding state.
- * @default {}
- */
- onboarding_state?: {
- [key: string]: unknown;
- };
- };
+ ServerSettingsResponseMetadata: Record;
/**
* ServerSettingsResponseResources
* @description Response resources for server settings.
@@ -7179,10 +7177,6 @@ export type components = {
display_announcements?: boolean | null;
/** Whether to display notifications about ZenML updates in the dashboard. */
display_updates?: boolean | null;
- /** The server's onboarding state. */
- onboarding_state?: {
- [key: string]: unknown;
- } | null;
};
/**
* ServiceAccountRequest
@@ -14158,6 +14152,41 @@ export type operations = {
};
};
};
+ /**
+ * Get Onboarding State
+ * @description Get the onboarding state of the server.
+ *
+ * Returns:
+ * The onboarding state of the server.
+ */
+ get_onboarding_state_api_v1_onboarding_state_get: {
+ responses: {
+ /** @description Successful Response */
+ 200: {
+ content: {
+ "application/json": string[];
+ };
+ };
+ /** @description Unauthorized */
+ 401: {
+ content: {
+ "application/json": components["schemas"]["ErrorModel"];
+ };
+ };
+ /** @description Not Found */
+ 404: {
+ content: {
+ "application/json": components["schemas"]["ErrorModel"];
+ };
+ };
+ /** @description Unprocessable Entity */
+ 422: {
+ content: {
+ "application/json": components["schemas"]["ErrorModel"];
+ };
+ };
+ };
+ };
/**
* Get Settings
* @description Get settings of the server.
diff --git a/src/types/onboarding.ts b/src/types/onboarding.ts
index 6ef9e63a9..e90d1c9c7 100644
--- a/src/types/onboarding.ts
+++ b/src/types/onboarding.ts
@@ -1,11 +1,15 @@
export type OnboardingChecklistItemName =
- | "connect_zenml"
- | "run_first_pipeline"
- | "create_service_connector"
- | "create_remote_artifact_store"
- | "create_remote_stack"
- | "run_remote_pipeline";
+ | "device_verified"
+ | "pipeline_run"
+ | "starter_setup_completed"
+ | "stack_with_remote_orchestrator_created"
+ | "pipeline_run_with_remote_orchestrator"
+ | "production_setup_completed";
-export type OnboardingState = {
- [key in OnboardingChecklistItemName]?: boolean;
+export type OnboardingResponse = OnboardingChecklistItemName[];
+
+export type OnboardingStep = {
+ completed: boolean;
+ active: boolean;
+ hasDownstreamStep: boolean;
};