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

API tokens - virtualized table (#7531)

API tokens table in both main list and project API tokens list can now
support more items - it doesn't slow the browser down if there is >500
items.
This commit is contained in:
Tymoteusz Czech 2024-07-09 13:22:55 +02:00 committed by GitHub
parent 2aea6e688c
commit f6c05eb877
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 65 additions and 91 deletions

View File

@ -25,8 +25,6 @@ export const ApiTokenPage = () => {
const { deleteToken } = useApiTokensApi();
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
@ -103,8 +101,6 @@ export const ApiTokenPage = () => {
headerGroups={headerGroups}
setHiddenColumns={setHiddenColumns}
prepareRow={prepareRow}
getTableBodyProps={getTableBodyProps}
getTableProps={getTableProps}
rows={rows}
columns={columns}
globalFilter={globalFilter}

View File

@ -22,13 +22,13 @@ describe('ProjectsList', () => {
expect(links[0]).toHaveTextContent('project');
});
it('should render asterisk if no projects are passed', async () => {
it('should render "*" if no projects are passed', async () => {
const { container } = render(<ProjectsList />);
expect(container.textContent).toEqual('*');
});
it('should render asterisk if empty projects array is passed', async () => {
it('should render "*" if empty projects array is passed', async () => {
const { container } = render(<ProjectsList projects={[]} />);
expect(container.textContent).toEqual('*');
@ -43,4 +43,16 @@ describe('ProjectsList', () => {
expect(container.textContent).toContain('4 projects');
});
it('should render "*" if project is "*" and no projects are passed', async () => {
const { container } = render(<ProjectsList project='*' />);
expect(container.textContent).toEqual('*');
});
it('should render "*" if projects has only "*"', async () => {
const { container } = render(<ProjectsList projects={['*']} />);
expect(container.textContent).toEqual('*');
});
});

View File

@ -59,30 +59,25 @@ export const ProjectsList: FC<IProjectsListProps> = ({ projects, project }) => {
}
if (
(projectsList.length === 1 && projectsList[0] === '*') ||
project === '*' ||
(!project && (!projectsList || projectsList.length === 0))
(projectsList.length === 1 && projectsList[0] !== '*') ||
(project && project !== '*')
) {
return (
<TextCell>
<HtmlTooltip
title='ALL current and future projects'
placement='bottom'
arrow
>
<span>
<Highlighter search={searchQuery}>*</Highlighter>
</span>
</HtmlTooltip>
</TextCell>
);
}
if (projectsList.length === 1 || project) {
const item = project || projectsList[0];
return <LinkCell to={`/projects/${item}`} title={item} />;
}
return null;
return (
<TextCell>
<HtmlTooltip
title='ALL current and future projects'
placement='bottom'
arrow
>
<span>
<Highlighter search={searchQuery}>*</Highlighter>
</span>
</HtmlTooltip>
</TextCell>
);
};

View File

@ -1,24 +1,6 @@
import type {
Row,
TablePropGetter,
TableProps,
TableBodyPropGetter,
TableBodyProps,
HeaderGroup,
} from 'react-table';
import {
SortableTableHeader,
TableCell,
TablePlaceholder,
} from 'component/common/Table';
import {
Box,
Table,
TableBody,
TableRow,
useMediaQuery,
Link,
} from '@mui/material';
import type { Row, HeaderGroup } from 'react-table';
import { TablePlaceholder, VirtualizedTable } from 'component/common/Table';
import { Box, useMediaQuery, Link } from '@mui/material';
import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
import { ApiTokenDocs } from 'component/admin/apiToken/ApiTokenDocs/ApiTokenDocs';
@ -27,7 +9,7 @@ import { ConditionallyRender } from 'component/common/ConditionallyRender/Condit
import { useConditionallyHiddenColumns } from 'hooks/useConditionallyHiddenColumns';
const hiddenColumnsSmall = ['Icon', 'createdAt'];
const hiddenColumnsNotExtraLarge = ['Icon', 'createdAt', 'seenAt'];
const hiddenColumnsCompact = ['Icon', 'project', 'seenAt'];
interface IApiTokenTableProps {
@ -37,34 +19,27 @@ interface IApiTokenTableProps {
columns: any[];
rows: Row<object>[];
prepareRow: (row: Row<object>) => void;
getTableProps: (
propGetter?: TablePropGetter<object> | undefined,
) => TableProps;
getTableBodyProps: (
propGetter?: TableBodyPropGetter<object> | undefined,
) => TableBodyProps;
headerGroups: HeaderGroup<object>[];
globalFilter: any;
}
export const ApiTokenTable = ({
compact = false,
setHiddenColumns,
columns,
loading,
rows,
getTableProps,
getTableBodyProps,
headerGroups,
globalFilter,
prepareRow,
}: IApiTokenTableProps) => {
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
const isNotExtraLarge = useMediaQuery(theme.breakpoints.down('xl'));
useConditionallyHiddenColumns(
[
{
condition: isSmallScreen,
columns: hiddenColumnsSmall,
condition: isNotExtraLarge,
columns: hiddenColumnsNotExtraLarge,
},
{
condition: compact,
@ -87,25 +62,11 @@ export const ApiTokenTable = ({
/>
<Box sx={{ overflowX: 'auto' }}>
<SearchHighlightProvider value={globalFilter}>
<Table {...getTableProps()}>
<SortableTableHeader
headerGroups={headerGroups as any}
/>
<TableBody {...getTableBodyProps()}>
{rows.map((row) => {
prepareRow(row);
return (
<TableRow hover {...row.getRowProps()}>
{row.cells.map((cell) => (
<TableCell {...cell.getCellProps()}>
{cell.render('Cell')}
</TableCell>
))}
</TableRow>
);
})}
</TableBody>
</Table>
<VirtualizedTable
rows={rows}
headerGroups={headerGroups}
prepareRow={prepareRow}
/>
</SearchHighlightProvider>
</Box>
<ConditionallyRender

View File

@ -4,7 +4,12 @@ import { DateCell } from 'component/common/Table/cells/DateCell/DateCell';
import { HighlightCell } from 'component/common/Table/cells/HighlightCell/HighlightCell';
import { IconCell } from 'component/common/Table/cells/IconCell/IconCell';
import { TimeAgoCell } from 'component/common/Table/cells/TimeAgoCell/TimeAgoCell';
import { useTable, useGlobalFilter, useSortBy } from 'react-table';
import {
useTable,
useGlobalFilter,
useSortBy,
useFlexLayout,
} from 'react-table';
import { sortTypes } from 'utils/sortTypes';
import { ProjectsList } from 'component/admin/apiToken/ProjectsList/ProjectsList';
import Key from '@mui/icons-material/Key';
@ -22,15 +27,16 @@ export const useApiTokenTable = (
return [
{
id: 'Icon',
width: '1%',
Cell: () => <IconCell icon={<Key color='disabled' />} />,
disableSortBy: true,
disableGlobalFilter: true,
width: 50,
},
{
Header: 'Username',
accessor: 'username',
Cell: HighlightCell,
minWidth: 35,
},
{
Header: 'Type',
@ -43,9 +49,10 @@ export const useApiTokenTable = (
<HighlightCell
value={tokenDescriptions[value.toLowerCase()].label}
subtitle={tokenDescriptions[value.toLowerCase()].title}
subtitleTooltip
/>
),
minWidth: 280,
width: 180,
},
{
Header: 'Project',
@ -56,33 +63,33 @@ export const useApiTokenTable = (
projects={props.row.original.projects}
/>
),
minWidth: 120,
width: 160,
},
{
Header: 'Environment',
accessor: 'environment',
Cell: HighlightCell,
minWidth: 120,
width: 120,
},
{
Header: 'Created',
accessor: 'createdAt',
Cell: DateCell,
minWidth: 150,
width: 150,
disableGlobalFilter: true,
},
{
Header: 'Last seen',
accessor: 'seenAt',
Cell: TimeAgoCell,
minWidth: 150,
width: 140,
disableGlobalFilter: true,
},
{
Header: 'Actions',
width: 120,
id: 'Actions',
align: 'center',
width: '1%',
disableSortBy: true,
disableGlobalFilter: true,
Cell: getActionCell,
@ -110,6 +117,7 @@ export const useApiTokenTable = (
},
useGlobalFilter,
useSortBy,
useFlexLayout,
);
return {

View File

@ -1,5 +1,5 @@
import type React from 'react';
import type { VFC } from 'react';
import type { FC } from 'react';
import { Highlighter } from 'component/common/Highlighter/Highlighter';
import { useSearchHighlightContext } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
import { Box, styled } from '@mui/material';
@ -10,6 +10,7 @@ interface IHighlightCellProps {
value: string;
subtitle?: string;
afterTitle?: React.ReactNode;
subtitleTooltip?: boolean;
}
const StyledContainer = styled(Box)(({ theme }) => ({
@ -40,16 +41,19 @@ const StyledSubtitle = styled('span')(({ theme }) => ({
WebkitBoxOrient: 'vertical',
}));
export const HighlightCell: VFC<IHighlightCellProps> = ({
export const HighlightCell: FC<IHighlightCellProps> = ({
value,
subtitle,
afterTitle,
subtitleTooltip,
}) => {
const { searchQuery } = useSearchHighlightContext();
const renderSubtitle = (
<ConditionallyRender
condition={Boolean(subtitle && subtitle.length > 40)}
condition={Boolean(
subtitle && (subtitle.length > 40 || subtitleTooltip),
)}
show={
<HtmlTooltip title={subtitle} placement='bottom-start' arrow>
<StyledSubtitle data-loading>

View File

@ -117,8 +117,6 @@ export const ProjectApiAccess = () => {
headerGroups={headerGroups}
setHiddenColumns={setHiddenColumns}
prepareRow={prepareRow}
getTableBodyProps={getTableBodyProps}
getTableProps={getTableProps}
rows={rows}
columns={columns}
globalFilter={globalFilter}