Skip to content

Commit 47085fe

Browse files
mpolotsk-akamaijaalah-akamaicorya-akamai
authored
feat: [UIE-8792] - add pagination to Roles table (#12264)
* feat: [UIE-8792] - add pagination to Roles table * Added changeset: IAM RBAC: add pagination to the Roles table * feat: [UIE-8792] - unit test fix * feat: [UIE-8792] - update default page size * feat: [UIE-8792] - use usePagination hook to handle page state --------- Co-authored-by: Jaalah Ramos <[email protected]> Co-authored-by: corya-akamai <[email protected]>
1 parent e4fa582 commit 47085fe

File tree

3 files changed

+75
-39
lines changed

3 files changed

+75
-39
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@linode/manager": Upcoming Features
3+
---
4+
5+
IAM RBAC: add pagination to the Roles table ([#12264](https://github.com/linode/manager/pull/12264))

packages/manager/src/features/IAM/Roles/RolesTable/RolesTable.tsx

Lines changed: 68 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Button, Select, Typography } from '@linode/ui';
22
import { capitalizeAllWords } from '@linode/utilities';
33
import Grid from '@mui/material/Grid';
44
import Paper from '@mui/material/Paper';
5+
import { Pagination } from 'akamai-cds-react-components/Pagination';
56
import {
67
sortRows,
78
Table,
@@ -23,89 +24,100 @@ import {
2324
getFacadeRoleDescription,
2425
mapEntityTypesForSelect,
2526
} from 'src/features/IAM/Shared/utilities';
27+
import { usePagination } from 'src/hooks/usePagination';
28+
29+
import { ROLES_TABLE_PREFERENCE_KEY } from '../../Shared/constants';
2630

2731
import type { RoleView } from '../../Shared/types';
2832
import type { SelectOption } from '@linode/ui';
2933
import type { Order } from 'akamai-cds-react-components/Table';
30-
3134
const ALL_ROLES_OPTION: SelectOption = {
3235
label: 'All Roles',
3336
value: 'all',
3437
};
3538

3639
interface Props {
37-
roles: RoleView[];
40+
roles?: RoleView[];
3841
}
3942

40-
export const RolesTable = ({ roles }: Props) => {
41-
const [rows, setRows] = useState(roles);
42-
43+
export const RolesTable = ({ roles = [] }: Props) => {
4344
// Filter string for the search bar
4445
const [filterString, setFilterString] = React.useState('');
45-
46-
// Get just the list of entity types from this list of roles, to be used in the selection filter
47-
const filterableOptions = React.useMemo(() => {
48-
return [ALL_ROLES_OPTION, ...mapEntityTypesForSelect(roles, ' Roles')];
49-
}, [roles]);
50-
5146
const [filterableEntityType, setFilterableEntityType] =
5247
useState<null | SelectOption>(ALL_ROLES_OPTION);
53-
5448
const [sort, setSort] = useState<
5549
undefined | { column: string; order: Order }
5650
>(undefined);
57-
5851
const [selectedRows, setSelectedRows] = useState<RoleView[]>([]);
5952
const [isDrawerOpen, setIsDrawerOpen] = useState<boolean>(false);
6053

54+
const pagination = usePagination(1, ROLES_TABLE_PREFERENCE_KEY);
55+
56+
// Filtering
57+
const getFilteredRows = (
58+
text: string,
59+
entityTypeVal = ALL_ROLES_OPTION.value
60+
) => {
61+
return roles.filter(
62+
(r) =>
63+
(entityTypeVal === ALL_ROLES_OPTION.value ||
64+
entityTypeVal === r.entity_type) &&
65+
(r.name.includes(text) ||
66+
r.description.includes(text) ||
67+
r.access.includes(text))
68+
);
69+
};
70+
71+
const filteredRows = React.useMemo(
72+
() => getFilteredRows(filterString, filterableEntityType?.value),
73+
[roles, filterString, filterableEntityType]
74+
);
75+
76+
// Get just the list of entity types from this list of roles, to be used in the selection filter
77+
const filterableOptions = React.useMemo(() => {
78+
return [ALL_ROLES_OPTION, ...mapEntityTypesForSelect(roles, ' Roles')];
79+
}, [roles]);
80+
81+
const sortedRows = React.useMemo(() => {
82+
if (!sort) return filteredRows;
83+
return sortRows(filteredRows, sort.order, sort.column);
84+
}, [filteredRows, sort]);
85+
86+
const paginatedRows = React.useMemo(() => {
87+
const start = (pagination.page - 1) * pagination.pageSize;
88+
return sortedRows.slice(start, start + pagination.pageSize);
89+
}, [sortedRows, pagination.page, pagination.pageSize]);
90+
6191
const areAllSelected = React.useMemo(() => {
6292
return (
63-
!!rows?.length &&
93+
!!paginatedRows?.length &&
6494
!!selectedRows?.length &&
65-
rows?.length === selectedRows?.length
95+
paginatedRows?.length === selectedRows?.length
6696
);
67-
}, [rows, selectedRows]);
97+
}, [paginatedRows, selectedRows]);
6898

6999
const handleSort = (event: CustomEvent, column: string) => {
70100
setSort({ column, order: event.detail as Order });
71-
const visibleRows = sortRows(rows, event.detail as Order, column);
72-
setRows(visibleRows);
73101
};
74102

75103
const handleSelect = (event: CustomEvent, row: 'all' | RoleView) => {
76104
if (row === 'all') {
77-
setSelectedRows(areAllSelected ? [] : rows);
105+
setSelectedRows(areAllSelected ? [] : paginatedRows);
78106
} else if (selectedRows.includes(row)) {
79107
setSelectedRows(selectedRows.filter((r) => r !== row));
80108
} else {
81109
setSelectedRows([...selectedRows, row]);
82110
}
83111
};
84112

85-
const getFilteredRows = (
86-
text: string,
87-
entityTypeVal = ALL_ROLES_OPTION.value
88-
) => {
89-
return roles.filter(
90-
(r) =>
91-
(entityTypeVal === ALL_ROLES_OPTION.value ||
92-
entityTypeVal === r.entity_type) &&
93-
(r.name.includes(text) ||
94-
r.description.includes(text) ||
95-
r.access.includes(text))
96-
);
97-
};
98-
99113
const handleTextFilter = (fs: string) => {
100114
setFilterString(fs);
101-
const filteredRows = getFilteredRows(fs, filterableEntityType?.value);
102-
setRows(filteredRows);
115+
pagination.handlePageChange(1);
103116
};
104117

105118
const handleChangeEntityTypeFilter = (_: never, entityType: SelectOption) => {
106119
setFilterableEntityType(entityType ?? ALL_ROLES_OPTION);
107-
const filteredRows = getFilteredRows(filterString, entityType?.value);
108-
setRows(filteredRows);
120+
pagination.handlePageChange(1);
109121
};
110122

111123
const assignRoleRow = (row: RoleView) => {
@@ -118,6 +130,15 @@ export const RolesTable = ({ roles }: Props) => {
118130
setIsDrawerOpen(true);
119131
};
120132

133+
const handlePageChange = (event: CustomEvent<{ page: number }>) => {
134+
pagination.handlePageChange(Number(event.detail));
135+
};
136+
137+
const handlePageSizeChange = (event: CustomEvent<{ pageSize: number }>) => {
138+
const newSize = event.detail.pageSize;
139+
pagination.handlePageSizeChange(newSize);
140+
pagination.handlePageChange(1);
141+
};
121142
return (
122143
<>
123144
<Paper sx={(theme) => ({ marginTop: theme.tokens.spacing.S16 })}>
@@ -208,14 +229,14 @@ export const RolesTable = ({ roles }: Props) => {
208229
</TableRow>
209230
</TableHead>
210231
<TableBody>
211-
{!rows?.length ? (
232+
{!paginatedRows?.length ? (
212233
<TableRow>
213234
<TableCell style={{ justifyContent: 'center' }}>
214235
No items to display.
215236
</TableCell>
216237
</TableRow>
217238
) : (
218-
rows.map((roleRow) => (
239+
paginatedRows.map((roleRow) => (
219240
<TableRow
220241
expandable
221242
hoverable
@@ -267,6 +288,14 @@ export const RolesTable = ({ roles }: Props) => {
267288
)}
268289
</TableBody>
269290
</Table>
291+
<Pagination
292+
count={filteredRows.length}
293+
onPageChange={handlePageChange}
294+
onPageSizeChange={handlePageSizeChange}
295+
page={pagination.page}
296+
pageSize={pagination.pageSize}
297+
style={{ borderTop: 0 }}
298+
/>
270299
</Paper>
271300
<AssignSelectedRolesDrawer
272301
onClose={() => setIsDrawerOpen(false)}

packages/manager/src/features/IAM/Shared/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,5 @@ export const PAID_ENTITY_TYPES = [
1919
'volume',
2020
'image',
2121
];
22+
23+
export const ROLES_TABLE_PREFERENCE_KEY = 'roles';

0 commit comments

Comments
 (0)