Merge pull request #2124 from automatisch/AUT-1232

fix: use correct default values when editing a role
This commit is contained in:
Ali BARIN
2024-11-08 12:24:31 +01:00
committed by GitHub
6 changed files with 148 additions and 33 deletions

View File

@@ -0,0 +1,51 @@
import React from 'react';
import { useFormContext } from 'react-hook-form';
import PropTypes from 'prop-types';
import ControlledCheckbox from 'components/ControlledCheckbox';
const ActionField = ({ action, subject, disabled, name, syncIsCreator }) => {
const { formState, resetField } = useFormContext();
const actionDefaultValue =
formState.defaultValues?.[name]?.[subject.key]?.[action.key].value;
const conditionFieldName = `${name}.${subject.key}.${action.key}.conditions.isCreator`;
const conditionFieldTouched =
formState.touchedFields?.[name]?.[subject.key]?.[action.key]?.conditions
?.isCreator === true;
const handleSyncIsCreator = (newValue) => {
if (
syncIsCreator &&
actionDefaultValue === false &&
!conditionFieldTouched
) {
resetField(conditionFieldName, { defaultValue: newValue });
}
};
return (
<ControlledCheckbox
disabled={disabled}
name={`${name}.${subject.key}.${action.key}.value`}
dataTest={`${action.key.toLowerCase()}-checkbox`}
onChange={(e, value) => {
handleSyncIsCreator(value);
}}
/>
);
};
ActionField.propTypes = {
action: PropTypes.shape({
key: PropTypes.string.isRequired,
subjects: PropTypes.arrayOf(PropTypes.string).isRequired,
}),
subject: PropTypes.shape({
key: PropTypes.string.isRequired,
}).isRequired,
disabled: PropTypes.bool,
name: PropTypes.string.isRequired,
syncIsCreator: PropTypes.bool,
};
export default ActionField;

View File

