Skip to content

Commit f065ad2

Browse files
feat: [UIE-9357] - IAM Delegation: refactoring
1 parent 0421ae7 commit f065ad2

File tree

7 files changed

+188
-72
lines changed

7 files changed

+188
-72
lines changed
Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
1-
import { useGetDefaultDelegationAccessQuery } from '@linode/queries';
21
import { Paper, Typography } from '@linode/ui';
32
import * as React from 'react';
43

54
import { AssignedRolesTable } from '../../Shared/AssignedRolesTable/AssignedRolesTable';
65

76
export const DefaultRoles = () => {
8-
const { data: assignedRoles, isLoading: assignedRolesLoading } =
9-
useGetDefaultDelegationAccessQuery();
10-
117
return (
128
<Paper>
139
<Typography variant="h2">Default Roles for Delegate Users</Typography>
@@ -19,11 +15,7 @@ export const DefaultRoles = () => {
1915
For existing delegate users, use their Assigned Roles page to update the
2016
assignment.
2117
</Typography>
22-
<AssignedRolesTable
23-
assignedRoles={assignedRoles}
24-
assignedRolesLoading={assignedRolesLoading}
25-
isDefaultRolesView={true}
26-
/>
18+
<AssignedRolesTable />
2719
</Paper>
2820
);
2921
};

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

Lines changed: 6 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,7 @@ describe('AssignedRolesTable', () => {
6868
data: {},
6969
});
7070

71-
renderWithTheme(
72-
<AssignedRolesTable
73-
assignedRoles={{ account_access: [], entity_access: [] }}
74-
assignedRolesLoading={false}
75-
username="test_user"
76-
/>
77-
);
71+
renderWithTheme(<AssignedRolesTable />);
7872

7973
expect(screen.getByText('No items to display.')).toBeVisible();
8074
});
@@ -92,13 +86,7 @@ describe('AssignedRolesTable', () => {
9286
data: mockEntities,
9387
});
9488

95-
renderWithTheme(
96-
<AssignedRolesTable
97-
assignedRoles={mockUserRoles}
98-
assignedRolesLoading={false}
99-
username="test_user"
100-
/>
101-
);
89+
renderWithTheme(<AssignedRolesTable />);
10290

10391
expect(screen.getByText('account_linode_admin')).toBeVisible();
10492
expect(screen.getAllByText('All Linodes')[0]).toBeVisible();
@@ -126,13 +114,7 @@ describe('AssignedRolesTable', () => {
126114
data: mockEntities,
127115
});
128116

129-
renderWithTheme(
130-
<AssignedRolesTable
131-
assignedRoles={mockUserRoles}
132-
assignedRolesLoading={false}
133-
username="test_user"
134-
/>
135-
);
117+
renderWithTheme(<AssignedRolesTable />);
136118

137119
const searchInput = screen.getByPlaceholderText('Search');
138120
await userEvent.type(searchInput, 'NonExistentRole');
@@ -155,13 +137,7 @@ describe('AssignedRolesTable', () => {
155137
data: mockEntities,
156138
});
157139

158-
renderWithTheme(
159-
<AssignedRolesTable
160-
assignedRoles={mockUserRoles}
161-
assignedRolesLoading={false}
162-
username="test_user"
163-
/>
164-
);
140+
renderWithTheme(<AssignedRolesTable />);
165141

166142
const searchInput = screen.getByPlaceholderText('Search');
167143
await userEvent.type(searchInput, 'account_linode_admin');
@@ -184,13 +160,7 @@ describe('AssignedRolesTable', () => {
184160
data: mockEntities,
185161
});
186162

187-
renderWithTheme(
188-
<AssignedRolesTable
189-
assignedRoles={mockUserRoles}
190-
assignedRolesLoading={false}
191-
username="test_user"
192-
/>
193-
);
163+
renderWithTheme(<AssignedRolesTable />);
194164

195165
const autocomplete = screen.getByPlaceholderText('All Assigned Roles');
196166
await userEvent.type(autocomplete, 'Firewall Roles');
@@ -209,13 +179,7 @@ describe('AssignedRolesTable', () => {
209179
data: mockEntities,
210180
});
211181

212-
renderWithTheme(
213-
<AssignedRolesTable
214-
assignedRoles={mockUserRoles}
215-
assignedRolesLoading={false}
216-
isDefaultRolesView={true}
217-
/>
218-
);
182+
renderWithTheme(<AssignedRolesTable />);
219183

220184
expect(screen.getByText('Add New Default Roles')).toBeVisible();
221185
expect(screen.queryByText('Assign New Roles')).not.toBeInTheDocument();

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

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { TableSortCell } from 'src/components/TableSortCell/TableSortCell';
1717
import { usePaginationV2 } from 'src/hooks/usePaginationV2';
1818
import { useAllAccountEntities } from 'src/queries/entities/entities';
1919

