Skip to content

Commit 767597e

Browse files
feat: [UIE-9357] - IAM Delegation: Default Roles table (#12990)
* feat: [UIE-9357] - IAM Delegation: Default Roles Table * feat: [UIE-9357] - IAM Delegation: unit test * feat: [UIE-9357] - IAM Delegation: refactoring * feat: [UIE-9357] - rebase * feat: [UIE-9357] - remove useAssignedRoles hook since it's no longer needed * Added changeset: IAM: Default Roles Table * feat: [UIE-9357] - review fix * feat: [UIE-9357] - test fix * feat: [UIE-9357] - review fix * feat: [UIE-9357] - mock data fix, view entities url fix * feat: [UIE-9357] - refactor --------- Co-authored-by: Alban Bailly <[email protected]>
1 parent 02d7050 commit 767597e

File tree

13 files changed

+255
-89
lines changed

13 files changed

+255
-89
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: Default Roles Table ([#12990](https://github.com/linode/manager/pull/12990))
Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
1-
import { Paper, Stack, Typography } from '@linode/ui';
1+
import { Paper, Typography } from '@linode/ui';
22
import * as React from 'react';
33

4+
import { AssignedRolesTable } from '../../Shared/AssignedRolesTable/AssignedRolesTable';
5+
46
export const DefaultRoles = () => {
57
return (
68
<Paper>
7-
<Stack>
8-
<Typography variant="h2">Default Roles for Delegate Users</Typography>
9-
<Typography marginTop={2}>
10-
View and manage roles to be assigned to delegate users by default.
11-
Note that changes implemented here will apply to only new delegate
12-
users. For existing delegate users, use their Assigned Roles page to
13-
update the assignment.
14-
</Typography>
15-
</Stack>
9+
<Typography variant="h2">Default Roles for Delegate Users</Typography>
10+
<Typography mt={2}>
11+
View and manage roles to be assigned to delegate users by default. Note
12+
that changes implemented here will apply to only new delegate users.
13+
</Typography>
14+
<Typography mb={2}>
15+
For existing delegate users, use their Assigned Roles page to update the
16+
assignment.
17+
</Typography>
18+
<AssignedRolesTable />
1619
</Paper>
1720
);
1821
};

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { ChangeRoleForEntityDrawer } from '../../Shared/AssignedEntitiesTable/Ch
1111
import type { EntitiesRole } from '../../Shared/types';
1212

1313
const queryMocks = vi.hoisted(() => ({
14-
useParams: vi.fn().mockReturnValue({}),
14+
useParams: vi.fn().mockReturnValue({ username: 'test_user' }),
1515
useAccountRoles: vi.fn().mockReturnValue({}),
1616
useUserRoles: vi.fn().mockReturnValue({}),
1717
}));
@@ -47,6 +47,7 @@ const props = {
4747
onClose: vi.fn(),
4848
open: true,
4949
role: mockRole,
50+
username: 'test_user',
5051
};
5152

5253
const mockUpdateUserRole = vi.fn();

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

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,25 @@ const queryMocks = vi.hoisted(() => ({
1414
useParams: vi.fn().mockReturnValue({}),
1515
useAccountRoles: vi.fn().mockReturnValue({}),
1616
useUserRoles: vi.fn().mockReturnValue({}),
17+
useGetDefaultDelegationAccessQuery: vi.fn().mockReturnValue({}),
18+
useIsDefaultDelegationRolesForChildAccount: vi.fn().mockReturnValue({
19+
isDefaultDelegationRolesForChildAccount: false,
20+
}),
1721
}));
1822

1923
vi.mock('@linode/queries', async () => {
20-
const actual = await vi.importActual<any>('@linode/queries');
24+
const actual = await vi.importActual('@linode/queries');
2125
return {
2226
...actual,
2327
useAccountRoles: queryMocks.useAccountRoles,
2428
useUserRoles: queryMocks.useUserRoles,
29+
useGetDefaultDelegationAccessQuery:
30+
queryMocks.useGetDefaultDelegationAccessQuery,
2531
};
2632
});
2733

2834
vi.mock('src/queries/entities/entities', async () => {
29-
const actual = await vi.importActual<any>('src/queries/entities/entities');
35+
const actual = await vi.importActual('src/queries/entities/entities');
3036
return {
3137
...actual,
3238
useAllAccountEntities: queryMocks.useAllAccountEntities,
@@ -41,6 +47,11 @@ vi.mock('@tanstack/react-router', async () => {
4147
};
4248
});
4349

50+
vi.mock('../../hooks/useDelegationRole', () => ({
51+
useIsDefaultDelegationRolesForChildAccount:
52+
queryMocks.useIsDefaultDelegationRolesForChildAccount,
53+
}));
54+
4455
const mockEntities = [
4556
accountEntityFactory.build({
4657
id: 7,
@@ -53,6 +64,9 @@ const mockEntities = [
5364
}),
5465
];
5566

67+
const mockUserRoles = userRolesFactory.build();
68+
const mockAccountRoles = accountRolesFactory.build();
69+
5670
describe('AssignedRolesTable', () => {
5771
beforeEach(() => {
5872
queryMocks.useParams.mockReturnValue({
@@ -72,11 +86,11 @@ describe('AssignedRolesTable', () => {
7286

7387
it('should display roles and menu when data is available', async () => {
7488
queryMocks.useUserRoles.mockReturnValue({
75-
data: userRolesFactory.build(),
89+
data: mockUserRoles,
7690
});
7791

7892
queryMocks.useAccountRoles.mockReturnValue({
79-
data: accountRolesFactory.build(),
93+
data: mockAccountRoles,
8094
});
8195

8296
queryMocks.useAllAccountEntities.mockReturnValue({
@@ -100,11 +114,11 @@ describe('AssignedRolesTable', () => {
100114

101115
it('should display empty state when no roles match filters', async () => {
102116
queryMocks.useUserRoles.mockReturnValue({
103-
data: userRolesFactory.build(),
117+
data: mockUserRoles,
104118
});
105119

106120
queryMocks.useAccountRoles.mockReturnValue({
107-
data: accountRolesFactory.build(),
121+
data: mockAccountRoles,
108122
});
109123

110124
queryMocks.useAllAccountEntities.mockReturnValue({
@@ -123,11 +137,11 @@ describe('AssignedRolesTable', () => {
123137

124138
it('should filter roles based on search query', async () => {
125139
queryMocks.useUserRoles.mockReturnValue({
126-
data: userRolesFactory.build(),
140+
data: mockUserRoles,
127141
});
128142

129143
queryMocks.useAccountRoles.mockReturnValue({
130-
data: accountRolesFactory.build(),
144+
data: mockAccountRoles,
131145
});
132146

133147
queryMocks.useAllAccountEntities.mockReturnValue({
@@ -146,11 +160,11 @@ describe('AssignedRolesTable', () => {
146160

147161
it('should filter roles based on selected resource type', async () => {
148162
queryMocks.useUserRoles.mockReturnValue({
149-
data: userRolesFactory.build(),
163+
data: mockUserRoles,
150164
});
151165

152166
queryMocks.useAccountRoles.mockReturnValue({
153-
data: accountRolesFactory.build(),
167+
data: mockAccountRoles,
154168
});
155169

156170
queryMocks.useAllAccountEntities.mockReturnValue({
@@ -166,4 +180,27 @@ describe('AssignedRolesTable', () => {
166180
expect(screen.queryByText('account_firewall_creator')).toBeVisible();
167181
});
168182
});
183+
184+
it('should show different button text for default roles view', async () => {
185+
queryMocks.useIsDefaultDelegationRolesForChildAccount.mockReturnValue({
186+
isDefaultDelegationRolesForChildAccount: true,
187+
});
188+
189+
queryMocks.useGetDefaultDelegationAccessQuery.mockReturnValue({
190+
data: mockUserRoles,
191+
});
192+
193+
queryMocks.useAccountRoles.mockReturnValue({
194+
data: mockAccountRoles,
195+
});
196+
197+
queryMocks.useAllAccountEntities.mockReturnValue({
198+
data: mockEntities,
199+
});
200+
201+
renderWithTheme(<AssignedRolesTable />);
202+
203+
expect(screen.getByText('Add New Default Roles')).toBeVisible();
204+
expect(screen.queryByText('Assign New Roles')).not.toBeInTheDocument();
205+
});
169206
});

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

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import { useAccountRoles, useUserRoles } from '@linode/queries';
1+
import {
2+
useAccountRoles,
3+
useGetDefaultDelegationAccessQuery,
4+
useUserRoles,
5+
} from '@linode/queries';
26
import { Button, CircleProgress, Select, Typography } from '@linode/ui';
37
import { useTheme } from '@mui/material';
48
import Grid from '@mui/material/Grid';
@@ -17,6 +21,7 @@ import { TableSortCell } from 'src/components/TableSortCell/TableSortCell';
1721
import { usePaginationV2 } from 'src/hooks/usePaginationV2';
1822
import { useAllAccountEntities } from 'src/queries/entities/entities';
1923

24+
import { useIsDefaultDelegationRolesForChildAccount } from '../../hooks/useDelegationRole';
2025
import { usePermissions } from '../../hooks/usePermissions';
2126
import { AssignedEntities } from '../../Users/UserRoles/AssignedEntities';
2227
import { AssignNewRoleDrawer } from '../../Users/UserRoles/AssignNewRoleDrawer';
@@ -65,9 +70,8 @@ const ALL_ROLES_OPTION: SelectOption = {
6570
label: 'All Assigned Roles',
6671
value: 'all',
6772
};
68-
6973
export const AssignedRolesTable = () => {
70-
const { username } = useParams({ from: '/iam/users/$username' });
74+
const { username } = useParams({ strict: false });
7175
const navigate = useNavigate();
7276
const theme = useTheme();
7377

@@ -76,8 +80,30 @@ export const AssignedRolesTable = () => {
7680
const [isInitialLoad, setIsInitialLoad] = React.useState(true);
7781
const { data: permissions } = usePermissions('account', ['is_account_admin']);
7882

83+
// Determine if we're on the default roles view based on delegation role and path
84+
const { isDefaultDelegationRolesForChildAccount } =
85+
useIsDefaultDelegationRolesForChildAccount();
86+
87+
const { data: defaultRolesData, isLoading: defaultRolesLoading } =
88+
useGetDefaultDelegationAccessQuery({
89+
enabled: isDefaultDelegationRolesForChildAccount,
90+
});
91+
92+
const { data: userRolesData, isLoading: userRolesLoading } = useUserRoles(
93+
username ?? '',
94+
!isDefaultDelegationRolesForChildAccount
95+
);
96+
97+
const assignedRoles = isDefaultDelegationRolesForChildAccount
98+
? defaultRolesData
99+
: userRolesData;
100+
const assignedRolesLoading = isDefaultDelegationRolesForChildAccount
101+
? defaultRolesLoading
102+
: userRolesLoading;
79103
const pagination = usePaginationV2({
80-
currentRoute: '/iam/users/$username/roles',
104+
currentRoute: isDefaultDelegationRolesForChildAccount
105+
? '/iam/roles/defaults/roles'
106+
: '/iam/users/$username/roles',
81107
initialPage: 1,
82108
preferenceKey: ASSIGNED_ROLES_TABLE_PREFERENCE_KEY,
83109
});
@@ -139,9 +165,6 @@ export const AssignedRolesTable = () => {
139165
{}
140166
);
141167

142-
const { data: assignedRoles, isLoading: assignedRolesLoading } = useUserRoles(
143-
username ?? ''
144-
);
145168
const { filterableOptions, roles } = React.useMemo(() => {
146169
if (!assignedRoles || !accountRoles) {
147170
return { filterableOptions: [], roles: [] };
@@ -173,8 +196,10 @@ export const AssignedRolesTable = () => {
173196
const handleViewEntities = (roleName: AccountRoleType | EntityRoleType) => {
174197
const selectedRole = roleName;
175198
navigate({
176-
to: '/iam/users/$username/entities',
177-
params: { username },
199+
to: isDefaultDelegationRolesForChildAccount
200+
? '/iam/roles/defaults/entity-access'
201+
: '/iam/users/$username/entities',
202+
params: { username: username || '' },
178203
search: { selectedRole },
179204
});
180205
};
@@ -388,7 +413,9 @@ export const AssignedRolesTable = () => {
388413
: undefined
389414
}
390415
>
391-
Assign New Roles
416+
{isDefaultDelegationRolesForChildAccount
417+
? 'Add New Default Roles'
418+
: 'Assign New Roles'}
392419
</Button>
393420
</Grid>
394421
</Grid>

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

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import {
22
useAccountRoles,
3+
useGetDefaultDelegationAccessQuery,
4+
useUpdateDefaultDelegationAccessQuery,
35
useUserRoles,
46
useUserRolesMutation,
57
} from '@linode/queries';
@@ -17,6 +19,7 @@ import { Controller, useForm } from 'react-hook-form';
1719

1820
import { Link } from 'src/components/Link';
1921

22+
import { useIsDefaultDelegationRolesForChildAccount } from '../../hooks/useDelegationRole';
2023
import { AssignedPermissionsPanel } from '../AssignedPermissionsPanel/AssignedPermissionsPanel';
2124
import { ROLES_LEARN_MORE_LINK } from '../constants';
2225
import {
@@ -40,15 +43,32 @@ interface Props {
4043

4144
export const ChangeRoleDrawer = ({ mode, onClose, open, role }: Props) => {
4245
const theme = useTheme();
43-
const { username } = useParams({ from: '/iam/users/$username' });
44-
46+
const { username } = useParams({ strict: false });
4547
const { data: accountRoles, isLoading: accountPermissionsLoading } =
4648
useAccountRoles();
4749

48-
const { data: assignedRoles } = useUserRoles(username ?? '');
50+
const { isDefaultDelegationRolesForChildAccount } =
51+
useIsDefaultDelegationRolesForChildAccount();
52+
const { data: defaultRolesData } = useGetDefaultDelegationAccessQuery({
53+
enabled: isDefaultDelegationRolesForChildAccount,
54+
});
4955

56+
const { data: userRolesData } = useUserRoles(
57+
username ?? '',
58+
!isDefaultDelegationRolesForChildAccount
59+
);
60+
61+
const assignedRoles = isDefaultDelegationRolesForChildAccount
62+
? defaultRolesData
63+
: userRolesData;
5064
const { mutateAsync: updateUserRoles } = useUserRolesMutation(username);
5165

66+
const { mutateAsync: updateDefaultRoles } =
67+
useUpdateDefaultDelegationAccessQuery();
68+
69+
const mutationFn = isDefaultDelegationRolesForChildAccount
70+
? updateDefaultRoles
71+
: updateUserRoles;
5272
const formattedAssignedEntities: EntitiesOption[] = React.useMemo(() => {
5373
if (!role || !role.entity_names || !role.entity_ids) {
5474
return [];
@@ -132,7 +152,7 @@ export const ChangeRoleDrawer = ({ mode, onClose, open, role }: Props) => {
132152
newRole,
133153
});
134154

135-
await updateUserRoles(updatedUserRoles);
155+
await mutationFn(updatedUserRoles);
136156

137157
handleClose();
138158
} catch (errors) {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ const props = {
2828
};
2929

3030
const queryMocks = vi.hoisted(() => ({
31-
useParams: vi.fn().mockReturnValue({}),
31+
useParams: vi.fn().mockReturnValue({ username: 'test_user' }),
3232
useAccountRoles: vi.fn().mockReturnValue({}),
3333
useUserRoles: vi.fn().mockReturnValue({}),
3434
}));

0 commit comments

Comments
 (0)