Skip to content

Commit

Permalink
Improve and simplify processor form configs (opensearch-project#339)
Browse files Browse the repository at this point in the history
Signed-off-by: Tyler Ohlsen <[email protected]>
  • Loading branch information
ohltyler authored Sep 4, 2024
1 parent 82a50d7 commit 0f565a6
Show file tree
Hide file tree
Showing 10 changed files with 230 additions and 129 deletions.
1 change: 1 addition & 0 deletions common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,7 @@ export const DATE_FORMAT_PATTERN = 'MM/DD/YY hh:mm A';
export const EMPTY_FIELD_STRING = '--';
export const INDEX_NOT_FOUND_EXCEPTION = 'index_not_found_exception';
export const ERROR_GETTING_WORKFLOW_MSG = 'Failed to retrieve template';
export const NO_TEMPLATES_FOUND_MSG = 'There are no templates';
export const NO_MODIFICATIONS_FOUND_TEXT =
'Template does not contain any modifications';
export const JSONPATH_ROOT_SELECTOR = '$.';
Expand Down
4 changes: 3 additions & 1 deletion public/pages/workflow_detail/workflow_detail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
ERROR_GETTING_WORKFLOW_MSG,
FETCH_ALL_QUERY,
MAX_WORKFLOW_NAME_TO_DISPLAY,
NO_TEMPLATES_FOUND_MSG,
getCharacterLimitedString,
} from '../../../common';
import { MountPoint } from '../../../../../src/core/public';
Expand Down Expand Up @@ -105,7 +106,8 @@ export function WorkflowDetail(props: WorkflowDetailProps) {
dispatch(searchModels({ apiBody: FETCH_ALL_QUERY, dataSourceId }));
}, []);

