Skip to content

Commit 0f565a6

Browse files
authored
Improve and simplify processor form configs (#339)
Signed-off-by: Tyler Ohlsen <ohltyler@amazon.com>
1 parent 82a50d7 commit 0f565a6

File tree

10 files changed

+230
-129
lines changed

10 files changed

+230
-129
lines changed

common/constants.ts

+1
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,7 @@ export const DATE_FORMAT_PATTERN = 'MM/DD/YY hh:mm A';
396396
export const EMPTY_FIELD_STRING = '--';
397397
export const INDEX_NOT_FOUND_EXCEPTION = 'index_not_found_exception';
398398
export const ERROR_GETTING_WORKFLOW_MSG = 'Failed to retrieve template';
399+
export const NO_TEMPLATES_FOUND_MSG = 'There are no templates';
399400
export const NO_MODIFICATIONS_FOUND_TEXT =
400401
'Template does not contain any modifications';
401402
export const JSONPATH_ROOT_SELECTOR = '$.';

public/pages/workflow_detail/workflow_detail.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {
3030
ERROR_GETTING_WORKFLOW_MSG,
3131
FETCH_ALL_QUERY,
3232
MAX_WORKFLOW_NAME_TO_DISPLAY,
33+
NO_TEMPLATES_FOUND_MSG,
3334
getCharacterLimitedString,
3435
} from '../../../common';
3536
import { MountPoint } from '../../../../../src/core/public';
@@ -105,7 +106,8 @@ export function WorkflowDetail(props: WorkflowDetailProps) {
105106
dispatch(searchModels({ apiBody: FETCH_ALL_QUERY, dataSourceId }));
106107
}, []);
107108

