mirror of
https://github.com/Unleash/unleash.git
synced 2025-06-14 01:16:17 +02:00
Custom roles redesign (#2439)
## About the changes Visual update to project permissions  Closes [linear 1-366](https://linear.app/unleash/issue/1-366/frontend-custom-roles-redesign) Relates to [roadmap](https://github.com/orgs/Unleash/projects/10) item: #2251
This commit is contained in:
parent
8e1fc73221
commit
42eadef8da
@ -17,24 +17,25 @@ const CreateProjectRole = () => {
|
|||||||
const {
|
const {
|
||||||
roleName,
|
roleName,
|
||||||
roleDesc,
|
roleDesc,
|
||||||
|
permissions,
|
||||||
|
checkedPermissions,
|
||||||
|
errors,
|
||||||
setRoleName,
|
setRoleName,
|
||||||
setRoleDesc,
|
setRoleDesc,
|
||||||
checkedPermissions,
|
|
||||||
handlePermissionChange,
|
handlePermissionChange,
|
||||||
checkAllProjectPermissions,
|
onToggleAllProjectPermissions: checkAllProjectPermissions,
|
||||||
checkAllEnvironmentPermissions,
|
onToggleAllEnvironmentPermissions: checkAllEnvironmentPermissions,
|
||||||
getProjectRolePayload,
|
getProjectRolePayload,
|
||||||
validatePermissions,
|
validatePermissions,
|
||||||
validateName,
|
validateName,
|
||||||
validateNameUniqueness,
|
validateNameUniqueness,
|
||||||
errors,
|
|
||||||
clearErrors,
|
clearErrors,
|
||||||
getRoleKey,
|
getRoleKey,
|
||||||
} = useProjectRoleForm();
|
} = useProjectRoleForm();
|
||||||
|
|
||||||
const { createRole, loading } = useProjectRolesApi();
|
const { createRole, loading } = useProjectRolesApi();
|
||||||
|
|
||||||
const handleSubmit = async (e: Event) => {
|
const onSubmit = async (e: Event) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
clearErrors();
|
clearErrors();
|
||||||
const validName = validateName();
|
const validName = validateName();
|
||||||
@ -66,7 +67,7 @@ const CreateProjectRole = () => {
|
|||||||
--data-raw '${JSON.stringify(getProjectRolePayload(), undefined, 2)}'`;
|
--data-raw '${JSON.stringify(getProjectRolePayload(), undefined, 2)}'`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCancel = () => {
|
const onCancel = () => {
|
||||||
navigate(GO_BACK);
|
navigate(GO_BACK);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -83,8 +84,9 @@ const CreateProjectRole = () => {
|
|||||||
>
|
>
|
||||||
<ProjectRoleForm
|
<ProjectRoleForm
|
||||||
errors={errors}
|
errors={errors}
|
||||||
handleSubmit={handleSubmit}
|
permissions={permissions}
|
||||||
handleCancel={handleCancel}
|
onSubmit={onSubmit}
|
||||||
|
onCancel={onCancel}
|
||||||
roleName={roleName}
|
roleName={roleName}
|
||||||
setRoleName={setRoleName}
|
setRoleName={setRoleName}
|
||||||
roleDesc={roleDesc}
|
roleDesc={roleDesc}
|
||||||
@ -93,7 +95,6 @@ const CreateProjectRole = () => {
|
|||||||
handlePermissionChange={handlePermissionChange}
|
handlePermissionChange={handlePermissionChange}
|
||||||
checkAllProjectPermissions={checkAllProjectPermissions}
|
checkAllProjectPermissions={checkAllProjectPermissions}
|
||||||
checkAllEnvironmentPermissions={checkAllEnvironmentPermissions}
|
checkAllEnvironmentPermissions={checkAllEnvironmentPermissions}
|
||||||
mode="Create"
|
|
||||||
clearErrors={clearErrors}
|
clearErrors={clearErrors}
|
||||||
validateNameUniqueness={validateNameUniqueness}
|
validateNameUniqueness={validateNameUniqueness}
|
||||||
getRoleKey={getRoleKey}
|
getRoleKey={getRoleKey}
|
||||||
|
@ -6,7 +6,6 @@ import useProjectRolesApi from 'hooks/api/actions/useProjectRolesApi/useProjectR
|
|||||||
import useProjectRole from 'hooks/api/getters/useProjectRole/useProjectRole';
|
import useProjectRole from 'hooks/api/getters/useProjectRole/useProjectRole';
|
||||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
import useToast from 'hooks/useToast';
|
import useToast from 'hooks/useToast';
|
||||||
import { IPermission } from 'interfaces/project';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import useProjectRoleForm from '../hooks/useProjectRoleForm';
|
import useProjectRoleForm from '../hooks/useProjectRoleForm';
|
||||||
import ProjectRoleForm from '../ProjectRoleForm/ProjectRoleForm';
|
import ProjectRoleForm from '../ProjectRoleForm/ProjectRoleForm';
|
||||||
@ -24,38 +23,20 @@ const EditProjectRole = () => {
|
|||||||
const {
|
const {
|
||||||
roleName,
|
roleName,
|
||||||
roleDesc,
|
roleDesc,
|
||||||
|
permissions,
|
||||||
|
checkedPermissions,
|
||||||
|
errors,
|
||||||
setRoleName,
|
setRoleName,
|
||||||
setRoleDesc,
|
setRoleDesc,
|
||||||
checkedPermissions,
|
|
||||||
handlePermissionChange,
|
handlePermissionChange,
|
||||||
checkAllProjectPermissions,
|
onToggleAllProjectPermissions,
|
||||||
checkAllEnvironmentPermissions,
|
onToggleAllEnvironmentPermissions,
|
||||||
getProjectRolePayload,
|
getProjectRolePayload,
|
||||||
validatePermissions,
|
validatePermissions,
|
||||||
validateName,
|
validateName,
|
||||||
errors,
|
|
||||||
clearErrors,
|
clearErrors,
|
||||||
getRoleKey,
|
getRoleKey,
|
||||||
handleInitialCheckedPermissions,
|
} = useProjectRoleForm(role.name, role.description, role?.permissions);
|
||||||
permissions,
|
|
||||||
} = useProjectRoleForm(role.name, role.description);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const initialCheckedPermissions = role?.permissions?.reduce(
|
|
||||||
(acc: { [key: string]: IPermission }, curr: IPermission) => {
|
|
||||||
acc[getRoleKey(curr)] = curr;
|
|
||||||
return acc;
|
|
||||||
},
|
|
||||||
{}
|
|
||||||
);
|
|
||||||
|
|
||||||
handleInitialCheckedPermissions(initialCheckedPermissions || {});
|
|
||||||
/* eslint-disable-next-line */
|
|
||||||
}, [
|
|
||||||
role?.permissions?.length,
|
|
||||||
permissions?.project?.length,
|
|
||||||
permissions?.environments?.length,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const formatApiCode = () => {
|
const formatApiCode = () => {
|
||||||
return `curl --location --request PUT '${
|
return `curl --location --request PUT '${
|
||||||
@ -69,7 +50,7 @@ const EditProjectRole = () => {
|
|||||||
const { refetch } = useProjectRole(projectId);
|
const { refetch } = useProjectRole(projectId);
|
||||||
const { editRole, loading } = useProjectRolesApi();
|
const { editRole, loading } = useProjectRolesApi();
|
||||||
|
|
||||||
const handleSubmit = async (e: Event) => {
|
const onSubmit = async (e: Event) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const payload = getProjectRolePayload();
|
const payload = getProjectRolePayload();
|
||||||
|
|
||||||
@ -93,7 +74,7 @@ const EditProjectRole = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCancel = () => {
|
const onCancel = () => {
|
||||||
navigate(GO_BACK);
|
navigate(GO_BACK);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -109,17 +90,19 @@ to resources within a project"
|
|||||||
formatApiCode={formatApiCode}
|
formatApiCode={formatApiCode}
|
||||||
>
|
>
|
||||||
<ProjectRoleForm
|
<ProjectRoleForm
|
||||||
handleSubmit={handleSubmit}
|
permissions={permissions}
|
||||||
handleCancel={handleCancel}
|
onSubmit={onSubmit}
|
||||||
|
onCancel={onCancel}
|
||||||
roleName={roleName}
|
roleName={roleName}
|
||||||
setRoleName={setRoleName}
|
setRoleName={setRoleName}
|
||||||
roleDesc={roleDesc}
|
roleDesc={roleDesc}
|
||||||
setRoleDesc={setRoleDesc}
|
setRoleDesc={setRoleDesc}
|
||||||
checkedPermissions={checkedPermissions}
|
checkedPermissions={checkedPermissions}
|
||||||
handlePermissionChange={handlePermissionChange}
|
handlePermissionChange={handlePermissionChange}
|
||||||
checkAllProjectPermissions={checkAllProjectPermissions}
|
checkAllProjectPermissions={onToggleAllProjectPermissions}
|
||||||
checkAllEnvironmentPermissions={checkAllEnvironmentPermissions}
|
checkAllEnvironmentPermissions={
|
||||||
mode="Edit"
|
onToggleAllEnvironmentPermissions
|
||||||
|
}
|
||||||
errors={errors}
|
errors={errors}
|
||||||
clearErrors={clearErrors}
|
clearErrors={clearErrors}
|
||||||
getRoleKey={getRoleKey}
|
getRoleKey={getRoleKey}
|
||||||
|
@ -1,35 +0,0 @@
|
|||||||
import { makeStyles } from 'tss-react/mui';
|
|
||||||
|
|
||||||
export const useStyles = makeStyles()(theme => ({
|
|
||||||
environmentPermissionContainer: {
|
|
||||||
marginBottom: '1.25rem',
|
|
||||||
},
|
|
||||||
accordionSummary: {
|
|
||||||
boxShadow: 'none',
|
|
||||||
padding: '0',
|
|
||||||
},
|
|
||||||
label: {
|
|
||||||
minWidth: '300px',
|
|
||||||
[theme.breakpoints.down(600)]: {
|
|
||||||
minWidth: 'auto',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
accordionHeader: {
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
[theme.breakpoints.down(500)]: {
|
|
||||||
flexDirection: 'column',
|
|
||||||
alignItems: 'flex-start',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
accordionBody: {
|
|
||||||
padding: '0',
|
|
||||||
flexWrap: 'wrap',
|
|
||||||
},
|
|
||||||
header: {
|
|
||||||
color: theme.palette.primary.main,
|
|
||||||
},
|
|
||||||
icon: {
|
|
||||||
fill: theme.palette.primary.main,
|
|
||||||
},
|
|
||||||
}));
|
|
@ -1,153 +0,0 @@
|
|||||||
import {
|
|
||||||
Accordion,
|
|
||||||
AccordionDetails,
|
|
||||||
AccordionSummary,
|
|
||||||
Checkbox,
|
|
||||||
FormControlLabel,
|
|
||||||
} from '@mui/material';
|
|
||||||
import { ExpandMore } from '@mui/icons-material';
|
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
import {
|
|
||||||
IPermission,
|
|
||||||
IProjectEnvironmentPermissions,
|
|
||||||
} from 'interfaces/project';
|
|
||||||
import StringTruncator from 'component/common/StringTruncator/StringTruncator';
|
|
||||||
import { ICheckedPermission } from 'component/admin/projectRoles/hooks/useProjectRoleForm';
|
|
||||||
import { useStyles } from './EnvironmentPermissionAccordion.styles';
|
|
||||||
|
|
||||||
type PermissionMap = { [key: string]: boolean };
|
|
||||||
|
|
||||||
interface IEnvironmentPermissionAccordionProps {
|
|
||||||
environment: IProjectEnvironmentPermissions;
|
|
||||||
handlePermissionChange: (permission: IPermission, type: string) => void;
|
|
||||||
checkAllEnvironmentPermissions: (envName: string) => void;
|
|
||||||
checkedPermissions: ICheckedPermission;
|
|
||||||
getRoleKey: (permission: { id: number; environment?: string }) => string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const EnvironmentPermissionAccordion = ({
|
|
||||||
environment,
|
|
||||||
handlePermissionChange,
|
|
||||||
checkAllEnvironmentPermissions,
|
|
||||||
checkedPermissions,
|
|
||||||
getRoleKey,
|
|
||||||
}: IEnvironmentPermissionAccordionProps) => {
|
|
||||||
const [permissionMap, setPermissionMap] = useState<PermissionMap>({});
|
|
||||||
const [permissionCount, setPermissionCount] = useState(0);
|
|
||||||
const { classes: styles } = useStyles();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const permissionMap = environment?.permissions?.reduce(
|
|
||||||
(acc: PermissionMap, curr: IPermission) => {
|
|
||||||
acc[getRoleKey(curr)] = true;
|
|
||||||
return acc;
|
|
||||||
},
|
|
||||||
{}
|
|
||||||
);
|
|
||||||
|
|
||||||
setPermissionMap(permissionMap);
|
|
||||||
/* eslint-disable-next-line */
|
|
||||||
}, [environment?.permissions?.length]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
let count = 0;
|
|
||||||
Object.keys(checkedPermissions).forEach(key => {
|
|
||||||
if (permissionMap[key]) {
|
|
||||||
count = count + 1;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
setPermissionCount(count);
|
|
||||||
/* eslint-disable-next-line */
|
|
||||||
}, [checkedPermissions]);
|
|
||||||
|
|
||||||
const renderPermissions = () => {
|
|
||||||
const envPermissions = environment?.permissions?.map(
|
|
||||||
(permission: IPermission) => {
|
|
||||||
return (
|
|
||||||
<FormControlLabel
|
|
||||||
classes={{ root: styles.label }}
|
|
||||||
key={getRoleKey(permission)}
|
|
||||||
control={
|
|
||||||
<Checkbox
|
|
||||||
checked={
|
|
||||||
checkedPermissions[getRoleKey(permission)]
|
|
||||||
? true
|
|
||||||
: false
|
|
||||||
}
|
|
||||||
onChange={() =>
|
|
||||||
handlePermissionChange(
|
|
||||||
permission,
|
|
||||||
environment.name
|
|
||||||
)
|
|
||||||
}
|
|
||||||
color="primary"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
label={permission.displayName}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
envPermissions.push(
|
|
||||||
<FormControlLabel
|
|
||||||
key={`check-all-environment-${environment?.name}`}
|
|
||||||
classes={{ root: styles.label }}
|
|
||||||
control={
|
|
||||||
<Checkbox
|
|
||||||
checked={
|
|
||||||
checkedPermissions[
|
|
||||||
`check-all-environment-${environment?.name}`
|
|
||||||
]
|
|
||||||
? true
|
|
||||||
: false
|
|
||||||
}
|
|
||||||
onChange={() =>
|
|
||||||
checkAllEnvironmentPermissions(environment?.name)
|
|
||||||
}
|
|
||||||
color="primary"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
label={'Select all permissions for this env'}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
return envPermissions;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.environmentPermissionContainer}>
|
|
||||||
<Accordion style={{ boxShadow: 'none' }}>
|
|
||||||
<AccordionSummary
|
|
||||||
className={styles.accordionSummary}
|
|
||||||
expandIcon={
|
|
||||||
<ExpandMore
|
|
||||||
className={styles.icon}
|
|
||||||
titleAccess="Toggle"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div className={styles.accordionHeader}>
|
|
||||||
<StringTruncator
|
|
||||||
text={environment.name}
|
|
||||||
className={styles.header}
|
|
||||||
maxWidth="120"
|
|
||||||
maxLength={25}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<p className={styles.header}>
|
|
||||||
({permissionCount} /{' '}
|
|
||||||
{environment?.permissions?.length} permissions)
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</AccordionSummary>
|
|
||||||
<AccordionDetails className={styles.accordionBody}>
|
|
||||||
{renderPermissions()}
|
|
||||||
</AccordionDetails>
|
|
||||||
</Accordion>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default EnvironmentPermissionAccordion;
|
|
@ -0,0 +1,178 @@
|
|||||||
|
import { ReactNode, useMemo, useState, VFC } from 'react';
|
||||||
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionDetails,
|
||||||
|
AccordionSummary,
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Checkbox,
|
||||||
|
Divider,
|
||||||
|
FormControlLabel,
|
||||||
|
IconButton,
|
||||||
|
styled,
|
||||||
|
Typography,
|
||||||
|
} from '@mui/material';
|
||||||
|
import { ExpandMore } from '@mui/icons-material';
|
||||||
|
import { IPermission } from 'interfaces/project';
|
||||||
|
import StringTruncator from 'component/common/StringTruncator/StringTruncator';
|
||||||
|
import { ICheckedPermission } from 'component/admin/projectRoles/hooks/useProjectRoleForm';
|
||||||
|
|
||||||
|
interface IEnvironmentPermissionAccordionProps {
|
||||||
|
permissions: IPermission[];
|
||||||
|
checkedPermissions: ICheckedPermission;
|
||||||
|
title: string;
|
||||||
|
Icon: ReactNode;
|
||||||
|
isInitiallyExpanded?: boolean;
|
||||||
|
context: 'project' | 'environment';
|
||||||
|
onPermissionChange: (permission: IPermission) => void;
|
||||||
|
onCheckAll: () => void;
|
||||||
|
getRoleKey: (permission: { id: number; environment?: string }) => string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AccordionHeader = styled(Box)(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
[theme.breakpoints.down(500)]: {
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledTitle = styled(StringTruncator)(({ theme }) => ({
|
||||||
|
fontWeight: theme.typography.fontWeightBold,
|
||||||
|
marginRight: theme.spacing(1),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const PermissionAccordion: VFC<IEnvironmentPermissionAccordionProps> = ({
|
||||||
|
title,
|
||||||
|
permissions,
|
||||||
|
checkedPermissions,
|
||||||
|
Icon,
|
||||||
|
isInitiallyExpanded,
|
||||||
|
context,
|
||||||
|
onPermissionChange,
|
||||||
|
onCheckAll,
|
||||||
|
getRoleKey,
|
||||||
|
}) => {
|
||||||
|
const [expanded, setExpanded] = useState(isInitiallyExpanded);
|
||||||
|
const permissionMap = useMemo(
|
||||||
|
() =>
|
||||||
|
permissions?.reduce(
|
||||||
|
(acc: Record<string, boolean>, curr: IPermission) => {
|
||||||
|
acc[getRoleKey(curr)] = true;
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
) || {},
|
||||||
|
[permissions]
|
||||||
|
);
|
||||||
|
const permissionCount = useMemo(
|
||||||
|
() =>
|
||||||
|
Object.keys(checkedPermissions).filter(key => permissionMap[key])
|
||||||
|
.length || 0,
|
||||||
|
[checkedPermissions, permissionMap]
|
||||||
|
);
|
||||||
|
|
||||||
|
const isAllChecked = useMemo(
|
||||||
|
() => permissionCount === permissions?.length,
|
||||||
|
[permissionCount, permissions]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
my: 2,
|
||||||
|
pb: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Accordion
|
||||||
|
expanded={expanded}
|
||||||
|
onChange={() => setExpanded(!expanded)}
|
||||||
|
sx={{
|
||||||
|
boxShadow: 'none',
|
||||||
|
px: 3,
|
||||||
|
py: 1,
|
||||||
|
border: theme => `1px solid ${theme.palette.divider}`,
|
||||||
|
borderRadius: theme => `${theme.shape.borderRadiusLarge}px`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AccordionSummary
|
||||||
|
expandIcon={
|
||||||
|
<IconButton>
|
||||||
|
<ExpandMore titleAccess="Toggle" />
|
||||||
|
</IconButton>
|
||||||
|
}
|
||||||
|
sx={{
|
||||||
|
boxShadow: 'none',
|
||||||
|
padding: '0',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AccordionHeader>
|
||||||
|
{Icon}
|
||||||
|
<StyledTitle
|
||||||
|
text={title}
|
||||||
|
maxWidth="120"
|
||||||
|
maxLength={25}
|
||||||
|
/>{' '}
|
||||||
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
({permissionCount} / {permissions?.length}{' '}
|
||||||
|
permissions)
|
||||||
|
</Typography>
|
||||||
|
</AccordionHeader>
|
||||||
|
</AccordionSummary>
|
||||||
|
<AccordionDetails
|
||||||
|
sx={{
|
||||||
|
px: 0,
|
||||||
|
py: 1,
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Divider sx={{ mb: 1 }} />
|
||||||
|
<Button
|
||||||
|
variant="text"
|
||||||
|
size="small"
|
||||||
|
onClick={onCheckAll}
|
||||||
|
sx={{
|
||||||
|
fontWeight: theme =>
|
||||||
|
theme.typography.fontWeightRegular,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isAllChecked ? 'Unselect ' : 'Select '}
|
||||||
|
all {context} permissions
|
||||||
|
</Button>
|
||||||
|
<Box>
|
||||||
|
{permissions?.map((permission: IPermission) => {
|
||||||
|
return (
|
||||||
|
<FormControlLabel
|
||||||
|
sx={{
|
||||||
|
minWidth: {
|
||||||
|
sm: '300px',
|
||||||
|
xs: 'auto',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
key={getRoleKey(permission)}
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
checked={
|
||||||
|
checkedPermissions[
|
||||||
|
getRoleKey(permission)
|
||||||
|
]
|
||||||
|
? true
|
||||||
|
: false
|
||||||
|
}
|
||||||
|
onChange={() =>
|
||||||
|
onPermissionChange(permission)
|
||||||
|
}
|
||||||
|
color="primary"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={permission.displayName}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Box>
|
||||||
|
</AccordionDetails>
|
||||||
|
</Accordion>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
@ -1,41 +0,0 @@
|
|||||||
import { makeStyles } from 'tss-react/mui';
|
|
||||||
|
|
||||||
export const useStyles = makeStyles()(theme => ({
|
|
||||||
container: {
|
|
||||||
maxWidth: '400px',
|
|
||||||
},
|
|
||||||
input: { width: '100%', marginBottom: '1rem' },
|
|
||||||
label: {
|
|
||||||
minWidth: '300px',
|
|
||||||
[theme.breakpoints.down(600)]: {
|
|
||||||
minWidth: 'auto',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
buttonContainer: {
|
|
||||||
marginTop: 'auto',
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'flex-end',
|
|
||||||
},
|
|
||||||
cancelButton: {
|
|
||||||
marginLeft: '1.5rem',
|
|
||||||
},
|
|
||||||
inputDescription: {
|
|
||||||
marginBottom: '0.5rem',
|
|
||||||
},
|
|
||||||
formHeader: {
|
|
||||||
fontWeight: 'normal',
|
|
||||||
marginTop: '0',
|
|
||||||
},
|
|
||||||
header: {
|
|
||||||
fontWeight: 'normal',
|
|
||||||
},
|
|
||||||
permissionErrorContainer: {
|
|
||||||
position: 'relative',
|
|
||||||
},
|
|
||||||
errorMessage: {
|
|
||||||
fontSize: theme.fontSizes.smallBody,
|
|
||||||
color: theme.palette.error.main,
|
|
||||||
position: 'absolute',
|
|
||||||
top: '-8px',
|
|
||||||
},
|
|
||||||
}));
|
|
@ -1,134 +1,69 @@
|
|||||||
import Input from 'component/common/Input/Input';
|
import React, { Dispatch, FC, ReactNode, SetStateAction } from 'react';
|
||||||
import EnvironmentPermissionAccordion from './EnvironmentPermissionAccordion/EnvironmentPermissionAccordion';
|
|
||||||
import { Button, Checkbox, FormControlLabel, TextField } from '@mui/material';
|
|
||||||
import useProjectRolePermissions from 'hooks/api/getters/useProjectRolePermissions/useProjectRolePermissions';
|
|
||||||
|
|
||||||
import { useStyles } from './ProjectRoleForm.styles';
|
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
|
||||||
import React, { ReactNode } from 'react';
|
|
||||||
import { IPermission } from 'interfaces/project';
|
|
||||||
import {
|
import {
|
||||||
ICheckedPermission,
|
Topic as TopicIcon,
|
||||||
PROJECT_CHECK_ALL_KEY,
|
CloudCircle as CloudCircleIcon,
|
||||||
} from '../hooks/useProjectRoleForm';
|
} from '@mui/icons-material';
|
||||||
|
import { Box, Button, TextField, Typography } from '@mui/material';
|
||||||
|
import Input from 'component/common/Input/Input';
|
||||||
|
import { PermissionAccordion } from './PermissionAccordion/PermissionAccordion';
|
||||||
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
|
import EnvironmentIcon from 'component/common/EnvironmentIcon/EnvironmentIcon';
|
||||||
|
import {
|
||||||
|
IPermission,
|
||||||
|
IProjectEnvironmentPermissions,
|
||||||
|
IProjectRolePermissions,
|
||||||
|
} from 'interfaces/project';
|
||||||
|
import { ICheckedPermission } from '../hooks/useProjectRoleForm';
|
||||||
|
|
||||||
interface IProjectRoleForm {
|
interface IProjectRoleForm {
|
||||||
roleName: string;
|
roleName: string;
|
||||||
roleDesc: string;
|
roleDesc: string;
|
||||||
setRoleName: React.Dispatch<React.SetStateAction<string>>;
|
|
||||||
setRoleDesc: React.Dispatch<React.SetStateAction<string>>;
|
|
||||||
checkedPermissions: ICheckedPermission;
|
checkedPermissions: ICheckedPermission;
|
||||||
handlePermissionChange: (permission: IPermission, type: string) => void;
|
errors: { [key: string]: string };
|
||||||
|
children: ReactNode;
|
||||||
|
permissions:
|
||||||
|
| IProjectRolePermissions
|
||||||
|
| {
|
||||||
|
project: IPermission[];
|
||||||
|
environments: IProjectEnvironmentPermissions[];
|
||||||
|
};
|
||||||
|
setRoleName: Dispatch<SetStateAction<string>>;
|
||||||
|
setRoleDesc: Dispatch<SetStateAction<string>>;
|
||||||
|
handlePermissionChange: (permission: IPermission) => void;
|
||||||
checkAllProjectPermissions: () => void;
|
checkAllProjectPermissions: () => void;
|
||||||
checkAllEnvironmentPermissions: (envName: string) => void;
|
checkAllEnvironmentPermissions: (envName: string) => void;
|
||||||
handleSubmit: (e: any) => void;
|
onSubmit: (e: any) => void;
|
||||||
handleCancel: () => void;
|
onCancel: () => void;
|
||||||
errors: { [key: string]: string };
|
|
||||||
mode?: string;
|
|
||||||
clearErrors: () => void;
|
clearErrors: () => void;
|
||||||
validateNameUniqueness?: () => void;
|
validateNameUniqueness?: () => void;
|
||||||
getRoleKey: (permission: { id: number; environment?: string }) => string;
|
getRoleKey: (permission: { id: number; environment?: string }) => string;
|
||||||
children: ReactNode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProjectRoleForm: React.FC<IProjectRoleForm> = ({
|
const ProjectRoleForm: FC<IProjectRoleForm> = ({
|
||||||
children,
|
children,
|
||||||
handleSubmit,
|
|
||||||
handleCancel,
|
|
||||||
roleName,
|
roleName,
|
||||||
roleDesc,
|
roleDesc,
|
||||||
|
checkedPermissions,
|
||||||
|
errors,
|
||||||
|
permissions,
|
||||||
|
onSubmit,
|
||||||
|
onCancel,
|
||||||
setRoleName,
|
setRoleName,
|
||||||
setRoleDesc,
|
setRoleDesc,
|
||||||
checkedPermissions,
|
|
||||||
handlePermissionChange,
|
handlePermissionChange,
|
||||||
checkAllProjectPermissions,
|
checkAllProjectPermissions,
|
||||||
checkAllEnvironmentPermissions,
|
checkAllEnvironmentPermissions,
|
||||||
errors,
|
|
||||||
mode,
|
|
||||||
validateNameUniqueness,
|
validateNameUniqueness,
|
||||||
clearErrors,
|
clearErrors,
|
||||||
getRoleKey,
|
getRoleKey,
|
||||||
}: IProjectRoleForm) => {
|
}: IProjectRoleForm) => {
|
||||||
const { classes: styles } = useStyles();
|
|
||||||
const { permissions } = useProjectRolePermissions({
|
|
||||||
revalidateIfStale: false,
|
|
||||||
revalidateOnReconnect: false,
|
|
||||||
revalidateOnFocus: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { project, environments } = permissions;
|
const { project, environments } = permissions;
|
||||||
|
|
||||||
const renderProjectPermissions = () => {
|
|
||||||
const projectPermissions = project.map(permission => {
|
|
||||||
return (
|
|
||||||
<FormControlLabel
|
|
||||||
key={getRoleKey(permission)}
|
|
||||||
classes={{ root: styles.label }}
|
|
||||||
control={
|
|
||||||
<Checkbox
|
|
||||||
checked={
|
|
||||||
checkedPermissions[getRoleKey(permission)]
|
|
||||||
? true
|
|
||||||
: false
|
|
||||||
}
|
|
||||||
onChange={() =>
|
|
||||||
handlePermissionChange(permission, 'project')
|
|
||||||
}
|
|
||||||
color="primary"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
label={permission.displayName}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
projectPermissions.push(
|
|
||||||
<FormControlLabel
|
|
||||||
key={PROJECT_CHECK_ALL_KEY}
|
|
||||||
classes={{ root: styles.label }}
|
|
||||||
control={
|
|
||||||
<Checkbox
|
|
||||||
checked={
|
|
||||||
checkedPermissions[PROJECT_CHECK_ALL_KEY]
|
|
||||||
? true
|
|
||||||
: false
|
|
||||||
}
|
|
||||||
onChange={() => checkAllProjectPermissions()}
|
|
||||||
color="primary"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
label={'Select all project permissions'}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
return projectPermissions;
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderEnvironmentPermissions = () => {
|
|
||||||
return environments.map(environment => {
|
|
||||||
return (
|
|
||||||
<EnvironmentPermissionAccordion
|
|
||||||
environment={environment}
|
|
||||||
key={environment.name}
|
|
||||||
checkedPermissions={checkedPermissions}
|
|
||||||
handlePermissionChange={handlePermissionChange}
|
|
||||||
checkAllEnvironmentPermissions={
|
|
||||||
checkAllEnvironmentPermissions
|
|
||||||
}
|
|
||||||
getRoleKey={getRoleKey}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={onSubmit}>
|
||||||
<div className={styles.container}>
|
<Box sx={{ maxWidth: '400px' }}>
|
||||||
<p className={styles.inputDescription}>
|
<Typography sx={{ mb: 1 }}>What is your role name?</Typography>
|
||||||
What is your role name?
|
|
||||||
</p>
|
|
||||||
<Input
|
<Input
|
||||||
className={styles.input}
|
|
||||||
label="Role name"
|
label="Role name"
|
||||||
value={roleName}
|
value={roleName}
|
||||||
onChange={e => setRoleName(e.target.value)}
|
onChange={e => setRoleName(e.target.value)}
|
||||||
@ -137,41 +72,76 @@ const ProjectRoleForm: React.FC<IProjectRoleForm> = ({
|
|||||||
onFocus={() => clearErrors()}
|
onFocus={() => clearErrors()}
|
||||||
onBlur={validateNameUniqueness}
|
onBlur={validateNameUniqueness}
|
||||||
autoFocus
|
autoFocus
|
||||||
|
sx={{ width: '100%', marginBottom: '1rem' }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<p className={styles.inputDescription}>
|
<Typography sx={{ mb: 1 }}>What is this role for?</Typography>
|
||||||
What is this role for?
|
|
||||||
</p>
|
|
||||||
<TextField
|
<TextField
|
||||||
className={styles.input}
|
|
||||||
label="Role description"
|
label="Role description"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
multiline
|
multiline
|
||||||
maxRows={4}
|
maxRows={4}
|
||||||
value={roleDesc}
|
value={roleDesc}
|
||||||
onChange={e => setRoleDesc(e.target.value)}
|
onChange={e => setRoleDesc(e.target.value)}
|
||||||
|
sx={{ width: '100%', marginBottom: '1rem' }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</Box>
|
||||||
<div className={styles.permissionErrorContainer}>
|
<div>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={Boolean(errors.permissions)}
|
condition={Boolean(errors.permissions)}
|
||||||
show={
|
show={
|
||||||
<span className={styles.errorMessage}>
|
<Typography variant="body2" color="error.main">
|
||||||
You must select at least one permission for a role.
|
You must select at least one permission for a role.
|
||||||
</span>
|
</Typography>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<h3 className={styles.header}>Project permissions</h3>
|
<PermissionAccordion
|
||||||
<div>{renderProjectPermissions()}</div>
|
isInitiallyExpanded
|
||||||
<h3 className={styles.header}>Environment permissions</h3>
|
title="Project permissions"
|
||||||
<div>{renderEnvironmentPermissions()}</div>
|
Icon={<TopicIcon color="disabled" sx={{ mr: 1 }} />}
|
||||||
<div className={styles.buttonContainer}>
|
permissions={project}
|
||||||
|
checkedPermissions={checkedPermissions}
|
||||||
|
onPermissionChange={(permission: IPermission) =>
|
||||||
|
handlePermissionChange(permission)
|
||||||
|
}
|
||||||
|
onCheckAll={checkAllProjectPermissions}
|
||||||
|
getRoleKey={getRoleKey}
|
||||||
|
context="project"
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
{environments.map(environment => (
|
||||||
|
<PermissionAccordion
|
||||||
|
title={environment.name}
|
||||||
|
Icon={
|
||||||
|
<CloudCircleIcon sx={{ mr: 1 }} color="disabled" />
|
||||||
|
}
|
||||||
|
permissions={environment.permissions}
|
||||||
|
key={environment.name}
|
||||||
|
checkedPermissions={checkedPermissions}
|
||||||
|
onPermissionChange={(permission: IPermission) =>
|
||||||
|
handlePermissionChange(permission)
|
||||||
|
}
|
||||||
|
onCheckAll={() =>
|
||||||
|
checkAllEnvironmentPermissions(environment.name)
|
||||||
|
}
|
||||||
|
getRoleKey={getRoleKey}
|
||||||
|
context="environment"
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
marginTop: 'auto',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
}}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
<Button onClick={handleCancel} className={styles.cancelButton}>
|
<Button onClick={onCancel} sx={{ ml: 2 }}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</Box>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -14,13 +14,19 @@ export interface ICheckedPermission {
|
|||||||
[key: string]: IPermission;
|
[key: string]: IPermission;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PROJECT_CHECK_ALL_KEY = 'check-all-project';
|
const getRoleKey = (permission: {
|
||||||
export const ENVIRONMENT_CHECK_ALL_KEY = 'check-all-environment';
|
id: number;
|
||||||
|
environment?: string;
|
||||||
|
}): string => {
|
||||||
|
return permission.environment
|
||||||
|
? `${permission.id}-${permission.environment}`
|
||||||
|
: `${permission.id}`;
|
||||||
|
};
|
||||||
|
|
||||||
const useProjectRoleForm = (
|
const useProjectRoleForm = (
|
||||||
initialRoleName = '',
|
initialRoleName = '',
|
||||||
initialRoleDesc = '',
|
initialRoleDesc = '',
|
||||||
initialCheckedPermissions = {}
|
initialCheckedPermissions: IPermission[] = []
|
||||||
) => {
|
) => {
|
||||||
const { uiConfig } = useUiConfig();
|
const { uiConfig } = useUiConfig();
|
||||||
const { permissions } = useProjectRolePermissions({
|
const { permissions } = useProjectRolePermissions({
|
||||||
@ -32,7 +38,25 @@ const useProjectRoleForm = (
|
|||||||
const [roleName, setRoleName] = useState(initialRoleName);
|
const [roleName, setRoleName] = useState(initialRoleName);
|
||||||
const [roleDesc, setRoleDesc] = useState(initialRoleDesc);
|
const [roleDesc, setRoleDesc] = useState(initialRoleDesc);
|
||||||
const [checkedPermissions, setCheckedPermissions] =
|
const [checkedPermissions, setCheckedPermissions] =
|
||||||
useState<ICheckedPermission>(initialCheckedPermissions);
|
useState<ICheckedPermission>({});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (initialCheckedPermissions.length > 0) {
|
||||||
|
setCheckedPermissions(
|
||||||
|
initialCheckedPermissions?.reduce(
|
||||||
|
(
|
||||||
|
acc: { [key: string]: IPermission },
|
||||||
|
curr: IPermission
|
||||||
|
) => {
|
||||||
|
acc[getRoleKey(curr)] = curr;
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [initialCheckedPermissions?.length]);
|
||||||
|
|
||||||
const [errors, setErrors] = useState({});
|
const [errors, setErrors] = useState({});
|
||||||
|
|
||||||
const { validateRole } = useProjectRolesApi();
|
const { validateRole } = useProjectRolesApi();
|
||||||
@ -45,71 +69,7 @@ const useProjectRoleForm = (
|
|||||||
setRoleDesc(initialRoleDesc);
|
setRoleDesc(initialRoleDesc);
|
||||||
}, [initialRoleDesc]);
|
}, [initialRoleDesc]);
|
||||||
|
|
||||||
const handleInitialCheckedPermissions = (
|
const handlePermissionChange = (permission: IPermission) => {
|
||||||
initialCheckedPermissions: ICheckedPermission
|
|
||||||
) => {
|
|
||||||
const formattedInitialCheckedPermissions =
|
|
||||||
isAllEnvironmentPermissionsChecked(
|
|
||||||
// @ts-expect-error
|
|
||||||
isAllProjectPermissionsChecked(initialCheckedPermissions)
|
|
||||||
);
|
|
||||||
|
|
||||||
setCheckedPermissions(formattedInitialCheckedPermissions || {});
|
|
||||||
};
|
|
||||||
|
|
||||||
const isAllProjectPermissionsChecked = (
|
|
||||||
initialCheckedPermissions: ICheckedPermission
|
|
||||||
) => {
|
|
||||||
const { project } = permissions;
|
|
||||||
if (!project || project.length === 0) return;
|
|
||||||
const isAllChecked = project.every((permission: IPermission) => {
|
|
||||||
return initialCheckedPermissions[getRoleKey(permission)];
|
|
||||||
});
|
|
||||||
|
|
||||||
if (isAllChecked) {
|
|
||||||
// @ts-expect-error
|
|
||||||
initialCheckedPermissions[PROJECT_CHECK_ALL_KEY] = true;
|
|
||||||
} else {
|
|
||||||
delete initialCheckedPermissions[PROJECT_CHECK_ALL_KEY];
|
|
||||||
}
|
|
||||||
|
|
||||||
return initialCheckedPermissions;
|
|
||||||
};
|
|
||||||
|
|
||||||
const isAllEnvironmentPermissionsChecked = (
|
|
||||||
initialCheckedPermissions: ICheckedPermission
|
|
||||||
) => {
|
|
||||||
const { environments } = permissions;
|
|
||||||
if (!environments || environments.length === 0) return;
|
|
||||||
environments.forEach(env => {
|
|
||||||
const isAllChecked = env.permissions.every(
|
|
||||||
(permission: IPermission) => {
|
|
||||||
return initialCheckedPermissions[getRoleKey(permission)];
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const key = `${ENVIRONMENT_CHECK_ALL_KEY}-${env.name}`;
|
|
||||||
|
|
||||||
if (isAllChecked) {
|
|
||||||
// @ts-expect-error
|
|
||||||
initialCheckedPermissions[key] = true;
|
|
||||||
} else {
|
|
||||||
delete initialCheckedPermissions[key];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return initialCheckedPermissions;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getCheckAllKeys = () => {
|
|
||||||
const { environments } = permissions;
|
|
||||||
const envKeys = environments.map(env => {
|
|
||||||
return `${ENVIRONMENT_CHECK_ALL_KEY}-${env.name}`;
|
|
||||||
});
|
|
||||||
|
|
||||||
return [...envKeys, PROJECT_CHECK_ALL_KEY];
|
|
||||||
};
|
|
||||||
|
|
||||||
const handlePermissionChange = (permission: IPermission, type: string) => {
|
|
||||||
let checkedPermissionsCopy = cloneDeep(checkedPermissions);
|
let checkedPermissionsCopy = cloneDeep(checkedPermissions);
|
||||||
|
|
||||||
if (checkedPermissionsCopy[getRoleKey(permission)]) {
|
if (checkedPermissionsCopy[getRoleKey(permission)]) {
|
||||||
@ -118,98 +78,64 @@ const useProjectRoleForm = (
|
|||||||
checkedPermissionsCopy[getRoleKey(permission)] = { ...permission };
|
checkedPermissionsCopy[getRoleKey(permission)] = { ...permission };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type === 'project') {
|
setCheckedPermissions(checkedPermissionsCopy);
|
||||||
// @ts-expect-error
|
};
|
||||||
checkedPermissionsCopy = isAllProjectPermissionsChecked(
|
|
||||||
checkedPermissionsCopy
|
const onToggleAllProjectPermissions = () => {
|
||||||
);
|
const { project } = permissions;
|
||||||
|
let checkedPermissionsCopy = cloneDeep(checkedPermissions);
|
||||||
|
|
||||||
|
const allChecked = project.every(
|
||||||
|
(permission: IPermission) =>
|
||||||
|
checkedPermissionsCopy[getRoleKey(permission)]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (allChecked) {
|
||||||
|
project.forEach((permission: IPermission) => {
|
||||||
|
delete checkedPermissionsCopy[getRoleKey(permission)];
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
// @ts-expect-error
|
project.forEach((permission: IPermission) => {
|
||||||
checkedPermissionsCopy = isAllEnvironmentPermissionsChecked(
|
checkedPermissionsCopy[getRoleKey(permission)] = {
|
||||||
checkedPermissionsCopy
|
...permission,
|
||||||
);
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setCheckedPermissions(checkedPermissionsCopy);
|
setCheckedPermissions(checkedPermissionsCopy);
|
||||||
};
|
};
|
||||||
|
|
||||||
const checkAllProjectPermissions = () => {
|
const onToggleAllEnvironmentPermissions = (envName: string) => {
|
||||||
const { project } = permissions;
|
|
||||||
const checkedPermissionsCopy = cloneDeep(checkedPermissions);
|
|
||||||
const checkedAll = checkedPermissionsCopy[PROJECT_CHECK_ALL_KEY];
|
|
||||||
project.forEach((permission: IPermission, index: number) => {
|
|
||||||
const lastItem = project.length - 1 === index;
|
|
||||||
if (checkedAll) {
|
|
||||||
if (checkedPermissionsCopy[getRoleKey(permission)]) {
|
|
||||||
delete checkedPermissionsCopy[getRoleKey(permission)];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lastItem) {
|
|
||||||
delete checkedPermissionsCopy[PROJECT_CHECK_ALL_KEY];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
checkedPermissionsCopy[getRoleKey(permission)] = {
|
|
||||||
...permission,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (lastItem) {
|
|
||||||
// @ts-expect-error
|
|
||||||
checkedPermissionsCopy[PROJECT_CHECK_ALL_KEY] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
setCheckedPermissions(checkedPermissionsCopy);
|
|
||||||
};
|
|
||||||
|
|
||||||
const checkAllEnvironmentPermissions = (envName: string) => {
|
|
||||||
const { environments } = permissions;
|
const { environments } = permissions;
|
||||||
const checkedPermissionsCopy = cloneDeep(checkedPermissions);
|
const checkedPermissionsCopy = cloneDeep(checkedPermissions);
|
||||||
const environmentCheckAllKey = `${ENVIRONMENT_CHECK_ALL_KEY}-${envName}`;
|
|
||||||
const env = environments.find(env => env.name === envName);
|
const env = environments.find(env => env.name === envName);
|
||||||
if (!env) return;
|
if (!env) return;
|
||||||
const checkedAll = checkedPermissionsCopy[environmentCheckAllKey];
|
|
||||||
|
|
||||||
env.permissions.forEach((permission: IPermission, index: number) => {
|
const allChecked = env.permissions.every(
|
||||||
const lastItem = env.permissions.length - 1 === index;
|
(permission: IPermission) =>
|
||||||
if (checkedAll) {
|
checkedPermissionsCopy[getRoleKey(permission)]
|
||||||
if (checkedPermissionsCopy[getRoleKey(permission)]) {
|
);
|
||||||
delete checkedPermissionsCopy[getRoleKey(permission)];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lastItem) {
|
if (allChecked) {
|
||||||
delete checkedPermissionsCopy[environmentCheckAllKey];
|
env.permissions.forEach((permission: IPermission) => {
|
||||||
}
|
delete checkedPermissionsCopy[getRoleKey(permission)];
|
||||||
} else {
|
});
|
||||||
|
} else {
|
||||||
|
env.permissions.forEach((permission: IPermission) => {
|
||||||
checkedPermissionsCopy[getRoleKey(permission)] = {
|
checkedPermissionsCopy[getRoleKey(permission)] = {
|
||||||
...permission,
|
...permission,
|
||||||
};
|
};
|
||||||
|
});
|
||||||
if (lastItem) {
|
}
|
||||||
// @ts-expect-error
|
|
||||||
checkedPermissionsCopy[environmentCheckAllKey] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
setCheckedPermissions(checkedPermissionsCopy);
|
setCheckedPermissions(checkedPermissionsCopy);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getProjectRolePayload = () => {
|
const getProjectRolePayload = () => ({
|
||||||
const checkAllKeys = getCheckAllKeys();
|
name: roleName,
|
||||||
const permissions = Object.keys(checkedPermissions)
|
description: roleDesc,
|
||||||
.filter(key => {
|
permissions: Object.values(checkedPermissions),
|
||||||
return !checkAllKeys.includes(key);
|
});
|
||||||
})
|
|
||||||
.map(permission => {
|
|
||||||
return checkedPermissions[permission];
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
name: roleName,
|
|
||||||
description: roleDesc,
|
|
||||||
permissions,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const validateNameUniqueness = async () => {
|
const validateNameUniqueness = async () => {
|
||||||
const payload = getProjectRolePayload();
|
const payload = getProjectRolePayload();
|
||||||
@ -244,15 +170,7 @@ const useProjectRoleForm = (
|
|||||||
setErrors({});
|
setErrors({});
|
||||||
};
|
};
|
||||||
|
|
||||||
const getRoleKey = (permission: {
|
// TODO: Clean up when feature is complete - changeRequests
|
||||||
id: number;
|
|
||||||
environment?: string;
|
|
||||||
}): string => {
|
|
||||||
return permission.environment
|
|
||||||
? `${permission.id}-${permission.environment}`
|
|
||||||
: `${permission.id}`;
|
|
||||||
};
|
|
||||||
// Clean up when feature is complete changeRequests
|
|
||||||
let filteredPermissions = cloneDeep(permissions);
|
let filteredPermissions = cloneDeep(permissions);
|
||||||
|
|
||||||
if (!uiConfig?.flags.changeRequests) {
|
if (!uiConfig?.flags.changeRequests) {
|
||||||
@ -278,21 +196,20 @@ const useProjectRoleForm = (
|
|||||||
return {
|
return {
|
||||||
roleName,
|
roleName,
|
||||||
roleDesc,
|
roleDesc,
|
||||||
|
errors,
|
||||||
|
checkedPermissions,
|
||||||
|
permissions: filteredPermissions,
|
||||||
setRoleName,
|
setRoleName,
|
||||||
setRoleDesc,
|
setRoleDesc,
|
||||||
handlePermissionChange,
|
handlePermissionChange,
|
||||||
checkAllProjectPermissions,
|
onToggleAllProjectPermissions,
|
||||||
checkAllEnvironmentPermissions,
|
onToggleAllEnvironmentPermissions,
|
||||||
checkedPermissions,
|
|
||||||
getProjectRolePayload,
|
getProjectRolePayload,
|
||||||
validatePermissions,
|
validatePermissions,
|
||||||
validateName,
|
validateName,
|
||||||
handleInitialCheckedPermissions,
|
|
||||||
clearErrors,
|
clearErrors,
|
||||||
validateNameUniqueness,
|
validateNameUniqueness,
|
||||||
errors,
|
|
||||||
getRoleKey,
|
getRoleKey,
|
||||||
permissions: filteredPermissions,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -25,29 +25,39 @@ interface IChangeRequestProps {
|
|||||||
onNavigate?: () => void;
|
onNavigate?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const StyledSingleChangeBox = styled(Box)<{
|
const StyledSingleChangeBox = styled(Box, {
|
||||||
hasConflict: boolean;
|
shouldForwardProp: (prop: string) => !prop.startsWith('$'),
|
||||||
isAfterWarning: boolean;
|
})<{
|
||||||
isLast: boolean;
|
$hasConflict: boolean;
|
||||||
inInConflictFeature: boolean;
|
$isAfterWarning: boolean;
|
||||||
}>(({ theme, hasConflict, inInConflictFeature, isAfterWarning, isLast }) => ({
|
$isLast: boolean;
|
||||||
borderLeft: '1px solid',
|
$isInConflictFeature: boolean;
|
||||||
borderRight: '1px solid',
|
}>(
|
||||||
borderTop: '1px solid',
|
({
|
||||||
borderBottom: isLast ? '1px solid' : 'none',
|
theme,
|
||||||
borderRadius: isLast
|
$hasConflict,
|
||||||
? `0 0
|
$isInConflictFeature,
|
||||||
|
$isAfterWarning,
|
||||||
|
$isLast,
|
||||||
|
}) => ({
|
||||||
|
borderLeft: '1px solid',
|
||||||
|
borderRight: '1px solid',
|
||||||
|
borderTop: '1px solid',
|
||||||
|
borderBottom: $isLast ? '1px solid' : 'none',
|
||||||
|
borderRadius: $isLast
|
||||||
|
? `0 0
|
||||||
${theme.shape.borderRadiusLarge}px ${theme.shape.borderRadiusLarge}px`
|
${theme.shape.borderRadiusLarge}px ${theme.shape.borderRadiusLarge}px`
|
||||||
: 0,
|
: 0,
|
||||||
borderColor:
|
borderColor:
|
||||||
hasConflict || inInConflictFeature
|
$hasConflict || $isInConflictFeature
|
||||||
? theme.palette.warning.border
|
? theme.palette.warning.border
|
||||||
: theme.palette.dividerAlternative,
|
: theme.palette.dividerAlternative,
|
||||||
borderTopColor:
|
borderTopColor:
|
||||||
(hasConflict || isAfterWarning) && !inInConflictFeature
|
($hasConflict || $isAfterWarning) && !$isInConflictFeature
|
||||||
? theme.palette.warning.border
|
? theme.palette.warning.border
|
||||||
: theme.palette.dividerAlternative,
|
: theme.palette.dividerAlternative,
|
||||||
}));
|
})
|
||||||
|
);
|
||||||
|
|
||||||
const StyledAlert = styled(Alert)(({ theme }) => ({
|
const StyledAlert = styled(Alert)(({ theme }) => ({
|
||||||
borderRadius: 0,
|
borderRadius: 0,
|
||||||
@ -94,14 +104,14 @@ export const ChangeRequest: VFC<IChangeRequestProps> = ({
|
|||||||
{featureToggleChange.changes.map((change, index) => (
|
{featureToggleChange.changes.map((change, index) => (
|
||||||
<StyledSingleChangeBox
|
<StyledSingleChangeBox
|
||||||
key={objectId(change)}
|
key={objectId(change)}
|
||||||
hasConflict={Boolean(change.conflict)}
|
$hasConflict={Boolean(change.conflict)}
|
||||||
inInConflictFeature={Boolean(
|
$isInConflictFeature={Boolean(
|
||||||
featureToggleChange.conflict
|
featureToggleChange.conflict
|
||||||
)}
|
)}
|
||||||
isAfterWarning={Boolean(
|
$isAfterWarning={Boolean(
|
||||||
featureToggleChange.changes[index - 1]?.conflict
|
featureToggleChange.changes[index - 1]?.conflict
|
||||||
)}
|
)}
|
||||||
isLast={
|
$isLast={
|
||||||
index + 1 === featureToggleChange.changes.length
|
index + 1 === featureToggleChange.changes.length
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
Loading…
Reference in New Issue
Block a user