Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

We have added a new notifications django app in openedx which will contain all the code related to notifications. 

Notification workflow

In the diagram below the green color represents actions that are going to occur outside the notifications system. The blue color indicates the actions that will occur inside the notifications systemIntroduction:
The Notifications App plays a critical role in our Learning Platform by keeping users informed about relevant activities in real-time. The App operates based on several key concepts including "Notification Type," "Notification Content," and "Notification Preference". Each Notification Type corresponds to a unique kind of notification, such as "New response on your post", and has a unique ID. The Notification Content defines how a specific type of notification is structured, while Notification Preference holds user-specific preferences for each type of notification.

Workflow:
Our platform apps, like "Discussions", interact with the Notifications App by emitting signals (using open edX events. These signals carry crucial information for generating notifications:

  1. The intended Notification Type.

  2. The list of users who should receive the notification.

  3. The context for the notification (e.g., "username", "post_title", "course_id").

The Notifications App is listening for these signals from the platform. Upon receiving a signal, the app follows a sequence of steps to process it and generate the relevant notifications:

  1. It identifies the Notification Type associated with the received signal.

  2. It fetches the preferences for that Notification Type for all users specified in the signal.

  3. It shortlists users who have opted in to receive this type of notification, according to their preferences.

  4. It uses the corresponding Notification Content and the context provided in the signal to create the notification message.

  5. It then dispatches the generated notification to all the shortlisted users.

As for the flow chart, I can provide a textual description of what it might look like:

  1. Start -> Discussions App emits a signal.

  2. Discussions App signal contains (Notification Type, Users, Context) -> Received by Notifications App.

  3. Notifications App identifies Notification Type.

  4. Fetches Notification Preferences for all listed users.

  5. Shortlists users who have opted in for this Notification Type.

  6. Creates the notification message using the Notification Content and provided Context.

  7. Dispatches the notification to the shortlisted users.

  8. End

Diagram

...

Notification Preferences

We want the users to be able to control the notifications they want to receive. The notification preferences will be created on a per course basis. The notification preferences database architecture is as follows. The notification preferences should be aware of all the notifications that are intended to be sent. So all new notifications must be added to the notification preferences.

...

Code Block
languagepy
from django.contrib.auth.models import User
from django.db import models
from jsonfield import JSONField

NOTIFICATION_CONFIG_VERSION = 1

DEFAULT_NOTIFICATION_CHANNEL_CONFIG = {
      "web": False,
      "push": False,
      "email": False,
}

COURSE_NOTIFICATION_CONFIG = {
  "discussion": {
    "new_post": DEFAULT_NOTIFICATION_CHANNEL_CONFIG,
    "new_comment": DEFAULT_NOTIFICATION_CHANNEL_CONFIG,
  },

  "coursework": {
    "new_assignment": DEFAULT_NOTIFICATION_CHANNEL_CONFIG,
    "new_grade": DEFAULT_NOTIFICATION_CHANNEL_CONFIG,
  }
}

# Non Course Notification Types (Assumption: Only email)
DEFAULT_NON_COURSE_NOTIFICATION_CONFIG = {
    "marketing": {
        "recommendation": True,
        "course_updates": True,
    },
}

class NotificationPreferences(models.Model):
    """
    A model to store user notification preferences.
    """
    user = models.ForeignKey(  # example: User1
        User,
        related_name="notification_preferences",
        on_delete=models.CASCADE,
        db_index=True,
        help_text="User whose notification preferences are being stored.",
    )
    course = models.CharField(    # example: "course-v1:edX+DemoX+Demo_Course"
        max_length=255,
        blank=True,
        default=None,
        null=True,
        help_text="Course whose notification preferences are being stored.",
    )
    notification_config = JSONField(default=COURSE_NOTIFICATION_CONFIG)
    preferences_version = models.BooleanField(default=1)

...

  1. When the user accesses the preferences page. In this case if the notification preferences version is out of date it will automatically be synchronized synchronised with the latest version.

  2. If the user is intended to receive a notification for which the  user notification preferences do not exist yet the notification preferences will be updated to the latest version. For example if the user is intended to receive a notification for a New Forum post but this notification type was added later on and the user preferences version does not contain this type of notification. The notification preferences version will be updated with the default values and the notification will be sent accordingly. 

  3. A user un enrols from a course the notification preferences for that course will be marked as in-active.

Notifications

The database model schema for a notification (In progress)

Code Block
languagepy
class NotificationApplication(models.TextChoices):
    DISCUSSION = 'DISCUSSION'

class NotificationTypes(models.TextChoices):
    NEW_POST = 'NEW_POST',
    NEW_RESPONSE = 'NEW_RESPONSE'

class NotificationTypeContent(models.TextChoices):
    NEW_POST: 'You have received a new response on your post {response_text}'

class Notification(TimeStampedModel):
    app_name = models.CharField(max_length=64, choices=NotificationApplication.choices)
    notification_type = models.CharField(max_length=64, choices=NotificationTypes.choices)
    # e.g "You have received a new response on your post {response_text}"
    # this content will be rendered with the given context and also translated
    content = models.CharField(max_length=1024, choices=NotificationTypeContent.choices)
    # e.g {"response_text": "This is a response"}
    content_context = models.JSONField(default={})
    # This is the url that the user will be redirected to when clicking on the notification [Optional]
    content_url = models.CharField(max_length=1024, null=True, blank=True)
    # the user foreign key will be indexed
    user = models.ForeignKey(User, related_name="notifications", on_delete=models.CASCADE)
    read = models.BooleanField(default=False)
    seen = models.BooleanField(default=False)

...

Notifications Expiry: Since notifications are only useful for the user for a short period of time it makes sense to delete notifications older than a particular date. The industry standard here is 2-3 months e.g Facebook only keeps notifications that are 2 months old. This will also solve the problem of the notifications table becoming too big in size and making queries slow. We are recording the created date for a notification and we can create a script that deletes notifications older than a particular date. The final call on the expiry date will be made by Product.We intend to create a generic method that will be used by all other apps to create notifications. 

Monitoring and tracking events

...

All of the above implementation is behind a course waffle flag. 

...