feat: add dynamic fields to validation schema and debug problem with dependsOn field
This commit is contained in:
@@ -11,9 +11,6 @@ import IconButton from '@mui/material/IconButton';
|
|||||||
import ErrorIcon from '@mui/icons-material/Error';
|
import ErrorIcon from '@mui/icons-material/Error';
|
||||||
import CircularProgress from '@mui/material/CircularProgress';
|
import CircularProgress from '@mui/material/CircularProgress';
|
||||||
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
|
||||||
import * as yup from 'yup';
|
|
||||||
|
|
||||||
import { EditorContext } from 'contexts/Editor';
|
import { EditorContext } from 'contexts/Editor';
|
||||||
import { StepExecutionsProvider } from 'contexts/StepExecutions';
|
import { StepExecutionsProvider } from 'contexts/StepExecutions';
|
||||||
import TestSubstep from 'components/TestSubstep';
|
import TestSubstep from 'components/TestSubstep';
|
||||||
@@ -33,77 +30,17 @@ import {
|
|||||||
Header,
|
Header,
|
||||||
Wrapper,
|
Wrapper,
|
||||||
} from './style';
|
} from './style';
|
||||||
import isEmpty from 'helpers/isEmpty';
|
|
||||||
import { StepPropType } from 'propTypes/propTypes';
|
import { StepPropType } from 'propTypes/propTypes';
|
||||||
import useTriggers from 'hooks/useTriggers';
|
import useTriggers from 'hooks/useTriggers';
|
||||||
import useActions from 'hooks/useActions';
|
import useActions from 'hooks/useActions';
|
||||||
import useTriggerSubsteps from 'hooks/useTriggerSubsteps';
|
import useTriggerSubsteps from 'hooks/useTriggerSubsteps';
|
||||||
import useActionSubsteps from 'hooks/useActionSubsteps';
|
import useActionSubsteps from 'hooks/useActionSubsteps';
|
||||||
import useStepWithTestExecutions from 'hooks/useStepWithTestExecutions';
|
import useStepWithTestExecutions from 'hooks/useStepWithTestExecutions';
|
||||||
|
import { generateValidationSchema } from './validation';
|
||||||
|
|
||||||
const validIcon = <CheckCircleIcon color="success" />;
|
const validIcon = <CheckCircleIcon color="success" />;
|
||||||
const errorIcon = <ErrorIcon color="error" />;
|
const errorIcon = <ErrorIcon color="error" />;
|
||||||
|
|
||||||
function generateValidationSchema(substeps) {
|
|
||||||
const fieldValidations = substeps?.reduce(
|
|
||||||
(allValidations, { arguments: args }) => {
|
|
||||||
if (!args || !Array.isArray(args)) return allValidations;
|
|
||||||
const substepArgumentValidations = {};
|
|
||||||
for (const arg of args) {
|
|
||||||
const { key, required } = arg;
|
|
||||||
// base validation for the field if not exists
|
|
||||||
if (!substepArgumentValidations[key]) {
|
|
||||||
substepArgumentValidations[key] = yup.mixed();
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
typeof substepArgumentValidations[key] === 'object' &&
|
|
||||||
(arg.type === 'string' || arg.type === 'dropdown')
|
|
||||||
) {
|
|
||||||
// if the field is required, add the required validation
|
|
||||||
if (required) {
|
|
||||||
substepArgumentValidations[key] = substepArgumentValidations[key]
|
|
||||||
.required(`${key} is required.`)
|
|
||||||
.test(
|
|
||||||
'empty-check',
|
|
||||||
`${key} must be not empty`,
|
|
||||||
(value) => !isEmpty(value),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// if the field depends on another field, add the dependsOn required validation
|
|
||||||
if (Array.isArray(arg.dependsOn) && arg.dependsOn.length > 0) {
|
|
||||||
for (const dependsOnKey of arg.dependsOn) {
|
|
||||||
const missingDependencyValueMessage = `We're having trouble loading '${key}' data as required field '${dependsOnKey}' is missing.`;
|
|
||||||
// TODO: make `dependsOnKey` agnostic to the field. However, nested validation schema is not supported.
|
|
||||||
// So the fields under the `parameters` key are subject to their siblings only and thus, `parameters.` is removed.
|
|
||||||
substepArgumentValidations[key] = substepArgumentValidations[
|
|
||||||
key
|
|
||||||
].when(`${dependsOnKey.replace('parameters.', '')}`, {
|
|
||||||
is: (value) => Boolean(value) === false,
|
|
||||||
then: (schema) =>
|
|
||||||
schema
|
|
||||||
.notOneOf([''], missingDependencyValueMessage)
|
|
||||||
.required(missingDependencyValueMessage),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...allValidations,
|
|
||||||
...substepArgumentValidations,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
{},
|
|
||||||
);
|
|
||||||
|
|
||||||
const validationSchema = yup.object({
|
|
||||||
parameters: yup.object(fieldValidations),
|
|
||||||
});
|
|
||||||
|
|
||||||
return yupResolver(validationSchema);
|
|
||||||
}
|
|
||||||
|
|
||||||
function FlowStep(props) {
|
function FlowStep(props) {
|
||||||
const { collapsed, onChange, onContinue, flowId } = props;
|
const { collapsed, onChange, onContinue, flowId } = props;
|
||||||
const editorContext = React.useContext(EditorContext);
|
const editorContext = React.useContext(EditorContext);
|
||||||
|
114
packages/web/src/components/FlowStep/validation.js
Normal file
114
packages/web/src/components/FlowStep/validation.js
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
import * as yup from 'yup';
|
||||||
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
|
import isEmpty from 'helpers/isEmpty';
|
||||||
|
|
||||||
|
function addRequiredValidation({ required, schema, key }) {
|
||||||
|
// if the field is required, add the required validation
|
||||||
|
if (required) {
|
||||||
|
return schema
|
||||||
|
.required(`${key} is required.`)
|
||||||
|
.test(
|
||||||
|
'empty-check',
|
||||||
|
`${key} must be not empty`,
|
||||||
|
(value) => !isEmpty(value),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addDependsOnValidation({ schema, dependsOn, key, args }) {
|
||||||
|
// if the field depends on another field, add the dependsOn required validation
|
||||||
|
if (Array.isArray(dependsOn) && dependsOn.length > 0) {
|
||||||
|
for (const dependsOnKey of dependsOn) {
|
||||||
|
const dependsOnKeyShort = dependsOnKey.replace('parameters.', '');
|
||||||
|
const dependsOnField = args.find(({ key }) => key === dependsOnKeyShort);
|
||||||
|
|
||||||
|
if (dependsOnField?.required) {
|
||||||
|
const missingDependencyValueMessage = `We're having trouble loading '${key}' data as required field '${dependsOnKey}' is missing.`;
|
||||||
|
|
||||||
|
// TODO: make `dependsOnKey` agnostic to the field. However, nested validation schema is not supported.
|
||||||
|
// So the fields under the `parameters` key are subject to their siblings only and thus, `parameters.` is removed.
|
||||||
|
return schema.when(dependsOnKeyShort, {
|
||||||
|
is: (dependsOnValue) => Boolean(dependsOnValue) === false,
|
||||||
|
then: (schema) =>
|
||||||
|
schema
|
||||||
|
.notOneOf([''], missingDependencyValueMessage)
|
||||||
|
.required(missingDependencyValueMessage),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateValidationSchema(substeps) {
|
||||||
|
const fieldValidations = substeps?.reduce(
|
||||||
|
(allValidations, { arguments: args }) => {
|
||||||
|
if (!args || !Array.isArray(args)) return allValidations;
|
||||||
|
|
||||||
|
const substepArgumentValidations = {};
|
||||||
|
|
||||||
|
for (const arg of args) {
|
||||||
|
const { key, required } = arg;
|
||||||
|
|
||||||
|
// base validation for the field if not exists
|
||||||
|
if (!substepArgumentValidations[key]) {
|
||||||
|
substepArgumentValidations[key] = yup.mixed();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg.type === 'dynamic') {
|
||||||
|
const fieldsSchema = {};
|
||||||
|
|
||||||
|
for (const field of arg.fields) {
|
||||||
|
fieldsSchema[field.key] = yup.mixed();
|
||||||
|
|
||||||
|
fieldsSchema[field.key] = addRequiredValidation({
|
||||||
|
required: field.required,
|
||||||
|
schema: fieldsSchema[field.key],
|
||||||
|
key: field.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
fieldsSchema[field.key] = addDependsOnValidation({
|
||||||
|
schema: fieldsSchema[field.key],
|
||||||
|
dependsOn: field.dependsOn,
|
||||||
|
key: field.key,
|
||||||
|
args,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
substepArgumentValidations[key] = yup
|
||||||
|
.array()
|
||||||
|
.of(yup.object(fieldsSchema));
|
||||||
|
} else if (
|
||||||
|
typeof substepArgumentValidations[key] === 'object' &&
|
||||||
|
(arg.type === 'string' || arg.type === 'dropdown')
|
||||||
|
) {
|
||||||
|
substepArgumentValidations[key] = addRequiredValidation({
|
||||||
|
required,
|
||||||
|
schema: substepArgumentValidations[key],
|
||||||
|
key,
|
||||||
|
});
|
||||||
|
|
||||||
|
substepArgumentValidations[key] = addDependsOnValidation({
|
||||||
|
schema: substepArgumentValidations[key],
|
||||||
|
dependsOn: arg.dependsOn,
|
||||||
|
key,
|
||||||
|
args,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...allValidations,
|
||||||
|
...substepArgumentValidations,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
||||||
|
const validationSchema = yup.object({
|
||||||
|
parameters: yup.object(fieldValidations),
|
||||||
|
});
|
||||||
|
|
||||||
|
return yupResolver(validationSchema);
|
||||||
|
}
|
Reference in New Issue
Block a user