From f17cb53e4ce095caa666f37159648051d06a461c Mon Sep 17 00:00:00 2001 From: Tyler Ohlsen Date: Tue, 29 Oct 2024 11:57:55 -0700 Subject: [PATCH] Add interim state besides existing index option Signed-off-by: Tyler Ohlsen --- common/interfaces.ts | 10 +- .../ingest_inputs/source_data.tsx | 6 +- .../ingest_inputs/source_data_modal.tsx | 290 +++++++++++------- .../input_fields/json_field.tsx | 4 +- .../input_fields/model_field.tsx | 4 +- .../input_fields/number_field.tsx | 4 +- .../input_fields/select_field.tsx | 4 +- .../select_with_custom_options.tsx | 4 +- .../input_fields/text_field.tsx | 4 +- public/utils/config_to_schema_utils.ts | 2 +- 10 files changed, 202 insertions(+), 130 deletions(-) diff --git a/common/interfaces.ts b/common/interfaces.ts index b7eb7781..6f13aa71 100644 --- a/common/interfaces.ts +++ b/common/interfaces.ts @@ -67,9 +67,6 @@ export type IndexConfig = { settings: IConfigField; }; -// TODO: may expand to just IndexConfig (including mappings/settings info) -// if we want to persist this for users using some existing index, -// and want to pass that index config around. export type SearchIndexConfig = { name: IConfigField; }; @@ -113,6 +110,13 @@ export type WorkflowSchemaObj = { }; export type WorkflowSchema = ObjectSchema; +// Form / schema interfaces for the ingest docs sub-form +export type IngestDocsFormValues = { + docs: FormikValues; +}; +export type IngestDocsSchemaObj = WorkflowSchemaObj; +export type IngestDocsSchema = WorkflowSchema; + /** ********** WORKSPACE TYPES/INTERFACES ********** */ diff --git a/public/pages/workflow_detail/workflow_inputs/ingest_inputs/source_data.tsx b/public/pages/workflow_detail/workflow_inputs/ingest_inputs/source_data.tsx index 85031145..76cdc92e 100644 --- a/public/pages/workflow_detail/workflow_inputs/ingest_inputs/source_data.tsx +++ b/public/pages/workflow_detail/workflow_inputs/ingest_inputs/source_data.tsx @@ -22,7 +22,7 @@ import { SearchHit, Workflow, WorkflowConfig, - WorkspaceFormValues, + WorkflowFormValues, customStringify, isVectorSearchUseCase, toFormattedDate, @@ -44,7 +44,7 @@ interface SourceDataProps { export function SourceData(props: SourceDataProps) { const dispatch = useAppDispatch(); const dataSourceId = getDataSourceId(); - const { values, setFieldValue } = useFormikContext(); + const { values, setFieldValue } = useFormikContext(); // empty/populated docs state let docs = []; @@ -236,7 +236,7 @@ export function SourceData(props: SourceDataProps) { // only be executed for workflows coming from preset vector search use cases. function getProcessorInfo( uiConfig: WorkflowConfig, - values: WorkspaceFormValues + values: WorkflowFormValues ): { processorId: string | undefined; inputMapEntry: MapEntry | undefined; diff --git a/public/pages/workflow_detail/workflow_inputs/ingest_inputs/source_data_modal.tsx b/public/pages/workflow_detail/workflow_inputs/ingest_inputs/source_data_modal.tsx index 7948892b..32287bed 100644 --- a/public/pages/workflow_detail/workflow_inputs/ingest_inputs/source_data_modal.tsx +++ b/public/pages/workflow_detail/workflow_inputs/ingest_inputs/source_data_modal.tsx @@ -3,9 +3,10 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import { useSelector } from 'react-redux'; -import { useFormikContext } from 'formik'; +import { Formik, getIn, useFormikContext } from 'formik'; +import * as yup from 'yup'; import { EuiSmallButton, EuiCompressedFilePicker, @@ -22,8 +23,14 @@ import { EuiCompressedSuperSelect, } from '@elastic/eui'; import { JsonField } from '../input_fields'; -import { SOURCE_OPTIONS, WorkspaceFormValues } from '../../../../../common'; +import { + IConfigField, + IngestDocsFormValues, + SOURCE_OPTIONS, + WorkflowFormValues, +} from '../../../../../common'; import { AppState } from '../../../../store'; +import { getFieldSchema, getInitialValue } from '../../../../utils'; interface SourceDataProps { selectedOption: SOURCE_OPTIONS; @@ -37,119 +44,180 @@ interface SourceDataProps { * Modal for configuring the source data for ingest. */ export function SourceDataModal(props: SourceDataProps) { - const { setFieldValue } = useFormikContext(); + const { values, setFieldValue } = useFormikContext(); const indices = useSelector((state: AppState) => state.opensearch.indices); - // files state. when a file is read, update the form value. - const fileReader = new FileReader(); - fileReader.onload = (e) => { - if (e.target) { - setFieldValue('ingest.docs', e.target.result); - } - }; + // sub-form values/schema + const docsFormValues = { + docs: getInitialValue('jsonArray'), + } as IngestDocsFormValues; + const docsFormSchema = yup.object({ + docs: getFieldSchema({ + type: 'jsonArray', + } as IConfigField), + }); + + // persist standalone values. update when there is changes detected to the parent form + const [tempDocs, setTempDocs] = useState('[]'); + useEffect(() => { + setTempDocs(getIn(values, 'ingest.docs')); + }, [getIn(values, 'ingest.docs')]); + + function onClose() { + props.setIsModalOpen(false); + } + + function onUpdate() { + // 1. Update the form with the temp docs + setFieldValue('ingest.docs', tempDocs); + + props.setIsModalOpen(false); + } return ( - props.setIsModalOpen(false)} - style={{ width: '70vw' }} + {}} + validate={(values) => {}} > - - -

