diff --git a/common/utils.ts b/common/utils.ts index 014e29e7..2e5bf2d9 100644 --- a/common/utils.ts +++ b/common/utils.ts @@ -15,6 +15,8 @@ import { TemplateFlows, WorkflowTemplate, DATE_FORMAT_PATTERN, + COMPONENT_CATEGORY, + NODE_CATEGORY, } from './'; // TODO: implement this and remove hardcoded return values @@ -42,32 +44,92 @@ export function toTemplateFlows( export function toWorkspaceFlow( templateFlows: TemplateFlows ): WorkspaceFlowState { - const id1 = generateId('text_embedding_processor'); - const id2 = generateId('text_embedding_processor'); - const id3 = generateId('knn_index'); - const dummyNodes = [ + const ingestId1 = generateId('text_embedding_processor'); + const ingestId2 = generateId('knn_index'); + const ingestGroupId = generateId(COMPONENT_CATEGORY.INGEST); + + const searchId1 = generateId('text_embedding_processor'); + const searchId2 = generateId('knn_index'); + const searchGroupId = generateId(COMPONENT_CATEGORY.SEARCH); + + const ingestNodes = [ + { + id: ingestGroupId, + position: { x: 400, y: 400 }, + type: NODE_CATEGORY.INGEST_GROUP, + data: { label: COMPONENT_CATEGORY.INGEST }, + style: { + width: 900, + height: 400, + overflowX: 'auto', + overflowY: 'auto', + }, + className: 'reactflow__group-node__ingest', + selectable: true, + }, + { + id: ingestId1, + position: { x: 100, y: 70 }, + data: initComponentData( + new TextEmbeddingTransformer().toObj(), + ingestId1 + ), + type: NODE_CATEGORY.CUSTOM, + parentNode: ingestGroupId, + extent: 'parent', + draggable: true, + }, + { + id: ingestId2, + position: { x: 500, y: 70 }, + data: initComponentData(new KnnIndexer().toObj(), ingestId2), + type: NODE_CATEGORY.CUSTOM, + parentNode: ingestGroupId, + extent: 'parent', + draggable: true, + }, + ] as ReactFlowComponent[]; + + const searchNodes = [ { - id: id1, - position: { x: 0, y: 500 }, - data: initComponentData(new TextEmbeddingTransformer().toObj(), id1), - type: 'customComponent', + id: searchGroupId, + position: { x: 400, y: 1000 }, + type: NODE_CATEGORY.SEARCH_GROUP, + data: { label: COMPONENT_CATEGORY.SEARCH }, + style: { + width: 900, + height: 400, + overflowX: 'auto', + overflowY: 'auto', + }, + className: 'reactflow__group-node__search', + selectable: true, }, { - id: id2, - position: { x: 0, y: 200 }, - data: initComponentData(new TextEmbeddingTransformer().toObj(), id2), - type: 'customComponent', + id: searchId1, + position: { x: 100, y: 70 }, + data: initComponentData( + new TextEmbeddingTransformer().toObj(), + searchId1 + ), + type: NODE_CATEGORY.CUSTOM, + parentNode: searchGroupId, + extent: 'parent', + draggable: true, }, { - id: id3, - position: { x: 500, y: 500 }, - data: initComponentData(new KnnIndexer().toObj(), id3), - type: 'customComponent', + id: searchId2, + position: { x: 500, y: 70 }, + data: initComponentData(new KnnIndexer().toObj(), searchId2), + type: NODE_CATEGORY.CUSTOM, + parentNode: searchGroupId, + extent: 'parent', + draggable: true, }, ] as ReactFlowComponent[]; return { - nodes: dummyNodes, + nodes: [...ingestNodes, ...searchNodes], edges: [] as ReactFlowEdge[], }; } diff --git a/public/pages/workflow_detail/component_details/component_inputs.tsx b/public/pages/workflow_detail/component_details/component_inputs.tsx index cae794df..3d9f076e 100644 --- a/public/pages/workflow_detail/component_details/component_inputs.tsx +++ b/public/pages/workflow_detail/component_details/component_inputs.tsx @@ -4,9 +4,9 @@ */ import React from 'react'; -import { EuiSpacer, EuiTitle } from '@elastic/eui'; +import { EuiSpacer, EuiText, EuiTitle } from '@elastic/eui'; import { InputFieldList } from './input_field_list'; -import { ReactFlowComponent } from '../../../../common'; +import { NODE_CATEGORY, ReactFlowComponent } from '../../../../common'; interface ComponentInputsProps { selectedComponent: ReactFlowComponent; @@ -14,16 +14,45 @@ interface ComponentInputsProps { } export function ComponentInputs(props: ComponentInputsProps) { - return ( - <> - -

