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 ? (
+
+ ) : (
+
+ )}
+
+
+
);
}
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) => (
+
+ 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);
});