@@ -25,7 +25,6 @@ function PermissionSettings(props) {
subject, subject,
actions, actions,
conditions, conditions,
defaultChecked,
} = props; } = props;
const formatMessage = useFormatMessage(); const formatMessage = useFormatMessage();
const { getValues, resetField } = useFormContext(); const { getValues, resetField } = useFormContext();
@@ -34,7 +33,7 @@ function PermissionSettings(props) {
for (const action of actions) { for (const action of actions) {
for (const condition of conditions) { for (const condition of conditions) {
const fieldName = `${fieldPrefix}.${action.key}.conditions.${condition.key}`; const fieldName = `${fieldPrefix}.${action.key}.conditions.${condition.key}`;
resetField(fieldName); resetField(fieldName, { keepTouched: true });
} }
} }
onClose(); onClose();
@@ -45,7 +44,7 @@ function PermissionSettings(props) {
for (const condition of conditions) { for (const condition of conditions) {
const fieldName = `${fieldPrefix}.${action.key}.conditions.${condition.key}`; const fieldName = `${fieldPrefix}.${action.key}.conditions.${condition.key}`;
const value = getValues(fieldName); const value = getValues(fieldName);
resetField(fieldName, { defaultValue: value }); resetField(fieldName, { defaultValue: value, keepTouched: true });
} }
} }
onClose(); onClose();
@@ -56,6 +55,7 @@ function PermissionSettings(props) {
open={open} open={open}
onClose={cancel} onClose={cancel}
data-test={`${subject}-role-conditions-modal`} data-test={`${subject}-role-conditions-modal`}
keepMounted
> >
<DialogTitle>{formatMessage('permissionSettings.title')}</DialogTitle> <DialogTitle>{formatMessage('permissionSettings.title')}</DialogTitle>
@@ -65,10 +65,10 @@ function PermissionSettings(props) {
<TableHead> <TableHead>
<TableRow> <TableRow>
<TableCell component="th" /> <TableCell component="th" />
{actions.map((action) => ( {actions.map((action) => (
<TableCell component="th" key={action.key}> <TableCell component="th" key={action.key}>
<Typography <Typography
component="div"
variant="subtitle1" variant="subtitle1"
align="center" align="center"
sx={{ sx={{
@@ -89,7 +89,7 @@ function PermissionSettings(props) {
sx={{ '&:last-child td': { border: 0 } }} sx={{ '&:last-child td': { border: 0 } }}
> >
<TableCell scope="row"> <TableCell scope="row">
<Typography variant="subtitle2"> <Typography variant="subtitle2" component="div">
{condition.label} {condition.label}
</Typography> </Typography>
</TableCell> </TableCell>
@@ -99,14 +99,13 @@ function PermissionSettings(props) {
key={`${action.key}.${condition.key}`} key={`${action.key}.${condition.key}`}
align="center" align="center"
> >
<Typography variant="subtitle2"> <Typography variant="subtitle2" component="div">
{action.subjects.includes(subject) && ( {action.subjects.includes(subject) && (
<ControlledCheckbox <ControlledCheckbox
name={`${fieldPrefix}.${action.key}.conditions.${condition.key}`} name={`${fieldPrefix}.${action.key}.conditions.${condition.key}`}
dataTest={`${ dataTest={`${
condition.key condition.key
}-${action.key.toLowerCase()}-checkbox`} }-${action.key.toLowerCase()}-checkbox`}
defaultValue={defaultChecked}
disabled={ disabled={
getValues( getValues(
`${fieldPrefix}.${action.key}.value`, `${fieldPrefix}.${action.key}.value`,
@@ -144,7 +143,6 @@ PermissionSettings.propTypes = {
fieldPrefix: PropTypes.string.isRequired, fieldPrefix: PropTypes.string.isRequired,
subject: PropTypes.string.isRequired, subject: PropTypes.string.isRequired,
open: PropTypes.bool, open: PropTypes.bool,
defaultChecked: PropTypes.bool,
actions: PropTypes.arrayOf( actions: PropTypes.arrayOf(
PropTypes.shape({ PropTypes.shape({
label: PropTypes.string, label: PropTypes.string,

View File

@@ -12,15 +12,15 @@ import TableRow from '@mui/material/TableRow';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import * as React from 'react'; import * as React from 'react';
import ControlledCheckbox from 'components/ControlledCheckbox';
import usePermissionCatalog from 'hooks/usePermissionCatalog.ee'; import usePermissionCatalog from 'hooks/usePermissionCatalog.ee';
import PermissionSettings from './PermissionSettings.ee'; import PermissionSettings from './PermissionSettings.ee';
import PermissionCatalogFieldLoader from './PermissionCatalogFieldLoader'; import PermissionCatalogFieldLoader from './PermissionCatalogFieldLoader';
import ActionField from './ActionField';
const PermissionCatalogField = ({ const PermissionCatalogField = ({
name = 'permissions', name = 'permissions',
disabled = false, disabled = false,
defaultChecked = false, syncIsCreator = false,
}) => { }) => {
const { data, isLoading: isPermissionCatalogLoading } = const { data, isLoading: isPermissionCatalogLoading } =
usePermissionCatalog(); usePermissionCatalog();
@@ -39,6 +39,7 @@ const PermissionCatalogField = ({
{permissionCatalog?.actions.map((action) => ( {permissionCatalog?.actions.map((action) => (
<TableCell component="th" key={action.key}> <TableCell component="th" key={action.key}>
<Typography <Typography
component="div"
variant="subtitle1" variant="subtitle1"
align="center" align="center"
sx={{ sx={{
@@ -62,20 +63,23 @@ const PermissionCatalogField = ({
data-test={`${subject.key}-permission-row`} data-test={`${subject.key}-permission-row`}
> >
<TableCell scope="row"> <TableCell scope="row">
<Typography variant="subtitle2">{subject.label}</Typography> <Typography variant="subtitle2" component="div">
{subject.label}
</Typography>
</TableCell> </TableCell>
{permissionCatalog?.actions.map((action) => ( {permissionCatalog?.actions.map((action) => (
<TableCell key={`${subject.key}.${action.key}`} align="center"> <TableCell key={`${subject.key}.${action.key}`} align="center">
<Typography variant="subtitle2"> <Typography variant="subtitle2" component="div">
{action.subjects.includes(subject.key) && ( {action.subjects.includes(subject.key) && (
<ControlledCheckbox <ActionField
action={action}
subject={subject}
disabled={disabled} disabled={disabled}
name={`${name}.${subject.key}.${action.key}.value`} name={name}
dataTest={`${action.key.toLowerCase()}-checkbox`} syncIsCreator={syncIsCreator}
/> />
)} )}
{!action.subjects.includes(subject.key) && '-'} {!action.subjects.includes(subject.key) && '-'}
</Typography> </Typography>
</TableCell> </TableCell>
@@ -100,7 +104,6 @@ const PermissionCatalogField = ({
subject={subject.key} subject={subject.key}
actions={permissionCatalog?.actions} actions={permissionCatalog?.actions}
conditions={permissionCatalog?.conditions} conditions={permissionCatalog?.conditions}
defaultChecked={defaultChecked}
/> />
</Stack> </Stack>
</TableCell> </TableCell>
@@ -114,7 +117,7 @@ const PermissionCatalogField = ({
PermissionCatalogField.propTypes = { PermissionCatalogField.propTypes = {
name: PropTypes.string, name: PropTypes.string,
disabled: PropTypes.bool, disabled: PropTypes.bool,
defaultChecked: PropTypes.bool, syncIsCreator: PropTypes.bool,
}; };
export default PermissionCatalogField; export default PermissionCatalogField;

View File

@@ -45,3 +45,36 @@ export function getPermissions(computedPermissions) {
[], [],
); );
} }
export const getComputedPermissionsDefaultValues = (
data,
conditionsInitialValues,
) => {
if (!data) return {};
const conditions = {};
data.conditions.forEach((condition) => {
conditions[condition.key] =
conditionsInitialValues?.[condition.key] || false;
});
const result = {};
data.subjects.forEach((subject) => {
const subjectKey = subject.key;
result[subjectKey] = {};
data.actions.forEach((action) => {
const actionKey = action.key;
if (action.subjects.includes(subjectKey)) {
result[subjectKey][actionKey] = {
value: false,
conditions: { ...conditions },
};
}
});
});
return result;
};

View File

@@ -11,9 +11,13 @@ import Form from 'components/Form';
import PageTitle from 'components/PageTitle'; import PageTitle from 'components/PageTitle';
import TextField from 'components/TextField'; import TextField from 'components/TextField';
import * as URLS from 'config/urls'; import * as URLS from 'config/urls';
import { getPermissions } from 'helpers/computePermissions.ee'; import {
getComputedPermissionsDefaultValues,
getPermissions,
} from 'helpers/computePermissions.ee';
import useFormatMessage from 'hooks/useFormatMessage'; import useFormatMessage from 'hooks/useFormatMessage';
import useAdminCreateRole from 'hooks/useAdminCreateRole'; import useAdminCreateRole from 'hooks/useAdminCreateRole';
import usePermissionCatalog from 'hooks/usePermissionCatalog.ee';
export default function CreateRole() { export default function CreateRole() {
const navigate = useNavigate(); const navigate = useNavigate();
@@ -21,6 +25,21 @@ export default function CreateRole() {
const enqueueSnackbar = useEnqueueSnackbar(); const enqueueSnackbar = useEnqueueSnackbar();
const { mutateAsync: createRole, isPending: isCreateRolePending } = const { mutateAsync: createRole, isPending: isCreateRolePending } =
useAdminCreateRole(); useAdminCreateRole();
const { data: permissionCatalogData } = usePermissionCatalog();
const defaultValues = React.useMemo(
() => ({
name: '',
description: '',
computedPermissions: getComputedPermissionsDefaultValues(
permissionCatalogData?.data,
{
isCreator: true,
},
),
}),
[permissionCatalogData],
);
const handleRoleCreation = async (roleData) => { const handleRoleCreation = async (roleData) => {
try { try {
@@ -64,7 +83,7 @@ export default function CreateRole() {
</Grid> </Grid>
<Grid item xs={12} justifyContent="flex-end" sx={{ pt: 5 }}> <Grid item xs={12} justifyContent="flex-end" sx={{ pt: 5 }}>
<Form onSubmit={handleRoleCreation}> <Form onSubmit={handleRoleCreation} defaultValues={defaultValues}>
<Stack direction="column" gap={2}> <Stack direction="column" gap={2}>
<TextField <TextField
required={true} required={true}
@@ -81,10 +100,7 @@ export default function CreateRole() {
data-test="description-input" data-test="description-input"
/> />
<PermissionCatalogField <PermissionCatalogField name="computedPermissions" />
name="computedPermissions"
defaultChecked={true}
/>
<LoadingButton <LoadingButton
type="submit" type="submit"

View File

@@ -5,6 +5,7 @@ import Stack from '@mui/material/Stack';
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar'; import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
import * as React from 'react'; import * as React from 'react';
import { useNavigate, useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
import { merge } from 'lodash';
import Container from 'components/Container'; import Container from 'components/Container';
import Form from 'components/Form'; import Form from 'components/Form';
@@ -13,21 +14,25 @@ import PermissionCatalogField from 'components/PermissionCatalogField/index.ee';
import TextField from 'components/TextField'; import TextField from 'components/TextField';
import * as URLS from 'config/urls'; import * as URLS from 'config/urls';
import { import {
getComputedPermissionsDefaultValues,
getPermissions, getPermissions,
getRoleWithComputedPermissions, getRoleWithComputedPermissions,
} from 'helpers/computePermissions.ee'; } from 'helpers/computePermissions.ee';
import useFormatMessage from 'hooks/useFormatMessage'; import useFormatMessage from 'hooks/useFormatMessage';
import useAdminUpdateRole from 'hooks/useAdminUpdateRole'; import useAdminUpdateRole from 'hooks/useAdminUpdateRole';
import useRole from 'hooks/useRole.ee'; import useRole from 'hooks/useRole.ee';
import usePermissionCatalog from 'hooks/usePermissionCatalog.ee';
export default function EditRole() { export default function EditRole() {
const formatMessage = useFormatMessage(); const formatMessage = useFormatMessage();
const navigate = useNavigate(); const navigate = useNavigate();
const { roleId } = useParams(); const { roleId } = useParams();
const { data, loading: isRoleLoading } = useRole({ roleId }); const { data: roleData, isLoading: isRoleLoading } = useRole({ roleId });
const { mutateAsync: updateRole, isPending: isUpdateRolePending } = const { mutateAsync: updateRole, isPending: isUpdateRolePending } =
useAdminUpdateRole(roleId); useAdminUpdateRole(roleId);
const role = data?.data; const { data: permissionCatalogData } = usePermissionCatalog();
const role = roleData?.data;
const permissionCatalog = permissionCatalogData?.data;
const enqueueSnackbar = useEnqueueSnackbar(); const enqueueSnackbar = useEnqueueSnackbar();
const handleRoleUpdate = async (roleData) => { const handleRoleUpdate = async (roleData) => {
@@ -52,7 +57,20 @@ export default function EditRole() {
} }
}; };
const defaultValues = React.useMemo(() => {
const roleWithComputedPermissions = getRoleWithComputedPermissions(role); const roleWithComputedPermissions = getRoleWithComputedPermissions(role);
const computedPermissionsDefaultValues =
getComputedPermissionsDefaultValues(permissionCatalog);
return {
...roleWithComputedPermissions,
computedPermissions: merge(
{},
computedPermissionsDefaultValues,
roleWithComputedPermissions.computedPermissions,
),
};
}, [role, permissionCatalog]);
return ( return (
<Container sx={{ py: 3, display: 'flex', justifyContent: 'center' }}> <Container sx={{ py: 3, display: 'flex', justifyContent: 'center' }}>
@@ -64,10 +82,7 @@ export default function EditRole() {
</Grid> </Grid>
<Grid item xs={12} justifyContent="flex-end" sx={{ pt: 5 }}> <Grid item xs={12} justifyContent="flex-end" sx={{ pt: 5 }}>
<Form <Form defaultValues={defaultValues} onSubmit={handleRoleUpdate}>
defaultValues={roleWithComputedPermissions}
onSubmit={handleRoleUpdate}
>
<Stack direction="column" gap={2}> <Stack direction="column" gap={2}>
{isRoleLoading && ( {isRoleLoading && (
<> <>
@@ -95,12 +110,11 @@ export default function EditRole() {
/> />
</> </>
)} )}
<PermissionCatalogField <PermissionCatalogField
name="computedPermissions" name="computedPermissions"
disabled={role?.isAdmin} disabled={role?.isAdmin}
syncIsCreator
/> />
<LoadingButton <LoadingButton
type="submit" type="submit"
variant="contained" variant="contained"