mirror of
https://github.com/Unleash/unleash.git
synced 2025-03-18 00:19:49 +01:00
chore: change access overview to lists in accordions (#9535)
https://linear.app/unleash/issue/2-3343/accordions-not-a-must-have https://linear.app/unleash/issue/2-3345/indicator-of-how-many-permissions Changes our Access Overview from tables to lists in accordions. Also includes the total permissions in the accordion summary. Looking at the designs it seems like lists would make the most sense, both visually and in terms of semantics. This will also allow us to group the permissions both visually and semantically in a future task.  ### Update Also improved our project permissions label.   --------- Co-authored-by: Gastón Fournier <gaston@getunleash.io>
This commit is contained in:
parent
872162eb7c
commit
8e67594f1b
@ -2,7 +2,6 @@ import { PageContent } from 'component/common/PageContent/PageContent';
|
||||
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||
import useUserInfo from 'hooks/api/getters/useUserInfo/useUserInfo';
|
||||
import { AccessOverviewTable } from './AccessOverviewTable';
|
||||
import { styled, useMediaQuery } from '@mui/material';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useEnvironments } from 'hooks/api/getters/useEnvironments/useEnvironments';
|
||||
@ -12,6 +11,7 @@ import { StringParam, useQueryParams } from 'use-query-params';
|
||||
import useProjects from 'hooks/api/getters/useProjects/useProjects';
|
||||
import { AccessOverviewSelect } from './AccessOverviewSelect';
|
||||
import { useUserAccessOverview } from 'hooks/api/getters/useUserAccessOverview/useUserAccessOverview';
|
||||
import { AccessOverviewAccordion } from './AccessOverviewAccordion/AccessOverviewAccordion';
|
||||
|
||||
const StyledActionsContainer = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
@ -24,8 +24,10 @@ const StyledActionsContainer = styled('div')(({ theme }) => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledTitle = styled('h2')(({ theme }) => ({
|
||||
margin: theme.spacing(2, 0),
|
||||
const StyledAccessOverviewContainer = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: theme.spacing(2),
|
||||
}));
|
||||
|
||||
export const AccessOverview = () => {
|
||||
@ -104,19 +106,24 @@ export const AccessOverview = () => {
|
||||
</PageHeader>
|
||||
}
|
||||
>
|
||||
<StyledTitle>
|
||||
Root permissions for role {rootRole?.name}
|
||||
</StyledTitle>
|
||||
<AccessOverviewTable permissions={overview?.root ?? []} />
|
||||
<StyledTitle>
|
||||
Project permissions for project {project} with project roles [
|
||||
{projectRoles?.map((role: any) => role.name).join(', ')}]
|
||||
</StyledTitle>
|
||||
<AccessOverviewTable permissions={overview?.project ?? []} />
|
||||
<StyledTitle>
|
||||
Environment permissions for environment {environment}
|
||||
</StyledTitle>
|
||||
<AccessOverviewTable permissions={overview?.environment ?? []} />
|
||||
<StyledAccessOverviewContainer>
|
||||
<AccessOverviewAccordion permissions={overview?.root ?? []}>
|
||||
Root permissions for role {rootRole?.name}
|
||||
</AccessOverviewAccordion>
|
||||
<AccessOverviewAccordion permissions={overview?.project ?? []}>
|
||||
Project permissions
|
||||
{project
|
||||
? ` for project ${project}${projectRoles?.length ? ` with project role${projectRoles.length !== 1 ? 's' : ''} ${projectRoles?.map((role: any) => role.name).join(', ')}` : ''}`
|
||||
: ''}
|
||||
</AccessOverviewAccordion>
|
||||
{environment && (
|
||||
<AccessOverviewAccordion
|
||||
permissions={overview?.environment ?? []}
|
||||
>
|
||||
Environment permissions for {environment}
|
||||
</AccessOverviewAccordion>
|
||||
)}
|
||||
</StyledAccessOverviewContainer>
|
||||
</PageContent>
|
||||
);
|
||||
};
|
||||
|
@ -0,0 +1,78 @@
|
||||
import ExpandMore from '@mui/icons-material/ExpandMore';
|
||||
import {
|
||||
Accordion,
|
||||
AccordionDetails,
|
||||
AccordionSummary,
|
||||
styled,
|
||||
} from '@mui/material';
|
||||
import type { IAccessOverviewPermission } from 'interfaces/permissions';
|
||||
import { AccessOverviewList } from './AccessOverviewList';
|
||||
|
||||
const StyledAccordion = styled(Accordion)(({ theme }) => ({
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
borderRadius: theme.shape.borderRadiusLarge,
|
||||
overflow: 'hidden',
|
||||
boxShadow: 'none',
|
||||
margin: 0,
|
||||
'&:before': {
|
||||
display: 'none',
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledAccordionSummary = styled(AccordionSummary)(({ theme }) => ({
|
||||
backgroundColor: theme.palette.background.elevation1,
|
||||
'& .MuiAccordionSummary-content': {
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
minHeight: '30px',
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledTitleContainer = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
alignItems: 'start',
|
||||
flexDirection: 'column',
|
||||
gap: theme.spacing(0.5),
|
||||
}));
|
||||
|
||||
const StyledTitle = styled('span')(({ theme }) => ({
|
||||
fontWeight: theme.fontWeight.bold,
|
||||
}));
|
||||
|
||||
const StyledSecondaryLabel = styled('span')(({ theme }) => ({
|
||||
color: theme.palette.text.secondary,
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
marginRight: theme.spacing(1),
|
||||
}));
|
||||
|
||||
const StyledAccordionDetails = styled(AccordionDetails)(({ theme }) => ({
|
||||
padding: 0,
|
||||
}));
|
||||
|
||||
interface IAccessAccordionProps {
|
||||
permissions: IAccessOverviewPermission[];
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const AccessOverviewAccordion = ({
|
||||
permissions,
|
||||
children,
|
||||
}: IAccessAccordionProps) => (
|
||||
<StyledAccordion>
|
||||
<StyledAccordionSummary expandIcon={<ExpandMore />}>
|
||||
<StyledTitleContainer>
|
||||
<StyledTitle>{children}</StyledTitle>
|
||||
</StyledTitleContainer>
|
||||
<StyledSecondaryLabel>
|
||||
{
|
||||
permissions.filter(({ hasPermission }) => hasPermission)
|
||||
.length
|
||||
}
|
||||
/{permissions.length} permissions
|
||||
</StyledSecondaryLabel>
|
||||
</StyledAccordionSummary>
|
||||
<StyledAccordionDetails>
|
||||
<AccessOverviewList permissions={permissions} />
|
||||
</StyledAccordionDetails>
|
||||
</StyledAccordion>
|
||||
);
|
@ -0,0 +1,73 @@
|
||||
import Check from '@mui/icons-material/Check';
|
||||
import Close from '@mui/icons-material/Close';
|
||||
import { Box, styled } from '@mui/material';
|
||||
import type { IAccessOverviewPermission } from 'interfaces/permissions';
|
||||
|
||||
const StyledList = styled('ul')(({ theme }) => ({
|
||||
listStyle: 'none',
|
||||
padding: 0,
|
||||
margin: 0,
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
'& li': {
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
padding: theme.spacing(2),
|
||||
'&:not(:last-child)': {
|
||||
borderBottom: `1px solid ${theme.palette.divider}`,
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledPermissionStatus = styled('div', {
|
||||
shouldForwardProp: (prop) => prop !== 'hasPermission',
|
||||
})<{ hasPermission: boolean }>(({ theme, hasPermission }) => ({
|
||||
display: 'flex',
|
||||
gap: theme.spacing(1),
|
||||
alignItems: 'center',
|
||||
width: theme.spacing(17.5),
|
||||
color: hasPermission
|
||||
? theme.palette.text.primary
|
||||
: theme.palette.text.secondary,
|
||||
'& > svg': {
|
||||
color: hasPermission
|
||||
? theme.palette.success.main
|
||||
: theme.palette.error.main,
|
||||
},
|
||||
}));
|
||||
|
||||
export const AccessOverviewList = ({
|
||||
permissions,
|
||||
}: {
|
||||
permissions: IAccessOverviewPermission[];
|
||||
}) => {
|
||||
return (
|
||||
<Box sx={{ maxHeight: 500, overflow: 'auto' }}>
|
||||
<StyledList>
|
||||
{permissions.map((permission) => (
|
||||
<li key={permission.name}>
|
||||
<div>{permission.displayName}</div>
|
||||
<PermissionStatus
|
||||
hasPermission={permission.hasPermission}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</StyledList>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const PermissionStatus = ({ hasPermission }: { hasPermission: boolean }) => (
|
||||
<StyledPermissionStatus hasPermission={hasPermission}>
|
||||
{hasPermission ? (
|
||||
<>
|
||||
<Check />
|
||||
Has permission
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Close />
|
||||
No permission
|
||||
</>
|
||||
)}
|
||||
</StyledPermissionStatus>
|
||||
);
|
@ -1,81 +0,0 @@
|
||||
import { useMemo, useRef } from 'react';
|
||||
import { TablePlaceholder, VirtualizedTable } from 'component/common/Table';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { useFlexLayout, useSortBy, useTable } from 'react-table';
|
||||
import { sortTypes } from 'utils/sortTypes';
|
||||
import { IconCell } from 'component/common/Table/cells/IconCell/IconCell';
|
||||
import Check from '@mui/icons-material/Check';
|
||||
import Close from '@mui/icons-material/Close';
|
||||
import { Box } from '@mui/material';
|
||||
import type { IAccessOverviewPermission } from 'interfaces/permissions';
|
||||
|
||||
export const AccessOverviewTable = ({
|
||||
permissions,
|
||||
}: {
|
||||
permissions: IAccessOverviewPermission[];
|
||||
}) => {
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
{
|
||||
Header: 'Permission',
|
||||
accessor: 'name',
|
||||
minWidth: 100,
|
||||
},
|
||||
{
|
||||
Header: 'Description',
|
||||
accessor: 'displayName',
|
||||
minWidth: 180,
|
||||
},
|
||||
{
|
||||
Header: 'Has permission',
|
||||
accessor: 'hasPermission',
|
||||
Cell: ({ value }: { value: boolean }) => (
|
||||
<IconCell
|
||||
icon={
|
||||
value ? (
|
||||
<Check color='success' />
|
||||
) : (
|
||||
<Close color='error' />
|
||||
)
|
||||
}
|
||||
/>
|
||||
),
|
||||
},
|
||||
],
|
||||
[permissions],
|
||||
);
|
||||
|
||||
const initialState = {
|
||||
sortBy: [{ id: 'name', desc: true }],
|
||||
};
|
||||
|
||||
const { headerGroups, rows, prepareRow } = useTable(
|
||||
{
|
||||
columns: columns as any,
|
||||
data: permissions ?? [],
|
||||
initialState,
|
||||
sortTypes,
|
||||
},
|
||||
useSortBy,
|
||||
useFlexLayout,
|
||||
);
|
||||
|
||||
const parentRef = useRef<HTMLElement | null>(null);
|
||||
|
||||
return (
|
||||
<Box sx={{ maxHeight: 500, overflow: 'auto' }} ref={parentRef}>
|
||||
<VirtualizedTable
|
||||
rows={rows}
|
||||
headerGroups={headerGroups}
|
||||
prepareRow={prepareRow}
|
||||
parentRef={parentRef}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={rows.length === 0}
|
||||
show={
|
||||
<TablePlaceholder>No permissions found.</TablePlaceholder>
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
Loading…
Reference in New Issue
Block a user