20+
import { useAssignedRoles } from '../../hooks/useAssignedRoles';
2021
import { usePermissions } from '../../hooks/usePermissions';
2122
import { AssignedEntities } from '../../Users/UserRoles/AssignedEntities';
2223
import { AssignNewRoleDrawer } from '../../Users/UserRoles/AssignNewRoleDrawer';
@@ -55,7 +56,6 @@ import type {
5556
AccessType,
5657
AccountRoleType,
5758
EntityRoleType,
58-
IamUserRoles,
5959
} from '@linode/api-v4';
6060
import type { SelectOption } from '@linode/ui';
6161
import type { TableItem } from 'src/components/CollapsibleTable/CollapsibleTable';
@@ -66,23 +66,17 @@ const ALL_ROLES_OPTION: SelectOption = {
6666
label: 'All Assigned Roles',
6767
value: 'all',
6868
};
69-
7069
interface Props {
71-
assignedRoles?: IamUserRoles;
72-
assignedRolesLoading?: boolean;
73-
isDefaultRolesView?: boolean;
7470
username?: string;
7571
}
76-
77-
export const AssignedRolesTable = ({
78-
isDefaultRolesView = false,
79-
assignedRoles,
80-
assignedRolesLoading,
81-
username,
82-
}: Props) => {
72+
export const AssignedRolesTable = (props: Props) => {
73+
const { username } = props;
8374
const navigate = useNavigate();
8475
const theme = useTheme();
8576

77+
const { assignedRoles, assignedRolesLoading, isDefaultRolesView } =
78+
useAssignedRoles(username);
79+
8680
const [order, setOrder] = React.useState<'asc' | 'desc'>('asc');
8781
const [orderBy, setOrderBy] = React.useState<OrderByKeys>('name');
8882
const [isInitialLoad, setIsInitialLoad] = React.useState(true);

packages/manager/src/features/IAM/Users/UserRoles/UserRoles.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,7 @@ export const UserRoles = () => {
7171
>
7272
View and manage roles assigned to the user.
7373
</Typography>
74-
<AssignedRolesTable
75-
assignedRoles={assignedRoles}
76-
assignedRolesLoading={isLoading}
77-
username={username}
78-
/>
74+
<AssignedRolesTable username={username} />
7975
</Paper>
8076
) : (
8177
<NoAssignedRoles
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { renderHook } from '@testing-library/react';
2+
import { vi } from 'vitest';
3+
4+
import {
5+
userDefaultRolesFactory,
6+
userRolesFactory,
7+
} from 'src/factories/userRoles';
8+
9+
import { useAssignedRoles } from './useAssignedRoles';
10+
11+
const {
12+
mockUseGetDefaultDelegationAccessQuery,
13+
mockUseUserRoles,
14+
mockUsePermissions,
15+
mockUseLocation,
16+
} = vi.hoisted(() => ({
17+
mockUseGetDefaultDelegationAccessQuery: vi.fn(),
18+
mockUseUserRoles: vi.fn(),
19+
mockUsePermissions: vi.fn(),
20+
mockUseLocation: vi.fn(),
21+
}));
22+
23+
vi.mock('@linode/queries', () => ({
24+
useGetDefaultDelegationAccessQuery: mockUseGetDefaultDelegationAccessQuery,
25+
useUserRoles: mockUseUserRoles,
26+
}));
27+
28+
vi.mock('@tanstack/react-router', () => ({
29+
useLocation: mockUseLocation,
30+
}));
31+
32+
vi.mock('./usePermissions', () => ({
33+
usePermissions: mockUsePermissions,
34+
}));
35+
36+
describe('useAssignedRoles', () => {
37+
const mockDefaultRolesData = userDefaultRolesFactory.build();
38+
const mockUserRolesData = userRolesFactory.build();
39+
40+
const mockPermissions = {
41+
is_account_admin: true,
42+
};
43+
44+
const USER_ROLES_PATH = '/iam/users/testuser/roles';
45+
const DEFAULT_ROLES_PATH = '/iam/roles/defaults/roles';
46+
47+
beforeEach(() => {
48+
vi.clearAllMocks();
49+
50+
// Default mock implementations
51+
mockUsePermissions.mockReturnValue({
52+
data: mockPermissions,
53+
});
54+
55+
mockUseGetDefaultDelegationAccessQuery.mockReturnValue({
56+
data: mockDefaultRolesData,
57+
isLoading: false,
58+
error: null,
59+
});
60+
61+
mockUseUserRoles.mockReturnValue({
62+
data: mockUserRolesData,
63+
isLoading: false,
64+
error: null,
65+
});
66+
});
67+
68+
describe('when on default roles view', () => {
69+
beforeEach(() => {
70+
mockUseLocation.mockReturnValue({
71+
pathname: DEFAULT_ROLES_PATH,
72+
});
73+
});
74+
75+
it('should return default roles data when on default roles view without username', () => {
76+
const { result } = renderHook(() => useAssignedRoles());
77+
78+
expect(result.current).toEqual({
79+
assignedRoles: mockDefaultRolesData,
80+
assignedRolesLoading: false,
81+
assignedRolesError: null,
82+
isDefaultRolesView: true,
83+
});
84+
});
85+
});
86+
87+
describe('when on user roles view', () => {
88+
beforeEach(() => {
89+
mockUseLocation.mockReturnValue({
90+
pathname: USER_ROLES_PATH,
91+
});
92+
});
93+
94+
it('should return user roles data when on user roles view', () => {
95+
const { result } = renderHook(() => useAssignedRoles('testuser'));
96+
97+
expect(result.current).toEqual({
98+
assignedRoles: mockUserRolesData,
99+
assignedRolesLoading: false,
100+
assignedRolesError: null,
101+
isDefaultRolesView: false,
102+
});
103+
});
104+
});
105+
});
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import {
2+
useGetDefaultDelegationAccessQuery,
3+
useUserRoles,
4+
} from '@linode/queries';
5+
import { useLocation } from '@tanstack/react-router';
6+
7+
import { usePermissions } from './usePermissions';
8+
9+
/**
10+
* Custom hook for fetching assigned roles data based on the current view context.
11+
*
12+
* This hook automatically determines whether to fetch default delegation access data
13+
* or user-specific roles data based on the current pathname. It supports both:
14+
* - Default roles view: Fetches default delegation access for all users
15+
* - User roles view: Fetches roles assigned to a specific user
16+
*
17+
* @param username - Optional username for fetching user-specific roles.
18+
* Not required for default roles view.
19+
* @returns Object containing:
20+
* - assignedRoles: The fetched roles data
21+
* - assignedRolesLoading: Loading state
22+
* - assignedRolesError: Error state if fetch fails
23+
* - isDefaultRolesView: Boolean indicating current view context
24+
*/
25+
export const useAssignedRoles = (username?: string) => {
26+
const { data: permissions } = usePermissions('account', ['is_account_admin']);
27+
const location = useLocation();
28+
// Check if we're on the default roles view based on the current path
29+
const isDefaultRolesView = location.pathname.includes(
30+
'/iam/roles/defaults/roles'
31+
);
32+
// TODO: use useIsDefaultDelegationRolesForChildAccount when available
33+
const shouldFetchDefaultRoles = isDefaultRolesView && !username;
34+
const shouldFetchUserRoles =
35+
!isDefaultRolesView && permissions?.is_account_admin;
36+
37+
const {
38+
data: defaultRolesData,
39+
isLoading: defaultRolesLoading,
40+
error: defaultRolesError,
41+
} = useGetDefaultDelegationAccessQuery(shouldFetchDefaultRoles);
42+
43+
const {
44+
data: userRolesData,
45+
isLoading: userRolesLoading,
46+
error: userRolesError,
47+
} = useUserRoles(username ?? '', shouldFetchUserRoles);
48+
49+
// Return the appropriate data based on the view
50+
if (isDefaultRolesView) {
51+
return {
52+
assignedRoles: defaultRolesData,
53+
assignedRolesLoading: defaultRolesLoading,
54+
assignedRolesError: defaultRolesError,
55+
isDefaultRolesView: true,
56+
};
57+
}
58+
59+
return {
60+
assignedRoles: userRolesData,
61+
assignedRolesLoading: userRolesLoading,
62+
assignedRolesError: userRolesError,
63+
isDefaultRolesView: false,
64+
};
65+
};

packages/queries/src/iam/delegation.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -323,12 +323,12 @@ export const useGenerateChildAccountTokenQuery = (): UseMutationResult<
323323
* - Audience: Child account administrators reviewing default delegate access.
324324
* - Data: IamUserRoles with `account_access` and `entity_access` for `GET /iam/delegation/default-role-permissions`.
325325
*/
326-
export const useGetDefaultDelegationAccessQuery = (): UseQueryResult<
327-
IamUserRoles,
328-
APIError[]
329-
> => {
326+
export const useGetDefaultDelegationAccessQuery = (
327+
enabled: boolean = true,
328+
): UseQueryResult<IamUserRoles, APIError[]> => {
330329
return useQuery<IamUserRoles, APIError[]>({
331330
...delegationQueries.defaultAccess,
331+
enabled,
332332
});
333333
};
334334

0 commit comments

Comments
 (0)