mirror of
https://github.com/Unleash/unleash.git
synced 2025-03-18 00:19:49 +01:00
chore: access overview search (#9547)
https://linear.app/unleash/issue/2-3407/add-search-to-the-access-overview Adds search to Access Overview. 
This commit is contained in:
parent
76b3e06fe5
commit
3a59886206
@ -3,7 +3,7 @@ import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
|||||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||||
import useUserInfo from 'hooks/api/getters/useUserInfo/useUserInfo';
|
import useUserInfo from 'hooks/api/getters/useUserInfo/useUserInfo';
|
||||||
import { styled, useMediaQuery } from '@mui/material';
|
import { styled, useMediaQuery } from '@mui/material';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import { useEnvironments } from 'hooks/api/getters/useEnvironments/useEnvironments';
|
import { useEnvironments } from 'hooks/api/getters/useEnvironments/useEnvironments';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import theme from 'themes/theme';
|
import theme from 'themes/theme';
|
||||||
@ -18,16 +18,21 @@ import {
|
|||||||
} from 'utils/permissions';
|
} from 'utils/permissions';
|
||||||
import type { IAccessOverviewPermissionCategory } from './AccessOverviewAccordion/AccessOverviewList';
|
import type { IAccessOverviewPermissionCategory } from './AccessOverviewAccordion/AccessOverviewList';
|
||||||
import { createProjectPermissionsStructure } from 'component/admin/roles/RoleForm/RolePermissionCategories/createProjectPermissionsStructure';
|
import { createProjectPermissionsStructure } from 'component/admin/roles/RoleForm/RolePermissionCategories/createProjectPermissionsStructure';
|
||||||
|
import { Search } from 'component/common/Search/Search';
|
||||||
|
import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
|
||||||
|
|
||||||
const StyledActionsContainer = styled('div')(({ theme }) => ({
|
const StyledActionsContainer = styled('div')(({ theme }) => ({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flex: 1,
|
flex: 1,
|
||||||
gap: theme.spacing(1),
|
gap: theme.spacing(1),
|
||||||
maxWidth: 600,
|
maxWidth: 800,
|
||||||
[theme.breakpoints.down('md')]: {
|
[theme.breakpoints.down('md')]: {
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
maxWidth: '100%',
|
maxWidth: '100%',
|
||||||
},
|
},
|
||||||
|
'& > div': {
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StyledAccessOverviewContainer = styled('div')(({ theme }) => ({
|
const StyledAccessOverviewContainer = styled('div')(({ theme }) => ({
|
||||||
@ -36,6 +41,20 @@ const StyledAccessOverviewContainer = styled('div')(({ theme }) => ({
|
|||||||
gap: theme.spacing(2),
|
gap: theme.spacing(2),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const filterCategory = (
|
||||||
|
category: IAccessOverviewPermissionCategory,
|
||||||
|
search: string,
|
||||||
|
): IAccessOverviewPermissionCategory | undefined => {
|
||||||
|
const searchLower = search.toLowerCase();
|
||||||
|
const filteredPermissions = category.permissions.filter(({ displayName }) =>
|
||||||
|
displayName.toLowerCase().includes(searchLower),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (filteredPermissions.length) {
|
||||||
|
return { ...category, permissions: filteredPermissions };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const AccessOverview = () => {
|
export const AccessOverview = () => {
|
||||||
const id = useRequiredPathParam('id');
|
const id = useRequiredPathParam('id');
|
||||||
const [query, setQuery] = useQueryParams({
|
const [query, setQuery] = useQueryParams({
|
||||||
@ -48,6 +67,7 @@ export const AccessOverview = () => {
|
|||||||
|
|
||||||
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
|
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
|
||||||
|
|
||||||
|
const [searchValue, setSearchValue] = useState('');
|
||||||
const [project, setProject] = useState(query.project ?? '');
|
const [project, setProject] = useState(query.project ?? '');
|
||||||
const [environment, setEnvironment] = useState(
|
const [environment, setEnvironment] = useState(
|
||||||
query.environment ?? undefined,
|
query.environment ?? undefined,
|
||||||
@ -71,6 +91,7 @@ export const AccessOverview = () => {
|
|||||||
|
|
||||||
const AccessActions = (
|
const AccessActions = (
|
||||||
<StyledActionsContainer>
|
<StyledActionsContainer>
|
||||||
|
<Search initialValue={searchValue} onChange={setSearchValue} />
|
||||||
<AccessOverviewSelect
|
<AccessOverviewSelect
|
||||||
label='Project'
|
label='Project'
|
||||||
options={projects}
|
options={projects}
|
||||||
@ -92,20 +113,40 @@ export const AccessOverview = () => {
|
|||||||
</StyledActionsContainer>
|
</StyledActionsContainer>
|
||||||
);
|
);
|
||||||
|
|
||||||
const rootCategories = getCategorizedRootPermissions(
|
const rootCategories = useMemo(() => {
|
||||||
overview?.root ?? [],
|
const categories = getCategorizedRootPermissions(
|
||||||
) as IAccessOverviewPermissionCategory[];
|
overview?.root ?? [],
|
||||||
|
) as IAccessOverviewPermissionCategory[];
|
||||||
|
|
||||||
const projectCategories = createProjectPermissionsStructure(
|
if (!searchValue) return categories;
|
||||||
overview?.project ?? [],
|
|
||||||
).map(({ label, permissions }) => ({
|
|
||||||
label,
|
|
||||||
permissions: permissions.map(([permission]) => permission),
|
|
||||||
})) as IAccessOverviewPermissionCategory[];
|
|
||||||
|
|
||||||
const environmentCategories = getCategorizedProjectPermissions(
|
return categories
|
||||||
overview?.environment ?? [],
|
.map((category) => filterCategory(category, searchValue))
|
||||||
) as IAccessOverviewPermissionCategory[];
|
.filter(Boolean) as IAccessOverviewPermissionCategory[];
|
||||||
|
}, [overview?.root, searchValue]);
|
||||||
|
|
||||||
|
const projectCategories = useMemo(() => {
|
||||||
|
const categories = createProjectPermissionsStructure(
|
||||||
|
overview?.project ?? [],
|
||||||
|
).map(({ label, permissions }) => ({
|
||||||
|
label,
|
||||||
|
permissions: permissions.map(([permission]) => permission),
|
||||||
|
})) as IAccessOverviewPermissionCategory[];
|
||||||
|
|
||||||
|
return categories
|
||||||
|
.map((category) => filterCategory(category, searchValue))
|
||||||
|
.filter(Boolean) as IAccessOverviewPermissionCategory[];
|
||||||
|
}, [overview?.project, searchValue]);
|
||||||
|
|
||||||
|
const environmentCategories = useMemo(() => {
|
||||||
|
const categories = getCategorizedProjectPermissions(
|
||||||
|
overview?.environment ?? [],
|
||||||
|
) as IAccessOverviewPermissionCategory[];
|
||||||
|
|
||||||
|
return categories
|
||||||
|
.map((category) => filterCategory(category, searchValue))
|
||||||
|
.filter(Boolean) as IAccessOverviewPermissionCategory[];
|
||||||
|
}, [overview?.environment, searchValue]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContent
|
<PageContent
|
||||||
@ -128,20 +169,24 @@ export const AccessOverview = () => {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<StyledAccessOverviewContainer>
|
<StyledAccessOverviewContainer>
|
||||||
<AccessOverviewAccordion categories={rootCategories}>
|
<SearchHighlightProvider value={searchValue}>
|
||||||
Root permissions for role {rootRole?.name}
|
<AccessOverviewAccordion categories={rootCategories}>
|
||||||
</AccessOverviewAccordion>
|
Root permissions for role {rootRole?.name}
|
||||||
<AccessOverviewAccordion categories={projectCategories}>
|
|
||||||
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 categories={environmentCategories}>
|
|
||||||
Environment permissions for {environment}
|
|
||||||
</AccessOverviewAccordion>
|
</AccessOverviewAccordion>
|
||||||
)}
|
<AccessOverviewAccordion categories={projectCategories}>
|
||||||
|
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
|
||||||
|
categories={environmentCategories}
|
||||||
|
>
|
||||||
|
Environment permissions for {environment}
|
||||||
|
</AccessOverviewAccordion>
|
||||||
|
)}
|
||||||
|
</SearchHighlightProvider>
|
||||||
</StyledAccessOverviewContainer>
|
</StyledAccessOverviewContainer>
|
||||||
</PageContent>
|
</PageContent>
|
||||||
);
|
);
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import Check from '@mui/icons-material/Check';
|
import Check from '@mui/icons-material/Check';
|
||||||
import Close from '@mui/icons-material/Close';
|
import Close from '@mui/icons-material/Close';
|
||||||
import { Box, styled } from '@mui/material';
|
import { Box, styled } from '@mui/material';
|
||||||
|
import { Highlighter } from 'component/common/Highlighter/Highlighter';
|
||||||
|
import { useSearchHighlightContext } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
|
||||||
import type {
|
import type {
|
||||||
IAccessOverviewPermission,
|
IAccessOverviewPermission,
|
||||||
IPermissionCategory,
|
IPermissionCategory,
|
||||||
@ -53,29 +55,37 @@ export const AccessOverviewList = ({
|
|||||||
categories,
|
categories,
|
||||||
}: {
|
}: {
|
||||||
categories: IAccessOverviewPermissionCategory[];
|
categories: IAccessOverviewPermissionCategory[];
|
||||||
}) => (
|
}) => {
|
||||||
<Box sx={{ maxHeight: 500, overflow: 'auto' }}>
|
const { searchQuery } = useSearchHighlightContext();
|
||||||
<StyledList>
|
|
||||||
{categories.map((category) => (
|
return (
|
||||||
<>
|
<Box sx={{ maxHeight: 500, overflow: 'auto' }}>
|
||||||
<li key={category.label}>
|
<StyledList>
|
||||||
<strong>{category.label}</strong>
|
{categories.map((category) => (
|
||||||
</li>
|
<>
|
||||||
<StyledList>
|
<li key={category.label}>
|
||||||
{category.permissions.map((permission) => (
|
<strong>{category.label}</strong>
|
||||||
<li key={permission.name}>
|
</li>
|
||||||
<div>{permission.displayName}</div>
|
<StyledList>
|
||||||
<PermissionStatus
|
{category.permissions.map((permission) => (
|
||||||
hasPermission={permission.hasPermission}
|
<li key={permission.name}>
|
||||||
/>
|
<div>
|
||||||
</li>
|
<Highlighter search={searchQuery}>
|
||||||
))}
|
{permission.displayName}
|
||||||
</StyledList>
|
</Highlighter>
|
||||||
</>
|
</div>
|
||||||
))}
|
<PermissionStatus
|
||||||
</StyledList>
|
hasPermission={permission.hasPermission}
|
||||||
</Box>
|
/>
|
||||||
);
|
</li>
|
||||||
|
))}
|
||||||
|
</StyledList>
|
||||||
|
</>
|
||||||
|
))}
|
||||||
|
</StyledList>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const PermissionStatus = ({ hasPermission }: { hasPermission: boolean }) => (
|
const PermissionStatus = ({ hasPermission }: { hasPermission: boolean }) => (
|
||||||
<StyledPermissionStatus hasPermission={hasPermission}>
|
<StyledPermissionStatus hasPermission={hasPermission}>
|
||||||
|
Loading…
Reference in New Issue
Block a user