From 5ccf18873fcacb1fd0924307025ccbe3182edbff Mon Sep 17 00:00:00 2001
From: Tyler Ohlsen <ohltyler@amazon.com>
Date: Thu, 13 Mar 2025 11:20:15 -0700
Subject: [PATCH 1/4] Make transform type dropdown consistent

Signed-off-by: Tyler Ohlsen <ohltyler@amazon.com>
---
 .../processor_inputs/ml_processor_inputs/model_inputs.tsx        | 1 +
 .../processor_inputs/ml_processor_inputs/model_outputs.tsx       | 1 +
 2 files changed, 2 insertions(+)

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 49a2958d..580f72d6 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
@@ -346,6 +346,7 @@ export function ModelInputs(props: ModelInputsProps) {
                               <EuiFlexItem grow={TYPE_FLEX_RATIO}>
                                 <EuiFlexItem>
                                   <EuiCompressedSuperSelect
+                                    fullWidth={true}
                                     disabled={false}
                                     options={INPUT_TRANSFORM_OPTIONS.map(
                                       (option) =>
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 e3d443e3..1762cede 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
@@ -211,6 +211,7 @@ export function ModelOutputs(props: ModelOutputsProps) {
                               <EuiFlexItem grow={TYPE_FLEX_RATIO}>
                                 <EuiFlexItem>
                                   <EuiCompressedSuperSelect
+                                    fullWidth={true}
                                     disabled={false}
                                     options={OUTPUT_TRANSFORM_OPTIONS.map(
                                       (option) =>

From 2e69e0517d21b843707ca78a90081a4085ddb890 Mon Sep 17 00:00:00 2001
From: Tyler Ohlsen <ohltyler@amazon.com>
Date: Thu, 13 Mar 2025 12:40:06 -0700
Subject: [PATCH 2/4] Get partially working for input map only

Signed-off-by: Tyler Ohlsen <ohltyler@amazon.com>
---
 .../ml_processor_inputs/model_inputs.tsx      | 80 +++++++++++++++++--
 1 file changed, 73 insertions(+), 7 deletions(-)

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 580f72d6..2f76d316 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
@@ -31,6 +31,7 @@ import {
   IndexMappings,
   InputMapEntry,
   InputMapFormValue,
+  Transform,
   TRANSFORM_TYPE,
   EMPTY_INPUT_MAP_ENTRY,
   WorkflowConfig,
@@ -98,6 +99,16 @@ export function ModelInputs(props: ModelInputsProps) {
     ModelInterface | undefined
   >(undefined);
 
+  // get the model interface based on the selected ID and list of known models
+  useEffect(() => {
+    if (!isEmpty(models)) {
+      const modelId = getIn(values, modelFieldPath)?.id;
+      if (modelId) {
+        setModelInterface(models[modelId]?.interface);
+      }
+    }
+  }, [models, getIn(values, modelFieldPath)?.id]);
+
   // various modal states
   const [templateModalIdx, setTemplateModalIdx] = useState<number | undefined>(
     undefined
@@ -106,15 +117,70 @@ export function ModelInputs(props: ModelInputsProps) {
     number | undefined
   >(undefined);
 
-  // get the model interface based on the selected ID and list of known models
+  const [debouncedInputMap, setDebouncedInputMap] = useState<any>();
   useEffect(() => {
-    if (!isEmpty(models)) {
-      const modelId = getIn(values, modelFieldPath)?.id;
-      if (modelId) {
-        setModelInterface(models[modelId]?.interface);
-      }
+    // Set a timeout to update debounced value after 500ms
+    const handler = setTimeout(() => {
+      setDebouncedInputMap(getIn(values, inputMapFieldPath));
+    }, 500);
+
+    // Cleanup the timeout if `query` changes before 500ms
+    return () => {
+      clearTimeout(handler);
+    };
+  }, [getIn(values, inputMapFieldPath)]);
+
+  // TODO: this works ok, but does not allow clearing out any form value, or it resets to the last-known/cached value.
+  // need to filter out on this use case.
+
+  // Temporarily cache any configured transformations for different transform types.
+  // For example, if a user configures a prompt, swaps the transform
+  // type to "Data field", and swaps back to "Prompt", the prompt will be persisted.
+  const [inputMapCache, _] = useState<{
+    [idx: number]: Transform[];
+  }>({});
+  useEffect(() => {
+    const curFormValues = debouncedInputMap as InputMapFormValue | undefined;
+    if (curFormValues !== undefined && !isEmpty(curFormValues)) {
+      // for each form value: populate the cache with a non-empty value and/or populate the
+      // form value with its cached value, if found.
+      curFormValues.forEach((mapEntry, idx) => {
+        const curCacheForIdx = inputMapCache[idx];
+        if (curCacheForIdx === undefined || isEmpty(curCacheForIdx)) {
+          // case 1: there is no persisted state for this entry index. create a fresh arr
+          inputMapCache[idx] = [mapEntry.value];
+        } else if (
+          !curCacheForIdx.some(
+            (transform: Transform) =>
+              transform.transformType === mapEntry.value.transformType
+          )
+        ) {
+          // case 2: there is persisted state for this entry index, but not for the particular
+          // transform type. append to the arr
+          inputMapCache[idx] = [...inputMapCache[idx], mapEntry.value];
+        } else {
+          // case 3: there is persisted state for this entry index, and for the particular transform type.
+          // Either update the cache with the current form value(s) (if non-empty), or update the form
+          // with any value found in the cache
+          inputMapCache[idx] = inputMapCache[idx].map((cachedEntry) => {
+            if (cachedEntry.transformType === mapEntry.value.transformType) {
+              const formValue = mapEntry.value.value;
+              // form is non-empty. update the cache
+              if (formValue !== undefined && !isEmpty(formValue)) {
+                return mapEntry.value;
+                // form is empty. update the form with cached value(s)
+              } else {
+                setFieldValue(`${inputMapFieldPath}.${idx}.value`, cachedEntry);
+                return cachedEntry;
+              }
+            } else {
+              return cachedEntry;
+            }
+          });
+        }
+      });
     }
-  }, [models, getIn(values, modelFieldPath)?.id]);
+  }, [debouncedInputMap]);
 
   // persisting doc/query/index mapping fields to collect a list
   // of options to display in the dropdowns when configuring input / output maps

From ca75cb3ac3403aff875243c2ac2a864a2f24fa3f Mon Sep 17 00:00:00 2001
From: Tyler Ohlsen <ohltyler@amazon.com>
Date: Thu, 13 Mar 2025 17:01:02 -0700
Subject: [PATCH 3/4] Simplify to only trigger cache updates on transform type
 changes

Signed-off-by: Tyler Ohlsen <ohltyler@amazon.com>
---
 .../ml_processor_inputs/model_inputs.tsx      | 107 ++++++++----------
 1 file changed, 45 insertions(+), 62 deletions(-)

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 2f76d316..c1295d14 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
@@ -117,70 +117,12 @@ export function ModelInputs(props: ModelInputsProps) {
     number | undefined
   >(undefined);
 
-  const [debouncedInputMap, setDebouncedInputMap] = useState<any>();
-  useEffect(() => {
-    // Set a timeout to update debounced value after 500ms
-    const handler = setTimeout(() => {
-      setDebouncedInputMap(getIn(values, inputMapFieldPath));
-    }, 500);
-
-    // Cleanup the timeout if `query` changes before 500ms
-    return () => {
-      clearTimeout(handler);
-    };
-  }, [getIn(values, inputMapFieldPath)]);
-
-  // TODO: this works ok, but does not allow clearing out any form value, or it resets to the last-known/cached value.
-  // need to filter out on this use case.
-
   // Temporarily cache any configured transformations for different transform types.
   // For example, if a user configures a prompt, swaps the transform
   // type to "Data field", and swaps back to "Prompt", the prompt will be persisted.
   const [inputMapCache, _] = useState<{
     [idx: number]: Transform[];
   }>({});
-  useEffect(() => {
-    const curFormValues = debouncedInputMap as InputMapFormValue | undefined;
-    if (curFormValues !== undefined && !isEmpty(curFormValues)) {
-      // for each form value: populate the cache with a non-empty value and/or populate the
-      // form value with its cached value, if found.
-      curFormValues.forEach((mapEntry, idx) => {
-        const curCacheForIdx = inputMapCache[idx];
-        if (curCacheForIdx === undefined || isEmpty(curCacheForIdx)) {
-          // case 1: there is no persisted state for this entry index. create a fresh arr
-          inputMapCache[idx] = [mapEntry.value];
-        } else if (
-          !curCacheForIdx.some(
-            (transform: Transform) =>
-              transform.transformType === mapEntry.value.transformType
-          )
-        ) {
-          // case 2: there is persisted state for this entry index, but not for the particular
-          // transform type. append to the arr
-          inputMapCache[idx] = [...inputMapCache[idx], mapEntry.value];
-        } else {
-          // case 3: there is persisted state for this entry index, and for the particular transform type.
-          // Either update the cache with the current form value(s) (if non-empty), or update the form
-          // with any value found in the cache
-          inputMapCache[idx] = inputMapCache[idx].map((cachedEntry) => {
-            if (cachedEntry.transformType === mapEntry.value.transformType) {
-              const formValue = mapEntry.value.value;
-              // form is non-empty. update the cache
-              if (formValue !== undefined && !isEmpty(formValue)) {
-                return mapEntry.value;
-                // form is empty. update the form with cached value(s)
-              } else {
-                setFieldValue(`${inputMapFieldPath}.${idx}.value`, cachedEntry);
-                return cachedEntry;
-              }
-            } else {
-              return cachedEntry;
-            }
-          });
-        }
-      });
-    }
-  }, [debouncedInputMap]);
 
   // persisting doc/query/index mapping fields to collect a list
   // of options to display in the dropdowns when configuring input / output maps
@@ -448,19 +390,60 @@ export function ModelInputs(props: ModelInputsProps) {
                                       ) || ''
                                     }
                                     onChange={(option) => {
+                                      // before updating, cache any form values
+                                      const curCache = inputMapCache[idx];
+                                      if (
+                                        curCache === undefined ||
+                                        isEmpty(curCache)
+                                      ) {
+                                        // case 1: there is no persisted state for this entry index. create a fresh arr
+                                        inputMapCache[idx] = [mapEntry.value];
+                                      } else if (
+                                        !curCache.some(
+                                          (transform: Transform) =>
+                                            transform.transformType ===
+                                            mapEntry.value.transformType
+                                        )
+                                      ) {
+                                        // case 2: there is persisted state for this entry index, but not for the particular
+                                        // transform type. append to the arr
+                                        inputMapCache[idx] = [
+                                          ...inputMapCache[idx],
+                                          mapEntry.value,
+                                        ];
+                                      } else {
+                                        // case 3: there is persisted state for this entry index, and for the particular transform type.
+                                        // Update the cache with the current form value(s)
+                                        inputMapCache[idx] = inputMapCache[
+                                          idx
+                                        ].map((cachedEntry) => {
+                                          if (
+                                            cachedEntry.transformType ===
+                                            mapEntry.value.transformType
+                                          ) {
+                                            return mapEntry.value;
+                                          } else {
+                                            return cachedEntry;
+                                          }
+                                        });
+                                      }
+
                                       setFieldValue(
                                         `${inputMapFieldPath}.${idx}.value.transformType`,
                                         option
                                       );
-                                      // If the transform type changes, clear any set value and/or nested vars,
-                                      // as it will likely not make sense under other types/contexts.
+                                      // Pre-populate with any cached values, if found
+                                      const curCacheForOption = curCache?.find(
+                                        (transform: Transform) =>
+                                          transform.transformType === option
+                                      );
                                       setFieldValue(
                                         `${inputMapFieldPath}.${idx}.value.value`,
-                                        ''
+                                        curCacheForOption?.value || ''
                                       );
                                       setFieldValue(
                                         `${inputMapFieldPath}.${idx}.value.nestedVars`,
-                                        []
+                                        curCacheForOption?.nestedVars || []
                                       );
                                     }}
                                   />

From 3afb8549f75718fb349b8f6a2a0af00f11b2a014 Mon Sep 17 00:00:00 2001
From: Tyler Ohlsen <ohltyler@amazon.com>
Date: Fri, 14 Mar 2025 09:41:03 -0700
Subject: [PATCH 4/4] Generalize to util fn; add for output map;

Signed-off-by: Tyler Ohlsen <ohltyler@amazon.com>
---
 common/interfaces.ts                          |  4 ++
 .../ml_processor_inputs/model_inputs.tsx      | 53 +++++--------------
 .../ml_processor_inputs/model_outputs.tsx     | 28 ++++++++--
 .../ml_processor_inputs/utils.ts              | 46 ++++++++++++++++
 4 files changed, 86 insertions(+), 45 deletions(-)
 create mode 100644 public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/utils.ts

diff --git a/common/interfaces.ts b/common/interfaces.ts
index aa31c7d2..43fbfa15 100644
--- a/common/interfaces.ts
+++ b/common/interfaces.ts
@@ -126,6 +126,10 @@ export type OutputMapEntry = InputMapEntry;
 export type InputMapFormValue = InputMapEntry[];
 export type OutputMapFormValue = OutputMapEntry[];
 
+export type MapCache = {
+  [idx: number]: Transform[];
+};
+
 export type InputMapArrayFormValue = InputMapFormValue[];
 export type OutputMapArrayFormValue = OutputMapFormValue[];
 
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 c1295d14..5572a101 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
@@ -37,6 +37,7 @@ import {
   WorkflowConfig,
   getCharacterLimitedString,
   INPUT_TRANSFORM_OPTIONS,
+  MapCache,
 } from '../../../../../../common';
 import {
   TextField,
@@ -50,6 +51,7 @@ import {
   sanitizeJSONPath,
 } from '../../../../../utils';
 import { ConfigureExpressionModal, ConfigureTemplateModal } from './modals/';
+import { updateCache } from './utils';
 
 interface ModelInputsProps {
   config: IProcessorConfig;
@@ -120,9 +122,7 @@ export function ModelInputs(props: ModelInputsProps) {
   // Temporarily cache any configured transformations for different transform types.
   // For example, if a user configures a prompt, swaps the transform
   // type to "Data field", and swaps back to "Prompt", the prompt will be persisted.
-  const [inputMapCache, _] = useState<{
-    [idx: number]: Transform[];
-  }>({});
+  const [inputMapCache, setInputMapCache] = useState<MapCache>({});
 
   // persisting doc/query/index mapping fields to collect a list
   // of options to display in the dropdowns when configuring input / output maps
@@ -391,49 +391,19 @@ export function ModelInputs(props: ModelInputsProps) {
                                     }
                                     onChange={(option) => {
                                       // before updating, cache any form values
-                                      const curCache = inputMapCache[idx];
-                                      if (
-                                        curCache === undefined ||
-                                        isEmpty(curCache)
-                                      ) {
-                                        // case 1: there is no persisted state for this entry index. create a fresh arr
-                                        inputMapCache[idx] = [mapEntry.value];
-                                      } else if (
-                                        !curCache.some(
-                                          (transform: Transform) =>
-                                            transform.transformType ===
-                                            mapEntry.value.transformType
-                                        )
-                                      ) {
-                                        // case 2: there is persisted state for this entry index, but not for the particular
-                                        // transform type. append to the arr
-                                        inputMapCache[idx] = [
-                                          ...inputMapCache[idx],
-                                          mapEntry.value,
-                                        ];
-                                      } else {
-                                        // case 3: there is persisted state for this entry index, and for the particular transform type.
-                                        // Update the cache with the current form value(s)
-                                        inputMapCache[idx] = inputMapCache[
-                                          idx
-                                        ].map((cachedEntry) => {
-                                          if (
-                                            cachedEntry.transformType ===
-                                            mapEntry.value.transformType
-                                          ) {
-                                            return mapEntry.value;
-                                          } else {
-                                            return cachedEntry;
-                                          }
-                                        });
-                                      }
-
+                                      const updatedCache = updateCache(
+                                        inputMapCache,
+                                        mapEntry,
+                                        idx
+                                      );
                                       setFieldValue(
                                         `${inputMapFieldPath}.${idx}.value.transformType`,
                                         option
                                       );
                                       // Pre-populate with any cached values, if found
-                                      const curCacheForOption = curCache?.find(
+                                      const curCacheForOption = updatedCache[
+                                        idx
+                                      ]?.find(
                                         (transform: Transform) =>
                                           transform.transformType === option
                                       );
@@ -445,6 +415,7 @@ export function ModelInputs(props: ModelInputsProps) {
                                         `${inputMapFieldPath}.${idx}.value.nestedVars`,
                                         curCacheForOption?.nestedVars || []
                                       );
+                                      setInputMapCache(updatedCache);
                                     }}
                                   />
                                 </EuiFlexItem>
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 1762cede..87ce1b76 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
@@ -34,10 +34,13 @@ import {
   EMPTY_OUTPUT_MAP_ENTRY,
   ExpressionVar,
   OUTPUT_TRANSFORM_OPTIONS,
+  Transform,
+  MapCache,
 } from '../../../../../../common';
 import { TextField } from '../../input_fields';
 import { AppState } from '../../../../../store';
 import { ConfigureMultiExpressionModal } from './modals';
+import { updateCache } from './utils';
 
 interface ModelOutputsProps {
   config: IProcessorConfig;
@@ -93,6 +96,11 @@ export function ModelOutputs(props: ModelOutputsProps) {
     }
   }, [models, getIn(values, modelFieldPath)?.id]);
 
+  // Temporarily cache any configured transformations for different transform types.
+  // For example, if a user configures a prompt, swaps the transform
+  // type to "Data field", and swaps back to "Prompt", the prompt will be persisted.
+  const [outputMapCache, setOutputMapCache] = useState<MapCache>({});
+
   // Adding a map entry to the end of the existing arr
   function addMapEntry(curEntries: OutputMapFormValue): void {
     const updatedEntries = [...curEntries, EMPTY_OUTPUT_MAP_ENTRY];
@@ -247,20 +255,32 @@ export function ModelOutputs(props: ModelOutputsProps) {
                                       ) || ''
                                     }
                                     onChange={(option) => {
+                                      // before updating, cache any form values
+                                      const updatedCache = updateCache(
+                                        outputMapCache,
+                                        mapEntry,
+                                        idx
+                                      );
                                       setFieldValue(
                                         `${outputMapFieldPath}.${idx}.value.transformType`,
                                         option
                                       );
-                                      // If the transform type changes, clear any set value and/or nested vars,
-                                      // as it will likely not make sense under other types/contexts.
+                                      // Pre-populate with any cached values, if found
+                                      const curCacheForOption = updatedCache[
+                                        idx
+                                      ]?.find(
+                                        (transform: Transform) =>
+                                          transform.transformType === option
+                                      );
                                       setFieldValue(
                                         `${outputMapFieldPath}.${idx}.value.value`,
-                                        ''
+                                        curCacheForOption?.value || ''
                                       );
                                       setFieldValue(
                                         `${outputMapFieldPath}.${idx}.value.nestedVars`,
-                                        []
+                                        curCacheForOption?.nestedVars || []
                                       );
+                                      setOutputMapCache(updatedCache);
                                     }}
                                   />
                                 </EuiFlexItem>
diff --git a/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/utils.ts b/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/utils.ts
new file mode 100644
index 00000000..358ea0d7
--- /dev/null
+++ b/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/utils.ts
@@ -0,0 +1,46 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { isEmpty } from 'lodash';
+import {
+  MapCache,
+  InputMapEntry,
+  OutputMapEntry,
+  Transform,
+} from '../../../../../../common';
+
+// Update a cache of data transform values based on a given form value
+export function updateCache(
+  cache: MapCache,
+  mapEntry: InputMapEntry | OutputMapEntry,
+  idx: number // the mapEntry index
+): MapCache {
+  const updatedCache = cache;
+  const curCache = updatedCache[idx];
+  if (curCache === undefined || isEmpty(curCache)) {
+    // case 1: there is no persisted state for this entry index. create a fresh arr
+    updatedCache[idx] = [mapEntry.value];
+  } else if (
+    !curCache.some(
+      (transform: Transform) =>
+        transform.transformType === mapEntry.value.transformType
+    )
+  ) {
+    // case 2: there is persisted state for this entry index, but not for the particular
+    // transform type. append to the arr
+    updatedCache[idx] = [...updatedCache[idx], mapEntry.value];
+  } else {
+    // case 3: there is persisted state for this entry index, and for the particular transform type.
+    // Update the cache with the current form value(s)
+    updatedCache[idx] = updatedCache[idx].map((cachedEntry) => {
+      if (cachedEntry.transformType === mapEntry.value.transformType) {
+        return mapEntry.value;
+      } else {
+        return cachedEntry;
+      }
+    });
+  }
+  return updatedCache;
+}