Skip to content

refactor: [M3-6570] - RQ-ify Events - Part 1: NotificationMenu, EventsLanding#9046

Merged
hkhalil-akamai merged 29 commits intolinode:feature/react-query-eventsfrom
hkhalil-akamai:M3-5181-rq-events
Jun 2, 2023
Merged

refactor: [M3-6570] - RQ-ify Events - Part 1: NotificationMenu, EventsLanding#9046
hkhalil-akamai merged 29 commits intolinode:feature/react-query-eventsfrom
hkhalil-akamai:M3-5181-rq-events

Conversation

@hkhalil-akamai
Copy link
Contributor

@hkhalil-akamai hkhalil-akamai commented Apr 22, 2023

Description 📝

Part 1 implements the RQ hooks in the Notifications menu and EventsLanding page. Part 2 will cover event handlers.

This PR migrates all Events endpoints to two React Query hooks (useEventsInfiniteQuery for fetching event notifications and useEventsPolling for registering event handlers) while hugely simplifying the current implementation.

The current Redux-based events system in Cloud Manager is pretty complex, making it difficult to understand and develop. Functionality is defined across several files, including events.ts, eventsPolling.ts, event.request.ts, event.reducer.ts, event.actions.ts and event.helpers.ts. Event handlers are used for important app functions like snackbar/toasts and cache invalidation but are defined via two redundant mechanisms (an rxjs subject and a custom Redux middleware).

The current system handles a lot of considerations and edge cases that must be accounted for:

  • Polling rate backoff to minimize API load
  • Increasing polling during background operations
  • Updates to in-progress events
  • Avoiding 'replaying' previously seen events
  • Separate read statuses for separate users under a single account

The plan is to reduce complexity by consolidating all Events-related functionality to these two hooks, remove the singular usage of the rxjs library and leverage the API-provided seen and read flags on events to avoid the complicated logic needed for maintaining a local events store.

How to test 🧪

Verify the notifications menu, which can be opened by clicking the bell icon, works as expected:

  • The icon displays the number of unseen notifications
  • In-progress events are rendered with a progress bar and are continuously updated
  • After closing the menu, events are marked as seen and the count disappears

Verify EventsLanding (accessed by opening the notifications menu and clicking "View all events"), works as expected:

  • Initially, the last 25 events are displayed
  • Scrolling down triggers additional events to be displayed
  • In-progress events are rendered with a progress bar and are continuously updated
  • New events are automatically displayed at the top of the list

Verify Linode Activity Feed, accessed by navigating to a linode and clicking on the "Activity Feed" tab, works as expected:

  • Only events related to the selected Linode are displayed
  • Same criteria as EventsLanding

Open DevTools Networking tab and filter to 'events' requests. Verify the polling interval starts at once per second and backs-off to once per 16 seconds (currently, polling is duplicated since the existing Events architecture is still enabled; it will be removed in Part 2).

Verify unit and E2E tests pass.

hkhalil-akamai and others added 2 commits April 19, 2023 16:11
Co-authored-by: Hussain Khalil <hussain@sanpilot.co>
Co-authored-by: Hussain Khalil <hussain@sanpilot.co>
@hkhalil-akamai hkhalil-akamai added Work in Progress React Query Relating to the transition to use React Query labels Apr 22, 2023
@hkhalil-akamai hkhalil-akamai self-assigned this Apr 22, 2023
@cypress
Copy link

cypress bot commented Apr 22, 2023

5 flaky tests on run #3978 ↗︎

0 167 3 0 Flakiness 5

Details:

Merge remote-tracking branch 'upstream/develop' into M3-5181-rq-events
Project: Cloud Manager E2E Commit: 769dd52d4c
Status: Passed Duration: 16:51 💡
Started: Jun 2, 2023 5:23 PM Ended: Jun 2, 2023 5:39 PM
Flakiness  images/machine-image-upload.spec.ts • 3 flaky tests

View Output Video

Test Artifacts
machine image > uploads machine image, mock finish event Output Screenshots Video
machine image > uploads machine image, mock upload canceled failed event Output Screenshots Video
machine image > uploads machine image, mock expired upload event Output Screenshots Video
Flakiness  firewalls/update-firewall.spec.ts • 1 flaky test

View Output Video

Test Artifacts
update firewall > updates a firewall's linodes and rules Output Screenshots Video
Flakiness  linodes/rescue-linode.spec.ts • 1 flaky test

View Output Video

Test Artifacts
Rescue Linodes > Cannot reboot a provisioning Linode into rescue mode Output Screenshots Video

This comment has been generated by cypress-bot as a result of this project's GitHub integration settings.

