Skip to content

Commit

Permalink
fix: saving workflow + feat: skeleton, do not reload page on delete, …
Browse files Browse the repository at this point in the history
…use common ui components
  • Loading branch information
Kiryous committed Jan 13, 2025
1 parent 2677f68 commit f409d6f
Show file tree
Hide file tree
Showing 26 changed files with 470 additions and 805 deletions.
35 changes: 10 additions & 25 deletions keep-ui/app/(keep)/workflows/[workflow_id]/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,22 @@
"use client";

import { Link } from "@/components/ui";
import { ArrowRightIcon } from "@heroicons/react/16/solid";
import { Icon, Subtitle } from "@tremor/react";
import { useParams } from "next/navigation";
import { getWorkflowWithRedirectSafe } from "@/shared/api/workflows";
import { WorkflowBreadcrumbs } from "./workflow-breadcrumbs";
import WorkflowDetailHeader from "./workflow-detail-header";

export default function Layout({
export default async function Layout({
children,
params,
}: {
children: any;
children: React.ReactNode;
params: { workflow_id: string };
}) {
const clientParams = useParams();
const workflow = await getWorkflowWithRedirectSafe(params.workflow_id);
return (
<div className="flex flex-col mb-4 h-full gap-6">
<Subtitle className="text-sm">
<Link href="/workflows">All Workflows</Link>{" "}
<Icon icon={ArrowRightIcon} color="gray" size="xs" />{" "}
{clientParams.workflow_execution_id ? (
<>
<Link href={`/workflows/${params.workflow_id}`}>
Workflow Details
</Link>
<Icon icon={ArrowRightIcon} color="gray" size="xs" /> Workflow
Execution Details
</>
) : (
"Workflow Details"
)}
</Subtitle>
<WorkflowDetailHeader workflow_id={params.workflow_id} />
<WorkflowBreadcrumbs workflowId={params.workflow_id} />
<WorkflowDetailHeader
workflowId={params.workflow_id}
initialData={workflow}
/>
<div className="flex-1 h-full">{children}</div>
</div>
);
Expand Down
10 changes: 8 additions & 2 deletions keep-ui/app/(keep)/workflows/[workflow_id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import { Metadata } from "next";
import WorkflowDetailPage from "./workflow-detail-page";
import { getWorkflowWithRedirectSafe } from "@/shared/api/workflows";

export default function Page({ params }: { params: { workflow_id: string } }) {
return <WorkflowDetailPage params={params} />;
export default async function Page({
params,
}: {
params: { workflow_id: string };
}) {
const initialData = await getWorkflowWithRedirectSafe(params.workflow_id);
return <WorkflowDetailPage params={params} initialData={initialData} />;
}

export const metadata: Metadata = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"use client";

import { Icon } from "@tremor/react";
import { useParams } from "next/navigation";
import { Link } from "@/components/ui";
import { Subtitle } from "@tremor/react";
import { ArrowRightIcon } from "@heroicons/react/16/solid";

export function WorkflowBreadcrumbs({ workflowId }: { workflowId: string }) {
const clientParams = useParams();

return (
<Subtitle className="text-sm">
<Link href="/workflows">All Workflows</Link>{" "}
<Icon icon={ArrowRightIcon} color="gray" size="xs" />{" "}
{clientParams.workflow_execution_id ? (
<>
<Link href={`/workflows/${workflowId}`}>Workflow Details</Link>
<Icon icon={ArrowRightIcon} color="gray" size="xs" /> Workflow
Execution Details
</>
) : (
"Workflow Details"
)}
</Subtitle>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ import { useWorkflowRun } from "@/utils/hooks/useWorkflowRun";
import AlertTriggerModal from "../workflow-run-with-alert-modal";

export default function WorkflowDetailHeader({
workflow_id,
workflowId: workflow_id,
initialData,
}: {
workflow_id: string;
workflowId: string;
initialData?: Workflow;
}) {
const api = useApi();
const {
Expand All @@ -20,10 +22,10 @@ export default function WorkflowDetailHeader({
error,
} = useSWR<Partial<Workflow>>(
api.isReady() ? `/workflows/${workflow_id}` : null,
(url: string) => api.get(url)
(url: string) => api.get(url),
{ fallbackData: initialData }
);


const {
isRunning,
handleRunClick,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ import { ErrorComponent, TabNavigationLink, YAMLCodeblock } from "@/shared/ui";

export default function WorkflowDetailPage({
params,
initialData,
}: {
params: { workflow_id: string };
initialData?: Workflow;
}) {
const api = useApi();
const [tabIndex, setTabIndex] = useState(0);
Expand All @@ -35,9 +37,12 @@ export default function WorkflowDetailPage({
data: workflow,
isLoading,
error,
} = useSWR<Partial<Workflow>>(
} = useSWR<Workflow>(
api.isReady() ? `/workflows/${params.workflow_id}` : null,
(url: string) => api.get(url)
(url: string) => api.get(url),
{
fallbackData: initialData,
}
);

if (error) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import Skeleton from "react-loading-skeleton";

export function WorkflowOverviewSkeleton() {
return (
<div className="flex flex-col gap-4">
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-4">
<div>
<Skeleton className="h-24" />
</div>
<div>
<Skeleton className="h-24" />
</div>
<div>
<Skeleton className="h-24" />
</div>
<div>
<Skeleton className="h-24" />
</div>
<div>
<Skeleton className="h-24" />
</div>
</div>
<div className="flex flex-col gap-4">
<Skeleton className="h-32" />
</div>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { Workflow } from "@/shared/api/workflows";
import WorkflowGraph from "../workflow-graph";
import { TableFilters } from "./table-filters";
import { ExecutionTable } from "./workflow-execution-table";
import { WorkflowOverviewSkeleton } from "./workflow-overview-skeleton";

interface Pagination {
limit: number;
Expand Down Expand Up @@ -80,7 +81,7 @@ export default function WorkflowOverview({
return (
<div className="flex flex-col gap-4">
{/* TODO: Add a working time filter */}
{!data || isLoading || (isValidating && <Loading />)}
{(!data || isLoading || isValidating) && <WorkflowOverviewSkeleton />}
{data?.items && (
<div className="flex flex-col gap-4">
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-4">
Expand Down
4 changes: 2 additions & 2 deletions keep-ui/app/(keep)/workflows/builder/builder-modal.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { XMarkIcon } from "@heroicons/react/24/outline";
import { Button, Card, Subtitle, Title } from "@tremor/react";
import { stringify } from "yaml";
import { Alert } from "./legacy-workflow.types";
import { LegacyWorkflow } from "./legacy-workflow.types";
import { YAMLCodeblock } from "@/shared/ui";

interface Props {
closeModal: () => void;
compiledAlert: Alert | string | null;
compiledAlert: LegacyWorkflow | string | null;
id?: string;
hideCloseButton?: boolean;
}
Expand Down
7 changes: 6 additions & 1 deletion keep-ui/app/(keep)/workflows/builder/builder-store.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ function addNodeBetween(
}
}

const useStore = create<FlowState>((set, get) => ({
const defaultState: FlowState = {
nodes: [],
edges: [],
selectedNode: null,
Expand All @@ -282,6 +282,10 @@ const useStore = create<FlowState>((set, get) => ({
errorNode: null,
synced: true,
canDeploy: false,
};

const useStore = create<FlowState>((set, get) => ({
...defaultState,
setCanDeploy: (deploy) => set({ canDeploy: deploy }),
setSynced: (sync) => set({ synced: sync }),
setErrorNode: (id) => set({ errorNode: id }),
Expand Down Expand Up @@ -553,6 +557,7 @@ const useStore = create<FlowState>((set, get) => ({
};
set({ nodes: [...get().nodes, newNode] });
},
reset: () => set(defaultState),
}));

export default useStore;
54 changes: 26 additions & 28 deletions keep-ui/app/(keep)/workflows/builder/builder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ import {
parseWorkflow,
generateWorkflow,
getToolboxConfiguration,
buildAlert,
getWorkflowFromDefinition,
wrapDefinitionV2,
} from "./utils";
import {
CheckCircleIcon,
ExclamationCircleIcon,
} from "@heroicons/react/20/solid";
import { globalValidatorV2, stepValidatorV2 } from "./builder-validators";
import { Alert } from "./legacy-workflow.types";
import { LegacyWorkflow } from "./legacy-workflow.types";
import BuilderModalContent from "./builder-modal";
import Loader from "./loader";
import { stringify } from "yaml";
Expand All @@ -37,8 +37,8 @@ import { useApi } from "@/shared/lib/hooks/useApi";
import { KeepApiError } from "@/shared/api";
import { showErrorToast, showSuccessToast } from "@/shared/ui";
import { YAMLException } from "js-yaml";
import { revalidatePath } from "next/cache";
import Modal from "@/components/ui/Modal";
import { useWorkflowActions } from "@/entities/workflows/model/useWorkflowActions";

interface Props {
loadedAlertFile: string | null;
Expand Down Expand Up @@ -87,11 +87,13 @@ function Builder({
const [runningWorkflowExecution, setRunningWorkflowExecution] = useState<
WorkflowExecutionDetail | WorkflowExecutionFailure | null
>(null);
const [compiledAlert, setCompiledAlert] = useState<Alert | null>(null);
const [legacyWorkflow, setLegacyWorkflow] = useState<LegacyWorkflow | null>(
null
);
const router = useRouter();

const searchParams = useSearchParams();
const { errorNode, setErrorNode, canDeploy, synced } = useStore();
const { errorNode, setErrorNode, canDeploy, synced, reset } = useStore();

const setStepValidationErrorV2 = (step: V2Step, error: string | null) => {
setStepValidationError(error);
Expand All @@ -113,7 +115,7 @@ function Builder({
};

const updateWorkflow = useCallback(() => {
const body = stringify(buildAlert(definition.value));
const body = stringify(getWorkflowFromDefinition(definition.value));
api
.request(`/workflows/${workflowId}`, {
method: "PUT",
Expand All @@ -130,7 +132,7 @@ function Builder({

const testRunWorkflow = () => {
setTestRunModalOpen(true);
const body = stringify(buildAlert(definition.value));
const body = stringify(getWorkflowFromDefinition(definition.value));
api
.request(`/workflows/test`, {
method: "POST",
Expand All @@ -150,25 +152,21 @@ function Builder({
});
};

const addWorkflow = useCallback(() => {
const body = stringify(buildAlert(definition.value));
api
.request(`/workflows/json`, {
method: "POST",
body,
headers: { "Content-Type": "text/html" },
})
.then(({ workflow_id }) => {
// This is important because it makes sure we will re-fetch the workflow if we get to this page again.
// router.push for instance, optimizes re-render of same pages and we don't want that here because of "cache".
showSuccessToast("Workflow added successfully");
revalidatePath("/workflows/builder");
router.push(`/workflows/${workflow_id}`);
})
.catch((error) => {
alert(`Error: ${error}`);
});
}, [api, definition.value, router]);
const { createWorkflow } = useWorkflowActions();

const addWorkflow = useCallback(async () => {
try {
const response = await createWorkflow(definition.value);
// reset the store to clear the nodes and edges
if (response?.workflow_id) {
reset();
router.push(`/workflows/${response.workflow_id}`);
}
} catch (error) {
// error is handled in the useWorkflowActions hook4
console.error(error);
}
}, [createWorkflow, definition.value, reset, router]);

useEffect(() => {
setIsLoading(true);
Expand Down Expand Up @@ -215,7 +213,7 @@ function Builder({

useEffect(() => {
if (triggerGenerate) {
setCompiledAlert(buildAlert(definition.value));
setLegacyWorkflow(getWorkflowFromDefinition(definition.value));
if (!generateModalIsOpen) setGenerateModalIsOpen(true);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
Expand Down Expand Up @@ -344,7 +342,7 @@ function Builder({
>
<BuilderModalContent
closeModal={closeGenerateModal}
compiledAlert={compiledAlert}
compiledAlert={legacyWorkflow}
/>
</Modal>
<Modal
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export interface Action extends Step {
foreach?: string;
}

export interface Alert {
export interface LegacyWorkflow {
id: string;
description?: string;
owners?: string[];
Expand Down
2 changes: 1 addition & 1 deletion keep-ui/app/(keep)/workflows/builder/page.client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export function WorkflowBuilderPageClient({
<main className="mx-auto max-w-full h-[98%]">
<div className="flex justify-between">
<div className="flex flex-col">
<Title>{workflow ? "Edit" : "New"} Workflow</Title>
<Title>{workflowId ? "Edit" : "New"} Workflow</Title>
</div>
<div className="flex gap-2">
{!workflow && (
Expand Down
Loading

0 comments on commit f409d6f

Please sign in to comment.