feat: Added audit access expiry soon notification (#36414)

* feat: Added audit access expiry soon notification
This commit is contained in:
jawad khan
2025-07-21 14:01:11 +05:00
committed by GitHub
parent 989ecfe5a0
commit 692caf0f46
6 changed files with 179 additions and 3 deletions

View File

@@ -0,0 +1,36 @@
"""
Enrollment notifications sender util.
"""
from django.conf import settings
from openedx_events.learning.data import UserNotificationData
from openedx_events.learning.signals import USER_NOTIFICATION_REQUESTED
class EnrollmentNotificationSender:
"""
Class to send notifications to user about their enrollments.
"""
def __init__(self, course, user_id, audit_access_expiry):
self.course = course
self.user_id = user_id
self.audit_access_expiry = audit_access_expiry
def send_audit_access_expiring_soon_notification(self):
"""
Send audit access expiring soon notification to user
"""
notification_data = UserNotificationData(
user_ids=[int(self.user_id)],
context={
'course': self.course.name,
'audit_access_expiry': self.audit_access_expiry,
},
notification_type='audit_access_expiring_soon',
content_url=f"{settings.LEARNING_MICROFRONTEND_URL}/course/{str(self.course.id)}/home",
app_name="enrollments",
course_key=self.course.id,
)
USER_NOTIFICATION_REQUESTED.send_event(notification_data=notification_data)

View File

@@ -0,0 +1,49 @@
"""
Unit tests for the EnrollmentsNotificationSender class
"""
import unittest
import datetime
from unittest.mock import MagicMock, patch
from django.conf import settings
import pytest
from openedx.core.djangoapps.enrollments.enrollments_notifications import EnrollmentNotificationSender
from openedx_events.learning.data import UserNotificationData
@pytest.mark.django_db
class TestEnrollmentsNotificationSender(unittest.TestCase):
"""
Tests for the EnrollmentsNotificationSender class
"""
def setUp(self):
self.course = MagicMock()
self.course.name = "test course"
self.course.id = 1
self.expiry_date = datetime.date.today() + datetime.timedelta(days=1)
self.user_id = '123'
self.notification_sender = EnrollmentNotificationSender(self.course, self.user_id, self.expiry_date)
@patch('openedx.core.djangoapps.enrollments.enrollments_notifications.USER_NOTIFICATION_REQUESTED.send_event')
def test_send_audit_access_expiring_soon_notification(self, mock_send_notification):
"""
Test that audit access expiring soon notification event is sent with correct parameters.
"""
self.notification_sender.send_audit_access_expiring_soon_notification()
mock_send_notification.assert_called_once()
notification_data = UserNotificationData(
user_ids=[int(self.user_id)],
context={
'course': self.course.name,
'audit_access_expiry': self.expiry_date,
},
notification_type='audit_access_expiring_soon',
content_url=f"{settings.LEARNING_MICROFRONTEND_URL}/course/{str(self.course.id)}/home",
app_name="enrollments",
course_key=self.course.id,
)
mock_send_notification.assert_called_with(notification_data=notification_data)

View File

@@ -248,6 +248,26 @@ COURSE_NOTIFICATION_TYPES = {
'email_template': '',
'filters': [FILTER_AUDIT_EXPIRED_USERS_WITH_NO_ROLE]
},
'audit_access_expiring_soon': {
'notification_app': 'enrollments',
'name': 'audit_access_expiring_soon',
'is_core': False,
'info': '',
'web': True,
'email': False,
'email_cadence': EmailCadence.DAILY,
'push': False,
'non_editable': [],
'content_template': _('<{p}>Your audit access for <{strong}>{course_name}</{strong}> is expiring on '
'<{strong}>{audit_access_expiry}</{strong}>. '
'Upgrade now to extend access and get a certificate!.</{p}>'),
'content_context': {
'course_name': 'Course name',
'audit_access_expiry': 'Audit access expiry date',
},
'email_template': '',
'filters': [FILTER_AUDIT_EXPIRED_USERS_WITH_NO_ROLE],
},
}
COURSE_NOTIFICATION_APPS = {
@@ -279,6 +299,15 @@ COURSE_NOTIFICATION_APPS = {
'core_email_cadence': EmailCadence.DAILY,
'non_editable': []
},
'enrollments': {
'enabled': True,
'core_info': _('Notifications for enrollments.'),
'core_web': True,
'core_email': True,
'core_push': True,
'core_email_cadence': EmailCadence.DAILY,
'non_editable': []
}
}

