From c46e55050d98caf948de147bc5e1d676d5287b90 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 13 Mar 2025 16:31:18 +0000 Subject: [PATCH] Simplify model i/o when interface defined (#676) Signed-off-by: Tyler Ohlsen (cherry picked from commit 47d59fe5f776fcb74ff79387ba8c7f234848479a) Signed-off-by: github-actions[bot] --- .../input_fields/model_field.tsx | 66 +++--- .../ml_processor_inputs/model_inputs.tsx | 197 ++++++++---------- .../ml_processor_inputs/model_outputs.tsx | 143 +++++-------- 3 files changed, 168 insertions(+), 238 deletions(-) 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 fc1be031..b8377dbf 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 @@ -124,13 +124,15 @@ export function ModelField(props: ModelFieldProps) { labelAppend={ props.modelCategory ? ( - ) : - - Learn more - - + ) : ( + + + Learn more + + + ) } - helpText={props.helpText || 'The model ID.'} + helpText={props.helpText} isInvalid={isInvalid} error={props.showError && getIn(errors, `${field.name}.id`)} > @@ -142,33 +144,33 @@ export function ModelField(props: ModelFieldProps) { disabled={isEmpty(deployedModels)} options={deployedModels.map( (option) => - ({ - value: option.id, - inputDisplay: ( - <> - {option.name} - - ), - dropdownDisplay: ( - <> - + ({ + value: option.id, + inputDisplay: ( + <> {option.name} - - - {isEmpty(option.interface) - ? 'Not ready - no model interface' - : 'Deployed'} - - - ), - disabled: false, - } as EuiSuperSelectOption) + + ), + dropdownDisplay: ( + <> + + {option.name} + + + {isEmpty(option.interface) + ? 'Not ready - no model interface' + : 'Deployed'} + + + ), + disabled: false, + } as EuiSuperSelectOption) )} valueOfSelected={field.value?.id || ''} onChange={(option: string) => { diff --git a/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/model_inputs.tsx b/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/model_inputs.tsx index 58b761d0..49a2958d 100644 --- a/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/model_inputs.tsx +++ b/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/model_inputs.tsx @@ -35,7 +35,6 @@ import { EMPTY_INPUT_MAP_ENTRY, WorkflowConfig, getCharacterLimitedString, - ModelInputFormField, INPUT_TRANSFORM_OPTIONS, } from '../../../../../../common'; import { @@ -47,7 +46,6 @@ import { AppState, getMappings, useAppDispatch } from '../../../../../store'; import { getDataSourceId, getObjsFromJSONLines, - parseModelInputs, sanitizeJSONPath, } from '../../../../../utils'; import { ConfigureExpressionModal, ConfigureTemplateModal } from './modals/'; @@ -108,7 +106,7 @@ export function ModelInputs(props: ModelInputsProps) { number | undefined >(undefined); - // on initial load of the models, update model interface states + // get the model interface based on the selected ID and list of known models useEffect(() => { if (!isEmpty(models)) { const modelId = getIn(values, modelFieldPath)?.id; @@ -116,7 +114,7 @@ export function ModelInputs(props: ModelInputsProps) { setModelInterface(models[modelId]?.interface); } } - }, [models]); + }, [models, getIn(values, modelFieldPath)?.id]); // persisting doc/query/index mapping fields to collect a list // of options to display in the dropdowns when configuring input / output maps @@ -217,31 +215,6 @@ export function ModelInputs(props: ModelInputsProps) { setFieldTouched(inputMapFieldPath, true); } - // The options for keys can change. We update what options are available, based - // on if there is a model interface found, and additionally filter out any - // options that are already being used in the input map, to discourage duplicate keys. - const [keyOptions, setKeyOptions] = useState([]); - useEffect(() => { - setKeyOptions(parseModelInputs(modelInterface)); - }, [modelInterface]); - useEffect(() => { - if (modelInterface !== undefined) { - const modelInputs = parseModelInputs(modelInterface); - if (getIn(values, inputMapFieldPath) !== undefined) { - const existingKeys = getIn(values, inputMapFieldPath).map( - (inputMapEntry: InputMapEntry) => inputMapEntry.key - ) as string[]; - setKeyOptions( - modelInputs.filter( - (modelInput) => !existingKeys.includes(modelInput.label) - ) - ); - } else { - setKeyOptions(modelInputs); - } - } - }, [getIn(values, inputMapFieldPath), modelInterface]); - const valueOptions = props.context === PROCESSOR_CONTEXT.INGEST ? docFields @@ -255,37 +228,38 @@ export function ModelInputs(props: ModelInputsProps) { const populatedMap = field.value?.length !== 0; return ( <> - - {props.context === PROCESSOR_CONTEXT.SEARCH_RESPONSE && ( - <> - - - {oneToOneChanged && ( - <> - - You have changed how source data will be processed. - You may need to update any existing input values to - reflect the updated data structure. - - } - /> - - - )} - - )} - {populatedMap ? ( + {populatedMap ? ( + + {props.context === PROCESSOR_CONTEXT.SEARCH_RESPONSE && ( + <> + + + {oneToOneChanged && ( + <> + + You have changed how source data will be + processed. You may need to update any existing + input values to reflect the updated data + structure. + + } + /> + + + )} + + )} <> {/** - * We determine if there is an interface based on if there are key options or not, - * as the options would be derived from the underlying interface. - * And if so, these values should be static. - * So, we only display the static text with no mechanism to change it's value. - * Note we still allow more entries, if a user wants to override / add custom - * keys if there is some gaps in the model interface. + * If there is a model interface, display the field name. + * Otherwise, leave as a free-form text box for a user to enter manually. */} - {!isEmpty(keyOptions) && - !isEmpty( - getIn( - values, - `${inputMapFieldPath}.${idx}.key` - ) - ) ? ( + {!isEmpty(modelInterface) ? ( - ) : !isEmpty(keyOptions) ? ( - ) : ( - - { - deleteMapEntry(field.value, idx); - }} - /> - + {/** + * Only allow deleting entries if no defined model interface + */} + {isEmpty(modelInterface) && ( + + { + deleteMapEntry(field.value, idx); + }} + /> + + )} @@ -646,33 +608,38 @@ export function ModelInputs(props: ModelInputsProps) { ); } )} - -
- { - addMapEntry(field.value); - }} - > - {`Add input`} - -
-
+ {/** + * Only allow adding entries if no defined model interface + */} + {isEmpty(modelInterface) && ( + +
+ { + addMapEntry(field.value); + }} + > + {`Add input`} + +
+
+ )}
- ) : ( - { - setFieldValue(field.name, [EMPTY_INPUT_MAP_ENTRY]); - }} - > - {'Configure'} - - )} -
+
+ ) : ( + { + setFieldValue(field.name, [EMPTY_INPUT_MAP_ENTRY]); + }} + > + {'Configure'} + + )} ); }} diff --git a/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/model_outputs.tsx b/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/model_outputs.tsx index 2b28c39e..e3d443e3 100644 --- a/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/model_outputs.tsx +++ b/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/model_outputs.tsx @@ -7,6 +7,18 @@ import React, { useState, useEffect } from 'react'; import { Field, FieldProps, getIn, useFormikContext } from 'formik'; import { isEmpty } from 'lodash'; import { useSelector } from 'react-redux'; +import { + EuiCompressedFormRow, + EuiCompressedSuperSelect, + EuiFlexGroup, + EuiFlexItem, + EuiPanel, + EuiSmallButton, + EuiSmallButtonEmpty, + EuiSmallButtonIcon, + EuiSuperSelectOption, + EuiText, +} from '@elastic/eui'; import { IProcessorConfig, IConfigField, @@ -21,25 +33,10 @@ import { OutputMapFormValue, EMPTY_OUTPUT_MAP_ENTRY, ExpressionVar, - ModelOutputFormField, OUTPUT_TRANSFORM_OPTIONS, } from '../../../../../../common'; -import { SelectWithCustomOptions, TextField } from '../../input_fields'; - +import { TextField } from '../../input_fields'; import { AppState } from '../../../../../store'; -import { parseModelOutputs } from '../../../../../utils'; -import { - EuiCompressedFormRow, - EuiCompressedSuperSelect, - EuiFlexGroup, - EuiFlexItem, - EuiPanel, - EuiSmallButton, - EuiSmallButtonEmpty, - EuiSmallButtonIcon, - EuiSuperSelectOption, - EuiText, -} from '@elastic/eui'; import { ConfigureMultiExpressionModal } from './modals'; interface ModelOutputsProps { @@ -75,10 +72,6 @@ export function ModelOutputs(props: ModelOutputsProps) { const modelFieldPath = `${props.baseConfigPath}.${props.config.id}.${modelField.id}`; // Assuming no more than one set of output map entries. const outputMapFieldPath = `${props.baseConfigPath}.${props.config.id}.output_map.0`; - const fullResponsePath = getIn( - values, - `${props.baseConfigPath}.${props.config.id}.full_response_path` - ); // various modal states const [expressionsModalIdx, setExpressionsModalIdx] = useState< @@ -90,7 +83,7 @@ export function ModelOutputs(props: ModelOutputsProps) { ModelInterface | undefined >(undefined); - // on initial load of the models, update model interface states + // get the model interface based on the selected ID and list of known models useEffect(() => { if (!isEmpty(models)) { const modelId = getIn(values, modelFieldPath)?.id; @@ -98,7 +91,7 @@ export function ModelOutputs(props: ModelOutputsProps) { setModelInterface(models[modelId]?.interface); } } - }, [models]); + }, [models, getIn(values, modelFieldPath)?.id]); // Adding a map entry to the end of the existing arr function addMapEntry(curEntries: OutputMapFormValue): void { @@ -118,31 +111,6 @@ export function ModelOutputs(props: ModelOutputsProps) { setFieldTouched(outputMapFieldPath, true); } - // The options for keys can change. We update what options are available, based - // on if there is a model interface found, what full_response_path is, and additionally filter out any - // options that are already being used in the output map, to discourage duplicate keys. - const [keyOptions, setKeyOptions] = useState([]); - useEffect(() => { - setKeyOptions(parseModelOutputs(modelInterface)); - }, [modelInterface]); - useEffect(() => { - if (modelInterface !== undefined && fullResponsePath === false) { - const modelOutputs = parseModelOutputs(modelInterface); - if (getIn(values, outputMapFieldPath) !== undefined) { - const existingKeys = getIn(values, outputMapFieldPath).map( - (outputMapEntry: OutputMapEntry) => outputMapEntry.key - ) as string[]; - setKeyOptions( - modelOutputs.filter( - (modelOutput) => !existingKeys.includes(modelOutput.label) - ) - ); - } else { - setKeyOptions(modelOutputs); - } - } - }, [getIn(values, outputMapFieldPath), modelInterface, fullResponsePath]); - return ( {({ field, form }: FieldProps) => { @@ -214,20 +182,10 @@ export function ModelOutputs(props: ModelOutputsProps) { <> {/** - * We determine if there is an interface based on if there are key options or not, - * as the options would be derived from the underlying interface. - * And if so, these values should be static. - * So, we only display the static text with no mechanism to change it's value. - * Note we still allow more entries, if a user wants to override / add custom - * keys if there is some gaps in the model interface. + * If there is a model interface, display the field name. + * Otherwise, leave as a free-form text box for a user to enter manually. */} - {!isEmpty(keyOptions) && - !isEmpty( - getIn( - values, - `${outputMapFieldPath}.${idx}.key` - ) - ) ? ( + {!isEmpty(modelInterface) ? ( - ) : !isEmpty(keyOptions) ? ( - ) : ( - - { - deleteMapEntry(field.value, idx); - }} - /> - + {/** + * Only allow deleting entries if no defined model interface + */} + {isEmpty(modelInterface) && ( + + { + deleteMapEntry(field.value, idx); + }} + /> + + )} @@ -456,20 +412,25 @@ export function ModelOutputs(props: ModelOutputsProps) { ); } )} - -
- { - addMapEntry(field.value); - }} - > - {`Add output`} - -
-
+ {/** + * Only allow adding entries if no defined model interface + */} + {isEmpty(modelInterface) && ( + +
+ { + addMapEntry(field.value); + }} + > + {`Add output`} + +
+
+ )}