Skip to content

Commit

Permalink
Add interim form state for input transform modal; clean up some props
Browse files Browse the repository at this point in the history
Signed-off-by: Tyler Ohlsen <[email protected]>
  • Loading branch information
ohltyler committed Oct 30, 2024
1 parent 2eb7e01 commit 293a984
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,13 @@ import {
import { Field, FieldProps, getIn, useFormikContext } from 'formik';
import {
EMPTY_MAP_ENTRY,
IConfigField,
MapArrayFormValue,
MapEntry,
WorkflowFormValues,
} from '../../../../../common';
import { MapField } from './map_field';

interface MapArrayFieldProps {
field: IConfigField;
fieldPath: string; // the full path in string-form to the field (e.g., 'ingest.enrich.processors.text_embedding_processor.inputField')
helpText?: string;
keyTitle?: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import {
PROCESSOR_CONTEXT,
WorkflowConfig,
JSONPATH_ROOT_SELECTOR,
ML_INFERENCE_DOCS_LINK,
WorkflowFormValues,
ModelInterface,
IndexMappings,
Expand Down Expand Up @@ -75,15 +74,9 @@ export function MLProcessorInputs(props: MLProcessorInputsProps) {
) as IConfigField;
const modelFieldPath = `${props.baseConfigPath}.${props.config.id}.${modelField.id}`;
const modelIdFieldPath = `${modelFieldPath}.id`;
const inputMapField = props.config.fields.find(
(field) => field.id === 'input_map'
) as IConfigField;
const inputMapFieldPath = `${props.baseConfigPath}.${props.config.id}.${inputMapField.id}`;
const inputMapFieldPath = `${props.baseConfigPath}.${props.config.id}.input_map`;
const inputMapValue = getIn(values, inputMapFieldPath);
const outputMapField = props.config.fields.find(
(field) => field.id === 'output_map'
) as IConfigField;
const outputMapFieldPath = `${props.baseConfigPath}.${props.config.id}.${outputMapField.id}`;
const outputMapFieldPath = `${props.baseConfigPath}.${props.config.id}.output_map`;
const outputMapValue = getIn(values, outputMapFieldPath);
const fullResponsePath = getIn(
values,
Expand Down Expand Up @@ -250,7 +243,6 @@ export function MLProcessorInputs(props: MLProcessorInputsProps) {
config={props.config}
baseConfigPath={props.baseConfigPath}
context={props.context}
inputMapField={inputMapField}
inputMapFieldPath={inputMapFieldPath}
modelInterface={modelInterface}
valueOptions={
Expand All @@ -269,7 +261,6 @@ export function MLProcessorInputs(props: MLProcessorInputsProps) {
config={props.config}
baseConfigPath={props.baseConfigPath}
context={props.context}
outputMapField={outputMapField}
outputMapFieldPath={outputMapFieldPath}
modelInterface={modelInterface}
onClose={() => setIsOutputTransformModalOpen(false)}
Expand Down Expand Up @@ -365,7 +356,6 @@ export function MLProcessorInputs(props: MLProcessorInputsProps) {
</EuiFlexGroup>
<EuiSpacer size="s" />
<MapArrayField
field={inputMapField}
fieldPath={inputMapFieldPath}
helpText={`An array specifying how to map fields from the ingested document to the model’s input. Dot notation is used by default. To explicitly use JSONPath, please ensure to prepend with the
root object selector "${JSONPATH_ROOT_SELECTOR}"`}
Expand Down Expand Up @@ -422,7 +412,6 @@ export function MLProcessorInputs(props: MLProcessorInputsProps) {
</EuiFlexGroup>
<EuiSpacer size="s" />
<MapArrayField
field={outputMapField}
fieldPath={outputMapFieldPath}
helpText={`An array specifying how to map the model’s output to new document fields. Dot notation is used by default. To explicitly use JSONPath, please ensure to prepend with the
root object selector "${JSONPATH_ROOT_SELECTOR}"`}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ interface InputTransformModalProps {
config: IProcessorConfig;
baseConfigPath: string;
context: PROCESSOR_CONTEXT;
inputMapField: IConfigField;
inputMapFieldPath: string;
modelInterface: ModelInterface | undefined;
valueOptions: { label: string }[];
Expand All @@ -90,7 +89,9 @@ const MAX_INPUT_DOCS = 10;
export function InputTransformModal(props: InputTransformModalProps) {
const dispatch = useAppDispatch();
const dataSourceId = getDataSourceId();
const { values } = useFormikContext<WorkflowFormValues>();
const { values, setFieldValue, setFieldTouched } = useFormikContext<
WorkflowFormValues
>();

// sub-form values/schema
const inputTransformFormValues = {
Expand All @@ -101,14 +102,18 @@ export function InputTransformModal(props: InputTransformModalProps) {
input_map: getFieldSchema({
type: 'mapArray',
} as IConfigField),
one_to_one: getFieldSchema({
type: 'boolean',
} as IConfigField),
one_to_one: getFieldSchema(
{
type: 'boolean',
} as IConfigField,
true
),
}) as InputTransformSchema;

// persist standalone values. update / initialize when it is first opened
// TODO: add more here
const [tempErrors, setTempErrors] = useState<boolean>(false);
const [tempOneToOne, setTempOneToOne] = useState<boolean>(false);
const [tempInputMap, setTempInputMap] = useState<MapArrayFormValue>([]);

// various prompt states
const [viewPromptDetails, setViewPromptDetails] = useState<boolean>(false);
Expand All @@ -126,9 +131,7 @@ export function InputTransformModal(props: InputTransformModalProps) {
const [transformedInput, setTransformedInput] = useState<string>('{}');

// get some current form values
const map = getIn(values, props.inputMapFieldPath) as MapArrayFormValue;
const oneToOnePath = `${props.baseConfigPath}.${props.config.id}.one_to_one`;
const oneToOne = getIn(values, oneToOnePath);
const docs = getIn(values, 'ingest.docs');
let docObjs = [] as {}[] | undefined;
try {
Expand All @@ -147,13 +150,13 @@ export function InputTransformModal(props: InputTransformModalProps) {
isEmpty(queryObj);

// selected transform state
const transformOptions = map.map((_, idx) => ({
const transformOptions = tempInputMap.map((_, idx) => ({
value: idx,
text: `Prediction ${idx + 1}`,
})) as EuiSelectOption[];
const [selectedTransformOption, setSelectedTransformOption] = useState<
number | undefined
>((transformOptions[0]?.value as number) ?? undefined);
number
>((transformOptions[0]?.value as number) ?? 0);

// popover state containing the model interface details, if applicable
const [popoverOpen, setPopoverOpen] = useState<boolean>(false);
Expand All @@ -171,35 +174,31 @@ export function InputTransformModal(props: InputTransformModalProps) {

// hook to re-generate the transform when any inputs to the transform are updated
useEffect(() => {
if (
!isEmpty(map) &&
!isEmpty(JSON.parse(sourceInput)) &&
selectedTransformOption !== undefined
) {
if (!isEmpty(tempInputMap) && !isEmpty(JSON.parse(sourceInput))) {
let sampleSourceInput = {} as {} | [];
try {
sampleSourceInput = JSON.parse(sourceInput);
const output =
// Edge case: users are collapsing input docs into a single input field when many-to-one is selected
// fo input transforms on search response processors.
oneToOne === false &&
tempOneToOne === false &&
props.context === PROCESSOR_CONTEXT.SEARCH_RESPONSE &&
Array.isArray(sampleSourceInput)
? generateArrayTransform(
sampleSourceInput as [],
map[selectedTransformOption]
tempInputMap[selectedTransformOption]
)
: generateTransform(
sampleSourceInput,
map[selectedTransformOption]
tempInputMap[selectedTransformOption]
);

setTransformedInput(customStringify(output));
} catch {}
} else {
setTransformedInput('{}');
}
}, [map, sourceInput, selectedTransformOption]);
}, [tempInputMap, sourceInput, selectedTransformOption]);

// hook to re-determine validity when the generated output changes
// utilize Ajv JSON schema validator library. For more info/examples, see
Expand Down Expand Up @@ -254,7 +253,7 @@ export function InputTransformModal(props: InputTransformModalProps) {
// hook to clear the source input when one_to_one is toggled
useEffect(() => {
setSourceInput('{}');
}, [oneToOne]);
}, [tempOneToOne]);

return (
<Formik
Expand All @@ -265,18 +264,24 @@ export function InputTransformModal(props: InputTransformModalProps) {
validate={(values) => {}}
>
{(formikProps) => {
// // override to parent form value when changes detected
// useEffect(() => {
// formikProps.setFieldValue(
// 'request',
// getIn(values, props.queryFieldPath)
// );
// }, [getIn(values, props.queryFieldPath)]);
// override to parent form values when changes detected
useEffect(() => {
formikProps.setFieldValue(
'input_map',
getIn(values, props.inputMapFieldPath)
);
}, [getIn(values, props.inputMapFieldPath)]);
useEffect(() => {
formikProps.setFieldValue('one_to_one', getIn(values, oneToOnePath));
}, [getIn(values, oneToOnePath)]);

// // update tempRequest when form changes are detected
// useEffect(() => {
// setTempRequest(getIn(formikProps.values, 'request'));
// }, [getIn(formikProps.values, 'request')]);
// update temp vars when form changes are detected
useEffect(() => {
setTempInputMap(getIn(formikProps.values, 'input_map'));
}, [getIn(formikProps.values, 'input_map')]);
useEffect(() => {
setTempOneToOne(getIn(formikProps.values, 'one_to_one'));
}, [getIn(formikProps.values, 'one_to_one')]);

// update tempErrors if errors detected
useEffect(() => {
Expand Down Expand Up @@ -314,13 +319,13 @@ export function InputTransformModal(props: InputTransformModalProps) {
<>
<BooleanField
label={'One-to-one'}
fieldPath={oneToOnePath}
fieldPath={'one_to_one'}
enabledOption={{
id: `${oneToOnePath}_true`,
id: `one_to_one_true`,
label: 'True',
}}
disabledOption={{
id: `${oneToOnePath}_false`,
id: `one_to_one_false`,
label: 'False',
}}
showLabel={true}
Expand Down Expand Up @@ -450,7 +455,9 @@ export function InputTransformModal(props: InputTransformModalProps) {
setSourceInput(
// if one-to-one, treat the source input as a single retrieved document
// else, treat it as all of the returned documents
customStringify(oneToOne ? hits[0] : hits)
customStringify(
tempOneToOne ? hits[0] : hits
)
);
}
})
Expand Down Expand Up @@ -494,8 +501,7 @@ export function InputTransformModal(props: InputTransformModalProps) {
<EuiText size="s">Define transform</EuiText>
<EuiSpacer size="s" />
<MapArrayField
field={props.inputMapField}
fieldPath={props.inputMapFieldPath}
fieldPath={'input_map'}
helpText={`An array specifying how to map fields from the ingested document to the model’s input. Dot notation is used by default. To explicitly use JSONPath, please ensure to prepend with the
root object selector "${JSONPATH_ROOT_SELECTOR}"`}
keyTitle="Name"
Expand Down Expand Up @@ -692,9 +698,6 @@ export function InputTransformModal(props: InputTransformModalProps) {
</EuiFlexGroup>
</EuiModalBody>
<EuiModalFooter>
{/* <EuiSmallButton onClick={props.onClose} fill={false} color="primary">
Close
</EuiSmallButton> */}
<EuiSmallButton
onClick={props.onClose}
fill={false}
Expand All @@ -705,11 +708,19 @@ export function InputTransformModal(props: InputTransformModalProps) {
</EuiSmallButton>
<EuiSmallButton
onClick={() => {
// setFieldValue(props.queryFieldPath, tempRequest);
// setFieldTouched(props.queryFieldPath, true);

// TODO: add logic here

// update the parent form values
if (props.context === PROCESSOR_CONTEXT.SEARCH_RESPONSE) {
setFieldValue(
oneToOnePath,
getIn(formikProps.values, 'one_to_one')
);
setFieldTouched(oneToOnePath, true);
}
setFieldValue(
props.inputMapFieldPath,
getIn(formikProps.values, 'input_map')
);
setFieldTouched(props.inputMapFieldPath, true);
props.onClose();
}}
isDisabled={tempErrors} // blocking update until valid input is given
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import {
EuiCallOut,
} from '@elastic/eui';
import {
IConfigField,
IProcessorConfig,
IngestPipelineConfig,
JSONPATH_ROOT_SELECTOR,
Expand Down Expand Up @@ -67,7 +66,6 @@ interface OutputTransformModalProps {
config: IProcessorConfig;
baseConfigPath: string;
context: PROCESSOR_CONTEXT;
outputMapField: IConfigField;
outputMapFieldPath: string;
modelInterface: ModelInterface | undefined;
onClose: () => void;
Expand Down Expand Up @@ -118,16 +116,12 @@ export function OutputTransformModal(props: OutputTransformModalProps) {
text: `Prediction ${idx + 1}`,
})) as EuiSelectOption[];
const [selectedTransformOption, setSelectedTransformOption] = useState<
number | undefined
>((transformOptions[0]?.value as number) ?? undefined);
number
>((transformOptions[0]?.value as number) ?? 0);

// hook to re-generate the transform when any inputs to the transform are updated
useEffect(() => {
if (
!isEmpty(map) &&
!isEmpty(JSON.parse(sourceOutput)) &&
selectedTransformOption !== undefined
) {
if (!isEmpty(map) && !isEmpty(JSON.parse(sourceOutput))) {
let sampleSourceOutput = {};
try {
sampleSourceOutput = JSON.parse(sourceOutput);
Expand Down Expand Up @@ -386,7 +380,6 @@ export function OutputTransformModal(props: OutputTransformModalProps) {
<EuiText size="s">Define transform</EuiText>
<EuiSpacer size="s" />
<MapArrayField
field={props.outputMapField}
fieldPath={props.outputMapFieldPath}
helpText={`An array specifying how to map the model’s output to new fields. Dot notation is used by default. To explicitly use JSONPath, please ensure to prepend with the
root object selector "${JSONPATH_ROOT_SELECTOR}"`}
Expand Down

0 comments on commit 293a984

Please sign in to comment.