Skip to content

Commit 6226131

Browse files
Add node deletion functionality in Workspace (#59) (#60)
Signed-off-by: Tyler Ohlsen <ohltyler@amazon.com> (cherry picked from commit 52e5cec) Co-authored-by: Tyler Ohlsen <ohltyler@amazon.com>
1 parent d2b7d3d commit 6226131

File tree

6 files changed

+89
-18
lines changed

6 files changed

+89
-18
lines changed

public/component_types/interfaces.ts

+8
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,11 @@ export interface IComponent {
7777
createFields?: IComponentField[];
7878
outputs?: IComponentOutput[];
7979
}
80+
81+
/**
82+
* We need to include some extra instance-specific data to the ReactFlow component
83+
* to perform extra functionality, such as deleting the node from the ReactFlowInstance.
84+
*/
85+
export interface IComponentData extends IComponent {
86+
id: string;
87+
}

public/pages/workflow_detail/workspace/workspace.tsx

+4-3
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import ReactFlow, {
1616
import { EuiFlexItem, EuiFlexGroup } from '@elastic/eui';
1717
import { rfContext, setDirty } from '../../../store';
1818
import { IComponent, Workflow } from '../../../../common';
19-
import { generateId } from '../../../utils';
19+
import { generateId, initComponentData } from '../../../utils';
2020
import { getCore } from '../../../services';
2121
import { WorkspaceComponent } from '../workspace_component';
2222
import { DeletableEdge } from '../workspace_edge';
@@ -84,11 +84,12 @@ export function Workspace(props: WorkspaceProps) {
8484

8585
// TODO: remove hardcoded values when more component info is passed in the event.
8686
// Only keep the calculated 'position' field.
87+
const id = generateId(nodeData.type);
8788
const newNode = {
88-
id: generateId(nodeData.type),
89+
id,
8990
type: nodeData.type,
9091
position,
91-
data: nodeData,
92+
data: initComponentData(nodeData, id),
9293
style: {
9394
background: 'white',
9495
},

public/pages/workflow_detail/workspace_component/workspace_component.tsx

+39-6
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,22 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
import React from 'react';
7-
import { EuiFlexGroup, EuiFlexItem, EuiCard } from '@elastic/eui';
8-
import { IComponent } from '../../../component_types';
6+
import React, { useContext } from 'react';
7+
import {
8+
EuiFlexGroup,
9+
EuiFlexItem,
10+
EuiCard,
11+
EuiText,
12+
EuiTitle,
13+
EuiButtonIcon,
14+
} from '@elastic/eui';
15+
import { rfContext } from '../../../store';
16+
import { IComponentData } from '../../../component_types';
917
import { InputHandle } from './input_handle';
1018
import { OutputHandle } from './output_handle';
1119

1220
interface WorkspaceComponentProps {
13-
data: IComponent;
21+
data: IComponentData;
1422
}
1523

1624
/**
@@ -20,18 +28,43 @@ interface WorkspaceComponentProps {
2028
*/
2129
export function WorkspaceComponent(props: WorkspaceComponentProps) {
2230
const component = props.data;
31+
const { deleteNode } = useContext(rfContext);
2332

2433
return (
25-
<EuiCard title={component.label}>
34+
<EuiCard
35+
textAlign="left"
36+
title={
37+
<EuiFlexGroup direction="row" justifyContent="spaceBetween">
38+
<EuiFlexItem grow={false}>
39+
<EuiTitle size="s">
40+
<h3>{component.label}</h3>
41+
</EuiTitle>
42+
</EuiFlexItem>
43+
<EuiFlexItem grow={false}>
44+
<EuiButtonIcon
45+
iconType="trash"
46+
onClick={() => {
47+
deleteNode(component.id);
48+
}}
49+
aria-label="Delete"
50+
/>
51+
</EuiFlexItem>
52+
</EuiFlexGroup>
53+
}
54+
>
2655
<EuiFlexGroup direction="column">
56+
<EuiFlexItem>
57+
<EuiText size="s" color="subdued">
58+
{component.description}
59+
</EuiText>
60+
</EuiFlexItem>
2761
{component.inputs?.map((input, index) => {
2862
return (
2963
<EuiFlexItem key={index}>
3064
<InputHandle input={input} data={component} />
3165
</EuiFlexItem>
3266
);
3367
})}
34-
{/* TODO: finalize from UX what we show in the component itself. Readonly fields? Configure in the component JSON definition? */}
3568
{component.outputs?.map((output, index) => {
3669
return (
3770
<EuiFlexItem key={index}>

public/store/context/react_flow_context_provider.tsx

+14-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import React, { createContext, useState } from 'react';
77
import { useDispatch } from 'react-redux';
8-
import { Edge } from 'reactflow';
8+
import { Edge, Node } from 'reactflow';
99
import { setDirty } from '../reducers';
1010

1111
const initialValues = {
@@ -30,8 +30,19 @@ export function ReactFlowContextProvider({ children }: any) {
3030
const [reactFlowInstance, setReactFlowInstance] = useState(null);
3131

3232
const deleteNode = (nodeId: string) => {
33-
// TODO: implement node deletion
34-
// reactFlowInstance.setNodes(...)
33+
reactFlowInstance.setNodes(
34+
reactFlowInstance.getNodes().filter((node: Node) => node.id !== nodeId)
35+
);
36+
// Also delete any dangling edges attached to the component
37+
reactFlowInstance.setEdges(
38+
reactFlowInstance
39+
.getEdges()
40+
.filter(
41+
(edge: Edge) => edge.source !== nodeId && edge.target !== nodeId
42+
)
43+
);
44+
45+
dispatch(setDirty());
3546
};
3647

3748
const deleteEdge = (edgeId: string) => {

public/store/reducers/workflows_reducer.ts

+10-6
Original file line numberDiff line numberDiff line change
@@ -11,26 +11,30 @@ import {
1111
KnnIndex,
1212
TextEmbeddingProcessor,
1313
generateId,
14+
initComponentData,
1415
} from '../../../common';
1516

1617
// TODO: remove after fetching from server-side
18+
const id1 = generateId('text_embedding_processor');
19+
const id2 = generateId('text_embedding_processor');
20+
const id3 = generateId('knn_index');
1721
const dummyNodes = [
1822
{
19-
id: generateId('text_embedding_processor'),
23+
id: id1,
2024
position: { x: 0, y: 500 },
21-
data: new TextEmbeddingProcessor().toObj(),
25+
data: initComponentData(new TextEmbeddingProcessor().toObj(), id1),
2226
type: 'customComponent',
2327
},
2428
{
25-
id: generateId('text_embedding_processor'),
29+
id: id2,
2630
position: { x: 0, y: 200 },
27-
data: new TextEmbeddingProcessor().toObj(),
31+
data: initComponentData(new TextEmbeddingProcessor().toObj(), id2),
2832
type: 'customComponent',
2933
},
3034
{
31-
id: generateId('knn_index'),
35+
id: id3,
3236
position: { x: 500, y: 500 },
33-
data: new KnnIndex().toObj(),
37+
data: initComponentData(new KnnIndex().toObj(), id3),
3438
type: 'customComponent',
3539
},
3640
] as ReactFlowComponent[];

public/utils/utils.ts

+14
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6+
import { IComponent, IComponentData } from '../../common';
7+
68
// Append 16 random characters
79
export function generateId(prefix: string) {
810
const uniqueChar = () => {
@@ -11,3 +13,15 @@ export function generateId(prefix: string) {
1113
};
1214
return `${prefix}_${uniqueChar()}${uniqueChar()}${uniqueChar()}${uniqueChar()}`;
1315
}
16+
17+
// Adding any instance metadata. Converting the base IComponent obj into
18+
// an instance-specific IComponentData obj.
19+
export function initComponentData(
20+
data: IComponent,
21+
componentId: string
22+
): IComponentData {
23+
return {
24+
...data,
25+
id: componentId,
26+
} as IComponentData;
27+
}

0 commit comments

Comments
 (0)