{`Import data`}

-
-
- - <> - - props.setSelectedOption(SOURCE_OPTIONS.MANUAL)} - data-testid="manualEditSourceDataButton" - > - Manual - - props.setSelectedOption(SOURCE_OPTIONS.UPLOAD)} - data-testid="uploadSourceDataButton" - > - Upload - - - props.setSelectedOption(SOURCE_OPTIONS.EXISTING_INDEX) - } - data-testid="selectIndexSourceDataButton" - > - Existing index - - - - {props.selectedOption === SOURCE_OPTIONS.UPLOAD && ( - <> - { - if (files && files.length > 0) { - fileReader.readAsText(files[0]); - } - }} - display="default" - /> - - - )} - {props.selectedOption === SOURCE_OPTIONS.EXISTING_INDEX && ( - <> - - Up to 5 sample documents will be automatically populated. - - - - ({ - value: option.name, - inputDisplay: {option.name}, - disabled: false, - } as EuiSuperSelectOption) + {(formikProps) => { + // internal hook to loop back and update tempDocs when form changes are detected + useEffect(() => { + setTempDocs(getIn(formikProps.values, 'docs')); + }, [getIn(formikProps.values, 'docs')]); + + return ( + onClose()} style={{ width: '70vw' }}> + + +

{`Import data`}

+
+
+ + <> + + + props.setSelectedOption(SOURCE_OPTIONS.MANUAL) + } + data-testid="manualEditSourceDataButton" + > + Manual + + + props.setSelectedOption(SOURCE_OPTIONS.UPLOAD) + } + data-testid="uploadSourceDataButton" + > + Upload + + + props.setSelectedOption(SOURCE_OPTIONS.EXISTING_INDEX) + } + data-testid="selectIndexSourceDataButton" + > + Existing index + + + + {props.selectedOption === SOURCE_OPTIONS.UPLOAD && ( + <> + { + if (files && files.length > 0) { + // create a custom filereader to update form with file values + const fileReader = new FileReader(); + fileReader.onload = (e) => { + if (e.target) { + formikProps.setFieldValue( + 'docs', + e.target.result as string + ); + } + }; + fileReader.readAsText(files[0]); + } + }} + display="default" + /> + + + )} + {props.selectedOption === SOURCE_OPTIONS.EXISTING_INDEX && ( + <> + + Up to 5 sample documents will be automatically populated. + + + + ({ + value: option.name, + inputDisplay: ( + {option.name} + ), + disabled: false, + } as EuiSuperSelectOption) + )} + valueOfSelected={props.selectedIndex} + onChange={(option) => { + props.setSelectedIndex(option); + }} + isInvalid={false} + /> + + )} - valueOfSelected={props.selectedIndex} - onChange={(option) => { - props.setSelectedIndex(option); - }} - isInvalid={false} - /> - - - )} - - - - - props.setIsModalOpen(false)} - fill={false} - color="primary" - data-testid="closeSourceDataButton" - > - Close - - -
+ + +
+ + onClose()} + fill={false} + color="primary" + data-testid="cancelSourceDataButton" + > + Cancel + + onUpdate()} + fill={true} + color="primary" + data-testid="updateSourceDataButton" + > + Update + + +
+ ); + }} + ); } diff --git a/public/pages/workflow_detail/workflow_inputs/input_fields/json_field.tsx b/public/pages/workflow_detail/workflow_inputs/input_fields/json_field.tsx index ca49bd97..88c61e8d 100644 --- a/public/pages/workflow_detail/workflow_inputs/input_fields/json_field.tsx +++ b/public/pages/workflow_detail/workflow_inputs/input_fields/json_field.tsx @@ -11,7 +11,7 @@ import { EuiLink, EuiText, } from '@elastic/eui'; -import { WorkspaceFormValues, customStringify } from '../../../../../common'; +import { WorkflowFormValues, customStringify } from '../../../../../common'; import { camelCaseToTitleString } from '../../../../utils'; interface JsonFieldProps { @@ -31,7 +31,7 @@ interface JsonFieldProps { export function JsonField(props: JsonFieldProps) { const validate = props.validate !== undefined ? props.validate : true; - const { errors, touched, values } = useFormikContext(); + const { errors, touched, values } = useFormikContext(); // temp input state. only format when users click out of the code editor const [jsonStr, setJsonStr] = useState('{}'); diff --git a/public/pages/workflow_detail/workflow_inputs/input_fields/model_field.tsx b/public/pages/workflow_detail/workflow_inputs/input_fields/model_field.tsx index b87ca877..cb8ec809 100644 --- a/public/pages/workflow_detail/workflow_inputs/input_fields/model_field.tsx +++ b/public/pages/workflow_detail/workflow_inputs/input_fields/model_field.tsx @@ -18,7 +18,7 @@ import { } from '@elastic/eui'; import { MODEL_STATE, - WorkspaceFormValues, + WorkflowFormValues, ModelFormValue, IConfigField, ML_CHOOSE_MODEL_LINK, @@ -46,7 +46,7 @@ export function ModelField(props: ModelFieldProps) { // keeps re-rendering this component (and subsequently re-fetching data) as they're building flows const models = useSelector((state: AppState) => state.ml.models); - const { errors, touched, values } = useFormikContext(); + const { errors, touched, values } = useFormikContext(); // Deployed models state const [deployedModels, setDeployedModels] = useState([]); diff --git a/public/pages/workflow_detail/workflow_inputs/input_fields/number_field.tsx b/public/pages/workflow_detail/workflow_inputs/input_fields/number_field.tsx index d847dfd9..c628c5de 100644 --- a/public/pages/workflow_detail/workflow_inputs/input_fields/number_field.tsx +++ b/public/pages/workflow_detail/workflow_inputs/input_fields/number_field.tsx @@ -11,7 +11,7 @@ import { EuiText, EuiCompressedFieldNumber, } from '@elastic/eui'; -import { WorkspaceFormValues } from '../../../../../common'; +import { WorkflowFormValues } from '../../../../../common'; import { camelCaseToTitleString, getInitialValue } from '../../../../utils'; interface NumberFieldProps { @@ -27,7 +27,7 @@ interface NumberFieldProps { * An input field for a component where users input numbers */ export function NumberField(props: NumberFieldProps) { - const { errors, touched } = useFormikContext(); + const { errors, touched } = useFormikContext(); return ( diff --git a/public/pages/workflow_detail/workflow_inputs/input_fields/select_field.tsx b/public/pages/workflow_detail/workflow_inputs/input_fields/select_field.tsx index 3146a944..e27f9d43 100644 --- a/public/pages/workflow_detail/workflow_inputs/input_fields/select_field.tsx +++ b/public/pages/workflow_detail/workflow_inputs/input_fields/select_field.tsx @@ -11,7 +11,7 @@ import { EuiSuperSelectOption, EuiText, } from '@elastic/eui'; -import { WorkspaceFormValues, IConfigField } from '../../../../../common'; +import { WorkflowFormValues, IConfigField } from '../../../../../common'; import { camelCaseToTitleString } from '../../../../utils'; interface SelectFieldProps { @@ -24,7 +24,7 @@ interface SelectFieldProps { * A generic select field from a list of preconfigured options */ export function SelectField(props: SelectFieldProps) { - const { errors, touched } = useFormikContext(); + const { errors, touched } = useFormikContext(); return ( diff --git a/public/pages/workflow_detail/workflow_inputs/input_fields/select_with_custom_options.tsx b/public/pages/workflow_detail/workflow_inputs/input_fields/select_with_custom_options.tsx index ac698805..6f0067ec 100644 --- a/public/pages/workflow_detail/workflow_inputs/input_fields/select_with_custom_options.tsx +++ b/public/pages/workflow_detail/workflow_inputs/input_fields/select_with_custom_options.tsx @@ -7,7 +7,7 @@ import React, { useEffect, useState } from 'react'; import { getIn, useFormikContext } from 'formik'; import { get, isEmpty } from 'lodash'; import { EuiComboBox, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; -import { WorkspaceFormValues } from '../../../../../common'; +import { WorkflowFormValues } from '../../../../../common'; interface SelectWithCustomOptionsProps { fieldPath: string; @@ -20,7 +20,7 @@ interface SelectWithCustomOptionsProps { */ export function SelectWithCustomOptions(props: SelectWithCustomOptionsProps) { const { values, setFieldTouched, setFieldValue } = useFormikContext< - WorkspaceFormValues + WorkflowFormValues >(); // selected option state diff --git a/public/pages/workflow_detail/workflow_inputs/input_fields/text_field.tsx b/public/pages/workflow_detail/workflow_inputs/input_fields/text_field.tsx index e8194663..0dd0b5a3 100644 --- a/public/pages/workflow_detail/workflow_inputs/input_fields/text_field.tsx +++ b/public/pages/workflow_detail/workflow_inputs/input_fields/text_field.tsx @@ -11,7 +11,7 @@ import { EuiLink, EuiText, } from '@elastic/eui'; -import { WorkspaceFormValues } from '../../../../../common'; +import { WorkflowFormValues } from '../../../../../common'; import { getInitialValue } from '../../../../utils'; interface TextFieldProps { @@ -28,7 +28,7 @@ interface TextFieldProps { * An input field for a component where users input plaintext */ export function TextField(props: TextFieldProps) { - const { errors, touched } = useFormikContext(); + const { errors, touched } = useFormikContext(); return ( {({ field, form }: FieldProps) => { diff --git a/public/utils/config_to_schema_utils.ts b/public/utils/config_to_schema_utils.ts index b441cab7..dd5a23d2 100644 --- a/public/utils/config_to_schema_utils.ts +++ b/public/utils/config_to_schema_utils.ts @@ -103,7 +103,7 @@ function processorsConfigToSchema(processorsConfig: ProcessorsConfig): Schema { **************** Yup (validation) utils ********************** */ -function getFieldSchema( +export function getFieldSchema( field: IConfigField, optional: boolean = false ): Schema {