diff --git a/public/component_types/interfaces.ts b/public/component_types/interfaces.ts index 2abf45d5..4fa63b80 100644 --- a/public/component_types/interfaces.ts +++ b/public/component_types/interfaces.ts @@ -77,3 +77,11 @@ export interface IComponent { createFields?: IComponentField[]; outputs?: IComponentOutput[]; } + +/** + * We need to include some extra instance-specific data to the ReactFlow component + * to perform extra functionality, such as deleting the node from the ReactFlowInstance. + */ +export interface IComponentData extends IComponent { + id: string; +} diff --git a/public/pages/workflow_detail/workspace/workspace.tsx b/public/pages/workflow_detail/workspace/workspace.tsx index e26ee1fb..a3d7b5b6 100644 --- a/public/pages/workflow_detail/workspace/workspace.tsx +++ b/public/pages/workflow_detail/workspace/workspace.tsx @@ -16,7 +16,7 @@ import ReactFlow, { import { EuiFlexItem, EuiFlexGroup } from '@elastic/eui'; import { rfContext, setDirty } from '../../../store'; import { IComponent, Workflow } from '../../../../common'; -import { generateId } from '../../../utils'; +import { generateId, initComponentData } from '../../../utils'; import { getCore } from '../../../services'; import { WorkspaceComponent } from '../workspace_component'; import { DeletableEdge } from '../workspace_edge'; @@ -84,11 +84,12 @@ export function Workspace(props: WorkspaceProps) { // TODO: remove hardcoded values when more component info is passed in the event. // Only keep the calculated 'position' field. + const id = generateId(nodeData.type); const newNode = { - id: generateId(nodeData.type), + id, type: nodeData.type, position, - data: nodeData, + data: initComponentData(nodeData, id), style: { background: 'white', }, diff --git a/public/pages/workflow_detail/workspace_component/workspace_component.tsx b/public/pages/workflow_detail/workspace_component/workspace_component.tsx index 7d0ac3de..20d4953c 100644 --- a/public/pages/workflow_detail/workspace_component/workspace_component.tsx +++ b/public/pages/workflow_detail/workspace_component/workspace_component.tsx @@ -3,14 +3,22 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiCard } from '@elastic/eui'; -import { IComponent } from '../../../component_types'; +import React, { useContext } from 'react'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiCard, + EuiText, + EuiTitle, + EuiButtonIcon, +} from '@elastic/eui'; +import { rfContext } from '../../../store'; +import { IComponentData } from '../../../component_types'; import { InputHandle } from './input_handle'; import { OutputHandle } from './output_handle'; interface WorkspaceComponentProps { - data: IComponent; + data: IComponentData; } /** @@ -20,10 +28,36 @@ interface WorkspaceComponentProps { */ export function WorkspaceComponent(props: WorkspaceComponentProps) { const component = props.data; + const { deleteNode } = useContext(rfContext); return ( - + + + +

{component.label}

+
+
+ + { + deleteNode(component.id); + }} + aria-label="Delete" + /> + + + } + > + + + {component.description} + + {component.inputs?.map((input, index) => { return ( @@ -31,7 +65,6 @@ export function WorkspaceComponent(props: WorkspaceComponentProps) { ); })} - {/* TODO: finalize from UX what we show in the component itself. Readonly fields? Configure in the component JSON definition? */} {component.outputs?.map((output, index) => { return ( diff --git a/public/store/context/react_flow_context_provider.tsx b/public/store/context/react_flow_context_provider.tsx index 73ef1cce..8268a0c9 100644 --- a/public/store/context/react_flow_context_provider.tsx +++ b/public/store/context/react_flow_context_provider.tsx @@ -5,7 +5,7 @@ import React, { createContext, useState } from 'react'; import { useDispatch } from 'react-redux'; -import { Edge } from 'reactflow'; +import { Edge, Node } from 'reactflow'; import { setDirty } from '../reducers'; const initialValues = { @@ -30,8 +30,19 @@ export function ReactFlowContextProvider({ children }: any) { const [reactFlowInstance, setReactFlowInstance] = useState(null); const deleteNode = (nodeId: string) => { - // TODO: implement node deletion - // reactFlowInstance.setNodes(...) + reactFlowInstance.setNodes( + reactFlowInstance.getNodes().filter((node: Node) => node.id !== nodeId) + ); + // Also delete any dangling edges attached to the component + reactFlowInstance.setEdges( + reactFlowInstance + .getEdges() + .filter( + (edge: Edge) => edge.source !== nodeId && edge.target !== nodeId + ) + ); + + dispatch(setDirty()); }; const deleteEdge = (edgeId: string) => { diff --git a/public/store/reducers/workflows_reducer.ts b/public/store/reducers/workflows_reducer.ts index efc07a07..20c60f76 100644 --- a/public/store/reducers/workflows_reducer.ts +++ b/public/store/reducers/workflows_reducer.ts @@ -11,26 +11,30 @@ import { KnnIndex, TextEmbeddingProcessor, generateId, + initComponentData, } from '../../../common'; // TODO: remove after fetching from server-side +const id1 = generateId('text_embedding_processor'); +const id2 = generateId('text_embedding_processor'); +const id3 = generateId('knn_index'); const dummyNodes = [ { - id: generateId('text_embedding_processor'), + id: id1, position: { x: 0, y: 500 }, - data: new TextEmbeddingProcessor().toObj(), + data: initComponentData(new TextEmbeddingProcessor().toObj(), id1), type: 'customComponent', }, { - id: generateId('text_embedding_processor'), + id: id2, position: { x: 0, y: 200 }, - data: new TextEmbeddingProcessor().toObj(), + data: initComponentData(new TextEmbeddingProcessor().toObj(), id2), type: 'customComponent', }, { - id: generateId('knn_index'), + id: id3, position: { x: 500, y: 500 }, - data: new KnnIndex().toObj(), + data: initComponentData(new KnnIndex().toObj(), id3), type: 'customComponent', }, ] as ReactFlowComponent[]; diff --git a/public/utils/utils.ts b/public/utils/utils.ts index 1304c5cf..b319a8af 100644 --- a/public/utils/utils.ts +++ b/public/utils/utils.ts @@ -3,6 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { IComponent, IComponentData } from '../../common'; + // Append 16 random characters export function generateId(prefix: string) { const uniqueChar = () => { @@ -11,3 +13,15 @@ export function generateId(prefix: string) { }; return `${prefix}_${uniqueChar()}${uniqueChar()}${uniqueChar()}${uniqueChar()}`; } + +// Adding any instance metadata. Converting the base IComponent obj into +// an instance-specific IComponentData obj. +export function initComponentData( + data: IComponent, + componentId: string +): IComponentData { + return { + ...data, + id: componentId, + } as IComponentData; +}