Skip to content

Commit 6d4337d

Browse files
feat: [UIE-9341, UIE-9342, UIE-9521] - IAM: fix perm for vpcs (linode#13050)
* UIE-9342 * UIE-9341 * feat: [UIE-9341, UIE-9342, UIE-9521] - IAM: fix perm for vpc * Added changeset: IAM: fix permissiom's check for vpc for assigning/unassigning linodes * filter linodes * e2e deferred getLinodes call --------- Co-authored-by: Alban Bailly <abailly@akamai.com>
1 parent 5254642 commit 6d4337d

File tree

6 files changed

+36
-123
lines changed

6 files changed

+36
-123
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@linode/manager": Changed
3+
---
4+
5+
IAM: fix permissiom's check for vpc for assigning/unassigning linodes ([#13050](https://github.com/linode/manager/pull/13050))

packages/manager/cypress/e2e/core/vpc/vpc-linodes-update.spec.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ describe('VPC assign/unassign flows', () => {
119119
.click();
120120
});
121121

122-
cy.wait(['@createSubnet', '@getVPC', '@getSubnets', '@getLinodes']);
122+
cy.wait(['@createSubnet', '@getVPC', '@getSubnets']);
123123

124124
mockGetSubnet(mockVPC.id, mockSubnet.id, mockSubnet);
125125

@@ -139,6 +139,8 @@ describe('VPC assign/unassign flows', () => {
139139
.should('be.visible')
140140
.click();
141141

142+
cy.wait(['@getLinodes']);
143+
142144
ui.drawer
143145
.findByTitle(`Assign Linodes to subnet: ${mockSubnet.label}`)
144146
.should('be.visible')
@@ -395,7 +397,7 @@ describe('VPC assign/unassign flows', () => {
395397
mockGetLinodes([mockLinode, mockSecondLinode]).as('getLinodes');
396398

397399
cy.visitWithLogin(`/vpcs/${mockVPC.id}`);
398-
cy.wait(['@getVPC', '@getSubnets', '@getLinodes', '@getFeatureFlags']);
400+
cy.wait(['@getVPC', '@getSubnets', '@getFeatureFlags']);
399401

400402
// confirm that subnet should get displayed on VPC's detail page
401403
cy.findByText(mockVPC.label).should('be.visible');
@@ -415,6 +417,8 @@ describe('VPC assign/unassign flows', () => {
415417
.should('be.visible')
416418
.click();
417419

420+
cy.wait(['@getLinodes']);
421+
418422
ui.drawer
419423
.findByTitle(
420424
`Unassign Linodes from subnet: ${mockSubnet.label} (0.0.0.0/0)`

packages/manager/src/features/VPCs/VPCDetail/SubnetActionMenu.test.tsx

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ import { SubnetActionMenu } from './SubnetActionMenu';
1010
const queryMocks = vi.hoisted(() => ({
1111
userPermissions: vi.fn(() => ({
1212
data: {
13-
update_linode: true,
14-
delete_linode: true,
1513
update_vpc: true,
1614
delete_vpc: true,
1715
},
@@ -128,45 +126,9 @@ describe('SubnetActionMenu', () => {
128126
expect(props.handleAssignLinodes).toHaveBeenCalled();
129127
});
130128

131-
it('should disable the Assign Linodes button if user does not have update_linode permission', async () => {
132-
queryMocks.userPermissions.mockReturnValue({
133-
data: {
134-
update_linode: false,
135-
delete_linode: false,
136-
update_vpc: false,
137-
delete_vpc: false,
138-
},
139-
});
140-
const view = renderWithTheme(<SubnetActionMenu {...props} />);
141-
const actionMenu = view.getByLabelText(`Action menu for Subnet subnet-1`);
142-
await userEvent.click(actionMenu);
143-
144-
const assignButton = view.getByRole('menuitem', { name: 'Assign Linodes' });
145-
expect(assignButton).toHaveAttribute('aria-disabled', 'true');
146-
});
147-
148-
it('should enable the Assign Linodes button if user has update_linode and update_vpc permissions', async () => {
149-
queryMocks.userPermissions.mockReturnValue({
150-
data: {
151-
update_linode: true,
152-
delete_linode: false,
153-
update_vpc: true,
154-
delete_vpc: false,
155-
},
156-
});
157-
const view = renderWithTheme(<SubnetActionMenu {...props} />);
158-
const actionMenu = view.getByLabelText(`Action menu for Subnet subnet-1`);
159-
await userEvent.click(actionMenu);
160-
161-
const assignButton = view.getByRole('menuitem', { name: 'Assign Linodes' });
162-
expect(assignButton).not.toHaveAttribute('aria-disabled', 'true');
163-
});
164-
165129
it('should disable the Edit button if user does not have update_vpc permission', async () => {
166130
queryMocks.userPermissions.mockReturnValue({
167131
data: {
168-
update_linode: false,
169-
delete_linode: false,
170132
update_vpc: false,
171133
delete_vpc: false,
172134
},
@@ -182,8 +144,6 @@ describe('SubnetActionMenu', () => {
182144
it('should enable the Edit button if user has update_vpc permission', async () => {
183145
queryMocks.userPermissions.mockReturnValue({
184146
data: {
185-
update_linode: false,
186-
delete_linode: false,
187147
update_vpc: true,
188148
delete_vpc: false,
189149
},

packages/manager/src/features/VPCs/VPCDetail/SubnetActionMenu.tsx

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export const SubnetActionMenu = (props: Props) => {
4040
['update_vpc', 'delete_vpc'],
4141
vpcId
4242
);
43+
4344
const canUpdateVPC = permissions?.update_vpc;
4445
const canDeleteVPC = permissions?.delete_vpc;
4546

@@ -49,20 +50,12 @@ export const SubnetActionMenu = (props: Props) => {
4950
handleAssignLinodes(subnet);
5051
},
5152
title: 'Assign Linodes',
52-
disabled: !canUpdateVPC,
53-
tooltip: !canUpdateVPC
54-
? 'You do not have permission to assign Linode to this subnet.'
55-
: undefined,
5653
},
5754
{
5855
onClick: () => {
5956
handleUnassignLinodes(subnet);
6057
},
6158
title: 'Unassign Linodes',
62-
disabled: !canUpdateVPC,
63-
tooltip: !canUpdateVPC
64-
? 'You do not have permission to unassign Linode from this subnet.'
65-
: undefined,
6659
},
6760
{
6861
onClick: () => {

packages/manager/src/features/VPCs/VPCDetail/SubnetAssignLinodesDrawer.tsx

Lines changed: 7 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,7 @@ import { DownloadCSV } from 'src/components/DownloadCSV/DownloadCSV';
2828
import { Link } from 'src/components/Link';
2929
import { RemovableSelectionsListTable } from 'src/components/RemovableSelectionsList/RemovableSelectionsListTable';
3030
import { FirewallSelect } from 'src/features/Firewalls/components/FirewallSelect';
31-
import {
32-
usePermissions,
33-
useQueryWithPermissions,
34-
} from 'src/features/IAM/hooks/usePermissions';
31+
import { useQueryWithPermissions } from 'src/features/IAM/hooks/usePermissions';
3532
import { getDefaultFirewallForInterfacePurpose } from 'src/features/Linodes/LinodeCreate/Networking/utilities';
3633
import {
3734
REMOVABLE_SELECTIONS_LINODES_TABLE_HEADERS,
@@ -164,43 +161,22 @@ export const SubnetAssignLinodesDrawer = (
164161
csvRef.current.link.click();
165162
};
166163

167-
const { data: permissions } = usePermissions('vpc', ['update_vpc'], vpcId);
168164
// TODO: change update_linode to create_linode_config_profile_interface once it's available
169-
// TODO: change delete_linode to delete_linode_config_profile_interface once it's available
170-
// TODO: refactor useQueryWithPermissions once API filter is available
171165
const { data: filteredLinodes, isLoading: isLoadingFilteredLinodes } =
172-
useQueryWithPermissions<Linode>(
173-
query,
174-
'linode',
175-
['update_linode', 'delete_linode'],
176-
open
177-
);
166+
useQueryWithPermissions<Linode>(query, 'linode', ['update_linode'], open);
167+
168+
const userCanAssignLinodes = filteredLinodes?.length > 0;
178169

179-
const userCanAssignLinodes =
180-
permissions?.update_vpc && filteredLinodes?.length > 0;
181-
// We need to filter to the linodes from this region that are not already
182-
// assigned to this subnet
183-
const findUnassignedLinodes = React.useCallback(() => {
170+
const linodeOptionsToAssign = React.useMemo(() => {
171+
// We need to filter to the linodes from this region that are not already
172+
// assigned to this subnet
184173
if (!filteredLinodes) return [];
185174

186175
return filteredLinodes?.filter((linode) => {
187176
return !subnet?.linodes.some((linodeInfo) => linodeInfo.id === linode.id);
188177
});
189178
}, [subnet, filteredLinodes]);
190179

191-
const [linodeOptionsToAssign, setLinodeOptionsToAssign] = React.useState<
192-
Linode[]
193-
>([]);
194-
195-
// Moved the list of linodes that are currently assignable to a subnet into a state variable (linodeOptionsToAssign)
196-
// and update that list whenever this subnet or the list of all linodes in this subnet's region changes. This takes
197-
// care of the MUI invalid value warning that was occurring before in the Linodes autocomplete [M3-6752]
198-
React.useEffect(() => {
199-
if (filteredLinodes) {
200-
setLinodeOptionsToAssign(findUnassignedLinodes() ?? []);
201-
}
202-
}, [filteredLinodes, setLinodeOptionsToAssign, findUnassignedLinodes]);
203-
204180
// Determine the configId based on the number of configurations
205181
function getConfigId(inputs: {
206182
isLinodeInterface: boolean;
@@ -594,12 +570,6 @@ export const SubnetAssignLinodesDrawer = (
594570
open={open}
595571
title={`Assign Linodes to subnet: ${subnet?.label ?? 'Unknown'}`}
596572
>
597-
{!userCanAssignLinodes && (
598-
<Notice
599-
text={`You don't have permissions to assign Linodes to ${subnet?.label}. Please contact an account administrator for details.`}
600-
variant="error"
601-
/>
602-
)}
603573
{assignLinodesErrors.none && (
604574
<Notice text={assignLinodesErrors.none} variant="error" />
605575
)}

packages/manager/src/features/VPCs/VPCDetail/SubnetUnassignLinodesDrawer.tsx

Lines changed: 17 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,7 @@ import * as React from 'react';
1515

1616
import { DownloadCSV } from 'src/components/DownloadCSV/DownloadCSV';
1717
import { RemovableSelectionsListTable } from 'src/components/RemovableSelectionsList/RemovableSelectionsListTable';
18-
import {
19-
usePermissions,
20-
useQueryWithPermissions,
21-
} from 'src/features/IAM/hooks/usePermissions';
18+
import { useQueryWithPermissions } from 'src/features/IAM/hooks/usePermissions';
2219
import { REMOVABLE_SELECTIONS_LINODES_TABLE_HEADERS } from 'src/features/VPCs/constants';
2320
import { useUnassignLinode } from 'src/hooks/useUnassignLinode';
2421
import { useVPCDualStack } from 'src/hooks/useVPCDualStack';
@@ -97,47 +94,37 @@ export const SubnetUnassignLinodesDrawer = React.memo(
9794

9895
const hasError = React.useRef(false); // This flag is used to prevent the drawer from closing if an error occurs.
9996

100-
const [linodeOptionsToUnassign, setLinodeOptionsToUnassign] =
101-
React.useState<Linode[]>([]);
10297
const [interfacesToDelete, setInterfacesToDelete] = React.useState<
10398
DeleteInterfaceIds[]
10499
>([]);
105100

106101
const { linodes: subnetLinodeIds } = subnet || {};
107102

108103
// 1. We need to get all the linodes.
104+
// TODO: change to 'delete_linode_config_profile_interface' once it's available
109105
const {
110-
data: linodes,
106+
data: filteredLinodes,
111107
error: linodesError,
108+
isLoading: isLoadingFilteredLinodes,
112109
refetch: getCSVData,
113-
} = useAllLinodesQuery();
110+
} = useQueryWithPermissions<Linode>(
111+
useAllLinodesQuery({}, {}, open),
112+
'linode',
113+
['delete_linode'],
114+
open
115+
);
116+
const userCanUnassignLinodes = filteredLinodes?.length > 0;
114117

115-
// 2. We need to filter only the linodes that are assigned to the subnet.
116-
const findAssignedLinodes = React.useCallback(() => {
117-
return linodes?.filter((linode) => {
118+
const linodeOptionsToUnassign = React.useMemo(() => {
119+
// 2. We need to filter only the linodes that are assigned to the subnet.
120+
if (!filteredLinodes) return [];
121+
122+
return filteredLinodes?.filter((linode) => {
118123
return subnetLinodeIds?.some(
119124
(linodeInfo) => linodeInfo.id === linode.id
120125
);
121126
});
122-
}, [linodes, subnetLinodeIds]);
123-
124-
const { data: permissions } = usePermissions('vpc', ['update_vpc'], vpcId);
125-
// TODO: change to 'delete_linode_config_profile_interface' once it's available
126-
const { data: filteredLinodes, isLoading: isLoadingFilteredLinodes } =
127-
useQueryWithPermissions<Linode>(
128-
useAllLinodesQuery({}, {}, open),
129-
'linode',
130-
['delete_linode'],
131-
open
132-
);
133-
const userCanUnassignLinodes =
134-
permissions.update_vpc && filteredLinodes?.length > 0;
135-
136-
React.useEffect(() => {
137-
if (linodes) {
138-
setLinodeOptionsToUnassign(findAssignedLinodes() ?? []);
139-
}
140-
}, [linodes, setLinodeOptionsToUnassign, findAssignedLinodes]);
127+
}, [subnetLinodeIds, filteredLinodes]);
141128

142129
// 3. When a linode is selected, we need to get the VPC interface to unassign.
143130
const getVPCInterface = React.useCallback(
@@ -340,12 +327,6 @@ export const SubnetUnassignLinodesDrawer = React.memo(
340327
subnet?.ipv4 ?? subnet?.ipv6 ?? 'Unknown'
341328
})`}
342329
>
343-
{!userCanUnassignLinodes && linodeOptionsToUnassign.length > 0 && (
344-
<Notice
345-
text={`You don't have permissions to unassign Linodes from ${subnet?.label}. Please contact an account administrator for details.`}
346-
variant="error"
347-
/>
348-
)}
349330
{unassignLinodesErrors.length > 0 && (
350331
<Notice text={unassignLinodesErrors[0].reason} variant="error" />
351332
)}

0 commit comments

Comments
 (0)