1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-02-09 00:18:00 +01:00

fix: multi project roles UI improvements (#4646)

https://linear.app/unleash/issue/2-1373/small-ui-fixes-and-improvements

- Update group root role `HelpIcon` tooltip to reflect the new behavior;
- Fixes a crash on groups search where `description` could be
`undefined`;
 - Improves `RoleDescription` design;
 - Fixes the role label in `ProjectGroupView`;
 

![image](https://github.com/Unleash/unleash/assets/14320932/f16ebe98-9408-4edd-8e2b-1e56ba2ad5c6)
This commit is contained in:
Nuno Góis 2023-09-08 11:42:58 +01:00 committed by GitHub
parent 10a62642d7
commit 61174a1d9c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 79 additions and 37 deletions

View File

@ -179,7 +179,7 @@ export const GroupForm: FC<IGroupForm> = ({
<StyledInputDescription> <StyledInputDescription>
<Box sx={{ display: 'flex' }}> <Box sx={{ display: 'flex' }}>
Do you want to associate a root role with this group? Do you want to associate a root role with this group?
<HelpIcon tooltip="When you associate an Admin or Editor role with this group, users in this group will automatically inherit the role globally. Note that groups with a root role association cannot be assigned to projects." /> <HelpIcon tooltip="When you associate a root role with this group, users in this group will automatically inherit the role globally." />
</Box> </Box>
</StyledInputDescription> </StyledInputDescription>
<StyledAutocompleteWrapper> <StyledAutocompleteWrapper>

View File

@ -30,7 +30,7 @@ const groupsSearch = (group: IGroup, searchValue: string) => {
}; };
return ( return (
group.name.toLowerCase().includes(search) || group.name.toLowerCase().includes(search) ||
group.description.toLowerCase().includes(search) || group.description?.toLowerCase().includes(search) ||
users.names?.some(name => name.includes(search)) || users.names?.some(name => name.includes(search)) ||
users.usernames?.some(username => username.includes(search)) || users.usernames?.some(username => username.includes(search)) ||
users.emails?.some(email => email.includes(search)) users.emails?.some(email => email.includes(search))

View File

@ -1,4 +1,5 @@
import { SxProps, Theme, styled } from '@mui/material'; import { SxProps, Theme, styled } from '@mui/material';
import { SupervisedUserCircle } from '@mui/icons-material';
import { ConditionallyRender } from '../ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from '../ConditionallyRender/ConditionallyRender';
import { useRole } from 'hooks/api/getters/useRole/useRole'; import { useRole } from 'hooks/api/getters/useRole/useRole';
import { import {
@ -24,20 +25,45 @@ const StyledDescription = styled('div', {
borderRadius: tooltip ? 0 : theme.shape.borderRadiusMedium, borderRadius: tooltip ? 0 : theme.shape.borderRadiusMedium,
})); }));
const StyledDescriptionBlock = styled('div')(({ theme }) => ({ const StyledPermissionsLabel = styled('p')(({ theme }) => ({
color: theme.palette.text.primary,
marginTop: theme.spacing(2), marginTop: theme.spacing(2),
marginBottom: theme.spacing(0.5),
})); }));
const StyledDescriptionHeader = styled('p')(({ theme }) => ({ const StyledPermissions = styled('div')(({ theme }) => ({
display: 'flex',
flexDirection: 'column',
gap: theme.spacing(1),
}));
const StyledRoleHeader = styled('p')(({ theme }) => ({
display: 'flex',
alignItems: 'center',
gap: theme.spacing(0.5),
color: theme.palette.text.primary, color: theme.palette.text.primary,
fontSize: theme.fontSizes.smallBody, fontSize: theme.fontSizes.bodySize,
fontWeight: theme.fontWeight.bold,
}));
const StyledSupervisedUserCircle = styled(SupervisedUserCircle)(
({ theme }) => ({
fontSize: theme.fontSizes.mainHeader,
})
);
const StyledDescriptionHeader = styled('p')(({ theme }) => ({
color: theme.palette.text.secondary,
fontWeight: theme.fontWeight.bold, fontWeight: theme.fontWeight.bold,
marginBottom: theme.spacing(1),
})); }));
const StyledDescriptionSubHeader = styled('p')(({ theme }) => ({ const StyledDescriptionSubHeader = styled('p')(({ theme }) => ({
fontSize: theme.fontSizes.smallBody, marginTop: theme.spacing(0.5),
marginTop: theme.spacing(1), }));
const StyledPermissionsList = styled('ul')(({ theme }) => ({
margin: 0,
paddingLeft: theme.spacing(2),
})); }));
interface IRoleDescriptionProps { interface IRoleDescriptionProps {
@ -66,28 +92,38 @@ export const RoleDescription = ({
return ( return (
<StyledDescription tooltip={tooltip} {...rest}> <StyledDescription tooltip={tooltip} {...rest}>
<StyledDescriptionHeader sx={{ mb: 0 }}> <StyledRoleHeader>
<StyledSupervisedUserCircle color="disabled" />
{name} {name}
</StyledDescriptionHeader> </StyledRoleHeader>
<StyledDescriptionSubHeader> <StyledDescriptionSubHeader>
{description} {description}
</StyledDescriptionSubHeader> </StyledDescriptionSubHeader>
<ConditionallyRender <ConditionallyRender
condition={!PREDEFINED_ROLE_TYPES.includes(role.type)} condition={!PREDEFINED_ROLE_TYPES.includes(role.type)}
show={() => show={() => (
categories.map(({ label, permissions }) => ( <>
<StyledDescriptionBlock key={label}> <StyledPermissionsLabel>
<StyledDescriptionHeader> Role permissions:
{label} </StyledPermissionsLabel>
</StyledDescriptionHeader> <StyledPermissions>
{permissions.map(permission => ( {categories.map(({ label, permissions }) => (
<p key={permission.id}> <div key={label}>
{permission.displayName} <StyledDescriptionHeader>
</p> {label}
</StyledDescriptionHeader>
<StyledPermissionsList>
{permissions.map(permission => (
<li key={permission.id}>
{permission.displayName}
</li>
))}
</StyledPermissionsList>
</div>
))} ))}
</StyledDescriptionBlock> </StyledPermissions>
)) </>
} )}
/> />
</StyledDescription> </StyledDescription>
); );

View File

@ -98,6 +98,11 @@ export const ProjectAccessTable: VFC = () => {
const [groupOpen, setGroupOpen] = useState(false); const [groupOpen, setGroupOpen] = useState(false);
const [selectedRow, setSelectedRow] = useState<IProjectAccess>(); const [selectedRow, setSelectedRow] = useState<IProjectAccess>();
const roleText = (roles: number[]): string =>
roles.length > 1
? `${roles.length} roles`
: access?.roles.find(({ id }) => id === roles[0])?.name || '';
const columns = useMemo( const columns = useMemo(
() => [ () => [
{ {
@ -150,14 +155,7 @@ export const ProjectAccessTable: VFC = () => {
{ {
id: 'role', id: 'role',
Header: 'Role', Header: 'Role',
accessor: (row: IProjectAccess) => accessor: (row: IProjectAccess) => roleText(row.entity.roles),
row.entity.roles
? row.entity.roles.length > 1
? `${row.entity.roles.length} roles`
: access?.roles.find(
({ id }) => id === row.entity.roleId
)?.name
: 'No Roles!',
Cell: ({ Cell: ({
value, value,
row: { original: row }, row: { original: row },
@ -490,11 +488,17 @@ export const ProjectAccessTable: VFC = () => {
setOpen={setGroupOpen} setOpen={setGroupOpen}
group={selectedRow?.entity as IGroup} group={selectedRow?.entity as IGroup}
projectId={projectId} projectId={projectId}
subtitle={`Role: ${ subtitle={
access?.roles.find( <>
({ id }) => id === selectedRow?.entity.roleId {selectedRow && selectedRow.entity.roles.length > 1
)?.name ? 'Roles:'
}`} : 'Role:'}
<RoleCell
value={roleText(selectedRow?.entity.roles || [])}
roles={selectedRow?.entity.roles || []}
/>
</>
}
onEdit={() => { onEdit={() => {
navigate(`edit/group/${selectedRow?.entity.id}`); navigate(`edit/group/${selectedRow?.entity.id}`);
}} }}

View File

@ -41,6 +41,8 @@ const StyledTitle = styled('div')(({ theme }) => ({
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
'& > span': { '& > span': {
display: 'flex',
alignItems: 'center',
color: theme.palette.text.secondary, color: theme.palette.text.secondary,
fontSize: theme.fontSizes.bodySize, fontSize: theme.fontSizes.bodySize,
}, },
@ -113,7 +115,7 @@ interface IProjectGroupViewProps {
setOpen: React.Dispatch<React.SetStateAction<boolean>>; setOpen: React.Dispatch<React.SetStateAction<boolean>>;
group: IGroup; group: IGroup;
projectId: string; projectId: string;
subtitle: string; subtitle: React.ReactNode;
onEdit: () => void; onEdit: () => void;
onRemove: () => void; onRemove: () => void;
} }