From b53e78a48f4c63935ee7df67908bfde1b251e690 Mon Sep 17 00:00:00 2001 From: Naman Anand Date: Tue, 6 Feb 2024 22:23:04 +0530 Subject: [PATCH 01/16] feat: add release version to pipeline view page --- .../toolkit/src/lib/vdp-sdk/pipeline/types.ts | 1 + .../ReadOnlyPipelineBuilderWithVersion.tsx | 178 +++++++++++++ .../src/view/pipeline/view-pipeline/Head.tsx | 236 +++++++++++++++++- .../view/pipeline/view-pipeline/InOutPut.tsx | 23 +- .../pipeline/view-pipeline/ViewPipeline.tsx | 3 +- 5 files changed, 422 insertions(+), 19 deletions(-) create mode 100644 packages/toolkit/src/view/pipeline-builder/components/ReadOnlyPipelineBuilderWithVersion.tsx diff --git a/packages/toolkit/src/lib/vdp-sdk/pipeline/types.ts b/packages/toolkit/src/lib/vdp-sdk/pipeline/types.ts index 03eabeebb7..ed7ee76d18 100644 --- a/packages/toolkit/src/lib/vdp-sdk/pipeline/types.ts +++ b/packages/toolkit/src/lib/vdp-sdk/pipeline/types.ts @@ -118,6 +118,7 @@ export type PipelineRelease = { visibility: Visibility; openapi_schema: OpenAPIV3.Document; metadata: GeneralRecord; + alias?: string; }; export type PipelineTrace = { diff --git a/packages/toolkit/src/view/pipeline-builder/components/ReadOnlyPipelineBuilderWithVersion.tsx b/packages/toolkit/src/view/pipeline-builder/components/ReadOnlyPipelineBuilderWithVersion.tsx new file mode 100644 index 0000000000..392d2aed20 --- /dev/null +++ b/packages/toolkit/src/view/pipeline-builder/components/ReadOnlyPipelineBuilderWithVersion.tsx @@ -0,0 +1,178 @@ +import cn from "clsx"; +import * as React from "react"; +import ReactFlow, { + Background, + BackgroundVariant, + Controls, + Edge, + Node, + ReactFlowInstance, +} from "reactflow"; + +import { + GeneralRecord, + InstillStore, + Nullable, + PipelineRecipe, + useInstillStore, + useNavigationObserver, + useShallow, +} from "../../../lib"; + +import { + checkIsValidPosition, + createGraphLayout, + createInitialGraphData, +} from "../lib"; +import { useRouter } from "next/router"; +import { + ConnectorNode, + EmptyNode, + EndOperatorNode, + OperatorNode, + StartOperatorNode, +} from "./nodes"; +import { CustomEdge } from "./CustomEdge"; + +const selector = (store: InstillStore) => ({ + nodes: store.nodes, + updateNodes: store.updateNodes, + updateEdges: store.updateEdges, + updateCurrentVersion: store.updateCurrentVersion, + pipelineIsReadOnly: store.pipelineIsReadOnly, + updatePipelineIsReadOnly: store.updatePipelineIsReadOnly, + currentVersion: store.currentVersion, +}); + +const nodeTypes = { + startNode: StartOperatorNode, + connectorNode: ConnectorNode, + emptyNode: EmptyNode, + endNode: EndOperatorNode, + operatorNode: OperatorNode, +}; + +const edgeTypes = { + customEdge: CustomEdge, +}; + +export type ReadOnlyPipelineBuilderWithVersionProps = { + pipelineName: Nullable; + recipe: Nullable; + metadata: Nullable; + className?: string; +}; + +export const ReadOnlyPipelineBuilderWithVersion = ({ + pipelineName, + className, + recipe, + metadata, +}: ReadOnlyPipelineBuilderWithVersionProps) => { + const router = useRouter(); + + const [edges, setEdges] = React.useState([]); + const [reactFlowInstance, setReactFlowInstance] = + React.useState>(null); + + const { + nodes, + updateNodes, + updateEdges, + updateCurrentVersion, + pipelineIsReadOnly, + updatePipelineIsReadOnly, + currentVersion, + } = useInstillStore(useShallow(selector)); + + React.useEffect(() => { + if (!recipe || !metadata) return; + + if (checkIsValidPosition(recipe, metadata)) { + const initialGraphData = createInitialGraphData(recipe, { + metadata, + }); + + updateNodes(() => initialGraphData.nodes); + updateEdges(() => initialGraphData.edges); + updateCurrentVersion(() => currentVersion || "latest"); + updatePipelineIsReadOnly(() => true); + return; + } + + const initialGraphData = createInitialGraphData(recipe); + + createGraphLayout(initialGraphData.nodes, initialGraphData.edges) + .then((graphData) => { + updateNodes(() => graphData.nodes); + updateEdges(() => graphData.edges); + updateCurrentVersion(() => currentVersion || "latest"); + updatePipelineIsReadOnly(() => true); + }) + .catch((err) => { + console.error(err); + }); + }, [ + recipe, + metadata, + reactFlowInstance, + updateCurrentVersion, + updatePipelineIsReadOnly, + currentVersion, + ]); + + // Clean up the pipelineIsReadOnly state when user navigate away + // from the page + useNavigationObserver({ + shouldStopNavigation: false, + onNavigate: () => { + updatePipelineIsReadOnly(() => false); + }, + router, + }); + + return ( +
+ + + + +
+ ); +}; diff --git a/packages/toolkit/src/view/pipeline/view-pipeline/Head.tsx b/packages/toolkit/src/view/pipeline/view-pipeline/Head.tsx index 9061f6dd4f..c8955b9109 100644 --- a/packages/toolkit/src/view/pipeline/view-pipeline/Head.tsx +++ b/packages/toolkit/src/view/pipeline/view-pipeline/Head.tsx @@ -1,16 +1,26 @@ import * as React from "react"; import { useRouter } from "next/router"; -import { useSortedReleases } from "../../pipeline-builder"; +import { + NodeData, + checkIsValidPosition, + createGraphLayout, + createInitialGraphData, + useSortedReleases, +} from "../../pipeline-builder"; import { Button, Icons, Tag, TabMenu, Skeleton, + Select, + Popover, + ScrollArea, } from "@instill-ai/design-system"; import { InstillStore, Nullable, + getHumanReadableStringFromTime, isPublicPipeline, useEntity, useInstillStore, @@ -22,19 +32,43 @@ import { } from "../../../lib"; import { ClonePipelineDialog, EntityAvatar } from "../../../components"; import { EditMetadataDialog } from "./EditMetadataDialog"; +import cn from "clsx"; +import { Edge, Node } from "reactflow"; const selector = (store: InstillStore) => ({ accessToken: store.accessToken, enabledQuery: store.enabledQuery, + pipelineName: store.pipelineName, + pipelineIsNew: store.pipelineIsNew, + updateNodes: store.updateNodes, + updateEdges: store.updateEdges, + nodes: store.nodes, + currentVersion: store.currentVersion, + updateCurrentVersion: store.updateCurrentVersion, + updateSelectedConnectorNodeId: store.updateSelectedConnectorNodeId, }); export const Head = () => { - const { accessToken, enabledQuery } = useInstillStore(useShallow(selector)); + const { + accessToken, + enabledQuery, + updateNodes, + updateEdges, + currentVersion, + nodes, + updateCurrentVersion, + updateSelectedConnectorNodeId, + } = useInstillStore(useShallow(selector)); + + console.log("nodes", nodes); + const router = useRouter(); const { id, entity } = router.query; const [selectedTab, setSelectedTab] = React.useState>("overview"); + const [isOpen, setIsOpen] = React.useState(false); + const entityObject = useEntity(); const user = useUser({ @@ -96,7 +130,7 @@ export const Head = () => { @@ -107,7 +141,7 @@ export const Head = () => { @@ -121,7 +155,7 @@ export const Head = () => { {pipeline.isSuccess ? ( -
+
{ router.push(`/${entity}`); @@ -134,19 +168,159 @@ export const Head = () => { {id}
{pipeline.isSuccess ? ( - + {isPublicPipeline(pipeline.data) ? "Public" : "Private"} ) : null} + + {pipeline.isSuccess ? ( + + + + + + +
+ {releases.length > 0 ? ( + + { + if (!pipeline.isSuccess) { + return; + } + + updateCurrentVersion(() => "latest"); + + let newNodes: Node[] = []; + let newEdges: Edge[] = []; + + if ( + checkIsValidPosition( + pipeline.data.recipe, + pipeline.data.metadata ?? null + ) + ) { + const { nodes, edges } = + createInitialGraphData( + pipeline.data.recipe, + { + metadata: pipeline.data.metadata, + } + ); + newNodes = nodes; + newEdges = edges; + } else { + const { nodes, edges } = + createInitialGraphData( + pipeline.data.recipe + ); + newNodes = nodes; + newEdges = edges; + } + + createGraphLayout(newNodes, newEdges) + .then((graphData) => { + updateNodes(() => graphData.nodes); + updateEdges(() => graphData.edges); + }) + .catch((err) => { + console.error(err); + }); + }} + /> + {releases.map((release) => ( + { + updateSelectedConnectorNodeId( + () => null + ); + + updateCurrentVersion(() => release.id); + + let newNodes: Node[] = []; + let newEdges: Edge[] = []; + + if ( + checkIsValidPosition( + release.recipe, + release.metadata ?? null + ) + ) { + const { nodes, edges } = + createInitialGraphData( + release.recipe, + { + metadata: release.metadata, + } + ); + newNodes = nodes; + newEdges = edges; + } else { + const { nodes, edges } = + createInitialGraphData( + release.recipe + ); + newNodes = nodes; + newEdges = edges; + } + + createGraphLayout(newNodes, newEdges) + .then((graphData) => { + updateNodes(() => graphData.nodes); + updateEdges(() => graphData.edges); + }) + .catch((err) => { + console.error(err); + }); + setIsOpen(false); + }} + /> + ))} + + ) : ( +
+ This pipeline has no released versions. +
+ )} +
+
+
+
+ ) : null} ) : ( )} - {releases[0] ? ( - - {releases[0]?.id} - - ) : null}
{pipeline.isSuccess ? ( @@ -161,7 +335,7 @@ export const Head = () => { /> ) : ( -
+

This is a placeholder brief of this pipeline

@@ -259,3 +433,41 @@ const HeaderControllerSkeleton = () => { ); }; + +const VersionButton = ({ + id, + currentVersion, + onClick, + createTime, +}: { + id: string; + currentVersion: Nullable; + createTime?: string; + onClick: () => void; +}) => { + return ( + + ); +}; diff --git a/packages/toolkit/src/view/pipeline/view-pipeline/InOutPut.tsx b/packages/toolkit/src/view/pipeline/view-pipeline/InOutPut.tsx index ba5c692b42..6fff394bb1 100644 --- a/packages/toolkit/src/view/pipeline/view-pipeline/InOutPut.tsx +++ b/packages/toolkit/src/view/pipeline/view-pipeline/InOutPut.tsx @@ -24,7 +24,7 @@ import { Skeleton, useToast, } from "@instill-ai/design-system"; -import { recursiveHelpers } from "../../pipeline-builder"; +import { recursiveHelpers, useSortedReleases } from "../../pipeline-builder"; import { ComponentOutputs } from "../../pipeline-builder/components/ComponentOutputs"; import { getPipelineInputOutputSchema } from "../../pipeline-builder/lib/getPipelineInputOutputSchema"; import { LoadingSpin } from "../../../components"; @@ -32,11 +32,14 @@ import { LoadingSpin } from "../../../components"; const selector = (store: InstillStore) => ({ accessToken: store.accessToken, enabledQuery: store.enabledQuery, + currentVersion: store.currentVersion, }); export const InOutPut = () => { const { amplitudeIsInit } = useAmplitudeCtx(); - const { accessToken, enabledQuery } = useInstillStore(useShallow(selector)); + const { accessToken, enabledQuery, currentVersion } = useInstillStore( + useShallow(selector) + ); const router = useRouter(); const [response, setResponse] = React.useState>(null); @@ -53,13 +56,21 @@ export const InOutPut = () => { accessToken, }); - const startComponent = React.useMemo(() => { - if (!pipeline.isSuccess) return null; + const releases = useSortedReleases({ + pipelineName: entityObject.pipelineName, + accessToken, + enabledQuery: enabledQuery && entityObject.isSuccess, + }); + const startComponent = React.useMemo(() => { + const pipelineVersion = releases.find( + (release) => + release.id === currentVersion || release.alias === currentVersion + ); return ( - pipeline.data.recipe.components.find((c) => c.id === "start") ?? null + pipelineVersion?.recipe.components.find((c) => c.id === "start") ?? null ); - }, [pipeline.isSuccess, pipeline.data]); + }, [releases, currentVersion]); const { fields, form, Schema } = useStartOperatorTriggerPipelineForm({ mode: "demo", diff --git a/packages/toolkit/src/view/pipeline/view-pipeline/ViewPipeline.tsx b/packages/toolkit/src/view/pipeline/view-pipeline/ViewPipeline.tsx index 45edb02568..127ac2936e 100644 --- a/packages/toolkit/src/view/pipeline/view-pipeline/ViewPipeline.tsx +++ b/packages/toolkit/src/view/pipeline/view-pipeline/ViewPipeline.tsx @@ -12,6 +12,7 @@ import { Head } from "./Head"; import { InOutPut } from "./InOutPut"; import { Readme } from "./Readme"; import { useRouter } from "next/router"; +import { ReadOnlyPipelineBuilderWithVersion } from "../../pipeline-builder/components/ReadOnlyPipelineBuilderWithVersion"; const selector = (store: InstillStore) => ({ accessToken: store.accessToken, @@ -56,7 +57,7 @@ export const ViewPipeline = () => {
- Date: Wed, 7 Feb 2024 15:56:14 +0530 Subject: [PATCH 02/16] feat: add delete ad share button --- .../src/view/pipeline/view-pipeline/Head.tsx | 33 +++++++ .../src/view/pipeline/view-pipeline/Menu.tsx | 88 +++++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 packages/toolkit/src/view/pipeline/view-pipeline/Menu.tsx diff --git a/packages/toolkit/src/view/pipeline/view-pipeline/Head.tsx b/packages/toolkit/src/view/pipeline/view-pipeline/Head.tsx index c8955b9109..01e324e6a5 100644 --- a/packages/toolkit/src/view/pipeline/view-pipeline/Head.tsx +++ b/packages/toolkit/src/view/pipeline/view-pipeline/Head.tsx @@ -16,12 +16,15 @@ import { Select, Popover, ScrollArea, + toast, } from "@instill-ai/design-system"; import { InstillStore, Nullable, getHumanReadableStringFromTime, isPublicPipeline, + toastInstillError, + useDeleteUserPipeline, useEntity, useInstillStore, useOrganization, @@ -34,6 +37,7 @@ import { ClonePipelineDialog, EntityAvatar } from "../../../components"; import { EditMetadataDialog } from "./EditMetadataDialog"; import cn from "clsx"; import { Edge, Node } from "reactflow"; +import { Menu } from "./Menu"; const selector = (store: InstillStore) => ({ accessToken: store.accessToken, @@ -109,6 +113,28 @@ export const Head = () => { enabledQuery: enabledQuery && entityObject.isSuccess, }); + const deletePipeline = useDeleteUserPipeline(); + async function handleDeletePipeline() { + try { + await deletePipeline.mutateAsync({ + pipelineName: pipeline.data?.name || "", + accessToken: accessToken ? accessToken : null, + }); + + toast({ + title: "Pipeline deleted", + variant: "alert-success", + size: "large", + }); + } catch (error) { + toastInstillError({ + title: "Something went wrong when delete the pipeline", + error, + toast, + }); + } + } + return (