@hkhalil-akamai hkhalil-akamai changed the title refactor: [M3-5181] - RQify Events refactor: [M3-5181] - RQ-ify Events Apr 22, 2023
Co-authored-by: Hussain Khalil <hussain@sanpilot.co>
Comment on lines 52 to 74
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be cleaner for this logic to live in its own function or in the onSuccess function of useEventsPolling. I don't think useEvents should necessarily be dependent on useEventsPolling like it is right now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you anticipate any issues with the dependency? I think composing the hooks like this de-deduplicates some code.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because useEventsPolling is just a wrapper over useQuery, I think for the sake of maintainability, we should try to keep this wrapper as "React Query"-like as possible. I think it would be good if we keep the param here of type UseQueryOptions. If you think (event: Event) => void is truly better, we can keep it, but I generally like to keep things as "React Query"-like as possible.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am more in favor of keeping the signature and usage of this hook as simple as possible. Even though it uses RQ under the hook, it's not really relevant to components that use it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is reverseing needed?

Copy link
Contributor Author

@hkhalil-akamai hkhalil-akamai May 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is done to ensure the callback is called in chronological order; by default, events are sorted with the most recent events coming first.

hkhalil-akamai and others added 3 commits May 5, 2023 11:02
Co-authored-by: Hussain Khalil <hussain@sanpilot.co>
Co-authored-by: Hussain Khalil <hussain@sanpilot.co>
hkhalil-akamai and others added 2 commits May 5, 2023 11:25
Co-authored-by: Hussain Khalil <hussain@sanpilot.co>
Fixes notification menu button background color disappearing on hover in dark mode

Fix Notification menu doesn't close when navigating to EventsLanding

Co-authored-by: Hussain Khalil <hussain@sanpilot.co>
Copy link
Member

@bnussman-akamai bnussman-akamai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Things are shaping up nicely. Awesome job so far.

Comment on lines +26 to +29
const events = await requestUnreadEvents(filter);
await markCompletedEventsAsRead(events);
setIntervalMultiplier(Math.min(intervalMultiplier + 1, 16));
return events;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I'm wondering if await markCompletedEventsAsRead(events); should be moved into the onSuccess. I think that would be more appropriate because we don't want this query to report an error if markCompletedEventsAsRead throws an error

Copy link
Contributor Author

@hkhalil-akamai hkhalil-akamai May 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was how I did it initially but the onSuccess callback is executed multiple times if there are multiple instances of this hook mounted. Having it in the actual request avoids this. I investigated alternatives to onSuccess that only execute once but I don't think RQ has that option.

Comment on lines +26 to +27
const events = await requestUnreadEvents(filter);
await markCompletedEventsAsRead(events);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Testing on my person account with thousands of unread events caused events to be continuously marked as read every time the internal runs.

This isn't that bad because it does all of this pretty gracefully but I worry we will run into a rate limit or hit the API pretty hard.

I think we talked about this before and a possible solution may be to add a { created: { '+gte': timestampMountedAt } }

Copy link
Contributor Author

@hkhalil-akamai hkhalil-akamai May 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Capturing our discussion here for future reference:

I implemented this idea but @bnussman-akamai pointed out that filtering out events that occur before the hook was mounted would ignore previous in-progress events.

Possible ways to mitigate this include: modifying the API to allow filtering on events' status and percent_complete fields or the updated field.

The current working solution to this is that the useEventsInfiniteQuery hook will report in-progress events to the useEventsPolling hook to poll separately from new events.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes since but I worry about multiple sources of polling. I'm starting to lean towards one more API change to solve the edge case of capturing in-progress events created before mount.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another option is to fix filtering not working when 'read' is used within '+and' and '+or'. Then, we could use a single polling hook and OR the two conditions together -- this would be more in line with what we had before.

hkhalil-akamai and others added 5 commits May 9, 2023 10:37
Co-authored-by: Hussain Khalil <hussain@sanpilot.co>
Co-authored-by: Hussain Khalil <hussain@sanpilot.co>
Co-authored-by: Hussain Khalil <hussain@sanpilot.co>
Co-authored-by: Hussain Khalil <hussain@sanpilot.co>
@hkhalil-akamai hkhalil-akamai changed the title refactor: [M3-5181] - RQ-ify Events refactor: [M3-5181] - RQ-ify Events - Part 1: NotificationMenu, EventsLanding May 10, 2023
@hkhalil-akamai hkhalil-akamai changed the base branch from develop to feature/react-query-events May 10, 2023 22:03
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you're reviewing the diff, I would recommend starting at queries/events.ts to see the new hooks and how they work.

Copy link
Contributor

@dwiley-akamai dwiley-akamai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 🎉

