diff --git a/public/component_types/interfaces.ts b/public/component_types/interfaces.ts index cc373b2f..bff6d22d 100644 --- a/public/component_types/interfaces.ts +++ b/public/component_types/interfaces.ts @@ -3,6 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { FormikValues } from 'formik'; import { COMPONENT_CATEGORY, COMPONENT_CLASS } from '../utils'; /** @@ -13,10 +14,10 @@ export type FieldType = 'string' | 'json' | 'select'; // TODO: this may expand to more types in the future. Formik supports 'any' so we can too. // For now, limiting scope to expected types. export type FieldValue = string | {}; - -/** - * ************ Base interfaces **************** - */ +export type ComponentFormValues = FormikValues; +export type WorkspaceFormValues = { + [componentId: string]: ComponentFormValues; +}; /** * Represents a single base class as an input handle for a component. diff --git a/public/pages/workflow_detail/component_details/component_details.tsx b/public/pages/workflow_detail/component_details/component_details.tsx index ab3f37a8..bd4a9016 100644 --- a/public/pages/workflow_detail/component_details/component_details.tsx +++ b/public/pages/workflow_detail/component_details/component_details.tsx @@ -5,23 +5,14 @@ import React, { useState, useContext } from 'react'; import { useOnSelectionChange } from 'reactflow'; -import { - EuiFlexGroup, - EuiFlexItem, - EuiSpacer, - EuiPanel, - EuiTitle, - EuiEmptyPrompt, - EuiText, -} from '@elastic/eui'; -import { Formik, Field, Form, FormikHelpers } from 'formik'; +import { EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui'; import { ReactFlowComponent } from '../../../../common'; import { rfContext } from '../../../store'; -import { InputFieldList } from './input_field_list'; +import { ComponentInputs } from './component_inputs'; +import { EmptyComponentInputs } from './empty_component_inputs'; // styling import '../workspace/workspace-styles.scss'; -import { ComponentInputs } from './component_inputs'; interface ComponentDetailsProps { onToggleChange: () => void; @@ -42,8 +33,6 @@ export function ComponentDetails(props: ComponentDetailsProps) { ReactFlowComponent >(); - const data = selectedComponent?.data; - /** * Hook provided by reactflow to listen on when nodes are selected / de-selected. * - populate panel content appropriately @@ -64,36 +53,20 @@ export function ComponentDetails(props: ComponentDetailsProps) { }); return ( - {}}> -
- - - - {selectedComponent ? ( - - ) : ( - No component selected} - titleSize="s" - body={ - <> - - Add a component, or select a component to view or edit - its configuration. - - - } - /> - )} - - - -
-
+ + + + {selectedComponent ? ( + + ) : ( + + )} + + + ); } diff --git a/public/pages/workflow_detail/component_details/component_inputs.tsx b/public/pages/workflow_detail/component_details/component_inputs.tsx index e763d9c5..bed3ad72 100644 --- a/public/pages/workflow_detail/component_details/component_inputs.tsx +++ b/public/pages/workflow_detail/component_details/component_inputs.tsx @@ -19,7 +19,7 @@ export function ComponentInputs(props: ComponentInputsProps) {

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

- + ); } diff --git a/public/pages/workflow_detail/component_details/empty_component_inputs.tsx b/public/pages/workflow_detail/component_details/empty_component_inputs.tsx new file mode 100644 index 00000000..18a19880 --- /dev/null +++ b/public/pages/workflow_detail/component_details/empty_component_inputs.tsx @@ -0,0 +1,25 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { EuiEmptyPrompt, EuiText } from '@elastic/eui'; + +export function EmptyComponentInputs() { + return ( + No component selected} + titleSize="s" + body={ + <> + + Add a component, or select a component to view or edit its + configuration. + + + } + /> + ); +} diff --git a/public/pages/workflow_detail/component_details/input_field_list.tsx b/public/pages/workflow_detail/component_details/input_field_list.tsx index e37979e6..906e9620 100644 --- a/public/pages/workflow_detail/component_details/input_field_list.tsx +++ b/public/pages/workflow_detail/component_details/input_field_list.tsx @@ -5,8 +5,8 @@ import React from 'react'; import { EuiFlexItem, EuiSpacer } from '@elastic/eui'; -import { IComponentField } from '../../../component_types'; import { TextField, JsonField, SelectField } from './input_fields'; +import { ReactFlowComponent } from '../../../../common'; /** * A helper component to format all of the input fields for a component. Dynamically @@ -14,21 +14,22 @@ import { TextField, JsonField, SelectField } from './input_fields'; */ interface InputFieldListProps { - inputFields?: IComponentField[]; + selectedComponent: ReactFlowComponent; } export function InputFieldList(props: InputFieldListProps) { + const inputFields = props.selectedComponent.data.fields || []; return ( - {props.inputFields?.map((field, idx) => { + {inputFields.map((field, idx) => { let el; switch (field.type) { case 'string': { el = ( diff --git a/public/pages/workflow_detail/component_details/input_fields/text_field.tsx b/public/pages/workflow_detail/component_details/input_fields/text_field.tsx index b22635db..ad92d5ca 100644 --- a/public/pages/workflow_detail/component_details/input_fields/text_field.tsx +++ b/public/pages/workflow_detail/component_details/input_fields/text_field.tsx @@ -4,11 +4,13 @@ */ import React from 'react'; -import { EuiFieldText } from '@elastic/eui'; +import { Field, FieldProps } from 'formik'; +import { EuiFieldText, EuiFormRow } from '@elastic/eui'; +import { IComponentField } from '../../../../../common'; interface TextFieldProps { - label: string; - placeholder: string; + field: IComponentField; + componentId: string; } /** @@ -16,10 +18,18 @@ interface TextFieldProps { */ export function TextField(props: TextFieldProps) { return ( - + + {({ field, form }: FieldProps) => { + return ( + + + + ); + }} + ); } diff --git a/public/pages/workflow_detail/workspace/resizable_workspace.tsx b/public/pages/workflow_detail/workspace/resizable_workspace.tsx index fb712e70..66415a91 100644 --- a/public/pages/workflow_detail/workspace/resizable_workspace.tsx +++ b/public/pages/workflow_detail/workspace/resizable_workspace.tsx @@ -3,10 +3,15 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React, { useRef, useState } from 'react'; +import React, { useRef, useState, useEffect } from 'react'; import { ReactFlowProvider } from 'reactflow'; -import { EuiResizableContainer } from '@elastic/eui'; -import { Workflow } from '../../../../common'; +import { Form, Formik } from 'formik'; +import { EuiButton, EuiResizableContainer } from '@elastic/eui'; +import { + Workflow, + WorkspaceFormValues, + componentDataToFormik, +} from '../../../../common'; import { Workspace } from './workspace'; import { ComponentDetails } from '../component_details'; @@ -21,48 +26,78 @@ const COMPONENT_DETAILS_PANEL_ID = 'component_details_panel_id'; * panels - the ReactFlow workspace panel and the selected component details panel. */ export function ResizableWorkspace(props: ResizableWorkspaceProps) { + // Component details side panel state const [isOpen, setIsOpen] = useState(true); const collapseFn = useRef( (id: string, options: { direction: 'left' | 'right' }) => {} ); - const onToggleChange = () => { collapseFn.current(COMPONENT_DETAILS_PANEL_ID, { direction: 'left' }); setIsOpen(!isOpen); }; + // Formik form state + const [formValues, setFormValues] = useState({}); + + // Initialize the form state to an existing workflow, if applicable. + useEffect(() => { + if (props.workflow?.workspaceFlowState) { + const nodes = props.workflow.workspaceFlowState.nodes; + const initFormValues = {} as WorkspaceFormValues; + nodes.forEach((node) => { + initFormValues[node.id] = componentDataToFormik(node.data); + }); + setFormValues(initFormValues); + } + }, [props.workflow]); + return ( - { + console.log('values on submit: ', values); + }} > - {(EuiResizablePanel, EuiResizableButton, { togglePanel }) => { - if (togglePanel) { - collapseFn.current = (panelId: string, { direction }) => - togglePanel(panelId, { direction }); - } + {(formikProps) => ( +
+ + {(EuiResizablePanel, EuiResizableButton, { togglePanel }) => { + if (togglePanel) { + collapseFn.current = (panelId: string, { direction }) => + togglePanel(panelId, { direction }); + } - return ( - - - - - - onToggleChange()} - > - - - - ); - }} - + return ( + + + + + + onToggleChange()} + > + + + + ); + }} + + formikProps.handleSubmit()}> + Submit + +
+ )} + ); } diff --git a/public/pages/workflow_detail/workspace/workspace.tsx b/public/pages/workflow_detail/workspace/workspace.tsx index a3d7b5b6..f58f077d 100644 --- a/public/pages/workflow_detail/workspace/workspace.tsx +++ b/public/pages/workflow_detail/workspace/workspace.tsx @@ -95,6 +95,8 @@ export function Workspace(props: WorkspaceProps) { }, }; + // TODO: on node addition, need to update the formik initial state to include this + // new component. setNodes((nds) => nds.concat(newNode)); dispatch(setDirty()); }, diff --git a/public/pages/workflow_detail/workspace_component/workspace_component.tsx b/public/pages/workflow_detail/workspace_component/workspace_component.tsx index 20d4953c..59041464 100644 --- a/public/pages/workflow_detail/workspace_component/workspace_component.tsx +++ b/public/pages/workflow_detail/workspace_component/workspace_component.tsx @@ -45,6 +45,8 @@ export function WorkspaceComponent(props: WorkspaceComponentProps) { iconType="trash" onClick={() => { deleteNode(component.id); + // TODO: update the form to remove this node based on node ID. + // Can fetch & update the values using useFormikContext(). }} aria-label="Delete" /> diff --git a/public/store/context/react_flow_context_provider.tsx b/public/store/context/react_flow_context_provider.tsx index 8268a0c9..1b10eade 100644 --- a/public/store/context/react_flow_context_provider.tsx +++ b/public/store/context/react_flow_context_provider.tsx @@ -41,7 +41,6 @@ export function ReactFlowContextProvider({ children }: any) { (edge: Edge) => edge.source !== nodeId && edge.target !== nodeId ) ); - dispatch(setDirty()); }; diff --git a/public/utils/utils.ts b/public/utils/utils.ts index bb79dcca..ac352951 100644 --- a/public/utils/utils.ts +++ b/public/utils/utils.ts @@ -35,7 +35,7 @@ export function initComponentData( // Converting stored values in component data to initial formik values export function componentDataToFormik(data: IComponentData): FormikValues { - let formikValues = {} as FormikValues; + const formikValues = {} as FormikValues; data.fields?.forEach((field) => { formikValues[field.name] = field.value || getInitialValue(field.type); });