From 42eadef8da187dd67f29927c77945ddab2eddcfa Mon Sep 17 00:00:00 2001 From: Tymoteusz Czech <2625371+Tymek@users.noreply.github.com> Date: Thu, 17 Nov 2022 15:33:23 +0100 Subject: [PATCH] Custom roles redesign (#2439) ## About the changes Visual update to project permissions ![image](https://user-images.githubusercontent.com/2625371/201968786-81d6e068-43e0-43ba-b3d9-d8e550345409.png) 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 --- .../CreateProjectRole/CreateProjectRole.tsx | 19 +- .../EditProjectRole/EditProjectRole.tsx | 47 ++-- .../EnvironmentPermissionAccordion.styles.ts | 35 --- .../EnvironmentPermissionAccordion.tsx | 153 ------------ .../PermissionAccordion.tsx | 178 ++++++++++++++ .../ProjectRoleForm/ProjectRoleForm.styles.ts | 41 ---- .../ProjectRoleForm/ProjectRoleForm.tsx | 204 +++++++--------- .../projectRoles/hooks/useProjectRoleForm.ts | 231 ++++++------------ .../ChangeRequest/ChangeRequest.tsx | 62 +++-- 9 files changed, 400 insertions(+), 570 deletions(-) delete mode 100644 frontend/src/component/admin/projectRoles/ProjectRoleForm/EnvironmentPermissionAccordion/EnvironmentPermissionAccordion.styles.ts delete mode 100644 frontend/src/component/admin/projectRoles/ProjectRoleForm/EnvironmentPermissionAccordion/EnvironmentPermissionAccordion.tsx create mode 100644 frontend/src/component/admin/projectRoles/ProjectRoleForm/PermissionAccordion/PermissionAccordion.tsx delete mode 100644 frontend/src/component/admin/projectRoles/ProjectRoleForm/ProjectRoleForm.styles.ts diff --git a/frontend/src/component/admin/projectRoles/CreateProjectRole/CreateProjectRole.tsx b/frontend/src/component/admin/projectRoles/CreateProjectRole/CreateProjectRole.tsx index cb0b547901..84fc4a347a 100644 --- a/frontend/src/component/admin/projectRoles/CreateProjectRole/CreateProjectRole.tsx +++ b/frontend/src/component/admin/projectRoles/CreateProjectRole/CreateProjectRole.tsx @@ -17,24 +17,25 @@ const CreateProjectRole = () => { const { roleName, roleDesc, + permissions, + checkedPermissions, + errors, setRoleName, setRoleDesc, - checkedPermissions, handlePermissionChange, - checkAllProjectPermissions, - checkAllEnvironmentPermissions, + onToggleAllProjectPermissions: checkAllProjectPermissions, + onToggleAllEnvironmentPermissions: checkAllEnvironmentPermissions, getProjectRolePayload, validatePermissions, validateName, validateNameUniqueness, - errors, clearErrors, getRoleKey, } = useProjectRoleForm(); const { createRole, loading } = useProjectRolesApi(); - const handleSubmit = async (e: Event) => { + const onSubmit = async (e: Event) => { e.preventDefault(); clearErrors(); const validName = validateName(); @@ -66,7 +67,7 @@ const CreateProjectRole = () => { --data-raw '${JSON.stringify(getProjectRolePayload(), undefined, 2)}'`; }; - const handleCancel = () => { + const onCancel = () => { navigate(GO_BACK); }; @@ -83,8 +84,9 @@ const CreateProjectRole = () => { > { handlePermissionChange={handlePermissionChange} checkAllProjectPermissions={checkAllProjectPermissions} checkAllEnvironmentPermissions={checkAllEnvironmentPermissions} - mode="Create" clearErrors={clearErrors} validateNameUniqueness={validateNameUniqueness} getRoleKey={getRoleKey} diff --git a/frontend/src/component/admin/projectRoles/EditProjectRole/EditProjectRole.tsx b/frontend/src/component/admin/projectRoles/EditProjectRole/EditProjectRole.tsx index f00d947fe7..e44d5639ed 100644 --- a/frontend/src/component/admin/projectRoles/EditProjectRole/EditProjectRole.tsx +++ b/frontend/src/component/admin/projectRoles/EditProjectRole/EditProjectRole.tsx @@ -6,7 +6,6 @@ import useProjectRolesApi from 'hooks/api/actions/useProjectRolesApi/useProjectR import useProjectRole from 'hooks/api/getters/useProjectRole/useProjectRole'; import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; import useToast from 'hooks/useToast'; -import { IPermission } from 'interfaces/project'; import { useNavigate } from 'react-router-dom'; import useProjectRoleForm from '../hooks/useProjectRoleForm'; import ProjectRoleForm from '../ProjectRoleForm/ProjectRoleForm'; @@ -24,38 +23,20 @@ const EditProjectRole = () => { const { roleName, roleDesc, + permissions, + checkedPermissions, + errors, setRoleName, setRoleDesc, - checkedPermissions, handlePermissionChange, - checkAllProjectPermissions, - checkAllEnvironmentPermissions, + onToggleAllProjectPermissions, + onToggleAllEnvironmentPermissions, getProjectRolePayload, validatePermissions, validateName, - errors, clearErrors, getRoleKey, - handleInitialCheckedPermissions, - 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, - ]); + } = useProjectRoleForm(role.name, role.description, role?.permissions); const formatApiCode = () => { return `curl --location --request PUT '${ @@ -69,7 +50,7 @@ const EditProjectRole = () => { const { refetch } = useProjectRole(projectId); const { editRole, loading } = useProjectRolesApi(); - const handleSubmit = async (e: Event) => { + const onSubmit = async (e: Event) => { e.preventDefault(); const payload = getProjectRolePayload(); @@ -93,7 +74,7 @@ const EditProjectRole = () => { } }; - const handleCancel = () => { + const onCancel = () => { navigate(GO_BACK); }; @@ -109,17 +90,19 @@ to resources within a project" formatApiCode={formatApiCode} > ({ - 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, - }, -})); diff --git a/frontend/src/component/admin/projectRoles/ProjectRoleForm/EnvironmentPermissionAccordion/EnvironmentPermissionAccordion.tsx b/frontend/src/component/admin/projectRoles/ProjectRoleForm/EnvironmentPermissionAccordion/EnvironmentPermissionAccordion.tsx deleted file mode 100644 index 95f715952b..0000000000 --- a/frontend/src/component/admin/projectRoles/ProjectRoleForm/EnvironmentPermissionAccordion/EnvironmentPermissionAccordion.tsx +++ /dev/null @@ -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({}); - 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 ( - - handlePermissionChange( - permission, - environment.name - ) - } - color="primary" - /> - } - label={permission.displayName} - /> - ); - } - ); - - envPermissions.push( - - checkAllEnvironmentPermissions(environment?.name) - } - color="primary" - /> - } - label={'Select all permissions for this env'} - /> - ); - - return envPermissions; - }; - - return ( -
- - - } - > -
- -   -