The only issue I'm seeing right now is when you power off a Linode, the percentage starts at 100% before adjusting to 0% when it first begins. I think this popped up on another PR recently but I don't see it in prod or develop, so merging the latest develop into the feature branch after this PR gets merged should probably solve that.

@mjac0bs
Copy link
Contributor

mjac0bs commented May 22, 2023

when you power off a Linode, the percentage starts at 100% before adjusting to 0% when it first begins. I think this popped up on another PR recently

This fix was in the v1.93.1 hotfix PR.

Also fixed in that PR was the regression where clicking on a secondary status (e.g. Migrating (0%)) on Linodes Landing or Linodes Detail didn't open the notification menu. We'll just want to make sure that fix is still looking good once develop is merged into this feature branch.

One thing I noticed on this branch but not prod or develop:

This Branch Prod
Screenshot 2023-05-22 at 12 51 34 PM Screenshot 2023-05-22 at 12 52 02 PM

When a linode is cloned, once the event is complete, the linode we're cloning from has a secondary status of Cloning (100%) that doesn't go away on the Linode Details page. I think this is most likely a bug we introduced and then fixed in the v1.93.0 (re)release process, but I hadn't noticed it before this branch, so wanted to note it.

@hkhalil-akamai hkhalil-akamai requested a review from mjac0bs May 24, 2023 15:20
@hkhalil-akamai
Copy link
Contributor Author

@dwiley-akamai @mjac0bs merging in the latest develop seems to fix most of issues we were seeing.

Copy link
Contributor

@mjac0bs mjac0bs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested notification functionality in various scenarios and pages and observed all the things you noted in the steps to test. Also confirmed that the issues we were seeing before (starting at 100% to power on/off, Cloning stuck at 100%, and the secondary status not opening the menu) were all resolved once develop was merged in.

Looks like you have a few other comments on the code to circle back to, but I'm going to go ahead and approve this. Thanks for your clear PR description, that was great!

showMore={{
target: '/events',
text: 'View all events',
onClick: notificationContext.closeMenu,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call. 👍

},
}),
}));

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was trying to figure out what the inconsistency was here, but couldn't seem to spot it. 🔍 I'll trust that it's fixed and this wasn't meant to address the blue button background in dark mode, which we do still need to fix but have a ticket for.

@hkhalil-akamai hkhalil-akamai added Approved Multiple approvals and ready to merge! and removed Ready for Review labels May 31, 2023
hkhalil-akamai and others added 4 commits May 31, 2023 09:27
* add `fs` import

* use cleaner import

---------

Co-authored-by: Banks Nussman <banks@nussman.us>
* move styles to theme and update stories

* remove incorrect color

---------

Co-authored-by: Banks Nussman <banks@nussman.us>
@bnussman-akamai bnussman-akamai self-requested a review June 1, 2023 14:40
hana-akamai and others added 3 commits June 1, 2023 11:09
## Description 📝
Migrate styles for the Tag component

## How to test 🧪
Confirm that there have been no Tag regressions across the app

