Skip to content

Commit

Permalink
Set up initial form state from resizable_workspace
Browse files Browse the repository at this point in the history
Signed-off-by: Tyler Ohlsen <[email protected]>
  • Loading branch information
ohltyler committed Oct 17, 2023
1 parent 00c8439 commit 95ffd20
Show file tree
Hide file tree
Showing 11 changed files with 147 additions and 99 deletions.
9 changes: 5 additions & 4 deletions public/component_types/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { FormikValues } from 'formik';
import { COMPONENT_CATEGORY, COMPONENT_CLASS } from '../utils';

/**
Expand All @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -64,36 +53,20 @@ export function ComponentDetails(props: ComponentDetailsProps) {
});

return (
<Formik initialValues={{}} onSubmit={() => {}}>
<Form>
<EuiFlexGroup
direction="column"
gutterSize="none"
className="workspace-panel"
>
<EuiFlexItem className="resizable-panel-border">
<EuiPanel paddingSize="m">
{selectedComponent ? (
<ComponentInputs selectedComponent={selectedComponent} />
) : (
<EuiEmptyPrompt
iconType={'cross'}
title={<h2>No component selected</h2>}
titleSize="s"
body={
<>
<EuiText>
Add a component, or select a component to view or edit
its configuration.
</EuiText>
</>
}
/>
)}
</EuiPanel>
</EuiFlexItem>
</EuiFlexGroup>
</Form>
</Formik>
<EuiFlexGroup
direction="column"
gutterSize="none"
className="workspace-panel"
>
<EuiFlexItem className="resizable-panel-border">
<EuiPanel paddingSize="m">
{selectedComponent ? (
<ComponentInputs selectedComponent={selectedComponent} />
) : (
<EmptyComponentInputs />
)}
</EuiPanel>
</EuiFlexItem>
</EuiFlexGroup>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export function ComponentInputs(props: ComponentInputsProps) {
<h2>{props.selectedComponent.data.label || ''}</h2>
</EuiTitle>
<EuiSpacer size="s" />
<InputFieldList inputFields={props.selectedComponent.data.fields || []} />
<InputFieldList selectedComponent={props.selectedComponent} />
</>
);
}
Original file line number Diff line number Diff line change
@@ -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 (
<EuiEmptyPrompt
iconType={'cross'}
title={<h2>No component selected</h2>}
titleSize="s"
body={
<>
<EuiText>
Add a component, or select a component to view or edit its
configuration.
</EuiText>
</>
}
/>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,31 @@

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
* render based on the input type.
*/

interface InputFieldListProps {
inputFields?: IComponentField[];
selectedComponent: ReactFlowComponent;
}

export function InputFieldList(props: InputFieldListProps) {
const inputFields = props.selectedComponent.data.fields || [];
return (
<EuiFlexItem grow={false}>
{props.inputFields?.map((field, idx) => {
{inputFields.map((field, idx) => {
let el;
switch (field.type) {
case 'string': {
el = (
<EuiFlexItem key={idx}>
<TextField
label={field.label}
placeholder={field.placeholder || ''}
field={field}
componentId={props.selectedComponent.id}
/>
<EuiSpacer size="s" />
</EuiFlexItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,32 @@
*/

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;
}

/**
* An input field for a component where users input plaintext
*/
export function TextField(props: TextFieldProps) {
return (
<EuiFieldText
prepend={props.label}
compressed={false}
placeholder={props.placeholder || ''}
/>
<Field name={`${props.componentId}.${props.field.name}`}>
{({ field, form }: FieldProps) => {
return (
<EuiFormRow label={props.field.label}>
<EuiFieldText
placeholder={props.field.placeholder || ''}
compressed={false}
{...field}
/>
</EuiFormRow>
);
}}
</Field>
);
}
103 changes: 69 additions & 34 deletions public/pages/workflow_detail/workspace/resizable_workspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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<boolean>(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<WorkspaceFormValues>({});

// 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 (
<EuiResizableContainer
direction="horizontal"
style={{ marginLeft: '-14px' }}
<Formik
enableReinitialize={true}
initialValues={formValues}
onSubmit={(values) => {
console.log('values on submit: ', values);
}}
>
{(EuiResizablePanel, EuiResizableButton, { togglePanel }) => {
if (togglePanel) {
collapseFn.current = (panelId: string, { direction }) =>
togglePanel(panelId, { direction });
}
{(formikProps) => (
<Form>
<EuiResizableContainer
direction="horizontal"
style={{ marginLeft: '-14px' }}
>
{(EuiResizablePanel, EuiResizableButton, { togglePanel }) => {
if (togglePanel) {
collapseFn.current = (panelId: string, { direction }) =>
togglePanel(panelId, { direction });
}

return (
<ReactFlowProvider>
<EuiResizablePanel mode="main" initialSize={75} minSize="50%">
<Workspace workflow={props.workflow} />
</EuiResizablePanel>
<EuiResizableButton />
<EuiResizablePanel
id={COMPONENT_DETAILS_PANEL_ID}
mode="collapsible"
initialSize={25}
minSize="10%"
onToggleCollapsedInternal={() => onToggleChange()}
>
<ComponentDetails
onToggleChange={onToggleChange}
isOpen={isOpen}
/>
</EuiResizablePanel>
</ReactFlowProvider>
);
}}
</EuiResizableContainer>
return (
<ReactFlowProvider>
<EuiResizablePanel mode="main" initialSize={75} minSize="50%">
<Workspace workflow={props.workflow} />
</EuiResizablePanel>
<EuiResizableButton />
<EuiResizablePanel
id={COMPONENT_DETAILS_PANEL_ID}
mode="collapsible"
initialSize={25}
minSize="10%"
onToggleCollapsedInternal={() => onToggleChange()}
>
<ComponentDetails
onToggleChange={onToggleChange}
isOpen={isOpen}
/>
</EuiResizablePanel>
</ReactFlowProvider>
);
}}
</EuiResizableContainer>
<EuiButton onClick={() => formikProps.handleSubmit()}>
Submit
</EuiButton>
</Form>
)}
</Formik>
);
}
2 changes: 2 additions & 0 deletions public/pages/workflow_detail/workspace/workspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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());
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
/>
Expand Down
1 change: 0 additions & 1 deletion public/store/context/react_flow_context_provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ export function ReactFlowContextProvider({ children }: any) {
(edge: Edge) => edge.source !== nodeId && edge.target !== nodeId
)
);

dispatch(setDirty());
};

Expand Down
2 changes: 1 addition & 1 deletion public/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
Expand Down

0 comments on commit 95ffd20

Please sign in to comment.