- ({permissionCount} /{' '} - {environment?.permissions?.length} permissions) -

-
-
- - {renderPermissions()} - -
-
- ); -}; - -export default EnvironmentPermissionAccordion; diff --git a/frontend/src/component/admin/projectRoles/ProjectRoleForm/PermissionAccordion/PermissionAccordion.tsx b/frontend/src/component/admin/projectRoles/ProjectRoleForm/PermissionAccordion/PermissionAccordion.tsx new file mode 100644 index 0000000000..120a49690e --- /dev/null +++ b/frontend/src/component/admin/projectRoles/ProjectRoleForm/PermissionAccordion/PermissionAccordion.tsx @@ -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 = ({ + title, + permissions, + checkedPermissions, + Icon, + isInitiallyExpanded, + context, + onPermissionChange, + onCheckAll, + getRoleKey, +}) => { + const [expanded, setExpanded] = useState(isInitiallyExpanded); + const permissionMap = useMemo( + () => + permissions?.reduce( + (acc: Record, 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 ( + + setExpanded(!expanded)} + sx={{ + boxShadow: 'none', + px: 3, + py: 1, + border: theme => `1px solid ${theme.palette.divider}`, + borderRadius: theme => `${theme.shape.borderRadiusLarge}px`, + }} + > + + + + } + sx={{ + boxShadow: 'none', + padding: '0', + }} + > + + {Icon} + {' '} + + ({permissionCount} / {permissions?.length}{' '} + permissions) + + + + + + + + {permissions?.map((permission: IPermission) => { + return ( + + onPermissionChange(permission) + } + color="primary" + /> + } + label={permission.displayName} + /> + ); + })} + + + + + ); +}; diff --git a/frontend/src/component/admin/projectRoles/ProjectRoleForm/ProjectRoleForm.styles.ts b/frontend/src/component/admin/projectRoles/ProjectRoleForm/ProjectRoleForm.styles.ts deleted file mode 100644 index 55eca794a7..0000000000 --- a/frontend/src/component/admin/projectRoles/ProjectRoleForm/ProjectRoleForm.styles.ts +++ /dev/null @@ -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', - }, -})); diff --git a/frontend/src/component/admin/projectRoles/ProjectRoleForm/ProjectRoleForm.tsx b/frontend/src/component/admin/projectRoles/ProjectRoleForm/ProjectRoleForm.tsx index c1244c38fa..adb553c229 100644 --- a/frontend/src/component/admin/projectRoles/ProjectRoleForm/ProjectRoleForm.tsx +++ b/frontend/src/component/admin/projectRoles/ProjectRoleForm/ProjectRoleForm.tsx @@ -1,134 +1,69 @@ -import Input from 'component/common/Input/Input'; -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 React, { Dispatch, FC, ReactNode, SetStateAction } from 'react'; import { - ICheckedPermission, - PROJECT_CHECK_ALL_KEY, -} from '../hooks/useProjectRoleForm'; + Topic as TopicIcon, + CloudCircle as CloudCircleIcon, +} 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 { roleName: string; roleDesc: string; - setRoleName: React.Dispatch>; - setRoleDesc: React.Dispatch>; checkedPermissions: ICheckedPermission; - handlePermissionChange: (permission: IPermission, type: string) => void; + errors: { [key: string]: string }; + children: ReactNode; + permissions: + | IProjectRolePermissions + | { + project: IPermission[]; + environments: IProjectEnvironmentPermissions[]; + }; + setRoleName: Dispatch>; + setRoleDesc: Dispatch>; + handlePermissionChange: (permission: IPermission) => void; checkAllProjectPermissions: () => void; checkAllEnvironmentPermissions: (envName: string) => void; - handleSubmit: (e: any) => void; - handleCancel: () => void; - errors: { [key: string]: string }; - mode?: string; + onSubmit: (e: any) => void; + onCancel: () => void; clearErrors: () => void; validateNameUniqueness?: () => void; getRoleKey: (permission: { id: number; environment?: string }) => string; - children: ReactNode; } -const ProjectRoleForm: React.FC = ({ +const ProjectRoleForm: FC = ({ children, - handleSubmit, - handleCancel, roleName, roleDesc, + checkedPermissions, + errors, + permissions, + onSubmit, + onCancel, setRoleName, setRoleDesc, - checkedPermissions, handlePermissionChange, checkAllProjectPermissions, checkAllEnvironmentPermissions, - errors, - mode, validateNameUniqueness, clearErrors, getRoleKey, }: IProjectRoleForm) => { - const { classes: styles } = useStyles(); - const { permissions } = useProjectRolePermissions({ - revalidateIfStale: false, - revalidateOnReconnect: false, - revalidateOnFocus: false, - }); - const { project, environments } = permissions; - const renderProjectPermissions = () => { - const projectPermissions = project.map(permission => { - return ( - - handlePermissionChange(permission, 'project') - } - color="primary" - /> - } - label={permission.displayName} - /> - ); - }); - - projectPermissions.push( - checkAllProjectPermissions()} - color="primary" - /> - } - label={'Select all project permissions'} - /> - ); - - return projectPermissions; - }; - - const renderEnvironmentPermissions = () => { - return environments.map(environment => { - return ( - - ); - }); - }; - return ( -
-
-

