Merge pull request #2143 from automatisch/AUT-1325
feat: introduce uniqueness validation for remote role name
This commit is contained in:
@@ -29,6 +29,8 @@ function ControlledAutocomplete(props) {
|
|||||||
options = [],
|
options = [],
|
||||||
dependsOn = [],
|
dependsOn = [],
|
||||||
showOptionValue,
|
showOptionValue,
|
||||||
|
renderInput,
|
||||||
|
showHelperText = true,
|
||||||
...autocompleteProps
|
...autocompleteProps
|
||||||
} = props;
|
} = props;
|
||||||
let dependsOnValues = [];
|
let dependsOnValues = [];
|
||||||
@@ -105,16 +107,18 @@ function ControlledAutocomplete(props) {
|
|||||||
)}
|
)}
|
||||||
</li>
|
</li>
|
||||||
)}
|
)}
|
||||||
|
renderInput={(params) => renderInput(params, fieldState)}
|
||||||
/>
|
/>
|
||||||
|
{showHelperText && (
|
||||||
<FormHelperText
|
<FormHelperText
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
error={Boolean(fieldState.isTouched && fieldState.error)}
|
error={Boolean(fieldState.isTouched && fieldState.error)}
|
||||||
>
|
>
|
||||||
{fieldState.isTouched
|
{fieldState.isTouched
|
||||||
? fieldState.error?.message || description
|
? fieldState.error?.message || description
|
||||||
: description}
|
: description}
|
||||||
</FormHelperText>
|
</FormHelperText>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -132,6 +136,8 @@ ControlledAutocomplete.propTypes = {
|
|||||||
onBlur: PropTypes.func,
|
onBlur: PropTypes.func,
|
||||||
onChange: PropTypes.func,
|
onChange: PropTypes.func,
|
||||||
options: PropTypes.array,
|
options: PropTypes.array,
|
||||||
|
renderInput: PropTypes.func.isRequired,
|
||||||
|
showHelperText: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ControlledAutocomplete;
|
export default ControlledAutocomplete;
|
||||||
|
@@ -13,12 +13,14 @@ function Form(props) {
|
|||||||
resolver,
|
resolver,
|
||||||
render,
|
render,
|
||||||
mode = 'all',
|
mode = 'all',
|
||||||
|
reValidateMode = 'onBlur',
|
||||||
|
automaticValidation = true,
|
||||||
...formProps
|
...formProps
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const methods = useForm({
|
const methods = useForm({
|
||||||
defaultValues,
|
defaultValues,
|
||||||
reValidateMode: 'onBlur',
|
reValidateMode,
|
||||||
resolver,
|
resolver,
|
||||||
mode,
|
mode,
|
||||||
});
|
});
|
||||||
@@ -30,7 +32,9 @@ function Form(props) {
|
|||||||
* For fields having `dependsOn` fields, we need to re-validate the form.
|
* For fields having `dependsOn` fields, we need to re-validate the form.
|
||||||
*/
|
*/
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
methods.trigger();
|
if (automaticValidation) {
|
||||||
|
methods.trigger();
|
||||||
|
}
|
||||||
}, [methods.trigger, form]);
|
}, [methods.trigger, form]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
@@ -56,6 +60,8 @@ Form.propTypes = {
|
|||||||
render: PropTypes.func,
|
render: PropTypes.func,
|
||||||
resolver: PropTypes.func,
|
resolver: PropTypes.func,
|
||||||
mode: PropTypes.oneOf(['onChange', 'onBlur', 'onSubmit', 'onTouched', 'all']),
|
mode: PropTypes.oneOf(['onChange', 'onBlur', 'onSubmit', 'onTouched', 'all']),
|
||||||
|
reValidateMode: PropTypes.oneOf(['onChange', 'onBlur', 'onSubmit']),
|
||||||
|
automaticValidation: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Form;
|
export default Form;
|
||||||
|
@@ -31,6 +31,7 @@ function TextField(props) {
|
|||||||
onBlur,
|
onBlur,
|
||||||
onChange,
|
onChange,
|
||||||
'data-test': dataTest,
|
'data-test': dataTest,
|
||||||
|
showError = false,
|
||||||
...textFieldProps
|
...textFieldProps
|
||||||
} = props;
|
} = props;
|
||||||
return (
|
return (
|
||||||
@@ -47,6 +48,7 @@ function TextField(props) {
|
|||||||
onBlur: controllerOnBlur,
|
onBlur: controllerOnBlur,
|
||||||
...field
|
...field
|
||||||
},
|
},
|
||||||
|
fieldState: { error },
|
||||||
}) => (
|
}) => (
|
||||||
<MuiTextField
|
<MuiTextField
|
||||||
{...textFieldProps}
|
{...textFieldProps}
|
||||||
@@ -72,6 +74,7 @@ function TextField(props) {
|
|||||||
inputProps={{
|
inputProps={{
|
||||||
'data-test': dataTest,
|
'data-test': dataTest,
|
||||||
}}
|
}}
|
||||||
|
{...(showError && { helperText: error?.message, error: !!error })}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -89,6 +92,7 @@ TextField.propTypes = {
|
|||||||
disabled: PropTypes.bool,
|
disabled: PropTypes.bool,
|
||||||
onBlur: PropTypes.func,
|
onBlur: PropTypes.func,
|
||||||
onChange: PropTypes.func,
|
onChange: PropTypes.func,
|
||||||
|
showError: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TextField;
|
export default TextField;
|
||||||
|
@@ -5,6 +5,8 @@ import Stack from '@mui/material/Stack';
|
|||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
|
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
|
import * as yup from 'yup';
|
||||||
|
|
||||||
import Form from 'components/Form';
|
import Form from 'components/Form';
|
||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
@@ -23,6 +25,42 @@ function generateFormRoleMappings(roleMappings) {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const uniqueRemoteRoleName = (array, context, formatMessage) => {
|
||||||
|
const seen = new Set();
|
||||||
|
for (const [index, value] of array.entries()) {
|
||||||
|
if (seen.has(value.remoteRoleName)) {
|
||||||
|
const path = `${context.path}[${index}].remoteRoleName`;
|
||||||
|
return context.createError({
|
||||||
|
message: `${formatMessage('roleMappingsForm.remoteRoleName')} must be unique`,
|
||||||
|
path,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
seen.add(value.remoteRoleName);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getValidationSchema = (formatMessage) =>
|
||||||
|
yup.object({
|
||||||
|
roleMappings: yup
|
||||||
|
.array()
|
||||||
|
.of(
|
||||||
|
yup.object({
|
||||||
|
roleId: yup
|
||||||
|
.string()
|
||||||
|
.required(`${formatMessage('roleMappingsForm.role')} is required`),
|
||||||
|
remoteRoleName: yup
|
||||||
|
.string()
|
||||||
|
.required(
|
||||||
|
`${formatMessage('roleMappingsForm.remoteRoleName')} is required`,
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.test('unique-remoteRoleName', '', (value, ctx) => {
|
||||||
|
return uniqueRemoteRoleName(value, ctx, formatMessage);
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
function RoleMappings({ provider, providerLoading }) {
|
function RoleMappings({ provider, providerLoading }) {
|
||||||
const formatMessage = useFormatMessage();
|
const formatMessage = useFormatMessage();
|
||||||
const enqueueSnackbar = useEnqueueSnackbar();
|
const enqueueSnackbar = useEnqueueSnackbar();
|
||||||
@@ -94,7 +132,15 @@ function RoleMappings({ provider, providerLoading }) {
|
|||||||
<Typography variant="h3">
|
<Typography variant="h3">
|
||||||
{formatMessage('roleMappingsForm.title')}
|
{formatMessage('roleMappingsForm.title')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Form defaultValues={defaultValues} onSubmit={handleRoleMappingsUpdate}>
|
<Form
|
||||||
|
defaultValues={defaultValues}
|
||||||
|
onSubmit={handleRoleMappingsUpdate}
|
||||||
|
resolver={yupResolver(getValidationSchema(formatMessage))}
|
||||||
|
mode="onSubmit"
|
||||||
|
reValidateMode="onChange"
|
||||||
|
noValidate
|
||||||
|
automaticValidation={false}
|
||||||
|
>
|
||||||
<Stack direction="column" spacing={2}>
|
<Stack direction="column" spacing={2}>
|
||||||
<RoleMappingsFieldArray />
|
<RoleMappingsFieldArray />
|
||||||
<LoadingButton
|
<LoadingButton
|
||||||
|
@@ -55,6 +55,7 @@ function RoleMappingsFieldArray() {
|
|||||||
label={formatMessage('roleMappingsForm.remoteRoleName')}
|
label={formatMessage('roleMappingsForm.remoteRoleName')}
|
||||||
fullWidth
|
fullWidth
|
||||||
required
|
required
|
||||||
|
showError
|
||||||
/>
|
/>
|
||||||
<ControlledAutocomplete
|
<ControlledAutocomplete
|
||||||
name={`roleMappings.${index}.roleId`}
|
name={`roleMappings.${index}.roleId`}
|
||||||
@@ -62,14 +63,17 @@ function RoleMappingsFieldArray() {
|
|||||||
disablePortal
|
disablePortal
|
||||||
disableClearable
|
disableClearable
|
||||||
options={generateRoleOptions(roles)}
|
options={generateRoleOptions(roles)}
|
||||||
renderInput={(params) => (
|
renderInput={(params, { error }) => (
|
||||||
<MuiTextField
|
<MuiTextField
|
||||||
{...params}
|
{...params}
|
||||||
label={formatMessage('roleMappingsForm.role')}
|
label={formatMessage('roleMappingsForm.role')}
|
||||||
required
|
required
|
||||||
|
error={!!error}
|
||||||
|
helperText={error?.message}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
loading={isRolesLoading}
|
loading={isRolesLoading}
|
||||||
|
showHelperText={false}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
<IconButton
|
<IconButton
|
||||||
|
Reference in New Issue
Block a user