Merge pull request #2143 from automatisch/AUT-1325

feat: introduce uniqueness validation for remote role name
This commit is contained in:
Ali BARIN
2024-11-12 13:18:07 +01:00
committed by GitHub
5 changed files with 79 additions and 13 deletions

View File

@@ -29,6 +29,8 @@ function ControlledAutocomplete(props) {
options = [],
dependsOn = [],
showOptionValue,
renderInput,
showHelperText = true,
...autocompleteProps
} = props;
let dependsOnValues = [];
@@ -105,8 +107,9 @@ function ControlledAutocomplete(props) {
)}
</li>
)}
renderInput={(params) => renderInput(params, fieldState)}
/>
{showHelperText && (
<FormHelperText
variant="outlined"
error={Boolean(fieldState.isTouched && fieldState.error)}
@@ -115,6 +118,7 @@ function ControlledAutocomplete(props) {
? 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;

View File

@@ -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(() => {
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;

View File

@@ -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;

View File

@@ -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

View File

@@ -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