- What is your role name? -

+ + + What is your role name? setRoleName(e.target.value)} @@ -137,41 +72,76 @@ const ProjectRoleForm: React.FC = ({ onFocus={() => clearErrors()} onBlur={validateNameUniqueness} autoFocus + sx={{ width: '100%', marginBottom: '1rem' }} /> -

- What is this role for? -

+ What is this role for? setRoleDesc(e.target.value)} + sx={{ width: '100%', marginBottom: '1rem' }} /> -
-
+ +
+ You must select at least one permission for a role. - + } />
-

Project permissions

-
{renderProjectPermissions()}
-

Environment permissions

-
{renderEnvironmentPermissions()}
-
+ } + permissions={project} + checkedPermissions={checkedPermissions} + onPermissionChange={(permission: IPermission) => + handlePermissionChange(permission) + } + onCheckAll={checkAllProjectPermissions} + getRoleKey={getRoleKey} + context="project" + /> +
+ {environments.map(environment => ( + + } + permissions={environment.permissions} + key={environment.name} + checkedPermissions={checkedPermissions} + onPermissionChange={(permission: IPermission) => + handlePermissionChange(permission) + } + onCheckAll={() => + checkAllEnvironmentPermissions(environment.name) + } + getRoleKey={getRoleKey} + context="environment" + /> + ))} +
+ {children} - -
+ ); }; diff --git a/frontend/src/component/admin/projectRoles/hooks/useProjectRoleForm.ts b/frontend/src/component/admin/projectRoles/hooks/useProjectRoleForm.ts index fb06c515f3..959e0ea51e 100644 --- a/frontend/src/component/admin/projectRoles/hooks/useProjectRoleForm.ts +++ b/frontend/src/component/admin/projectRoles/hooks/useProjectRoleForm.ts @@ -14,13 +14,19 @@ export interface ICheckedPermission { [key: string]: IPermission; } -export const PROJECT_CHECK_ALL_KEY = 'check-all-project'; -export const ENVIRONMENT_CHECK_ALL_KEY = 'check-all-environment'; +const getRoleKey = (permission: { + id: number; + environment?: string; +}): string => { + return permission.environment + ? `${permission.id}-${permission.environment}` + : `${permission.id}`; +}; const useProjectRoleForm = ( initialRoleName = '', initialRoleDesc = '', - initialCheckedPermissions = {} + initialCheckedPermissions: IPermission[] = [] ) => { const { uiConfig } = useUiConfig(); const { permissions } = useProjectRolePermissions({ @@ -32,7 +38,25 @@ const useProjectRoleForm = ( const [roleName, setRoleName] = useState(initialRoleName); const [roleDesc, setRoleDesc] = useState(initialRoleDesc); const [checkedPermissions, setCheckedPermissions] = - useState(initialCheckedPermissions); + useState({}); + + 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 { validateRole } = useProjectRolesApi(); @@ -45,71 +69,7 @@ const useProjectRoleForm = ( setRoleDesc(initialRoleDesc); }, [initialRoleDesc]); - const handleInitialCheckedPermissions = ( - 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) => { + const handlePermissionChange = (permission: IPermission) => { let checkedPermissionsCopy = cloneDeep(checkedPermissions); if (checkedPermissionsCopy[getRoleKey(permission)]) { @@ -118,98 +78,64 @@ const useProjectRoleForm = ( checkedPermissionsCopy[getRoleKey(permission)] = { ...permission }; } - if (type === 'project') { - // @ts-expect-error - checkedPermissionsCopy = isAllProjectPermissionsChecked( - checkedPermissionsCopy - ); + setCheckedPermissions(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 { - // @ts-expect-error - checkedPermissionsCopy = isAllEnvironmentPermissionsChecked( - checkedPermissionsCopy - ); + project.forEach((permission: IPermission) => { + checkedPermissionsCopy[getRoleKey(permission)] = { + ...permission, + }; + }); } setCheckedPermissions(checkedPermissionsCopy); }; - const checkAllProjectPermissions = () => { - 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 onToggleAllEnvironmentPermissions = (envName: string) => { const { environments } = permissions; const checkedPermissionsCopy = cloneDeep(checkedPermissions); - const environmentCheckAllKey = `${ENVIRONMENT_CHECK_ALL_KEY}-${envName}`; const env = environments.find(env => env.name === envName); if (!env) return; - const checkedAll = checkedPermissionsCopy[environmentCheckAllKey]; - env.permissions.forEach((permission: IPermission, index: number) => { - const lastItem = env.permissions.length - 1 === index; - if (checkedAll) { - if (checkedPermissionsCopy[getRoleKey(permission)]) { - delete checkedPermissionsCopy[getRoleKey(permission)]; - } + const allChecked = env.permissions.every( + (permission: IPermission) => + checkedPermissionsCopy[getRoleKey(permission)] + ); - if (lastItem) { - delete checkedPermissionsCopy[environmentCheckAllKey]; - } - } else { + if (allChecked) { + env.permissions.forEach((permission: IPermission) => { + delete checkedPermissionsCopy[getRoleKey(permission)]; + }); + } else { + env.permissions.forEach((permission: IPermission) => { checkedPermissionsCopy[getRoleKey(permission)] = { ...permission, }; - - if (lastItem) { - // @ts-expect-error - checkedPermissionsCopy[environmentCheckAllKey] = true; - } - } - }); + }); + } setCheckedPermissions(checkedPermissionsCopy); }; - const getProjectRolePayload = () => { - const checkAllKeys = getCheckAllKeys(); - const permissions = Object.keys(checkedPermissions) - .filter(key => { - return !checkAllKeys.includes(key); - }) - .map(permission => { - return checkedPermissions[permission]; - }); - return { - name: roleName, - description: roleDesc, - permissions, - }; - }; + const getProjectRolePayload = () => ({ + name: roleName, + description: roleDesc, + permissions: Object.values(checkedPermissions), + }); const validateNameUniqueness = async () => { const payload = getProjectRolePayload(); @@ -244,15 +170,7 @@ const useProjectRoleForm = ( setErrors({}); }; - const getRoleKey = (permission: { - id: number; - environment?: string; - }): string => { - return permission.environment - ? `${permission.id}-${permission.environment}` - : `${permission.id}`; - }; - // Clean up when feature is complete changeRequests + // TODO: Clean up when feature is complete - changeRequests let filteredPermissions = cloneDeep(permissions); if (!uiConfig?.flags.changeRequests) { @@ -278,21 +196,20 @@ const useProjectRoleForm = ( return { roleName, roleDesc, + errors, + checkedPermissions, + permissions: filteredPermissions, setRoleName, setRoleDesc, handlePermissionChange, - checkAllProjectPermissions, - checkAllEnvironmentPermissions, - checkedPermissions, + onToggleAllProjectPermissions, + onToggleAllEnvironmentPermissions, getProjectRolePayload, validatePermissions, validateName, - handleInitialCheckedPermissions, clearErrors, validateNameUniqueness, - errors, getRoleKey, - permissions: filteredPermissions, }; }; diff --git a/frontend/src/component/changeRequest/ChangeRequest/ChangeRequest.tsx b/frontend/src/component/changeRequest/ChangeRequest/ChangeRequest.tsx index 6c09095281..50fe6a2b06 100644 --- a/frontend/src/component/changeRequest/ChangeRequest/ChangeRequest.tsx +++ b/frontend/src/component/changeRequest/ChangeRequest/ChangeRequest.tsx @@ -25,29 +25,39 @@ interface IChangeRequestProps { onNavigate?: () => void; } -const StyledSingleChangeBox = styled(Box)<{ - hasConflict: boolean; - isAfterWarning: boolean; - isLast: boolean; - inInConflictFeature: boolean; -}>(({ theme, hasConflict, inInConflictFeature, isAfterWarning, isLast }) => ({ - borderLeft: '1px solid', - borderRight: '1px solid', - borderTop: '1px solid', - borderBottom: isLast ? '1px solid' : 'none', - borderRadius: isLast - ? `0 0 +const StyledSingleChangeBox = styled(Box, { + shouldForwardProp: (prop: string) => !prop.startsWith('$'), +})<{ + $hasConflict: boolean; + $isAfterWarning: boolean; + $isLast: boolean; + $isInConflictFeature: boolean; +}>( + ({ + theme, + $hasConflict, + $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` - : 0, - borderColor: - hasConflict || inInConflictFeature - ? theme.palette.warning.border - : theme.palette.dividerAlternative, - borderTopColor: - (hasConflict || isAfterWarning) && !inInConflictFeature - ? theme.palette.warning.border - : theme.palette.dividerAlternative, -})); + : 0, + borderColor: + $hasConflict || $isInConflictFeature + ? theme.palette.warning.border + : theme.palette.dividerAlternative, + borderTopColor: + ($hasConflict || $isAfterWarning) && !$isInConflictFeature + ? theme.palette.warning.border + : theme.palette.dividerAlternative, + }) +); const StyledAlert = styled(Alert)(({ theme }) => ({ borderRadius: 0, @@ -94,14 +104,14 @@ export const ChangeRequest: VFC = ({ {featureToggleChange.changes.map((change, index) => (