Skip to content

Commit

Permalink
Add validation; fix multi-modal cache issue
Browse files Browse the repository at this point in the history
Signed-off-by: Tyler Ohlsen <[email protected]>
  • Loading branch information
ohltyler committed Nov 27, 2024
1 parent 4354ea8 commit 566fe16
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import React, { useEffect, useState } from 'react';
import { useFormikContext, getIn, Formik } from 'formik';
import { isEmpty } from 'lodash';
import * as yup from 'yup';
import Ajv from 'ajv';
import {
EuiCodeEditor,
EuiFlexGroup,
Expand All @@ -20,6 +21,7 @@ import {
EuiText,
EuiSmallButtonEmpty,
EuiSpacer,
EuiIconTip,
} from '@elastic/eui';
import {
customStringify,
Expand Down Expand Up @@ -74,6 +76,7 @@ const MAX_INPUT_DOCS = 10;

/**
* A modal to configure a JSONPath expression / transform. Used for configuring model input transforms.
* Performs field-level validation, if the configured field is available in the model interface.
*/
export function ConfigureExpressionModal(props: ConfigureExpressionModalProps) {
const dispatch = useAppDispatch();
Expand Down Expand Up @@ -124,6 +127,10 @@ export function ConfigureExpressionModal(props: ConfigureExpressionModalProps) {
// button updating state
const [isUpdating, setIsUpdating] = useState<boolean>(false);

// validation state utilizing the model interface, if applicable. undefined if
// there is no model interface and/or no source input
const [isValid, setIsValid] = useState<boolean | undefined>(undefined);

// source input / transformed input state
const [sourceInput, setSourceInput] = useState<string>('{}');
const [transformedInput, setTransformedInput] = useState<string>('{}');
Expand Down Expand Up @@ -173,6 +180,47 @@ export function ConfigureExpressionModal(props: ConfigureExpressionModalProps) {
}
}, [tempExpression, sourceInput]);

// hook to re-determine validity when the generated output changes
// utilize Ajv JSON schema validator library. For more info/examples, see
// https://www.npmjs.com/package/ajv
useEffect(() => {
if (
!isEmpty(JSON.parse(sourceInput)) &&
!isEmpty(props.modelInterface?.input?.properties?.parameters) &&
!isEmpty(
getIn(
props.modelInterface?.input?.properties?.parameters?.properties,
props.modelInputFieldName
)
)
) {
// we customize the model interface JSON schema to just
// include the field we are transforming. Overriding any
// other config fields that could make this unnecessarily fail
// (required, additionalProperties, etc.)
try {
const customJSONSchema = {
...props.modelInterface?.input?.properties?.parameters,
properties: {
[props.modelInputFieldName]: getIn(
props.modelInterface?.input?.properties?.parameters.properties,
props.modelInputFieldName
),
},
required: [],
additionalProperties: true,
};

const validateFn = new Ajv().compile(customJSONSchema);
setIsValid(validateFn(JSON.parse(transformedInput)));
} catch {
setIsValid(undefined);
}
} else {
setIsValid(undefined);
}
}, [transformedInput]);

// if updating, take the temp vars and assign it to the parent form
function onUpdate() {
setIsUpdating(true);
Expand Down Expand Up @@ -209,7 +257,11 @@ export function ConfigureExpressionModal(props: ConfigureExpressionModalProps) {
}, [formikProps.errors]);

return (
<EuiModal onClose={props.onClose} style={{ width: '70vw' }}>
<EuiModal
onClose={props.onClose}
style={{ width: '70vw' }}
id={props.fieldPath}
>
<EuiModalHeader>
<EuiModalHeaderTitle>
<p>{`Extract data with expression`}</p>
Expand Down Expand Up @@ -438,7 +490,31 @@ export function ConfigureExpressionModal(props: ConfigureExpressionModalProps) {
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="s">Extracted data</EuiText>
<EuiFlexGroup direction="row" justifyContent="flexStart">
<EuiFlexItem grow={false}>
<EuiText size="s">Extracted data</EuiText>
</EuiFlexItem>
{isValid !== undefined && (
<EuiFlexItem
grow={false}
style={{
marginTop: '14px',
marginLeft: '-4px',
}}
>
<EuiIconTip
type={isValid ? 'check' : 'cross'}
color={isValid ? 'success' : 'danger'}
size="m"
content={
isValid
? 'Meets model interface requirements'
: 'Does not meet model interface requirements'
}
/>
</EuiFlexItem>
)}
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiCodeEditor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,12 @@ export function ModelInputs(props: ModelInputsProps) {
>(undefined);

// various modal states
const [isTemplateModalOpen, setIsTemplateModalOpen] = useState<boolean>(
false
);
const [isExpressionModalOpen, setIsExpressionModalOpen] = useState<boolean>(
false
const [templateModalIdx, setTemplateModalIdx] = useState<number | undefined>(
undefined
);
const [expressionModalIdx, setExpressionModalIdx] = useState<
number | undefined
>(undefined);

// on initial load of the models, update model interface states
useEffect(() => {
Expand Down Expand Up @@ -372,7 +372,7 @@ export function ModelInputs(props: ModelInputsProps) {
* Conditionally render the value form component based on the transform type.
* It may be a button, dropdown, or simply freeform text.
*/}
{isTemplateModalOpen && (
{templateModalIdx === (idx as number) && (
<ConfigureTemplateModal
config={props.config}
baseConfigPath={props.baseConfigPath}
Expand All @@ -384,11 +384,11 @@ export function ModelInputs(props: ModelInputsProps) {
props.isDataFetchingAvailable
}
onClose={() =>
setIsTemplateModalOpen(false)
setTemplateModalIdx(undefined)
}
/>
)}
{isExpressionModalOpen && (
{expressionModalIdx === (idx as number) && (
<ConfigureExpressionModal
config={props.config}
baseConfigPath={props.baseConfigPath}
Expand All @@ -404,7 +404,7 @@ export function ModelInputs(props: ModelInputsProps) {
props.isDataFetchingAvailable
}
onClose={() =>
setIsExpressionModalOpen(false)
setExpressionModalIdx(undefined)
}
/>
)}
Expand All @@ -423,7 +423,7 @@ export function ModelInputs(props: ModelInputsProps) {
style={{ width: '100px' }}
fill={false}
onClick={() =>
setIsTemplateModalOpen(true)
setTemplateModalIdx(idx)
}
data-testid="configureTemplateButton"
>
Expand Down Expand Up @@ -459,11 +459,9 @@ export function ModelInputs(props: ModelInputsProps) {
iconType="pencil"
disabled={false}
color={'primary'}
onClick={() => {
setIsTemplateModalOpen(
true
);
}}
onClick={() =>
setTemplateModalIdx(idx)
}
/>
</EuiFlexItem>
</EuiFlexGroup>
Expand All @@ -482,7 +480,7 @@ export function ModelInputs(props: ModelInputsProps) {
style={{ width: '100px' }}
fill={false}
onClick={() =>
setIsExpressionModalOpen(true)
setExpressionModalIdx(idx)
}
data-testid="configureExpressionButton"
>
Expand Down Expand Up @@ -518,11 +516,9 @@ export function ModelInputs(props: ModelInputsProps) {
iconType="pencil"
disabled={false}
color={'primary'}
onClick={() => {
setIsExpressionModalOpen(
true
);
}}
onClick={() =>
setExpressionModalIdx(idx)
}
/>
</EuiFlexItem>
</EuiFlexGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,9 @@ export function ModelOutputs(props: ModelOutputsProps) {
);

// various modal states
const [isExpressionsModalOpen, setIsExpressionsModalOpen] = useState<boolean>(
false
);
const [expressionsModalIdx, setExpressionsModalIdx] = useState<
number | undefined
>(undefined);

// model interface state
const [modelInterface, setModelInterface] = useState<
Expand Down Expand Up @@ -293,7 +293,8 @@ export function ModelOutputs(props: ModelOutputsProps) {
* Conditionally render the value form component based on the transform type.
* It may be a button, dropdown, or simply freeform text.
*/}
{isExpressionsModalOpen && (
{expressionsModalIdx ===
(idx as number) && (
<ConfigureMultiExpressionModal
config={props.config}
baseConfigPath={props.baseConfigPath}
Expand All @@ -311,7 +312,7 @@ export function ModelOutputs(props: ModelOutputsProps) {
props.isDataFetchingAvailable
}
onClose={() =>
setIsExpressionsModalOpen(false)
setExpressionsModalIdx(undefined)
}
/>
)}
Expand All @@ -330,9 +331,7 @@ export function ModelOutputs(props: ModelOutputsProps) {
style={{ width: '100px' }}
fill={false}
onClick={() =>
setIsExpressionsModalOpen(
true
)
setExpressionsModalIdx(idx)
}
data-testid="configureExpressionsButton"
>
Expand Down Expand Up @@ -384,8 +383,8 @@ export function ModelOutputs(props: ModelOutputsProps) {
disabled={false}
color={'primary'}
onClick={() => {
setIsExpressionsModalOpen(
true
setExpressionsModalIdx(
idx
);
}}
/>
Expand Down

0 comments on commit 566fe16

Please sign in to comment.