Skip to content

Commit bbddca5

Browse files
fix: [UIE-9733] - Performance improvement in IAM users pages (#13159)
* Save progress * Save progress * Same for AssignedEntitiesTable * Abstraction through common hook * test * test fixes * moar test fixes (mocking) * Cleanup & comments * small fix * Added changeset: Performance improvement in IAM users pages
1 parent 5ea7b89 commit bbddca5

File tree

18 files changed

+596
-125
lines changed

18 files changed

+596
-125
lines changed

packages/api-v4/src/entities/types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@ export type EntityType =
1212
| 'volume'
1313
| 'vpc';
1414

15+
export type EntityId = number | string;
16+
1517
export interface AccountEntity {
16-
id: number;
18+
id: EntityId;
1719
label: string;
1820
type: EntityType;
1921
}

packages/api-v4/src/iam/iam.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,3 +142,30 @@ export const getUserEntitiesByPermission = ({
142142
setParams(params),
143143
setXFilter(filter),
144144
);
145+
146+
/**
147+
* getUserEntities
148+
*
149+
* Returns the available entities for a given user.
150+
* It is using the getUserEntitiesByPermission method, but without the permission parameter.
151+
* This is used to get all entities for a given user, per entity type.
152+
*/
153+
export interface GetUserEntitiesParams {
154+
entityType: EntityType;
155+
filter?: Filter;
156+
params?: Params;
157+
username: string;
158+
}
159+
160+
export const getUserEntities = ({
161+
username,
162+
entityType,
163+
params,
164+
filter,
165+
}: GetUserEntitiesParams) =>
166+
Request<ResourcePage<EntityByPermission>>(
167+
setURL(`${BETA_API_ROOT}/iam/users/${username}/entities/${entityType}`),
168+
setMethod('GET'),
169+
setParams(params),
170+
setXFilter(filter),
171+
);

packages/api-v4/src/iam/types.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { EntityType } from '../entities/types';
1+
import type { EntityId, EntityType } from '../entities/types';
22

33
export type AccountType = 'account';
44

@@ -436,7 +436,7 @@ export interface IamUserRoles {
436436
}
437437

438438
export interface EntityAccess {
439-
id: number;
439+
id: EntityId;
440440
roles: EntityRoleType[];
441441
type: AccessType;
442442
}
@@ -457,7 +457,7 @@ export interface Roles {
457457
permissions: PermissionType[];
458458
}
459459
export interface EntityByPermission {
460-
id: number | string;
460+
id: EntityId;
461461
label: string;
462462
type: EntityType;
463463
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@linode/manager": Fixed
3+
---
4+
5+
Performance improvement in IAM users pages ([#13159](https://github.com/linode/manager/pull/13159))

packages/manager/src/features/IAM/Shared/AssignedEntitiesTable/AssignedEntitiesTable.test.tsx

Lines changed: 10 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -9,27 +9,23 @@ import { renderWithTheme } from 'src/utilities/testHelpers';
99
import { AssignedEntitiesTable } from '../../Shared/AssignedEntitiesTable/AssignedEntitiesTable';
1010

1111
const queryMocks = vi.hoisted(() => ({
12-
useAllAccountEntities: vi.fn().mockReturnValue({}),
1312
useParams: vi.fn().mockReturnValue({}),
1413
useSearch: vi.fn().mockReturnValue({}),
1514
useUserRoles: vi.fn().mockReturnValue({}),
15+
useGetUserEntities: vi.fn().mockReturnValue({}),
1616
}));
1717

1818
vi.mock('@linode/queries', async () => {
19-
const actual = await vi.importActual<any>('@linode/queries');
19+
const actual = await vi.importActual('@linode/queries');
2020
return {
2121
...actual,
2222
useUserRoles: queryMocks.useUserRoles,
2323
};
2424
});
2525

26-
vi.mock('src/queries/entities/entities', async () => {
27-
const actual = await vi.importActual('src/queries/entities/entities');
28-
return {
29-
...actual,
30-
useAllAccountEntities: queryMocks.useAllAccountEntities,
31-
};
32-
});
26+
vi.mock('../../hooks/useGetUserEntities', () => ({
27+
useGetUserEntities: queryMocks.useGetUserEntities,
28+
}));
3329

3430
vi.mock('@tanstack/react-router', async () => {
3531
const actual = await vi.importActual('@tanstack/react-router');
@@ -56,6 +52,11 @@ describe('AssignedEntitiesTable', () => {
5652
queryMocks.useSearch.mockReturnValue({
5753
query: '',
5854
});
55+
queryMocks.useGetUserEntities.mockReturnValue({
56+
userEntities: mockEntities,
57+
isLoading: false,
58+
error: null,
59+
});
5960
});
6061

6162
it('should display no roles text if there are no roles assigned to user', async () => {
@@ -73,10 +74,6 @@ describe('AssignedEntitiesTable', () => {
7374
data: userRolesFactory.build(),
7475
});
7576

76-
queryMocks.useAllAccountEntities.mockReturnValue({
77-
data: mockEntities,
78-
});
79-
8077
renderWithTheme(<AssignedEntitiesTable />);
8178

8279
expect(screen.getByText('no_devices')).toBeVisible();
@@ -98,10 +95,6 @@ describe('AssignedEntitiesTable', () => {
9895
data: userRolesFactory.build(),
9996
});
10097

101-
queryMocks.useAllAccountEntities.mockReturnValue({
102-
data: mockEntities,
103-
});
104-
10598
renderWithTheme(<AssignedEntitiesTable />);
10699

107100
const searchInput = screen.getByPlaceholderText('Search');
@@ -117,10 +110,6 @@ describe('AssignedEntitiesTable', () => {
117110
data: userRolesFactory.build(),
118111
});
119112

120-
queryMocks.useAllAccountEntities.mockReturnValue({
121-
data: mockEntities,
122-
});
123-
124113
renderWithTheme(<AssignedEntitiesTable />);
125114

126115
const searchInput = screen.getByPlaceholderText('Search');
@@ -136,10 +125,6 @@ describe('AssignedEntitiesTable', () => {
136125
data: userRolesFactory.build(),
137126
});
138127

139-
queryMocks.useAllAccountEntities.mockReturnValue({
140-
data: mockEntities,
141-
});
142-
143128
renderWithTheme(<AssignedEntitiesTable />);
144129

145130
const autocomplete = screen.getByPlaceholderText('All Entities');

packages/manager/src/features/IAM/Shared/AssignedEntitiesTable/AssignedEntitiesTable.tsx

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ import { TableRowError } from 'src/components/TableRowError/TableRowError';
2121
import { TableRowLoading } from 'src/components/TableRowLoading/TableRowLoading';
2222
import { TableSortCell } from 'src/components/TableSortCell';
2323
import { usePaginationV2 } from 'src/hooks/usePaginationV2';
24-
import { useAllAccountEntities } from 'src/queries/entities/entities';
2524

2625
import { useIsDefaultDelegationRolesForChildAccount } from '../../hooks/useDelegationRole';
26+
import { useGetUserEntities } from '../../hooks/useGetUserEntities';
2727
import { usePermissions } from '../../hooks/usePermissions';
2828
import {
2929
addEntityNamesToRoles,
@@ -102,12 +102,6 @@ export const AssignedEntitiesTable = ({ username }: Props) => {
102102
React.useState<boolean>(false);
103103
const [selectedRole, setSelectedRole] = React.useState<EntitiesRole>();
104104

105-
const {
106-
data: entities,
107-
error: entitiesError,
108-
isLoading: entitiesLoading,
109-
} = useAllAccountEntities({});
110-
111105
const {
112106
data: assignedUserRoles,
113107
error: assignedUserRolesError,
@@ -138,11 +132,20 @@ export const AssignedEntitiesTable = ({ username }: Props) => {
138132
? permissions?.update_default_delegate_access
139133
: permissions?.is_account_admin;
140134

135+
const {
136+
userEntities,
137+
isLoading: entitiesLoading,
138+
error: entitiesError,
139+
} = useGetUserEntities({
140+
username: username ?? '',
141+
userRoles: assignedRoles,
142+
});
143+
141144
const { filterableOptions, roles } = React.useMemo(() => {
142-
if (!assignedRoles || !entities) {
145+
if (!assignedRoles || !userEntities) {
143146
return { filterableOptions: [], roles: [] };
144147
}
145-
const transformedEntities = groupAccountEntitiesByType(entities);
148+
const transformedEntities = groupAccountEntitiesByType(userEntities);
146149

147150
const roles = addEntityNamesToRoles(assignedRoles, transformedEntities);
148151

@@ -152,7 +155,7 @@ export const AssignedEntitiesTable = ({ username }: Props) => {
152155
];
153156

154157
return { filterableOptions, roles };
155-
}, [assignedRoles, entities]);
158+
}, [assignedRoles, userEntities]);
156159

157160
const handleChangeRole = (role: EntitiesRole, mode: DrawerModes) => {
158161
setIsChangeRoleForEntityDrawerOpen(true);
@@ -211,11 +214,11 @@ export const AssignedEntitiesTable = ({ username }: Props) => {
211214
);
212215
}
213216

214-
if (!entities || !assignedRoles || filteredRoles.length === 0) {
217+
if (!userEntities || !assignedRoles || filteredRoles.length === 0) {
215218
return <TableRowEmpty colSpan={3} message={'No items to display.'} />;
216219
}
217220

218-
if (assignedRoles && entities) {
221+
if (assignedRoles && userEntities) {
219222
return (
220223
<>
221224
{filteredAndSortedRoles

packages/manager/src/features/IAM/Shared/AssignedRolesTable/AssignedRolesTable.test.tsx

Lines changed: 10 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@ import { renderWithTheme } from 'src/utilities/testHelpers';
1010
import { AssignedRolesTable } from './AssignedRolesTable';
1111

1212
const queryMocks = vi.hoisted(() => ({
13-
useAllAccountEntities: vi.fn().mockReturnValue({}),
1413
useParams: vi.fn().mockReturnValue({}),
1514
useAccountRoles: vi.fn().mockReturnValue({}),
1615
useUserRoles: vi.fn().mockReturnValue({}),
1716
useGetDefaultDelegationAccessQuery: vi.fn().mockReturnValue({}),
1817
useIsDefaultDelegationRolesForChildAccount: vi.fn().mockReturnValue({
1918
isDefaultDelegationRolesForChildAccount: false,
2019
}),
20+
useGetUserEntities: vi.fn().mockReturnValue({}),
2121
}));
2222

2323
vi.mock('@linode/queries', async () => {
@@ -31,14 +31,6 @@ vi.mock('@linode/queries', async () => {
3131
};
3232
});
3333

34-
vi.mock('src/queries/entities/entities', async () => {
35-
const actual = await vi.importActual('src/queries/entities/entities');
36-
return {
37-
...actual,
38-
useAllAccountEntities: queryMocks.useAllAccountEntities,
39-
};
40-
});
41-
4234
vi.mock('@tanstack/react-router', async () => {
4335
const actual = await vi.importActual('@tanstack/react-router');
4436
return {
@@ -52,6 +44,10 @@ vi.mock('../../hooks/useDelegationRole', () => ({
5244
queryMocks.useIsDefaultDelegationRolesForChildAccount,
5345
}));
5446

47+
vi.mock('../../hooks/useGetUserEntities', () => ({
48+
useGetUserEntities: queryMocks.useGetUserEntities,
49+
}));
50+
5551
const mockEntities = [
5652
accountEntityFactory.build({
5753
id: 7,
@@ -72,6 +68,11 @@ describe('AssignedRolesTable', () => {
7268
queryMocks.useParams.mockReturnValue({
7369
username: 'test_user',
7470
});
71+
queryMocks.useGetUserEntities.mockReturnValue({
72+
userEntities: mockEntities,
73+
isLoading: false,
74+
error: null,
75+
});
7576
});
7677

7778
it('should display no roles text if there are no roles assigned to user', async () => {
@@ -93,10 +94,6 @@ describe('AssignedRolesTable', () => {
9394
data: mockAccountRoles,
9495
});
9596

96-
queryMocks.useAllAccountEntities.mockReturnValue({
97-
data: mockEntities,
98-
});
99-
10097
renderWithTheme(<AssignedRolesTable />);
10198

10299
expect(screen.getByText('account_linode_admin')).toBeVisible();
@@ -121,10 +118,6 @@ describe('AssignedRolesTable', () => {
121118
data: mockAccountRoles,
122119
});
123120

124-
queryMocks.useAllAccountEntities.mockReturnValue({
125-
data: mockEntities,
126-
});
127-
128121
renderWithTheme(<AssignedRolesTable />);
129122

130123
const searchInput = screen.getByPlaceholderText('Search');
@@ -144,10 +137,6 @@ describe('AssignedRolesTable', () => {
144137
data: mockAccountRoles,
145138
});
146139

147-
queryMocks.useAllAccountEntities.mockReturnValue({
148-
data: mockEntities,
149-
});
150-
151140
renderWithTheme(<AssignedRolesTable />);
152141

153142
const searchInput = screen.getByPlaceholderText('Search');
@@ -167,10 +156,6 @@ describe('AssignedRolesTable', () => {
167156
data: mockAccountRoles,
168157
});
169158

170-
queryMocks.useAllAccountEntities.mockReturnValue({
171-
data: mockEntities,
172-
});
173-
174159
renderWithTheme(<AssignedRolesTable />);
175160

176161
const autocomplete = screen.getByPlaceholderText('All Assigned Roles');
@@ -194,10 +179,6 @@ describe('AssignedRolesTable', () => {
194179
data: mockAccountRoles,
195180
});
196181

197-
queryMocks.useAllAccountEntities.mockReturnValue({
198-
data: mockEntities,
199-
});
200-
201182
renderWithTheme(<AssignedRolesTable />);
202183

203184
expect(screen.getByText('Add New Default Roles')).toBeVisible();

packages/manager/src/features/IAM/Shared/AssignedRolesTable/AssignedRolesTable.tsx

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ import { TableRow } from 'src/components/TableRow';
1919
import { TableRowEmpty } from 'src/components/TableRowEmpty/TableRowEmpty';
2020
import { TableSortCell } from 'src/components/TableSortCell/TableSortCell';
2121
import { usePaginationV2 } from 'src/hooks/usePaginationV2';
22-
import { useAllAccountEntities } from 'src/queries/entities/entities';
2322

2423
import { useIsDefaultDelegationRolesForChildAccount } from '../../hooks/useDelegationRole';
24+
import { useGetUserEntities } from '../../hooks/useGetUserEntities';
2525
import { usePermissions } from '../../hooks/usePermissions';
2626
import { AssignedEntities } from '../../Users/UserRoles/AssignedEntities';
2727
import { AssignNewRoleDrawer } from '../../Users/UserRoles/AssignNewRoleDrawer';
@@ -168,9 +168,11 @@ export const AssignedRolesTable = () => {
168168

169169
const { data: accountRoles, isLoading: accountPermissionsLoading } =
170170
useAccountRoles();
171-
const { data: entities, isLoading: entitiesLoading } = useAllAccountEntities(
172-
{}
173-
);
171+
172+
const { userEntities, isLoading: entitiesLoading } = useGetUserEntities({
173+
username: username ?? '',
174+
userRoles: assignedRoles,
175+
});
174176

175177
const { filterableOptions, roles } = React.useMemo(() => {
176178
if (!assignedRoles || !accountRoles) {
@@ -185,14 +187,13 @@ export const AssignedRolesTable = () => {
185187
...mapEntityTypesForSelect(roles, ' Roles'),
186188
];
187189

188-
if (entities) {
189-
const transformedEntities = groupAccountEntitiesByType(entities);
190-
190+
if (userEntities) {
191+
const transformedEntities = groupAccountEntitiesByType(userEntities);
191192
roles = addEntitiesNamesToRoles(roles, transformedEntities);
192193
}
193194

194195
return { filterableOptions, roles };
195-
}, [assignedRoles, accountRoles, entities]);
196+
}, [assignedRoles, accountRoles, userEntities]);
196197

197198
const [query, setQuery] = React.useState('');
198199

0 commit comments

Comments
 (0)