diff --git a/packages/manager/.changeset/pr-9814-upcoming-features-1697651400469.md b/packages/manager/.changeset/pr-9814-upcoming-features-1697651400469.md new file mode 100644 index 00000000000..2ff46b30211 --- /dev/null +++ b/packages/manager/.changeset/pr-9814-upcoming-features-1697651400469.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Upcoming Features +--- + +Invalidate VPC-related queries when deleting a Linode ([#9814](https://github.com/linode/manager/pull/9814)) diff --git a/packages/manager/src/features/Linodes/LinodesLanding/DeleteLinodeDialog.tsx b/packages/manager/src/features/Linodes/LinodesLanding/DeleteLinodeDialog.tsx index d0a8e4e7c32..a1c051af713 100644 --- a/packages/manager/src/features/Linodes/LinodesLanding/DeleteLinodeDialog.tsx +++ b/packages/manager/src/features/Linodes/LinodesLanding/DeleteLinodeDialog.tsx @@ -1,4 +1,5 @@ import * as React from 'react'; +import { useQueryClient } from 'react-query'; import { Notice } from 'src/components/Notice/Notice'; import { TypeToConfirmDialog } from 'src/components/TypeToConfirmDialog/TypeToConfirmDialog'; @@ -9,6 +10,14 @@ import { useLinodeQuery, } from 'src/queries/linodes/linodes'; +import { useAllLinodeConfigsQuery } from 'src/queries/linodes/configs'; +import { vpcQueryKey, subnetQueryKey } from 'src/queries/vpcs'; +import { useFlags } from 'src/hooks/useFlags'; +import { useAccount } from 'src/queries/account'; +import { isFeatureEnabled } from 'src/utilities/accountCapabilities'; + +import { getVPCsFromLinodeConfigs } from './utils'; + interface Props { linodeId: number | undefined; onClose: () => void; @@ -17,6 +26,16 @@ interface Props { } export const DeleteLinodeDialog = (props: Props) => { + const queryClient = useQueryClient(); + const flags = useFlags(); + const { data: account } = useAccount(); + + const enableVPCActions = isFeatureEnabled( + 'VPCs', + Boolean(flags.vpc), + account?.capabilities ?? [] + ); + const { linodeId, onClose, onSuccess, open } = props; const { data: linode } = useLinodeQuery( @@ -24,6 +43,11 @@ export const DeleteLinodeDialog = (props: Props) => { linodeId !== undefined && open ); + const { data: configs } = useAllLinodeConfigsQuery( + linodeId ?? -1, + linodeId !== undefined && open && enableVPCActions + ); + const { error, isLoading, mutateAsync, reset } = useDeleteLinodeMutation( linodeId ?? -1 ); @@ -36,6 +60,24 @@ export const DeleteLinodeDialog = (props: Props) => { const onDelete = async () => { await mutateAsync(); + const vpcIds = enableVPCActions + ? getVPCsFromLinodeConfigs(configs ?? []) + : []; + // @TODO VPC: potentially revisit using the linodeEventsHandler in linode/events.ts to invalidate queries rather than here + // See PR #9814 for more details + if (vpcIds.length > 0) { + queryClient.invalidateQueries([vpcQueryKey, 'paginated']); + // invalidate data for specific vpcs this linode is assigned to + vpcIds.forEach((vpcId) => { + queryClient.invalidateQueries([vpcQueryKey, 'vpc', vpcId]); + queryClient.invalidateQueries([ + vpcQueryKey, + 'vpc', + vpcId, + subnetQueryKey, + ]); + }); + } onClose(); resetEventsPolling(); diff --git a/packages/manager/src/features/Linodes/LinodesLanding/utils.test.ts b/packages/manager/src/features/Linodes/LinodesLanding/utils.test.ts index e2fc29b9139..bf1a98e828d 100644 --- a/packages/manager/src/features/Linodes/LinodesLanding/utils.test.ts +++ b/packages/manager/src/features/Linodes/LinodesLanding/utils.test.ts @@ -1,4 +1,9 @@ -import { parseMaintenanceStartTime } from './utils'; +import { parseMaintenanceStartTime, getVPCsFromLinodeConfigs } from './utils'; +import { + configFactory, + LinodeConfigInterfaceFactory, + LinodeConfigInterfaceFactoryWithVPC, +} from 'src/factories'; describe('Linode Landing Utilites', () => { it('should return "Maintenance Window Unknown" for invalid dates', () => { @@ -26,4 +31,39 @@ describe('Linode Landing Utilites', () => { expect(parseMaintenanceStartTime(undefined)).toBe('No Maintenance Needed'); expect(parseMaintenanceStartTime(null)).toBe('No Maintenance Needed'); }); + + describe('getVPCsFromLinodeConfigs', () => { + const vpcInterfaceList = LinodeConfigInterfaceFactoryWithVPC.buildList(2); + + it('returns an empty list if there are no vpc interfaces in the configs', () => { + const configs = configFactory.buildList(3); + const vpcIds = getVPCsFromLinodeConfigs(configs); + expect(vpcIds).toEqual([]); + }); + + it('returns the IDs of vpc-related interfaces', () => { + const config = configFactory.build({ + interfaces: [ + ...vpcInterfaceList, + ...LinodeConfigInterfaceFactory.buildList(4), + ], + }); + const vpcIds = getVPCsFromLinodeConfigs([ + ...configFactory.buildList(3), + config, + ]); + expect(vpcIds).toEqual([2, 3]); + }); + + it('returns unique vpc ids (no duplicates)', () => { + const vpcInterface = LinodeConfigInterfaceFactoryWithVPC.build({ + vpc_id: 2, + }); + const config = configFactory.build({ + interfaces: [...vpcInterfaceList, vpcInterface], + }); + const vpcIds = getVPCsFromLinodeConfigs([config]); + expect(vpcIds).toEqual([2, 3]); + }); + }); }); diff --git a/packages/manager/src/features/Linodes/LinodesLanding/utils.ts b/packages/manager/src/features/Linodes/LinodesLanding/utils.ts index 551444ac39c..afbcf34fabb 100644 --- a/packages/manager/src/features/Linodes/LinodesLanding/utils.ts +++ b/packages/manager/src/features/Linodes/LinodesLanding/utils.ts @@ -1,4 +1,4 @@ -import { LinodeStatus } from '@linode/api-v4/lib/linodes'; +import { Config, LinodeStatus } from '@linode/api-v4/lib/linodes'; import { reportException } from 'src/exceptionReporting'; @@ -51,3 +51,17 @@ export const getLinodeIconStatus = (status: LinodeStatus) => { } return 'other'; }; + +// Return all (unique) vpc IDs that a linode is assigned to +export const getVPCsFromLinodeConfigs = (configs: Config[]): number[] => { + const vpcIds = new Set(); + for (const config of configs) { + for (const linodeInterface of config.interfaces) { + if (linodeInterface.purpose === 'vpc' && linodeInterface.vpc_id) { + vpcIds.add(linodeInterface.vpc_id); + } + } + } + + return Array.from(vpcIds); +}; diff --git a/packages/manager/src/features/VPCs/VPCCreate/SubnetNode.tsx b/packages/manager/src/features/VPCs/VPCCreate/SubnetNode.tsx index 09baf52f7c1..11a77bbc396 100644 --- a/packages/manager/src/features/VPCs/VPCCreate/SubnetNode.tsx +++ b/packages/manager/src/features/VPCs/VPCCreate/SubnetNode.tsx @@ -26,7 +26,7 @@ interface Props { subnet: SubnetFieldState; } -// TODO: VPC - currently only supports IPv4, must update when/if IPv6 is also supported +// @TODO VPC: currently only supports IPv4, must update when/if IPv6 is also supported export const SubnetNode = (props: Props) => { const { disabled, idx, isRemovable, onChange, subnet } = props; diff --git a/packages/manager/src/features/VPCs/VPCDetail/SubnetCreateDrawer.tsx b/packages/manager/src/features/VPCs/VPCDetail/SubnetCreateDrawer.tsx index a837f3f81da..1e39fee014f 100644 --- a/packages/manager/src/features/VPCs/VPCDetail/SubnetCreateDrawer.tsx +++ b/packages/manager/src/features/VPCs/VPCDetail/SubnetCreateDrawer.tsx @@ -60,7 +60,7 @@ export const SubnetCreateDrawer = (props: Props) => { const { dirty, handleSubmit, resetForm, setValues, values } = useFormik({ enableReinitialize: true, initialValues: { - // TODO VPC - add IPv6 when that is supported + // @TODO VPC: add IPv6 when that is supported label: '', ip: { ipv4: DEFAULT_SUBNET_IPV4_VALUE, diff --git a/packages/manager/src/features/VPCs/VPCDetail/VPCDetail.tsx b/packages/manager/src/features/VPCs/VPCDetail/VPCDetail.tsx index ba5b32f44c2..47eea5a26b8 100644 --- a/packages/manager/src/features/VPCs/VPCDetail/VPCDetail.tsx +++ b/packages/manager/src/features/VPCs/VPCDetail/VPCDetail.tsx @@ -189,6 +189,7 @@ const VPCDetail = () => { {numLinodes > 0 && (