View File

@@ -26,7 +26,7 @@ NOTIFICATION_CHANNELS = ['web', 'push', 'email']
ADDITIONAL_NOTIFICATION_CHANNEL_SETTINGS = ['email_cadence']
# Update this version when there is a change to any course specific notification type or app.
COURSE_NOTIFICATION_CONFIG_VERSION = 14
COURSE_NOTIFICATION_CONFIG_VERSION = 15
def get_course_notification_preference_config():

View File

@@ -25,6 +25,7 @@ def send_ace_msg_to_push_channel(audience_ids, notification_object):
notification_type = notification_object.notification_type
post_data = {
'notification_id': notification_object.id,
'notification_type': notification_type,
'course_id': str(notification_object.course_id),
'content_url': notification_object.content_url,

View File

@@ -399,6 +399,27 @@ class UserNotificationPreferenceAPITest(ModuleStoreTestCase):
'non_editable': {
'ora_grade_assigned': ['push']
}
},
"enrollments": {
"enabled": True,
"core_notification_types": [],
"notification_types": {
"audit_access_expiring_soon": {
"web": True,
"email": False,
"push": False,
"email_cadence": "Daily",
"info": ""
},
"core": {
"web": True,
"email": True,
"push": True,
"email_cadence": "Daily",
"info": "Notifications for enrollments."
}
},
"non_editable": {}
}
}
}
@@ -824,7 +845,8 @@ class NotificationCountViewSetTestCase(ModuleStoreTestCase):
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data['count'], 4)
self.assertEqual(response.data['count_by_app_name'], {
'App Name 1': 2, 'App Name 2': 1, 'App Name 3': 1, 'discussion': 0, 'updates': 0, 'grading': 0})
'App Name 1': 2, 'App Name 2': 1, 'App Name 3': 1, 'discussion': 0,
'updates': 0, 'grading': 0, 'enrollments': 0})
self.assertEqual(response.data['show_notifications_tray'], True)
def test_get_unseen_notifications_count_for_unauthenticated_user(self):
@@ -845,7 +867,8 @@ class NotificationCountViewSetTestCase(ModuleStoreTestCase):
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data['count'], 0)
self.assertEqual(response.data['count_by_app_name'], {'discussion': 0, 'updates': 0, 'grading': 0})
self.assertEqual(response.data['count_by_app_name'], {'discussion': 0, 'updates': 0,
'grading': 0, 'enrollments': 0})
def test_get_expiry_days_in_count_view(self):
"""
@@ -1539,6 +1562,25 @@ class TestNotificationPreferencesView(APITestCase):
"ora_grade_assigned": ["push"],
"ora_staff_notifications": ["push"]
}
},
"enrollments": {
"enabled": True,
"core_notification_types": [],
"notification_types": {
"audit_access_expiring_soon": {
"web": True,
"email": False,
"push": False,
"email_cadence": "Daily"
},
"core": {
"web": True,
"email": True,
"push": True,
"email_cadence": "Daily"
}
},
"non_editable": {}
}
}
}
@@ -1674,6 +1716,25 @@ class TestNotificationPreferencesView(APITestCase):
"ora_grade_assigned": ["push"],
"ora_staff_notifications": ["push"]
}
},
"enrollments": {
"enabled": True,
"core_notification_types": [],
"notification_types": {
"audit_access_expiring_soon": {
"web": False,
"email": False,
"push": False,
"email_cadence": "Daily"
},
"core": {
"web": True,
"email": True,
"push": True,
"email_cadence": "Daily"
}
},
"non_editable": {}
}
}
}