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 = [],
|
||||
dependsOn = [],
|
||||
showOptionValue,
|
||||
renderInput,
|
||||
showHelperText = true,
|
||||
...autocompleteProps
|
||||
} = props;
|
||||
let dependsOnValues = [];
|
||||
@@ -105,16 +107,18 @@ function ControlledAutocomplete(props) {
|
||||
)}
|
||||
</li>
|
||||
)}
|
||||
renderInput={(params) => renderInput(params, fieldState)}
|
||||
/>
|
||||
|
||||
<FormHelperText
|
||||
variant="outlined"
|
||||
error={Boolean(fieldState.isTouched && fieldState.error)}
|
||||
>
|
||||
{fieldState.isTouched
|
||||
? fieldState.error?.message || description
|
||||
: description}
|
||||
</FormHelperText>
|
||||
{showHelperText && (
|
||||
<FormHelperText
|
||||
variant="outlined"
|
||||
error={Boolean(fieldState.isTouched && fieldState.error)}
|
||||
>
|
||||
{fieldState.isTouched
|
||||
? fieldState.error?.message || description
|
||||
: description}
|
||||
</FormHelperText>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
@@ -132,6 +136,8 @@ ControlledAutocomplete.propTypes = {
|
||||
onBlur: PropTypes.func,
|
||||
onChange: PropTypes.func,
|
||||
options: PropTypes.array,
|
||||
renderInput: PropTypes.func.isRequired,
|
||||
showHelperText: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default ControlledAutocomplete;
|
||||
|
@@ -13,12 +13,14 @@ function Form(props) {
|
||||
resolver,
|
||||
render,
|
||||
mode = 'all',
|
||||
reValidateMode = 'onBlur',
|
||||
automaticValidation = true,
|
||||
...formProps
|
||||
} = props;
|
||||
|
||||
const methods = useForm({
|
||||
defaultValues,
|
||||
reValidateMode: 'onBlur',
|
||||
reValidateMode,
|
||||
resolver,
|
||||
mode,
|
||||
});
|
||||
@@ -30,7 +32,9 @@ function Form(props) {
|
||||
* For fields having `dependsOn` fields, we need to re-validate the form.
|
||||
*/
|
||||
React.useEffect(() => {
|
||||
methods.trigger();
|
||||
if (automaticValidation) {
|
||||
methods.trigger();
|
||||
}
|
||||
}, [methods.trigger, form]);
|
||||
|
||||
React.useEffect(() => {
|
||||
@@ -56,6 +60,8 @@ Form.propTypes = {
|
||||
render: PropTypes.func,
|
||||
resolver: PropTypes.func,
|
||||
mode: PropTypes.oneOf(['onChange', 'onBlur', 'onSubmit', 'onTouched', 'all']),
|
||||
reValidateMode: PropTypes.oneOf(['onChange', 'onBlur', 'onSubmit']),
|
||||
automaticValidation: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default Form;
|
||||
|
@@ -31,6 +31,7 @@ function TextField(props) {
|
||||
onBlur,
|
||||
onChange,
|
||||
'data-test': dataTest,
|
||||
showError = false,
|
||||
...textFieldProps
|
||||
} = props;
|
||||
return (
|
||||
@@ -47,6 +48,7 @@ function TextField(props) {
|
||||
onBlur: controllerOnBlur,
|
||||
...field
|
||||
},
|
||||
fieldState: { error },
|
||||
}) => (
|
||||
<MuiTextField
|
||||
{...textFieldProps}
|
||||
@@ -72,6 +74,7 @@ function TextField(props) {
|
||||
inputProps={{
|
||||
'data-test': dataTest,
|
||||
}}
|
||||
{...(showError && { helperText: error?.message, error: !!error })}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
@@ -89,6 +92,7 @@ TextField.propTypes = {
|
||||
disabled: PropTypes.bool,
|
||||
onBlur: PropTypes.func,
|
||||
onChange: PropTypes.func,
|
||||
showError: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default TextField;
|
||||
|
@@ -5,6 +5,8 @@ import Stack from '@mui/material/Stack';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
|
||||
import { useMemo } from 'react';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import * as yup from 'yup';
|
||||
|
||||
import Form from 'components/Form';
|
||||
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 }) {
|
||||
const formatMessage = useFormatMessage();
|
||||
const enqueueSnackbar = useEnqueueSnackbar();
|
||||
@@ -94,7 +132,15 @@ function RoleMappings({ provider, providerLoading }) {
|
||||
<Typography variant="h3">
|
||||
{formatMessage('roleMappingsForm.title')}
|
||||
</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}>
|
||||
<RoleMappingsFieldArray />
|
||||
<LoadingButton
|
||||
|
@@ -55,6 +55,7 @@ function RoleMappingsFieldArray() {
|
||||
label={formatMessage('roleMappingsForm.remoteRoleName')}
|
||||
fullWidth
|
||||
required
|
||||
showError
|
||||
/>
|
||||
<ControlledAutocomplete
|
||||
name={`roleMappings.${index}.roleId`}
|
||||
@@ -62,14 +63,17 @@ function RoleMappingsFieldArray() {
|
||||
disablePortal
|
||||
disableClearable
|
||||
options={generateRoleOptions(roles)}
|
||||
renderInput={(params) => (
|
||||
renderInput={(params, { error }) => (
|
||||
<MuiTextField
|
||||
{...params}
|
||||
label={formatMessage('roleMappingsForm.role')}
|
||||
required
|
||||
error={!!error}
|
||||
helperText={error?.message}
|
||||
/>
|
||||
)}
|
||||
loading={isRolesLoading}
|
||||
showHelperText={false}
|
||||
/>
|
||||
</Stack>
|
||||
<IconButton
|
||||
|
Reference in New Issue
Block a user