Co-authored-by: Jaalah Ramos <jaalah.ramos@gmail.com>
…l, Tags, AddTag` (linode#9186)

## Description 📝
Migrate styles for the TagCell and AddTag component
Update Tags component to match our current patterns

## How to test 🧪
- Check tags around the app for regressions
- Ensure the Tags story renders in Storybook
@hkhalil-akamai
Copy link
Contributor Author

I think I discovered the cause for the failing test ("uploads machine image, mock expired upload event"):

This test intercepts the getEvents call, but since these calls are duplicated for the time being (see here for why), it intercepts the wrong call 50% of the time. I'm going ahead with the merge since this failure should be addressed in part 2 when I remove the previous events polling.

cc @jdamore-linode

@hkhalil-akamai hkhalil-akamai merged commit e5c466d into linode:feature/react-query-events Jun 2, 2023
@hkhalil-akamai hkhalil-akamai deleted the M3-5181-rq-events branch June 9, 2023 21:44
hkhalil-akamai added a commit that referenced this pull request Jul 20, 2023
* refactor: [M3-6570] - RQ-ify Events - Part 1: NotificationMenu, EventsLanding (#9046)

* Initial idea for RQ events

Co-authored-by: Hussain Khalil <hussain@sanpilot.co>

* Initial working version of useEventsPolling hook

Co-authored-by: Hussain Khalil <hussain@sanpilot.co>

* Re-implementation of useEventsPolling hook

Co-authored-by: Hussain Khalil <hussain@sanpilot.co>

* Completed initial implementation of useEvents hook

Co-authored-by: Hussain Khalil <hussain@sanpilot.co>

* Invalidate events cache when new events are fetched

Co-authored-by: Hussain Khalil <hussain@sanpilot.co>

* Use read instead of seen flag for polling

Co-authored-by: Hussain Khalil <hussain@sanpilot.co>

* Implement polling interval

Co-authored-by: Hussain Khalil <hussain@sanpilot.co>

* Switch to RQ hook for notifications menu

Co-authored-by: Hussain Khalil <hussain@sanpilot.co>

* Switch EventsLanding to RQ hook and enhance top-menu

Fixes notification menu button background color disappearing on hover in dark mode

Fix Notification menu doesn't close when navigating to EventsLanding

Co-authored-by: Hussain Khalil <hussain@sanpilot.co>

* Implement markEventsAsSeen mutation

Co-authored-by: Hussain Khalil <hussain@sanpilot.co>

* Temporarily re-enable redux polling

Co-authored-by: Hussain Khalil <hussain@sanpilot.co>

* Track previously existing in-progress events

Co-authored-by: Hussain Khalil <hussain@sanpilot.co>

* Remove old test

Co-authored-by: Hussain Khalil <hussain@sanpilot.co>

* Move NotificationMenu styled component to dedicated file

* Follow style guide in EventsLanding

* Rename TopMenuIconWrapper

* fix: Re-add `fs` import to Cypress config (#9199)

* add `fs` import

* use cleaner import

---------

Co-authored-by: Banks Nussman <banks@nussman.us>

* refactor: [M3-6382] - MUI v5 - `Components > Radio` (#9185)

* move styles to theme and update stories

* remove incorrect color

---------

Co-authored-by: Banks Nussman <banks@nussman.us>

* refactor: [M3-6411] - MUI v5 - `Components > Tag` (#9164)

## Description 📝
Migrate styles for the Tag component

## How to test 🧪
Confirm that there have been no Tag regressions across the app

Co-authored-by: Jaalah Ramos <jaalah.ramos@gmail.com>

* refactor: [M3-6412, M3-6413, M3-6650] - MUI v5 - `Components > TagCell, Tags, AddTag` (#9186)

## Description 📝
Migrate styles for the TagCell and AddTag component
Update Tags component to match our current patterns

## How to test 🧪
- Check tags around the app for regressions
- Ensure the Tags story renders in Storybook

---------

Co-authored-by: Hussain Khalil <hussain@sanpilot.co>
Co-authored-by: Banks Nussman <115251059+bnussman-akamai@users.noreply.github.com>
Co-authored-by: Banks Nussman <banks@nussman.us>
Co-authored-by: Hana Xu <115299789+hana-linode@users.noreply.github.com>
Co-authored-by: Jaalah Ramos <jaalah.ramos@gmail.com>

* Undo no-longer needed changes to top menu icons

* refactor: [M3-6571] - RQ-ify Events - Part 2: Event Handlers (#9293)

* Make polling part of EventsInfiniteQuery

* Move event utilities to utilities folder

* Create events query container

* Migrate App events handlers to new events hook

* Migrate toast notifications to new hook

* Move app event handlers to hook

* Finish migrating event handlers

* Remove events redux store

* Fix failing test

* Fix issue causing multiple pages of events to refresh

* implement page zero for new events

* Optimize/reduce calls to events endpoint

* Implement rolling-time polling

* Fix events being duplicated

* Fix unsupported API filter

* Fix index out of bounds bug

* Fix broken in-progress events polling

* Get rid of separate page zero cache and store new events in first page instead

* Undo accidental changes to cachedData

* remove leftover pageZero stuff

* Fix undefined recent event

* Start from page 1

* Feedback @jaalah-akamai

* New hook for synchronizing polling intervals

* Remove unused exports

* Custom implementation of partition util

* Sort in-progress events to top

* Use events instead of data

* Use React.useMemo

* Fix state accidental deletion

* Clean up useToastNotifications

* Add changesets

* Remove Redux connector in EventsLanding

* Rename to NotificationMenu.styles.ts

* nodebalanacer -> nodebalancer

* Import from src/components instead of MUI

* Replace CombinedProps with EventsLandingProps

---------

Co-authored-by: Hussain Khalil <hussain@sanpilot.co>
Co-authored-by: Banks Nussman <115251059+bnussman-akamai@users.noreply.github.com>
Co-authored-by: Banks Nussman <banks@nussman.us>
Co-authored-by: Hana Xu <115299789+hana-linode@users.noreply.github.com>
Co-authored-by: Jaalah Ramos <jaalah.ramos@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Approved Multiple approvals and ready to merge! React Query Relating to the transition to use React Query

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants