diff --git a/packages/manager/.changeset/pr-13030-fixed-1761674529303.md b/packages/manager/.changeset/pr-13030-fixed-1761674529303.md new file mode 100644 index 00000000000..19951a077c4 --- /dev/null +++ b/packages/manager/.changeset/pr-13030-fixed-1761674529303.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Fixed +--- + +IAM: Linodes without required permissions visible and selectable in Assign/Unassign Linodes selector ([#13030](https://github.com/linode/manager/pull/13030)) diff --git a/packages/manager/src/features/VPCs/VPCDetail/SubnetAssignLinodesDrawer.test.tsx b/packages/manager/src/features/VPCs/VPCDetail/SubnetAssignLinodesDrawer.test.tsx index b744e8f27cb..a920de4eaea 100644 --- a/packages/manager/src/features/VPCs/VPCDetail/SubnetAssignLinodesDrawer.test.tsx +++ b/packages/manager/src/features/VPCs/VPCDetail/SubnetAssignLinodesDrawer.test.tsx @@ -17,6 +17,17 @@ const queryMocks = vi.hoisted(() => ({ useFirewallSettingsQuery: vi.fn().mockReturnValue({}), })); +const iamMocks = vi.hoisted(() => ({ + usePermissions: vi.fn().mockReturnValue({ data: { update_vpc: true } }), + useQueryWithPermissions: vi.fn().mockReturnValue({ + data: [], + isLoading: false, + error: null, + isError: false, + hasFiltered: false, + }), +})); + vi.mock('@linode/queries', async () => { const actual = await vi.importActual('@linode/queries'); return { @@ -25,6 +36,11 @@ vi.mock('@linode/queries', async () => { }; }); +vi.mock('src/features/IAM/hooks/usePermissions', () => ({ + usePermissions: iamMocks.usePermissions, + useQueryWithPermissions: iamMocks.useQueryWithPermissions, +})); + const props = { isFetching: false, onClose: vi.fn(), @@ -48,6 +64,17 @@ describe('Subnet Assign Linodes Drawer', () => { region: props.vpcRegion, }); + beforeEach(() => { + // Set up the default mock to return the linode + iamMocks.useQueryWithPermissions.mockReturnValue({ + data: [linode], + isLoading: false, + error: null, + isError: false, + hasFiltered: false, + }); + }); + server.use( http.get('*/linode/instances', () => { return HttpResponse.json(makeResourcePage([linode])); diff --git a/packages/manager/src/features/VPCs/VPCDetail/SubnetAssignLinodesDrawer.tsx b/packages/manager/src/features/VPCs/VPCDetail/SubnetAssignLinodesDrawer.tsx index ada602f3e86..c8ff57284f2 100644 --- a/packages/manager/src/features/VPCs/VPCDetail/SubnetAssignLinodesDrawer.tsx +++ b/packages/manager/src/features/VPCs/VPCDetail/SubnetAssignLinodesDrawer.tsx @@ -149,40 +149,44 @@ export const SubnetAssignLinodesDrawer = ( const [allowPublicIPv6Access, setAllowPublicIPv6Access] = React.useState(false); - const { data: permissions } = usePermissions('vpc', ['update_vpc'], vpcId); - // TODO: change update_linode to create_linode_config_profile_interface once it's available - // TODO: change delete_linode to delete_linode_config_profile_interface once it's available - // TODO: refactor useQueryWithPermissions once API filter is available - const { data: filteredLinodes } = useQueryWithPermissions( - useAllLinodesQuery(), - 'linode', - ['update_linode', 'delete_linode'], + // We only want the linodes from the same region as the VPC + const query = useAllLinodesQuery( + {}, + { + region: vpcRegion, + }, open ); - const userCanAssignLinodes = - permissions?.update_vpc && filteredLinodes?.length > 0; - const downloadCSV = async () => { - await getCSVData(); + // getCSVData + await query.refetch(); csvRef.current.link.click(); }; - // We only want the linodes from the same region as the VPC - const { data: linodes, refetch: getCSVData } = useAllLinodesQuery( - {}, - { - region: vpcRegion, - } - ); + const { data: permissions } = usePermissions('vpc', ['update_vpc'], vpcId); + // TODO: change update_linode to create_linode_config_profile_interface once it's available + // TODO: change delete_linode to delete_linode_config_profile_interface once it's available + // TODO: refactor useQueryWithPermissions once API filter is available + const { data: filteredLinodes, isLoading: isLoadingFilteredLinodes } = + useQueryWithPermissions( + query, + 'linode', + ['update_linode', 'delete_linode'], + open + ); + const userCanAssignLinodes = + permissions?.update_vpc && filteredLinodes?.length > 0; // We need to filter to the linodes from this region that are not already // assigned to this subnet const findUnassignedLinodes = React.useCallback(() => { - return linodes?.filter((linode) => { + if (!filteredLinodes) return []; + + return filteredLinodes?.filter((linode) => { return !subnet?.linodes.some((linodeInfo) => linodeInfo.id === linode.id); }); - }, [subnet, linodes]); + }, [subnet, filteredLinodes]); const [linodeOptionsToAssign, setLinodeOptionsToAssign] = React.useState< Linode[] @@ -192,10 +196,10 @@ export const SubnetAssignLinodesDrawer = ( // and update that list whenever this subnet or the list of all linodes in this subnet's region changes. This takes // care of the MUI invalid value warning that was occurring before in the Linodes autocomplete [M3-6752] React.useEffect(() => { - if (linodes) { + if (filteredLinodes) { setLinodeOptionsToAssign(findUnassignedLinodes() ?? []); } - }, [linodes, setLinodeOptionsToAssign, findUnassignedLinodes]); + }, [filteredLinodes, setLinodeOptionsToAssign, findUnassignedLinodes]); // Determine the configId based on the number of configurations function getConfigId(inputs: { @@ -551,7 +555,7 @@ export const SubnetAssignLinodesDrawer = ( try { const data = await getAllLinodeConfigs(linode.id); setLinodeConfigs(data); - } catch (errors) { + } catch { // force error to appear at top of drawer setAssignLinodesErrors({ none: 'Could not load configurations for selected linode', @@ -585,7 +589,7 @@ export const SubnetAssignLinodesDrawer = ( return (