{props.selectedComponent.data.label || ''}

-
- - - - ); + // Have custom layouts for parent/group flows + if (props.selectedComponent.type === NODE_CATEGORY.INGEST_GROUP) { + return ( + <> + +

Ingest flow

+
+ + + Configure a flow to transform your data as it is ingested into + OpenSearch. + + + ); + } else if (props.selectedComponent.type === NODE_CATEGORY.SEARCH_GROUP) { + return ( + <> + +

Search flow

+
+ + + Configure a flow to transform input when searching against your + OpenSearch cluster. + + + ); + } else { + return ( + <> + +

{props.selectedComponent.data.label || ''}

+
+ + + + ); + } } diff --git a/public/pages/workflow_detail/components/header.tsx b/public/pages/workflow_detail/components/header.tsx index 94b63195..c3c2e967 100644 --- a/public/pages/workflow_detail/components/header.tsx +++ b/public/pages/workflow_detail/components/header.tsx @@ -3,12 +3,9 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React, { useContext } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; +import React from 'react'; import { EuiPageHeader, EuiButton, EuiLoadingSpinner } from '@elastic/eui'; import { DEFAULT_NEW_WORKFLOW_NAME, Workflow } from '../../../../common'; -import { saveWorkflow } from '../utils'; -import { rfContext, AppState, removeDirty } from '../../../store'; interface WorkflowDetailHeaderProps { tabs: any[]; @@ -17,10 +14,6 @@ interface WorkflowDetailHeaderProps { } export function WorkflowDetailHeader(props: WorkflowDetailHeaderProps) { - const dispatch = useDispatch(); - const { reactFlowInstance } = useContext(rfContext); - const isDirty = useSelector((state: AppState) => state.workspace.isDirty); - return ( state.workflows ); - const { isDirty } = useSelector((state: AppState) => state.workspace); // selected workflow state const workflowId = props.match?.params?.workflowId; diff --git a/public/pages/workflow_detail/workspace/reactflow-styles.scss b/public/pages/workflow_detail/workspace/reactflow-styles.scss index 1bd9b1aa..900bdc45 100644 --- a/public/pages/workflow_detail/workspace/reactflow-styles.scss +++ b/public/pages/workflow_detail/workspace/reactflow-styles.scss @@ -19,12 +19,28 @@ $handle-color-invalid: $euiColorDanger; .reactflow-workspace .react-flow__node { width: 300px; + height: 250px; +} + +.reactflow__group-node { + width: 1200px; + height: 700px; + overflow-x: auto; + overflow-y: auto; + border: 'none'; + + &__ingest { + background: rgba($euiColorVis0, 0.3); + } + &__search { + background: rgba($euiColorVis1, 0.3); + } } // Overriding the styling for the reactflow node when it is selected. // We need to use important tag to override ReactFlow's wrapNode that sets the box-shadow. // Ref: https://github.com/wbkd/react-flow/blob/main/packages/core/src/components/Nodes/wrapNode.tsx#L187 -.reactflow-workspace .react-flow__node-customComponent.selected { +.reactflow-workspace .react-flow__node-custom.selected { box-shadow: 0 0 2px 2px rgba(0, 0, 0, 0.5); border-radius: 5px; &:focus { diff --git a/public/pages/workflow_detail/workspace/resizable_workspace.tsx b/public/pages/workflow_detail/workspace/resizable_workspace.tsx index b827fdc9..11f93956 100644 --- a/public/pages/workflow_detail/workspace/resizable_workspace.tsx +++ b/public/pages/workflow_detail/workspace/resizable_workspace.tsx @@ -3,15 +3,17 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React, { useRef, useState, useEffect, useContext } from 'react'; +import React, { useRef, useState, useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import { useOnSelectionChange } from 'reactflow'; +import { ReactFlowProvider, useReactFlow } from 'reactflow'; import { Form, Formik } from 'formik'; import * as yup from 'yup'; import { cloneDeep } from 'lodash'; import { EuiButton, EuiCallOut, + EuiFlexGroup, + EuiFlexItem, EuiPageHeader, EuiResizableContainer, } from '@elastic/eui'; @@ -28,11 +30,14 @@ import { WorkspaceFlowState, toTemplateFlows, } from '../../../../common'; -import { AppState, removeDirty, setDirty, rfContext } from '../../../store'; +import { AppState, removeDirty, setDirty } from '../../../store'; import { Workspace } from './workspace'; import { ComponentDetails } from '../component_details'; import { processNodes, saveWorkflow } from '../utils'; +// styling +import './workspace-styles.scss'; + interface ResizableWorkspaceProps { isNewWorkflow: boolean; workflow?: Workflow; @@ -77,29 +82,30 @@ export function ResizableWorkspace(props: ResizableWorkspaceProps) { }; // Selected component state - const { reactFlowInstance } = useContext(rfContext); + const reactFlowInstance = useReactFlow(); const [selectedComponent, setSelectedComponent] = useState< ReactFlowComponent >(); /** - * Hook provided by reactflow to listen on when nodes are selected / de-selected. + * Custom listener on when nodes are selected / de-selected. Passed to + * downstream ReactFlow components you can listen using + * the out-of-the-box useOnSelectionChange hook. * - populate panel content appropriately * - open the panel if a node is selected and the panel is closed * - it is assumed that only one node can be selected at once */ - useOnSelectionChange({ - onChange: ({ nodes, edges }) => { - if (nodes && nodes.length > 0) { - setSelectedComponent(nodes[0]); - if (!isDetailsPanelOpen) { - onToggleChange(); - } - } else { - setSelectedComponent(undefined); + // TODO: make more typesafe + function onSelectionChange({ nodes, edges }) { + if (nodes && nodes.length > 0) { + setSelectedComponent(nodes[0]); + if (!isDetailsPanelOpen) { + onToggleChange(); } - }, - }); + } else { + setSelectedComponent(undefined); + } + } // Hook to update the workflow's flow state, if applicable. It may not exist if // it is a backend-only-created workflow, or a new, unsaved workflow. If so, @@ -290,13 +296,26 @@ export function ResizableWorkspace(props: ResizableWorkspaceProps) { minSize="50%" paddingSize="s" > - + + + + + + + void; + id: string; + // TODO: make more typesafe + onSelectionChange: ({ nodes, edges }) => void; } -const nodeTypes = { customComponent: WorkspaceComponent }; +const nodeTypes = { + custom: WorkspaceComponent, + ingestGroup: IngestGroupComponent, + searchGroup: SearchGroupComponent, +}; const edgeTypes = { customEdge: DeletableEdge }; export function Workspace(props: WorkspaceProps) { const dispatch = useDispatch(); - const reactFlowWrapper = useRef(null); - const { reactFlowInstance, setReactFlowInstance } = useContext(rfContext); + // ReactFlow state + const reactFlowWrapper = useRef(null); + const reactFlowInstance = useReactFlow(); const [nodes, setNodes, onNodesChange] = useNodesState([]); const [edges, setEdges, onEdgesChange] = useEdgesState([]); @@ -56,6 +68,14 @@ export function Workspace(props: WorkspaceProps) { props.onNodesChange(nodes); }, [nodesLength]); + /** + * Hook provided by reactflow to listen on when nodes are selected / de-selected. + * Trigger the callback fn to propagate changes to parent components. + */ + useOnSelectionChange({ + onChange: props.onSelectionChange, + }); + const onConnect = useCallback( (params) => { const edge = { @@ -68,53 +88,6 @@ export function Workspace(props: WorkspaceProps) { [setEdges] ); - const onDragOver = useCallback((event) => { - event.preventDefault(); - event.dataTransfer.dropEffect = 'move'; - }, []); - - const onDrop = useCallback( - (event) => { - event.preventDefault(); - // Get the node info from the event metadata - const nodeData = event.dataTransfer.getData( - 'application/reactflow' - ) as IComponent; - - // check if the dropped element is valid - if (typeof nodeData === 'undefined' || !nodeData) { - return; - } - - // Fetch bounds based on the ref'd div component, adjust as needed. - // TODO: remove hardcoded bounds and fetch from a constant somewhere - // @ts-ignore - const reactFlowBounds = reactFlowWrapper.current.getBoundingClientRect(); - // @ts-ignore - const position = reactFlowInstance.project({ - x: event.clientX - reactFlowBounds.left - 80, - y: event.clientY - reactFlowBounds.top - 90, - }); - - // 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, - type: nodeData.type, - position, - data: initComponentData(nodeData, id), - style: { - background: 'white', - }, - }; - - setNodes((nds) => nds.concat(newNode)); - dispatch(setDirty()); - }, - [reactFlowInstance] - ); - // Initialization. Set the nodes and edges to an existing workflow state, useEffect(() => { const workflow = { ...props.workflow }; @@ -129,7 +102,6 @@ export function Workspace(props: WorkspaceProps) { direction="column" gutterSize="none" justifyContent="spaceBetween" - className="workspace-panel" > {/** @@ -139,6 +111,7 @@ export function Workspace(props: WorkspaceProps) {
- + + + +

{props.data.label}

+
+
+ + + ); +} diff --git a/public/pages/workflow_detail/workspace_component/index.ts b/public/pages/workflow_detail/workspace/workspace_components/index.ts similarity index 52% rename from public/pages/workflow_detail/workspace_component/index.ts rename to public/pages/workflow_detail/workspace/workspace_components/index.ts index 6bd992e7..938fb16a 100644 --- a/public/pages/workflow_detail/workspace_component/index.ts +++ b/public/pages/workflow_detail/workspace/workspace_components/index.ts @@ -4,3 +4,5 @@ */ export { WorkspaceComponent } from './workspace_component'; +export { IngestGroupComponent } from './ingest_group_component'; +export { SearchGroupComponent } from './search_group_component'; diff --git a/public/pages/workflow_detail/workspace/workspace_components/ingest_group_component.tsx b/public/pages/workflow_detail/workspace/workspace_components/ingest_group_component.tsx new file mode 100644 index 00000000..a7ddea12 --- /dev/null +++ b/public/pages/workflow_detail/workspace/workspace_components/ingest_group_component.tsx @@ -0,0 +1,19 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { GroupComponent } from './group_component'; + +interface IngestGroupComponentProps { + data: { label: string }; +} + +/** + * A lightweight wrapper on the group component. + * Any specific additions to ingest can be specified here. + */ +export function IngestGroupComponent(props: IngestGroupComponentProps) { + return ; +} diff --git a/public/pages/workflow_detail/workspace_component/input_handle.tsx b/public/pages/workflow_detail/workspace/workspace_components/input_handle.tsx similarity index 76% rename from public/pages/workflow_detail/workspace_component/input_handle.tsx rename to public/pages/workflow_detail/workspace/workspace_components/input_handle.tsx index 33407d9e..aac57ffe 100644 --- a/public/pages/workflow_detail/workspace_component/input_handle.tsx +++ b/public/pages/workflow_detail/workspace/workspace_components/input_handle.tsx @@ -3,12 +3,11 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React, { useState, useRef, useEffect, useContext } from 'react'; -import { Connection, Handle, Position } from 'reactflow'; +import React, { useState, useRef, useEffect } from 'react'; +import { Connection, Handle, Position, useReactFlow } from 'reactflow'; import { EuiText } from '@elastic/eui'; -import { IComponent, IComponentInput } from '../../../component_types'; +import { IComponent, IComponentInput } from '../../../../component_types'; import { calculateHandlePosition, isValidConnection } from './utils'; -import { rfContext } from '../../../store'; interface InputHandleProps { data: IComponent; @@ -17,7 +16,7 @@ interface InputHandleProps { export function InputHandle(props: InputHandleProps) { const ref = useRef(null); - const { reactFlowInstance } = useContext(rfContext); + const reactFlowInstance = useReactFlow(); const [position, setPosition] = useState(0); useEffect(() => { diff --git a/public/pages/workflow_detail/workspace_component/new_or_existing_tabs.tsx b/public/pages/workflow_detail/workspace/workspace_components/new_or_existing_tabs.tsx similarity index 100% rename from public/pages/workflow_detail/workspace_component/new_or_existing_tabs.tsx rename to public/pages/workflow_detail/workspace/workspace_components/new_or_existing_tabs.tsx diff --git a/public/pages/workflow_detail/workspace_component/output_handle.tsx b/public/pages/workflow_detail/workspace/workspace_components/output_handle.tsx similarity index 77% rename from public/pages/workflow_detail/workspace_component/output_handle.tsx rename to public/pages/workflow_detail/workspace/workspace_components/output_handle.tsx index b19d2d61..c8276b60 100644 --- a/public/pages/workflow_detail/workspace_component/output_handle.tsx +++ b/public/pages/workflow_detail/workspace/workspace_components/output_handle.tsx @@ -3,12 +3,11 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React, { useState, useRef, useEffect, useContext } from 'react'; -import { Connection, Handle, Position } from 'reactflow'; +import React, { useState, useRef, useEffect } from 'react'; +import { Connection, Handle, Position, useReactFlow } from 'reactflow'; import { EuiText } from '@elastic/eui'; -import { IComponent, IComponentOutput } from '../../../component_types'; +import { IComponent, IComponentOutput } from '../../../../component_types'; import { calculateHandlePosition, isValidConnection } from './utils'; -import { rfContext } from '../../../store'; interface OutputHandleProps { data: IComponent; @@ -17,7 +16,7 @@ interface OutputHandleProps { export function OutputHandle(props: OutputHandleProps) { const ref = useRef(null); - const { reactFlowInstance } = useContext(rfContext); + const reactFlowInstance = useReactFlow(); const [position, setPosition] = useState(0); const outputClasses = props.output.baseClasses.join('|'); diff --git a/public/pages/workflow_detail/workspace/workspace_components/search_group_component.tsx b/public/pages/workflow_detail/workspace/workspace_components/search_group_component.tsx new file mode 100644 index 00000000..925a845e --- /dev/null +++ b/public/pages/workflow_detail/workspace/workspace_components/search_group_component.tsx @@ -0,0 +1,19 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { GroupComponent } from './group_component'; + +interface SearchGroupComponentProps { + data: { label: string }; +} + +/** + * A lightweight wrapper on the group component. + * Any specific additions to search can be specified here. + */ +export function SearchGroupComponent(props: SearchGroupComponentProps) { + return ; +} diff --git a/public/pages/workflow_detail/workspace_component/utils.ts b/public/pages/workflow_detail/workspace/workspace_components/utils.ts similarity index 97% rename from public/pages/workflow_detail/workspace_component/utils.ts rename to public/pages/workflow_detail/workspace/workspace_components/utils.ts index fc424e8b..9f8d1715 100644 --- a/public/pages/workflow_detail/workspace_component/utils.ts +++ b/public/pages/workflow_detail/workspace/workspace_components/utils.ts @@ -4,7 +4,7 @@ */ import { Connection, ReactFlowInstance } from 'reactflow'; -import { IComponentInput } from '../../../../common'; +import { IComponentInput } from '../../../../../common'; /** * Collection of utility functions for the workspace component diff --git a/public/pages/workflow_detail/workspace_component/workspace_component.tsx b/public/pages/workflow_detail/workspace/workspace_components/workspace_component.tsx similarity index 66% rename from public/pages/workflow_detail/workspace_component/workspace_component.tsx rename to public/pages/workflow_detail/workspace/workspace_components/workspace_component.tsx index 20d4953c..6822592c 100644 --- a/public/pages/workflow_detail/workspace_component/workspace_component.tsx +++ b/public/pages/workflow_detail/workspace/workspace_components/workspace_component.tsx @@ -3,19 +3,23 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React, { useContext } from 'react'; +import React from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiCard, EuiText, EuiTitle, - EuiButtonIcon, } from '@elastic/eui'; -import { rfContext } from '../../../store'; -import { IComponentData } from '../../../component_types'; +import { setDirty } from '../../../../store'; +import { IComponentData } from '../../../../component_types'; import { InputHandle } from './input_handle'; import { OutputHandle } from './output_handle'; +import { Edge, useReactFlow } from 'reactflow'; +import { useDispatch } from 'react-redux'; + +// styling +import '../../workspace/reactflow-styles.scss'; interface WorkspaceComponentProps { data: IComponentData; @@ -27,11 +31,29 @@ interface WorkspaceComponentProps { * As users interact with it (input data, add connections), the stored IComponent data will update. */ export function WorkspaceComponent(props: WorkspaceComponentProps) { + const dispatch = useDispatch(); const component = props.data; - const { deleteNode } = useContext(rfContext); + const reactFlowInstance = useReactFlow(); + + // TODO: re-enable deletion + const deleteNode = (nodeId: string) => { + 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()); + }; return ( @@ -41,13 +63,16 @@ export function WorkspaceComponent(props: WorkspaceComponentProps) { - { deleteNode(component.id); }} aria-label="Delete" - /> + /> */} } diff --git a/public/pages/workflow_detail/workspace_edge/deletable-edge-styles.scss b/public/pages/workflow_detail/workspace/workspace_edge/deletable-edge-styles.scss similarity index 100% rename from public/pages/workflow_detail/workspace_edge/deletable-edge-styles.scss rename to public/pages/workflow_detail/workspace/workspace_edge/deletable-edge-styles.scss diff --git a/public/pages/workflow_detail/workspace_edge/deletable_edge.tsx b/public/pages/workflow_detail/workspace/workspace_edge/deletable_edge.tsx similarity index 82% rename from public/pages/workflow_detail/workspace_edge/deletable_edge.tsx rename to public/pages/workflow_detail/workspace/workspace_edge/deletable_edge.tsx index 692afddd..81ca6063 100644 --- a/public/pages/workflow_detail/workspace_edge/deletable_edge.tsx +++ b/public/pages/workflow_detail/workspace/workspace_edge/deletable_edge.tsx @@ -3,17 +3,20 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React, { useContext } from 'react'; +import React from 'react'; import { BaseEdge, + Edge, EdgeLabelRenderer, EdgeProps, getBezierPath, + useReactFlow, } from 'reactflow'; -import { rfContext } from '../../../store'; +import { setDirty } from '../../../../store'; // styling import './deletable-edge-styles.scss'; +import { useDispatch } from 'react-redux'; type DeletableEdgeProps = EdgeProps; @@ -23,6 +26,8 @@ type DeletableEdgeProps = EdgeProps; * see https://reactflow.dev/docs/examples/edges/edge-types/ */ export function DeletableEdge(props: DeletableEdgeProps) { + const dispatch = useDispatch(); + const [edgePath, labelX, labelY] = getBezierPath({ sourceX: props.sourceX, sourceY: props.sourceY, @@ -32,7 +37,14 @@ export function DeletableEdge(props: DeletableEdgeProps) { targetPosition: props.targetPosition, }); - const { deleteEdge } = useContext(rfContext); + const reactFlowInstance = useReactFlow(); + + const deleteEdge = (edgeId: string) => { + reactFlowInstance.setEdges( + reactFlowInstance.getEdges().filter((edge: Edge) => edge.id !== edgeId) + ); + dispatch(setDirty()); + }; const onEdgeClick = (event: any, edgeId: string) => { // Prevent this event from bubbling up and putting reactflow into an unexpected state. @@ -52,6 +64,7 @@ export function DeletableEdge(props: DeletableEdgeProps) { transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`, fontSize: 12, pointerEvents: 'all', + zIndex: 1, }} className="nodrag nopan" > diff --git a/public/pages/workflow_detail/workspace_edge/index.ts b/public/pages/workflow_detail/workspace/workspace_edge/index.ts similarity index 100% rename from public/pages/workflow_detail/workspace_edge/index.ts rename to public/pages/workflow_detail/workspace/workspace_edge/index.ts diff --git a/public/render_app.tsx b/public/render_app.tsx index e1618fb8..279d4325 100644 --- a/public/render_app.tsx +++ b/public/render_app.tsx @@ -9,7 +9,7 @@ import { BrowserRouter as Router, Route } from 'react-router-dom'; import { Provider } from 'react-redux'; import { AppMountParameters, CoreStart } from '../../../src/core/public'; import { FlowFrameworkDashboardsApp } from './app'; -import { store, ReactFlowContextProvider } from './store'; +import { store } from './store'; export const renderApp = ( coreStart: CoreStart, @@ -17,13 +17,9 @@ export const renderApp = ( ) => { ReactDOM.render( - - - } - /> - - + + } /> + , element ); diff --git a/public/store/context/index.ts b/public/store/context/index.ts deleted file mode 100644 index 1584425e..00000000 --- a/public/store/context/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -export * from './react_flow_context_provider'; diff --git a/public/store/context/react_flow_context_provider.tsx b/public/store/context/react_flow_context_provider.tsx deleted file mode 100644 index 1b10eade..00000000 --- a/public/store/context/react_flow_context_provider.tsx +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import React, { createContext, useState } from 'react'; -import { useDispatch } from 'react-redux'; -import { Edge, Node } from 'reactflow'; -import { setDirty } from '../reducers'; - -const initialValues = { - reactFlowInstance: null, - setReactFlowInstance: () => {}, - deleteNode: (nodeId: string) => {}, - deleteEdge: (edgeId: string) => {}, -}; - -export const rfContext = createContext(initialValues); - -/** - * This returns a provider from the rfContext context created above. The initial - * values are set so any nested components can use useContext to access these - * values. - * - * This is how we can manage ReactFlow context consistently across the various - * nested child components. - */ -export function ReactFlowContextProvider({ children }: any) { - const dispatch = useDispatch(); - const [reactFlowInstance, setReactFlowInstance] = useState(null); - - const deleteNode = (nodeId: string) => { - 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) => { - reactFlowInstance.setEdges( - reactFlowInstance.getEdges().filter((edge: Edge) => edge.id !== edgeId) - ); - dispatch(setDirty()); - }; - - return ( - - {children} - - ); -} diff --git a/public/store/index.ts b/public/store/index.ts index 45aab826..ccc2465d 100644 --- a/public/store/index.ts +++ b/public/store/index.ts @@ -5,4 +5,3 @@ export * from './store'; export * from './reducers'; -export * from './context'; diff --git a/public/utils/constants.ts b/public/utils/constants.ts index eb078018..e7f9f800 100644 --- a/public/utils/constants.ts +++ b/public/utils/constants.ts @@ -32,6 +32,12 @@ export enum COMPONENT_CATEGORY { SEARCH = 'Search', } +export enum NODE_CATEGORY { + CUSTOM = 'custom', + INGEST_GROUP = 'ingestGroup', + SEARCH_GROUP = 'searchGroup', +} + // TODO: subject to change /** * A base set of component classes / types.