From c7409c12cbd9e2ae015a23edcec7da7295b6ec59 Mon Sep 17 00:00:00 2001 From: Banks Nussman Date: Fri, 23 Aug 2024 11:44:45 -0400 Subject: [PATCH 1/4] wrap invalidate queries --- .../manager/src/hooks/EventHandlerData.ts | 0 .../manager/src/hooks/useEventHandlers.ts | 18 +++++- .../manager/src/queries/account/billing.ts | 7 ++- packages/manager/src/queries/account/oauth.ts | 6 +- .../manager/src/queries/databases/events.ts | 6 +- packages/manager/src/queries/domains.ts | 8 +-- packages/manager/src/queries/firewalls.ts | 33 +++++------ packages/manager/src/queries/images.ts | 8 +-- .../manager/src/queries/linodes/events.ts | 56 ++++++++++--------- packages/manager/src/queries/nodebalancers.ts | 8 +-- .../manager/src/queries/profile/profile.ts | 6 +- .../manager/src/queries/profile/tokens.ts | 6 +- packages/manager/src/queries/stackscripts.ts | 6 +- packages/manager/src/queries/support.ts | 6 +- .../manager/src/queries/volumes/events.ts | 8 +-- 15 files changed, 98 insertions(+), 84 deletions(-) create mode 100644 packages/manager/src/hooks/EventHandlerData.ts diff --git a/packages/manager/src/hooks/EventHandlerData.ts b/packages/manager/src/hooks/EventHandlerData.ts new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/manager/src/hooks/useEventHandlers.ts b/packages/manager/src/hooks/useEventHandlers.ts index da32d9a3059..9f6312c36ea 100644 --- a/packages/manager/src/hooks/useEventHandlers.ts +++ b/packages/manager/src/hooks/useEventHandlers.ts @@ -16,10 +16,14 @@ import { supportTicketEventHandler } from 'src/queries/support'; import { volumeEventsHandler } from 'src/queries/volumes/events'; import type { Event } from '@linode/api-v4'; -import type { QueryClient } from '@tanstack/react-query'; +import type { + InvalidateQueryFilters, + QueryClient, +} from '@tanstack/react-query'; export interface EventHandlerData { event: Event; + invalidateQueries: (filters: InvalidateQueryFilters) => Promise; queryClient: QueryClient; } @@ -91,6 +95,16 @@ export const eventHandlers: { export const useEventHandlers = () => { const queryClient = useQueryClient(); + /* + * We wrap invalidateQueries because we need to enforce some options. + * + * We set `cancelRefetch` to `false` because it ensures no refetch will + * be made if there is already a request running. This is important for + * event handlers because they are envoked once for every event polled. + */ + const invalidateQueries = (filters: InvalidateQueryFilters) => + queryClient.invalidateQueries(filters, { cancelRefetch: false }); + /** * Given an event, this function finds the corresponding * event handler and invokes it. @@ -98,7 +112,7 @@ export const useEventHandlers = () => { const handleEvent = (event: Event) => { for (const eventHandler of eventHandlers) { if (eventHandler.filter(event)) { - eventHandler.handler({ event, queryClient }); + eventHandler.handler({ event, invalidateQueries, queryClient }); return; } } diff --git a/packages/manager/src/queries/account/billing.ts b/packages/manager/src/queries/account/billing.ts index 0b85e8c0cc2..9eddbf90160 100644 --- a/packages/manager/src/queries/account/billing.ts +++ b/packages/manager/src/queries/account/billing.ts @@ -29,9 +29,12 @@ export const useAllAccountPayments = ( }); }; -export const taxIdEventHandler = ({ event, queryClient }: EventHandlerData) => { +export const taxIdEventHandler = ({ + event, + invalidateQueries, +}: EventHandlerData) => { if (event.action === 'tax_id_invalid' || event.action === 'tax_id_valid') { - queryClient.invalidateQueries({ + invalidateQueries({ queryKey: accountQueries.notifications.queryKey, }); } diff --git a/packages/manager/src/queries/account/oauth.ts b/packages/manager/src/queries/account/oauth.ts index 653b2aa1de0..50a0fd75b88 100644 --- a/packages/manager/src/queries/account/oauth.ts +++ b/packages/manager/src/queries/account/oauth.ts @@ -70,10 +70,12 @@ export const useUpdateOAuthClientMutation = (id: string) => { }); }; -export const oauthClientsEventHandler = ({ queryClient }: EventHandlerData) => { +export const oauthClientsEventHandler = ({ + invalidateQueries, +}: EventHandlerData) => { // We may over-fetch because on `onSuccess` also invalidates, but this will be // good for UX because Cloud will always be up to date - queryClient.invalidateQueries({ + invalidateQueries({ queryKey: accountQueries.oauthClients._def, }); }; diff --git a/packages/manager/src/queries/databases/events.ts b/packages/manager/src/queries/databases/events.ts index c138284e787..33644af4a58 100644 --- a/packages/manager/src/queries/databases/events.ts +++ b/packages/manager/src/queries/databases/events.ts @@ -7,10 +7,10 @@ import type { EventHandlerData } from 'src/hooks/useEventHandlers'; export const databaseEventsHandler = ({ event, - queryClient, + invalidateQueries, }: EventHandlerData) => { if (['failed', 'finished', 'notification'].includes(event.status)) { - queryClient.invalidateQueries({ + invalidateQueries({ queryKey: databaseQueries.databases.queryKey, }); @@ -35,7 +35,7 @@ export const databaseEventsHandler = ({ ); } - queryClient.invalidateQueries({ + invalidateQueries({ queryKey: databaseQueries.database(engine as Engine, event.entity.id) .queryKey, }); diff --git a/packages/manager/src/queries/domains.ts b/packages/manager/src/queries/domains.ts index 41ab63b8be9..527ac5637c4 100644 --- a/packages/manager/src/queries/domains.ts +++ b/packages/manager/src/queries/domains.ts @@ -186,7 +186,7 @@ export const useUpdateDomainMutation = () => { export const domainEventsHandler = ({ event, - queryClient, + invalidateQueries, }: EventHandlerData) => { const domainId = event.entity?.id; @@ -196,17 +196,17 @@ export const domainEventsHandler = ({ if (event.action.startsWith('domain_record')) { // Invalidate the domain's records because they may have changed - queryClient.invalidateQueries({ + invalidateQueries({ queryKey: domainQueries.domain(domainId)._ctx.records.queryKey, }); } else { // Invalidate paginated lists - queryClient.invalidateQueries({ + invalidateQueries({ queryKey: domainQueries.domains.queryKey, }); // Invalidate the domain's details - queryClient.invalidateQueries({ + invalidateQueries({ exact: true, queryKey: domainQueries.domain(domainId).queryKey, }); diff --git a/packages/manager/src/queries/firewalls.ts b/packages/manager/src/queries/firewalls.ts index 6ad96e8ba59..bda609a9819 100644 --- a/packages/manager/src/queries/firewalls.ts +++ b/packages/manager/src/queries/firewalls.ts @@ -408,6 +408,7 @@ export const useUpdateFirewallRulesMutation = (firewallId: number) => { export const firewallEventsHandler = ({ event, + invalidateQueries, queryClient, }: EventHandlerData) => { if (!event.entity) { @@ -418,7 +419,7 @@ export const firewallEventsHandler = ({ switch (event.action) { case 'firewall_delete': // Invalidate firewall lists - queryClient.invalidateQueries({ + invalidateQueries({ queryKey: firewallQueries.firewalls.queryKey, }); @@ -428,7 +429,7 @@ export const firewallEventsHandler = ({ }); case 'firewall_create': // Invalidate firewall lists - queryClient.invalidateQueries({ + invalidateQueries({ queryKey: firewallQueries.firewalls.queryKey, }); case 'firewall_device_add': @@ -438,13 +439,9 @@ export const firewallEventsHandler = ({ // If a Linode is added or removed as a firewall device, invalidate it's firewalls if (event.secondary_entity && event.secondary_entity.type === 'linode') { - queryClient.invalidateQueries({ - queryKey: [ - 'linodes', - 'linode', - event.secondary_entity.id, - 'firewalls', - ], + invalidateQueries({ + queryKey: linodeQueries.linode(event.secondary_entity.id)._ctx + .firewalls.queryKey, }); } @@ -453,23 +450,19 @@ export const firewallEventsHandler = ({ event.secondary_entity && event.secondary_entity.type === 'nodebalancer' ) { - queryClient.invalidateQueries({ - queryKey: [ - 'nodebalancers', - 'nodebalancer', - event.secondary_entity.id, - 'firewalls', - ], + invalidateQueries({ + queryKey: nodebalancerQueries.nodebalancer(event.secondary_entity.id) + ._ctx.firewalls.queryKey, }); } // Invalidate the firewall - queryClient.invalidateQueries({ + invalidateQueries({ queryKey: firewallQueries.firewall(event.entity.id).queryKey, }); // Invalidate firewall lists - queryClient.invalidateQueries({ + invalidateQueries({ queryKey: firewallQueries.firewalls.queryKey, }); case 'firewall_disable': @@ -477,11 +470,11 @@ export const firewallEventsHandler = ({ case 'firewall_rules_update': case 'firewall_update': // invalidate the firewall - queryClient.invalidateQueries({ + invalidateQueries({ queryKey: firewallQueries.firewall(event.entity.id).queryKey, }); // Invalidate firewall lists - queryClient.invalidateQueries({ + invalidateQueries({ queryKey: firewallQueries.firewalls.queryKey, }); } diff --git a/packages/manager/src/queries/images.ts b/packages/manager/src/queries/images.ts index 5df6ed104f1..e79b1d5ed9b 100644 --- a/packages/manager/src/queries/images.ts +++ b/packages/manager/src/queries/images.ts @@ -174,13 +174,13 @@ export const useUpdateImageRegionsMutation = (imageId: string) => { export const imageEventsHandler = ({ event, - queryClient, + invalidateQueries, }: EventHandlerData) => { if (['failed', 'finished', 'notification'].includes(event.status)) { - queryClient.invalidateQueries({ + invalidateQueries({ queryKey: imageQueries.all._def, }); - queryClient.invalidateQueries({ queryKey: imageQueries.paginated._def }); + invalidateQueries({ queryKey: imageQueries.paginated._def }); if (event.entity) { /* @@ -194,7 +194,7 @@ export const imageEventsHandler = ({ */ const imageId = `private/${event.entity.id}`; - queryClient.invalidateQueries({ + invalidateQueries({ queryKey: imageQueries.image(imageId).queryKey, }); } diff --git a/packages/manager/src/queries/linodes/events.ts b/packages/manager/src/queries/linodes/events.ts index 7f44a64ba75..cfcf2151a77 100644 --- a/packages/manager/src/queries/linodes/events.ts +++ b/packages/manager/src/queries/linodes/events.ts @@ -14,6 +14,7 @@ import type { EventHandlerData } from 'src/hooks/useEventHandlers'; */ export const linodeEventsHandler = ({ event, + invalidateQueries, queryClient, }: EventHandlerData) => { const linodeId = event.entity?.id; @@ -29,7 +30,7 @@ export const linodeEventsHandler = ({ // Some Linode events are an indication that the reponse from /v4/account/notifications // has changed, so refetch notifications. if (shouldRequestNotifications(event)) { - queryClient.invalidateQueries(accountQueries.notifications); + invalidateQueries(accountQueries.notifications); } switch (event.action) { @@ -43,65 +44,63 @@ export const linodeEventsHandler = ({ case 'linode_resize_warm_create': case 'linode_reboot': case 'linode_update': - queryClient.invalidateQueries(linodeQueries.linodes); - queryClient.invalidateQueries({ + invalidateQueries(linodeQueries.linodes); + invalidateQueries({ exact: true, queryKey: linodeQueries.linode(linodeId).queryKey, }); return; case 'linode_boot': case 'linode_shutdown': - queryClient.invalidateQueries(linodeQueries.linodes); - queryClient.invalidateQueries({ + invalidateQueries(linodeQueries.linodes); + invalidateQueries({ exact: true, queryKey: linodeQueries.linode(linodeId).queryKey, }); // Ensure configs are fresh when Linode is booted up (see https://github.com/linode/manager/pull/9914) - queryClient.invalidateQueries({ + invalidateQueries({ queryKey: linodeQueries.linode(linodeId)._ctx.configs.queryKey, }); return; case 'linode_snapshot': - queryClient.invalidateQueries(linodeQueries.linodes); - queryClient.invalidateQueries({ + invalidateQueries(linodeQueries.linodes); + invalidateQueries({ exact: true, queryKey: linodeQueries.linode(linodeId).queryKey, }); - queryClient.invalidateQueries( - linodeQueries.linode(linodeId)._ctx.backups - ); + invalidateQueries(linodeQueries.linode(linodeId)._ctx.backups); return; case 'linode_addip': case 'linode_deleteip': - queryClient.invalidateQueries(linodeQueries.linodes); - queryClient.invalidateQueries({ + invalidateQueries(linodeQueries.linodes); + invalidateQueries({ queryKey: linodeQueries.linode(linodeId)._ctx.ips.queryKey, }); - queryClient.invalidateQueries({ + invalidateQueries({ exact: true, queryKey: linodeQueries.linode(linodeId).queryKey, }); return; case 'linode_create': case 'linode_clone': - queryClient.invalidateQueries({ + invalidateQueries({ queryKey: linodeQueries.linode(linodeId)._ctx.disks.queryKey, }); - queryClient.invalidateQueries(linodeQueries.linodes); - queryClient.invalidateQueries({ + invalidateQueries(linodeQueries.linodes); + invalidateQueries({ exact: true, queryKey: linodeQueries.linode(linodeId).queryKey, }); return; case 'linode_rebuild': - queryClient.invalidateQueries({ + invalidateQueries({ queryKey: linodeQueries.linode(linodeId)._ctx.disks.queryKey, }); - queryClient.invalidateQueries({ + invalidateQueries({ queryKey: linodeQueries.linode(linodeId)._ctx.configs.queryKey, }); - queryClient.invalidateQueries(linodeQueries.linodes); - queryClient.invalidateQueries({ + invalidateQueries(linodeQueries.linodes); + invalidateQueries({ exact: true, queryKey: linodeQueries.linode(linodeId).queryKey, }); @@ -110,20 +109,20 @@ export const linodeEventsHandler = ({ queryClient.removeQueries({ queryKey: linodeQueries.linode(linodeId).queryKey, }); - queryClient.invalidateQueries({ + invalidateQueries({ queryKey: linodeQueries.linodes.queryKey, }); // A Linode made have been on a Firewall's device list, but now that it is deleted, // it will no longer be listed as a device on that firewall. Here, we invalidate outdated firewall data. - queryClient.invalidateQueries({ queryKey: firewallQueries._def }); + invalidateQueries({ queryKey: firewallQueries._def }); // A Linode may have been attached to a Volume, but deleted. We need to refetch volumes data so that // the Volumes table does not show a Volume attached to a non-existant Linode. - queryClient.invalidateQueries({ queryKey: volumeQueries.lists.queryKey }); + invalidateQueries({ queryKey: volumeQueries.lists.queryKey }); return; case 'linode_config_create': case 'linode_config_delete': case 'linode_config_update': - queryClient.invalidateQueries({ + invalidateQueries({ queryKey: linodeQueries.linode(linodeId)._ctx.configs.queryKey, }); return; @@ -136,14 +135,17 @@ export const linodeEventsHandler = ({ * Disks have their own handler beacuse the actions are not prefixed with "linode_". * They are prefixed with "disk_". For example "disk_create" or "disk_delete". */ -export const diskEventHandler = ({ event, queryClient }: EventHandlerData) => { +export const diskEventHandler = ({ + event, + invalidateQueries, +}: EventHandlerData) => { const linodeId = event.entity?.id; if (!linodeId || ['scheduled', 'started'].includes(event.status)) { return; } - queryClient.invalidateQueries({ + invalidateQueries({ queryKey: linodeQueries.linode(linodeId)._ctx.disks.queryKey, }); }; diff --git a/packages/manager/src/queries/nodebalancers.ts b/packages/manager/src/queries/nodebalancers.ts index b48c977d1c5..bbfe635ad9e 100644 --- a/packages/manager/src/queries/nodebalancers.ts +++ b/packages/manager/src/queries/nodebalancers.ts @@ -303,7 +303,7 @@ export const useNodeBalancerTypesQuery = () => export const nodebalancerEventHandler = ({ event, - queryClient, + invalidateQueries, }: EventHandlerData) => { const nodebalancerId = event.entity?.id; @@ -319,7 +319,7 @@ export const nodebalancerEventHandler = ({ if (event.action.startsWith('nodebalancer_config')) { // If the event is about a NodeBalancer's configs, just invalidate the configs - queryClient.invalidateQueries({ + invalidateQueries({ queryKey: nodebalancerQueries.nodebalancer(nodebalancerId)._ctx .configurations.queryKey, }); @@ -327,13 +327,13 @@ export const nodebalancerEventHandler = ({ // If we've made it here, the event is about a NodeBalancer // Invalidate the specific NodeBalancer - queryClient.invalidateQueries({ + invalidateQueries({ exact: true, queryKey: nodebalancerQueries.nodebalancer(nodebalancerId).queryKey, }); // Invalidate all paginated lists - queryClient.invalidateQueries({ + invalidateQueries({ queryKey: nodebalancerQueries.nodebalancers.queryKey, }); } diff --git a/packages/manager/src/queries/profile/profile.ts b/packages/manager/src/queries/profile/profile.ts index 13fb33f7783..726cd66b1cb 100644 --- a/packages/manager/src/queries/profile/profile.ts +++ b/packages/manager/src/queries/profile/profile.ts @@ -195,15 +195,15 @@ export const useDeleteSSHKeyMutation = (id: number) => { }); }; -export const sshKeyEventHandler = (event: EventHandlerData) => { +export const sshKeyEventHandler = ({ invalidateQueries }: EventHandlerData) => { // This event handler is a bit agressive and will over-fetch, but UX will // be great because this will ensure Cloud has up to date data all the time. - event.queryClient.invalidateQueries({ + invalidateQueries({ queryKey: profileQueries.sshKeys._def, }); // also invalidate the /account/users data because that endpoint returns some SSH key data - event.queryClient.invalidateQueries({ + invalidateQueries({ queryKey: accountQueries.users._ctx.paginated._def, }); }; diff --git a/packages/manager/src/queries/profile/tokens.ts b/packages/manager/src/queries/profile/tokens.ts index 19f9db88fa4..ef9ebd8da79 100644 --- a/packages/manager/src/queries/profile/tokens.ts +++ b/packages/manager/src/queries/profile/tokens.ts @@ -90,11 +90,11 @@ export const useRevokeAppAccessTokenMutation = (id: number) => { }); }; -export function tokenEventHandler({ queryClient }: EventHandlerData) { - queryClient.invalidateQueries({ +export function tokenEventHandler({ invalidateQueries }: EventHandlerData) { + invalidateQueries({ queryKey: profileQueries.appTokens._def, }); - queryClient.invalidateQueries({ + invalidateQueries({ queryKey: profileQueries.personalAccessTokens._def, }); } diff --git a/packages/manager/src/queries/stackscripts.ts b/packages/manager/src/queries/stackscripts.ts index b520d1390bc..42bb6de6138 100644 --- a/packages/manager/src/queries/stackscripts.ts +++ b/packages/manager/src/queries/stackscripts.ts @@ -68,16 +68,16 @@ export const useStackScriptsInfiniteQuery = ( export const stackScriptEventHandler = ({ event, - queryClient, + invalidateQueries, }: EventHandlerData) => { // Keep the infinite store up to date - queryClient.invalidateQueries({ + invalidateQueries({ queryKey: stackscriptQueries.infinite._def, }); // If the event has a StackScript entity attached, invalidate it if (event.entity?.id) { - queryClient.invalidateQueries({ + invalidateQueries({ queryKey: stackscriptQueries.stackscript(event.entity.id).queryKey, }); } diff --git a/packages/manager/src/queries/support.ts b/packages/manager/src/queries/support.ts index 5c4ea96838c..94524983762 100644 --- a/packages/manager/src/queries/support.ts +++ b/packages/manager/src/queries/support.ts @@ -112,7 +112,7 @@ export const useSupportTicketCloseMutation = (id: number) => { export const supportTicketEventHandler = ({ event, - queryClient, + invalidateQueries, }: EventHandlerData) => { /** * Ticket events have entities that look like this: @@ -126,13 +126,13 @@ export const supportTicketEventHandler = ({ */ // Invalidate paginated support tickets - queryClient.invalidateQueries({ + invalidateQueries({ queryKey: supportQueries.tickets._def, }); if (event.entity) { // If there is an entity associated with the event, invalidate that ticket - queryClient.invalidateQueries({ + invalidateQueries({ queryKey: supportQueries.ticket(event.entity.id).queryKey, }); } diff --git a/packages/manager/src/queries/volumes/events.ts b/packages/manager/src/queries/volumes/events.ts index 84f2c7c6fba..3f2415f0891 100644 --- a/packages/manager/src/queries/volumes/events.ts +++ b/packages/manager/src/queries/volumes/events.ts @@ -10,10 +10,10 @@ import type { EventHandlerData } from 'src/hooks/useEventHandlers'; */ export const volumeEventsHandler = ({ event, - queryClient, + invalidateQueries, }: EventHandlerData) => { if (['failed', 'finished', 'notification'].includes(event.status)) { - queryClient.invalidateQueries({ + invalidateQueries({ queryKey: volumeQueries.lists.queryKey, }); } @@ -24,7 +24,7 @@ export const volumeEventsHandler = ({ ) { // if a migration finishes, we want to re-request notifications so that the `volume_migration_imminent` // notification goes away. - queryClient.invalidateQueries({ + invalidateQueries({ queryKey: accountQueries.notifications.queryKey, }); } @@ -33,7 +33,7 @@ export const volumeEventsHandler = ({ // The API gives us no way to know when a cloned volume transitions from // creating to active, so we will just refresh after 10 seconds setTimeout(() => { - queryClient.invalidateQueries({ + invalidateQueries({ queryKey: volumeQueries.lists.queryKey, }); }, 10000); From a51e9b4eb9d517426e26730b22a8ba9b0eac2ac6 Mon Sep 17 00:00:00 2001 From: Banks Nussman Date: Fri, 23 Aug 2024 12:00:41 -0400 Subject: [PATCH 2/4] Added changeset: Event handlers making a proportional number of GET requests to the number of incoming events --- packages/manager/.changeset/pr-10824-fixed-1724428840947.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 packages/manager/.changeset/pr-10824-fixed-1724428840947.md diff --git a/packages/manager/.changeset/pr-10824-fixed-1724428840947.md b/packages/manager/.changeset/pr-10824-fixed-1724428840947.md new file mode 100644 index 00000000000..94eda937dd4 --- /dev/null +++ b/packages/manager/.changeset/pr-10824-fixed-1724428840947.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Fixed +--- + +Event handlers making a proportional number of GET requests to the number of incoming events ([#10824](https://github.com/linode/manager/pull/10824)) From 7e409f09572f0223e4d69b67513caeb8dd6f06ca Mon Sep 17 00:00:00 2001 From: Banks Nussman Date: Fri, 23 Aug 2024 13:57:17 -0400 Subject: [PATCH 3/4] add cypress test that would have caught bug --- .../events-fetching.spec.ts | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 packages/manager/cypress/e2e/core/notificationsAndEvents/events-fetching.spec.ts diff --git a/packages/manager/cypress/e2e/core/notificationsAndEvents/events-fetching.spec.ts b/packages/manager/cypress/e2e/core/notificationsAndEvents/events-fetching.spec.ts new file mode 100644 index 00000000000..38a7fc01275 --- /dev/null +++ b/packages/manager/cypress/e2e/core/notificationsAndEvents/events-fetching.spec.ts @@ -0,0 +1,40 @@ +import { eventFactory } from 'src/factories'; +import { mockGetEvents } from 'support/intercepts/events'; +import { mockGetVolumes } from 'support/intercepts/volumes'; + +describe('Event Handlers', () => { + it('invokes event handlers when new events are polled and makes the correct number of requests', () => { + mockGetEvents([]).as('getEvents'); + mockGetVolumes([]).as('getInitialVolumes'); + + cy.visitWithLogin('/volumes'); + + // Wait for the initial events fetch and initial volumes fetch + cy.wait(['@getEvents', '@getInitialVolumes']); + + // Wait for the first polling interval to happen + cy.wait('@getEvents'); + + const polledEvents = [ + eventFactory.build({ action: 'volume_update', status: 'notification' }), + eventFactory.build({ action: 'volume_update', status: 'notification' }), + eventFactory.build({ action: 'volume_update', status: 'notification' }), + ]; + + // Pretend a volume was updated 3 times in a row + mockGetEvents(polledEvents).as('getEvents'); + + // Intercept GET volumes so we can later check how many times it is fetched + mockGetVolumes([]).as('getVolumes'); + + // Wait for volume update events to be polled + cy.wait('@getEvents'); + + // On the next interval, mock no new events + mockGetEvents([]).as('getEvents'); + cy.wait('@getEvents'); + + // Finally, verify the volume endpoint was only fetched once + cy.get('@getVolumes.all').should('have.length', 1); + }); +}); From 2face420a2d63e0de99c4e5966ca19f0fe9a5d9c Mon Sep 17 00:00:00 2001 From: Banks Nussman Date: Fri, 23 Aug 2024 14:03:15 -0400 Subject: [PATCH 4/4] link PR for context --- .../e2e/core/notificationsAndEvents/events-fetching.spec.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/manager/cypress/e2e/core/notificationsAndEvents/events-fetching.spec.ts b/packages/manager/cypress/e2e/core/notificationsAndEvents/events-fetching.spec.ts index 38a7fc01275..e1f3804b6a5 100644 --- a/packages/manager/cypress/e2e/core/notificationsAndEvents/events-fetching.spec.ts +++ b/packages/manager/cypress/e2e/core/notificationsAndEvents/events-fetching.spec.ts @@ -4,6 +4,8 @@ import { mockGetVolumes } from 'support/intercepts/volumes'; describe('Event Handlers', () => { it('invokes event handlers when new events are polled and makes the correct number of requests', () => { + // See https://github.com/linode/manager/pull/10824 + mockGetEvents([]).as('getEvents'); mockGetVolumes([]).as('getInitialVolumes');