Refreshing records associated with a notification

Introduction

This document describes how to refresh records associated with a notification, as well as the background and design decisions made regarding this functionality. If what you are looking for is how to update the code to handle a new notification type, feel free to jump straight to the “Adding New Record Types for Notifications” section.

Background and Design Decisions

At LiteFarm, resources are often cached in Redux, reducing the number of requests which need to be made to the server. In general, this is good as it reduces the data usage of the app and makes it more responsive. However, it can cause issues with notifications. Notifications keep a HTTP connection open for a long period of time (as long as the user has connectivity), and use it to send Server Sent Events (SSE). This long HTTP connection allows the alert count to update in real time as events trigger notifications for the user, but it also means that the notifications can get out of sync with the records which they are referencing. For example, if the user is on the tasks page currently then LiteFarm will have cached the tasks. If a notification is generated because one of these tasks was reassigned to another user, then when the user clicks on the “Take me there” button in the notification, they will be taken to the task but the task will not have been updated to display the new assignee.

Two approaches were considered to address this issue of records getting out of sync with the notifications:

  1. When the SSE connection indicates that there is a new notification, update the associated resource. Advantages of this approach are that as the user browses the app, they will see the most up to date records for everything associated with notifications. Disadvantages of this approach are that records may update as the user is currently looking at them without any indication to the user why the record updated (e.g. the assignee of a task may change as the user is looking at the task), and this will make significantly more requests which is especially problematic on the slower connections many of LiteFarm’s users are on.

  2. When the user goes to the notifications page, refresh all of the records associated with the current list of notifications. The advantages of this approach are that records are only updated when the user sees the necessary context for why they updated (e.g. Task 1 was reassigned by Alice), and we can batch the requests, reducing the amount of data transferred. The disadvantage is that the user is more likely to be looking at out of date records.

Most resources are retrieved in bulk at LiteFarm, so there are very few API endpoints to retrieve just a single record. For example, to get a task on a farm, at the time of writing this you have to get all of the tasks on that farm. Given this pattern of bulk requests, the second approach made more sense as we are able to dispatch one request to get all the updated records associated with notifications, rather than dispatching bulk get requests every time there is a new notification.

To ensure that the bulk updates are efficient, a set of unique update strings is created and populated as the notifications are looped over to be rendered. Once all of the notifications have been looped over, the elements in this set are then added to an Array (which is a React state variable). This array is treated as a queue of pending updates to be made. Whenever the length of this array is updated, all the updates within the array are dispatched, and then the array is emptied.

For a more detailed look at how this was all implemented, you can see the PR here: https://github.com/LiteFarmOrg/LiteFarm/pull/2258

Adding New Record Types for Notifications

To add a new record type which should be refreshed on a notification, you have to update one to two parts of packages/webapp/src/containers/Notification/index.jsx:

  1. The code has to be able to figure out what type of record needs to be updated from a notification. If the notification is associated with a standard record(e.g. tasks), then this should be stored under notification.ref.entity.type in the notification. In this scenario, nothing needs to be changed. If for some reason the type of record which needs to be updated is not in this form, you need to update the function getUpdateFromNotification to return an appropriate string given the structure of the ref for that notification use case.

  2. The code has to know what redux action (or more generally, any function) to dispatch for each type of update. For tasks, this action is the getTasks() redux action, although this will change for every type of record which needs to be updated. In order to tell the code what type of redux action must be dispatched for a specific update type, all you need to do is update the dispatchUpdate function to handle the updateType. This updateType should be the string returned from getUpdateFromNotification.

If you want to see how these are implemented for task notifications, you can see the PR here: https://github.com/LiteFarmOrg/LiteFarm/pull/2258