return errorMessage.includes(ERROR_GETTING_WORKFLOW_MSG) ? (
return errorMessage.includes(ERROR_GETTING_WORKFLOW_MSG) ||
errorMessage.includes(NO_TEMPLATES_FOUND_MSG) ? (
<EuiFlexGroup direction="column" alignItems="center">
<EuiFlexItem grow={3}>
<EuiEmptyPrompt
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,6 @@ export function MapField(props: MapFieldProps) {
fieldPath={`${props.fieldPath}.${idx}.key`}
options={props.keyOptions as any[]}
placeholder={props.keyPlaceholder || 'Input'}
autofill={
props.keyOptions?.length === 1 && idx === 0
}
/>
) : (
<TextField
Expand All @@ -124,7 +121,7 @@ export function MapField(props: MapFieldProps) {
</EuiFlexItem>
<EuiFlexItem
grow={false}
style={{ marginTop: '14px' }}
style={{ marginTop: '10px' }}
>
<EuiIcon type="sortRight" />
</EuiFlexItem>
Expand All @@ -137,10 +134,6 @@ export function MapField(props: MapFieldProps) {
placeholder={
props.valuePlaceholder || 'Output'
}
autofill={
props.valueOptions?.length === 1 &&
idx === 0
}
/>
) : (
<TextField
Expand All @@ -158,7 +151,6 @@ export function MapField(props: MapFieldProps) {
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiSmallButtonIcon
style={{ marginTop: '8px' }}
iconType={'trash'}
color="danger"
aria-label="Delete"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ interface SelectWithCustomOptionsProps {
fieldPath: string;
placeholder: string;
options: any[];
autofill: boolean;
}

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

// update the selected option when the form is updated. if the form is empty,
// default to the top option. by default, this will re-trigger this hook with a populated
// value, to then finally update the displayed option.
// set the visible option when the underlying form is updated.
useEffect(() => {
if (props.autofill) {
const formValue = getIn(values, props.fieldPath);
if (!isEmpty(formValue)) {
setSelectedOption([{ label: getIn(values, props.fieldPath) }]);
} else {
if (props.options.length > 0) {
setFieldValue(props.fieldPath, props.options[0].label);
}
}
const formValue = getIn(values, props.fieldPath);
if (!isEmpty(formValue)) {
setSelectedOption([{ label: formValue }]);
}
}, [getIn(values, props.fieldPath)]);

Expand Down Expand Up @@ -73,7 +64,7 @@ export function SelectWithCustomOptions(props: SelectWithCustomOptionsProps) {
return (
<EuiComboBox
fullWidth={true}
compressed={false}
compressed={true}
placeholder={props.placeholder}
singleSelection={{ asPlainText: true }}
isClearable={false}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
JSONPATH_ROOT_SELECTOR,
ML_INFERENCE_DOCS_LINK,
MapArrayFormValue,
ModelInterface,
PROCESSOR_CONTEXT,
SearchHit,
SimulateIngestPipelineResponse,
Expand All @@ -47,7 +48,7 @@ import {
useAppDispatch,
} from '../../../../store';
import { getCore } from '../../../../services';
import { getDataSourceId } from '../../../../utils/utils';
import { getDataSourceId, parseModelInputs } from '../../../../utils/utils';
import { MapArrayField } from '../input_fields';

interface InputTransformModalProps {
Expand All @@ -56,7 +57,7 @@ interface InputTransformModalProps {
context: PROCESSOR_CONTEXT;
inputMapField: IConfigField;
inputMapFieldPath: string;
inputFields: any[];
modelInterface: ModelInterface | undefined;
onClose: () => void;
}

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

// TODO: integrated with Ajv to fetch any model interface and perform validation
// on the produced output on-the-fly. For examples, see
// https://www.npmjs.com/package/ajv

return (
<EuiModal onClose={props.onClose} style={{ width: '70vw' }}>
<EuiModalHeader>
Expand Down Expand Up @@ -254,7 +259,7 @@ export function InputTransformModal(props: InputTransformModalProps) {
? 'Query field'
: 'Document field'
}
keyOptions={props.inputFields}
keyOptions={parseModelInputs(props.modelInterface)}
// If the map we are adding is the first one, populate the selected option to index 0
onMapAdd={(curArray) => {
if (isEmpty(curArray)) {
Expand All @@ -274,15 +279,19 @@ export function InputTransformModal(props: InputTransformModalProps) {
</EuiFlexItem>
<EuiFlexItem>
<>
<EuiCompressedSelect
prepend={<EuiText>Expected output for</EuiText>}
options={outputOptions}
value={selectedOutputOption}
onChange={(e) => {
setSelectedOutputOption(Number(e.target.value));
setTransformedOutput('{}');
}}
/>
{outputOptions.length === 1 ? (
<EuiText>Expected output</EuiText>
) : (
<EuiCompressedSelect
prepend={<EuiText>Expected output for</EuiText>}
options={outputOptions}
value={selectedOutputOption}
onChange={(e) => {
setSelectedOutputOption(Number(e.target.value));
setTransformedOutput('{}');
}}
/>
)}
<EuiSpacer size="s" />
<EuiSmallButton
style={{ width: '100px' }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,9 @@ import {
PROCESSOR_CONTEXT,
WorkflowConfig,
JSONPATH_ROOT_SELECTOR,
ModelInputFormField,
ModelOutputFormField,
ML_INFERENCE_DOCS_LINK,
WorkflowFormValues,
ModelInterface,
} from '../../../../../common';
import { MapArrayField, ModelField } from '../input_fields';
import { isEmpty } from 'lodash';
Expand Down Expand Up @@ -108,9 +107,9 @@ export function MLProcessorInputs(props: MLProcessorInputsProps) {
>(false);

// model interface state
const [hasModelInterface, setHasModelInterface] = useState<boolean>(true);
const [inputFields, setInputFields] = useState<ModelInputFormField[]>([]);
const [outputFields, setOutputFields] = useState<ModelOutputFormField[]>([]);
const [modelInterface, setModelInterface] = useState<
ModelInterface | undefined
>(undefined);

// Hook to listen when the selected model has changed. We do a few checks here:
// 1: update model interface states
Expand All @@ -136,15 +135,7 @@ export function MLProcessorInputs(props: MLProcessorInputsProps) {
// reusable function to update interface states based on the model ID
function updateModelInterfaceStates(modelId: string) {
const newSelectedModel = models[modelId];
if (newSelectedModel?.interface !== undefined) {
setInputFields(parseModelInputs(newSelectedModel.interface));
setOutputFields(parseModelOutputs(newSelectedModel.interface));
setHasModelInterface(true);
} else {
setInputFields([]);
setOutputFields([]);
setHasModelInterface(false);
}
setModelInterface(newSelectedModel?.interface);
}

return (
Expand All @@ -156,7 +147,7 @@ export function MLProcessorInputs(props: MLProcessorInputsProps) {
context={props.context}
inputMapField={inputMapField}
inputMapFieldPath={inputMapFieldPath}
inputFields={inputFields}
modelInterface={modelInterface}
onClose={() => setIsInputTransformModalOpen(false)}
/>
)}
Expand All @@ -167,14 +158,14 @@ export function MLProcessorInputs(props: MLProcessorInputsProps) {
context={props.context}
outputMapField={outputMapField}
outputMapFieldPath={outputMapFieldPath}
outputFields={outputFields}
modelInterface={modelInterface}
onClose={() => setIsOutputTransformModalOpen(false)}
/>
)}
<ModelField
field={modelField}
fieldPath={modelFieldPath}
hasModelInterface={hasModelInterface}
hasModelInterface={modelInterface !== undefined}
onModelChange={onModelChange}
/>
{!isEmpty(getIn(values, modelFieldPath)?.id) && (
Expand Down Expand Up @@ -226,7 +217,7 @@ export function MLProcessorInputs(props: MLProcessorInputsProps) {
? 'Query field'
: 'Document field'
}
keyOptions={inputFields}
keyOptions={parseModelInputs(modelInterface)}
/>
<EuiSpacer size="l" />
<EuiFlexGroup direction="row">
Expand Down Expand Up @@ -274,7 +265,7 @@ export function MLProcessorInputs(props: MLProcessorInputsProps) {
: 'Document field'
}
valuePlaceholder="Model output field"
valueOptions={outputFields}
valueOptions={parseModelOutputs(modelInterface)}
/>
<EuiSpacer size="s" />
{inputMapValue.length !== outputMapValue.length &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
JSONPATH_ROOT_SELECTOR,
ML_INFERENCE_DOCS_LINK,
MapArrayFormValue,
ModelInterface,
PROCESSOR_CONTEXT,
SearchHit,
SearchPipelineConfig,
Expand All @@ -49,15 +50,15 @@ import {
} from '../../../../store';
import { getCore } from '../../../../services';
import { MapArrayField } from '../input_fields';
import { getDataSourceId } from '../../../../utils/utils';
import { getDataSourceId, parseModelOutputs } from '../../../../utils/utils';

interface OutputTransformModalProps {
uiConfig: WorkflowConfig;
config: IProcessorConfig;
context: PROCESSOR_CONTEXT;
outputMapField: IConfigField;
outputMapFieldPath: string;
outputFields: any[];
modelInterface: ModelInterface | undefined;
onClose: () => void;
}

Expand All @@ -79,7 +80,7 @@ export function OutputTransformModal(props: OutputTransformModalProps) {
// selected output state
const outputOptions = map.map((_, idx) => ({
value: idx,
text: `Prediction output ${idx + 1}`,
text: `Prediction ${idx + 1}`,
})) as EuiSelectOption[];
const [selectedOutputOption, setSelectedOutputOption] = useState<
number | undefined
Expand Down Expand Up @@ -237,7 +238,7 @@ export function OutputTransformModal(props: OutputTransformModalProps) {
helpLink={ML_INFERENCE_DOCS_LINK}
keyPlaceholder="Document field"
valuePlaceholder="Model output field"
valueOptions={props.outputFields}
valueOptions={parseModelOutputs(props.modelInterface)}
// If the map we are adding is the first one, populate the selected option to index 0
onMapAdd={(curArray) => {
if (isEmpty(curArray)) {
Expand All @@ -257,15 +258,19 @@ export function OutputTransformModal(props: OutputTransformModalProps) {
</EuiFlexItem>
<EuiFlexItem>
<>
<EuiCompressedSelect
prepend={<EuiText>Expected output for</EuiText>}
options={outputOptions}
value={selectedOutputOption}
onChange={(e) => {
setSelectedOutputOption(Number(e.target.value));
setTransformedOutput('{}');
}}
/>
{outputOptions.length === 1 ? (
<EuiText>Expected output</EuiText>
) : (
<EuiCompressedSelect
prepend={<EuiText>Expected output for</EuiText>}
options={outputOptions}
value={selectedOutputOption}
onChange={(e) => {
setSelectedOutputOption(Number(e.target.value));
setTransformedOutput('{}');
}}
/>
)}
<EuiSpacer size="s" />
<EuiSmallButton
style={{ width: '100px' }}
Expand Down
Loading

0 comments on commit 0f565a6

Please sign in to comment.