108-
return errorMessage.includes(ERROR_GETTING_WORKFLOW_MSG) ? (
109+
return errorMessage.includes(ERROR_GETTING_WORKFLOW_MSG) ||
110+
errorMessage.includes(NO_TEMPLATES_FOUND_MSG) ? (
109111
<EuiFlexGroup direction="column" alignItems="center">
110112
<EuiFlexItem grow={3}>
111113
<EuiEmptyPrompt

public/pages/workflow_detail/workflow_inputs/input_fields/map_field.tsx

+1-9
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,6 @@ export function MapField(props: MapFieldProps) {
108108
fieldPath={`${props.fieldPath}.${idx}.key`}
109109
options={props.keyOptions as any[]}
110110
placeholder={props.keyPlaceholder || 'Input'}
111-
autofill={
112-
props.keyOptions?.length === 1 && idx === 0
113-
}
114111
/>
115112
) : (
116113
<TextField
@@ -124,7 +121,7 @@ export function MapField(props: MapFieldProps) {
124121
</EuiFlexItem>
125122
<EuiFlexItem
126123
grow={false}
127-
style={{ marginTop: '14px' }}
124+
style={{ marginTop: '10px' }}
128125
>
129126
<EuiIcon type="sortRight" />
130127
</EuiFlexItem>
@@ -137,10 +134,6 @@ export function MapField(props: MapFieldProps) {
137134
placeholder={
138135
props.valuePlaceholder || 'Output'
139136
}
140-
autofill={
141-
props.valueOptions?.length === 1 &&
142-
idx === 0
143-
}
144137
/>
145138
) : (
146139
<TextField
@@ -158,7 +151,6 @@ export function MapField(props: MapFieldProps) {
158151
</EuiFlexItem>
159152
<EuiFlexItem grow={false}>
160153
<EuiSmallButtonIcon
161-
style={{ marginTop: '8px' }}
162154
iconType={'trash'}
163155
color="danger"
164156
aria-label="Delete"

public/pages/workflow_detail/workflow_inputs/input_fields/select_with_custom_options.tsx

+5-14
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ interface SelectWithCustomOptionsProps {
1313
fieldPath: string;
1414
placeholder: string;
1515
options: any[];
16-
autofill: boolean;
1716
}
1817

1918
/**
@@ -27,19 +26,11 @@ export function SelectWithCustomOptions(props: SelectWithCustomOptionsProps) {
2726
// selected option state
2827
const [selectedOption, setSelectedOption] = useState<any[]>([]);
2928

30-
// update the selected option when the form is updated. if the form is empty,
31-
// default to the top option. by default, this will re-trigger this hook with a populated
32-
// value, to then finally update the displayed option.
29+
// set the visible option when the underlying form is updated.
3330
useEffect(() => {
34-
if (props.autofill) {
35-
const formValue = getIn(values, props.fieldPath);
36-
if (!isEmpty(formValue)) {
37-
setSelectedOption([{ label: getIn(values, props.fieldPath) }]);
38-
} else {
39-
if (props.options.length > 0) {
40-
setFieldValue(props.fieldPath, props.options[0].label);
41-
}
42-
}
31+
const formValue = getIn(values, props.fieldPath);
32+
if (!isEmpty(formValue)) {
33+
setSelectedOption([{ label: formValue }]);
4334
}
4435
}, [getIn(values, props.fieldPath)]);
4536

@@ -73,7 +64,7 @@ export function SelectWithCustomOptions(props: SelectWithCustomOptionsProps) {
7364
return (
7465
<EuiComboBox
7566
fullWidth={true}
76-
compressed={false}
67+
compressed={true}
7768
placeholder={props.placeholder}
7869
singleSelection={{ asPlainText: true }}
7970
isClearable={false}

public/pages/workflow_detail/workflow_inputs/processor_inputs/input_transform_modal.tsx

+21-12
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
JSONPATH_ROOT_SELECTOR,
2929
ML_INFERENCE_DOCS_LINK,
3030
MapArrayFormValue,
31+
ModelInterface,
3132
PROCESSOR_CONTEXT,
3233
SearchHit,
3334
SimulateIngestPipelineResponse,
@@ -47,7 +48,7 @@ import {
4748
useAppDispatch,
4849
} from '../../../../store';
4950
import { getCore } from '../../../../services';
50-
import { getDataSourceId } from '../../../../utils/utils';
51+
import { getDataSourceId, parseModelInputs } from '../../../../utils/utils';
5152
import { MapArrayField } from '../input_fields';
5253

5354
interface InputTransformModalProps {
@@ -56,7 +57,7 @@ interface InputTransformModalProps {
5657
context: PROCESSOR_CONTEXT;
5758
inputMapField: IConfigField;
5859
inputMapFieldPath: string;
59-
inputFields: any[];
60+
modelInterface: ModelInterface | undefined;
6061
onClose: () => void;
6162
}
6263

@@ -88,6 +89,10 @@ export function InputTransformModal(props: InputTransformModalProps) {
8889
number | undefined
8990
>((outputOptions[0]?.value as number) ?? undefined);
9091

92+
// TODO: integrated with Ajv to fetch any model interface and perform validation
93+
// on the produced output on-the-fly. For examples, see
94+
// https://www.npmjs.com/package/ajv
95+
9196
return (
9297
<EuiModal onClose={props.onClose} style={{ width: '70vw' }}>
9398
<EuiModalHeader>
@@ -254,7 +259,7 @@ export function InputTransformModal(props: InputTransformModalProps) {
254259
? 'Query field'
255260
: 'Document field'
256261
}
257-
keyOptions={props.inputFields}
262+
keyOptions={parseModelInputs(props.modelInterface)}
258263
// If the map we are adding is the first one, populate the selected option to index 0
259264
onMapAdd={(curArray) => {
260265
if (isEmpty(curArray)) {
@@ -274,15 +279,19 @@ export function InputTransformModal(props: InputTransformModalProps) {
274279
</EuiFlexItem>
275280
<EuiFlexItem>
276281
<>
277-
<EuiCompressedSelect
278-
prepend={<EuiText>Expected output for</EuiText>}
279-
options={outputOptions}
280-
value={selectedOutputOption}
281-
onChange={(e) => {
282-
setSelectedOutputOption(Number(e.target.value));
283-
setTransformedOutput('{}');
284-
}}
285-
/>
282+
{outputOptions.length === 1 ? (
283+
<EuiText>Expected output</EuiText>
284+
) : (
285+
<EuiCompressedSelect
286+
prepend={<EuiText>Expected output for</EuiText>}
287+
options={outputOptions}
288+
value={selectedOutputOption}
289+
onChange={(e) => {
290+
setSelectedOutputOption(Number(e.target.value));
291+
setTransformedOutput('{}');
292+
}}
293+
/>
294+
)}
286295
<EuiSpacer size="s" />
287296
<EuiSmallButton
288297
style={{ width: '100px' }}

public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs.tsx

+10-19
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,9 @@ import {
2222
PROCESSOR_CONTEXT,
2323
WorkflowConfig,
2424
JSONPATH_ROOT_SELECTOR,
25-
ModelInputFormField,
26-
ModelOutputFormField,
2725
ML_INFERENCE_DOCS_LINK,
2826
WorkflowFormValues,
27+
ModelInterface,
2928
} from '../../../../../common';
3029
import { MapArrayField, ModelField } from '../input_fields';
3130
import { isEmpty } from 'lodash';
@@ -108,9 +107,9 @@ export function MLProcessorInputs(props: MLProcessorInputsProps) {
108107
>(false);
109108

110109
// model interface state
111-
const [hasModelInterface, setHasModelInterface] = useState<boolean>(true);
112-
const [inputFields, setInputFields] = useState<ModelInputFormField[]>([]);
113-
const [outputFields, setOutputFields] = useState<ModelOutputFormField[]>([]);
110+
const [modelInterface, setModelInterface] = useState<
111+
ModelInterface | undefined
112+
>(undefined);
114113

115114
// Hook to listen when the selected model has changed. We do a few checks here:
116115
// 1: update model interface states
@@ -136,15 +135,7 @@ export function MLProcessorInputs(props: MLProcessorInputsProps) {
136135
// reusable function to update interface states based on the model ID
137136
function updateModelInterfaceStates(modelId: string) {
138137
const newSelectedModel = models[modelId];
139-
if (newSelectedModel?.interface !== undefined) {
140-
setInputFields(parseModelInputs(newSelectedModel.interface));
141-
setOutputFields(parseModelOutputs(newSelectedModel.interface));
142-
setHasModelInterface(true);
143-
} else {
144-
setInputFields([]);
145-
setOutputFields([]);
146-
setHasModelInterface(false);
147-
}
138+
setModelInterface(newSelectedModel?.interface);
148139
}
149140

150141
return (
@@ -156,7 +147,7 @@ export function MLProcessorInputs(props: MLProcessorInputsProps) {
156147
context={props.context}
157148
inputMapField={inputMapField}
158149
inputMapFieldPath={inputMapFieldPath}
159-
inputFields={inputFields}
150+
modelInterface={modelInterface}
160151
onClose={() => setIsInputTransformModalOpen(false)}
161152
/>
162153
)}
@@ -167,14 +158,14 @@ export function MLProcessorInputs(props: MLProcessorInputsProps) {
167158
context={props.context}
168159
outputMapField={outputMapField}
169160
outputMapFieldPath={outputMapFieldPath}
170-
outputFields={outputFields}
161+
modelInterface={modelInterface}
171162
onClose={() => setIsOutputTransformModalOpen(false)}
172163
/>
173164
)}
174165
<ModelField
175166
field={modelField}
176167
fieldPath={modelFieldPath}
177-
hasModelInterface={hasModelInterface}
168+
hasModelInterface={modelInterface !== undefined}
178169
onModelChange={onModelChange}
179170
/>
180171
{!isEmpty(getIn(values, modelFieldPath)?.id) && (
@@ -226,7 +217,7 @@ export function MLProcessorInputs(props: MLProcessorInputsProps) {
226217
? 'Query field'
227218
: 'Document field'
228219
}
229-
keyOptions={inputFields}
220+
keyOptions={parseModelInputs(modelInterface)}
230221
/>
231222
<EuiSpacer size="l" />
232223
<EuiFlexGroup direction="row">
@@ -274,7 +265,7 @@ export function MLProcessorInputs(props: MLProcessorInputsProps) {
274265
: 'Document field'
275266
}
276267
valuePlaceholder="Model output field"
277-
valueOptions={outputFields}
268+
valueOptions={parseModelOutputs(modelInterface)}
278269
/>
279270
<EuiSpacer size="s" />
280271
{inputMapValue.length !== outputMapValue.length &&

public/pages/workflow_detail/workflow_inputs/processor_inputs/output_transform_modal.tsx

+18-13
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
JSONPATH_ROOT_SELECTOR,
2929
ML_INFERENCE_DOCS_LINK,
3030
MapArrayFormValue,
31+
ModelInterface,
3132
PROCESSOR_CONTEXT,
3233
SearchHit,
3334
SearchPipelineConfig,
@@ -49,15 +50,15 @@ import {
4950
} from '../../../../store';
5051
import { getCore } from '../../../../services';
5152
import { MapArrayField } from '../input_fields';
52-
import { getDataSourceId } from '../../../../utils/utils';
53+
import { getDataSourceId, parseModelOutputs } from '../../../../utils/utils';
5354

5455
interface OutputTransformModalProps {
5556
uiConfig: WorkflowConfig;
5657
config: IProcessorConfig;
5758
context: PROCESSOR_CONTEXT;
5859
outputMapField: IConfigField;
5960
outputMapFieldPath: string;
60-
outputFields: any[];
61+
modelInterface: ModelInterface | undefined;
6162
onClose: () => void;
6263
}
6364

@@ -79,7 +80,7 @@ export function OutputTransformModal(props: OutputTransformModalProps) {
7980
// selected output state
8081
const outputOptions = map.map((_, idx) => ({
8182
value: idx,
82-
text: `Prediction output ${idx + 1}`,
83+
text: `Prediction ${idx + 1}`,
8384
})) as EuiSelectOption[];
8485
const [selectedOutputOption, setSelectedOutputOption] = useState<
8586
number | undefined
@@ -237,7 +238,7 @@ export function OutputTransformModal(props: OutputTransformModalProps) {
237238
helpLink={ML_INFERENCE_DOCS_LINK}
238239
keyPlaceholder="Document field"
239240
valuePlaceholder="Model output field"
240-
valueOptions={props.outputFields}
241+
valueOptions={parseModelOutputs(props.modelInterface)}
241242
// If the map we are adding is the first one, populate the selected option to index 0
242243
onMapAdd={(curArray) => {
243244
if (isEmpty(curArray)) {
@@ -257,15 +258,19 @@ export function OutputTransformModal(props: OutputTransformModalProps) {
257258
</EuiFlexItem>
258259
<EuiFlexItem>
259260
<>
260-
<EuiCompressedSelect
261-
prepend={<EuiText>Expected output for</EuiText>}
262-
options={outputOptions}
263-
value={selectedOutputOption}
264-
onChange={(e) => {
265-
setSelectedOutputOption(Number(e.target.value));
266-
setTransformedOutput('{}');
267-
}}
268-
/>
261+
{outputOptions.length === 1 ? (
262+
<EuiText>Expected output</EuiText>
263+
) : (
264+
<EuiCompressedSelect
265+
prepend={<EuiText>Expected output for</EuiText>}
266+
options={outputOptions}
267+
value={selectedOutputOption}
268+
onChange={(e) => {
269+
setSelectedOutputOption(Number(e.target.value));
270+
setTransformedOutput('{}');
271+
}}
272+
/>
273+
)}
269274
<EuiSpacer size="s" />
270275
<EuiSmallButton
271276
style={{ width: '100px' }}

0 commit comments

Comments
 (0)