feat: added channels column to send notification on specific channels (#34289)

* feat: added channels column to send notifications on specific channels
This commit is contained in:
Ahtisham Shahid
2024-03-20 13:17:45 +05:00
committed by GitHub
parent cee42fd543
commit c344fdc698
8 changed files with 105 additions and 19 deletions

View File

@@ -2681,7 +2681,7 @@ EDXAPP_PARSE_KEYS = {}
############## NOTIFICATIONS EXPIRY ##############
NOTIFICATIONS_EXPIRY = 60
EXPIRED_NOTIFICATIONS_DELETE_BATCH_SIZE = 10000
NOTIFICATION_CREATION_BATCH_SIZE = 99
NOTIFICATION_CREATION_BATCH_SIZE = 83
############################ AI_TRANSLATIONS ##################################
AI_TRANSLATIONS_API_URL = 'http://localhost:18760/api/v1'

View File

@@ -5390,7 +5390,7 @@ SUBSCRIPTIONS_SERVICE_WORKER_USERNAME = 'subscriptions_worker'
############## NOTIFICATIONS ##############
NOTIFICATIONS_EXPIRY = 60
EXPIRED_NOTIFICATIONS_DELETE_BATCH_SIZE = 10000
NOTIFICATION_CREATION_BATCH_SIZE = 99
NOTIFICATION_CREATION_BATCH_SIZE = 83
NOTIFICATIONS_DEFAULT_FROM_EMAIL = "no-reply@example.com"
NOTIFICATION_TYPE_ICONS = {}
DEFAULT_NOTIFICATION_ICON_URL = ""

View File

@@ -0,0 +1,23 @@
# Generated by Django 4.2.10 on 2024-03-18 12:59
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('notifications', '0004_auto_20230607_0757'),
]
operations = [
migrations.AddField(
model_name='notification',
name='email',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='notification',
name='web',
field=models.BooleanField(default=True),
),
]

View File

@@ -96,6 +96,8 @@ class Notification(TimeStampedModel):
notification_type = models.CharField(max_length=64)
content_context = models.JSONField(default=dict)
content_url = models.URLField(null=True, blank=True)
web = models.BooleanField(default=True, null=False, blank=False)
email = models.BooleanField(default=False, null=False, blank=False)
last_read = models.DateTimeField(null=True, blank=True)
last_seen = models.DateTimeField(null=True, blank=True)
@@ -205,6 +207,27 @@ class CourseNotificationPreference(TimeStampedModel):
return self.get_core_config(app_name).get('web', False)
return self.get_notification_type_config(app_name, notification_type).get('web', False)
def is_enabled_for_any_channel(self, app_name, notification_type) -> bool:
"""
Returns True if the notification type is enabled for any channel.
"""
if self.is_core(app_name, notification_type):
return any(self.get_core_config(app_name).get(channel, False) for channel in NOTIFICATION_CHANNELS)
return any(self.get_notification_type_config(app_name, notification_type).get(channel, False) for channel in
NOTIFICATION_CHANNELS)
def get_channels_for_notification_type(self, app_name, notification_type) -> list:
"""
Returns the channels for the given app name and notification type.
if notification is core then return according to core settings
Sample Response:
['web', 'push']
"""
if self.is_core(app_name, notification_type):
return [channel for channel in NOTIFICATION_CHANNELS if self.get_core_config(app_name).get(channel, False)]
return [channel for channel in NOTIFICATION_CHANNELS if
self.get_notification_type_config(app_name, notification_type).get(channel, False)]
def is_core(self, app_name, notification_type) -> bool:
"""
Returns True if the given notification type is a core notification type.

View File

@@ -24,7 +24,7 @@ from openedx.core.djangoapps.notifications.filters import NotificationFilter
from openedx.core.djangoapps.notifications.models import (
CourseNotificationPreference,
Notification,
get_course_notification_preference_config_version
get_course_notification_preference_config_version,
)
from openedx.core.djangoapps.notifications.utils import get_list_in_batches
@@ -132,11 +132,13 @@ def send_notifications(user_ids, course_key: str, app_name, notification_type, c
for preference in preferences:
user_id = preference.user_id
preference = update_user_preference(preference, user_id, course_key)
if (
preference and
preference.get_web_config(app_name, notification_type) and
preference.is_enabled_for_any_channel(app_name, notification_type) and
preference.get_app_config(app_name).get('enabled', False)
):
notification_preferences = preference.get_channels_for_notification_type(app_name, notification_type)
notifications.append(
Notification(
user_id=user_id,
@@ -145,6 +147,8 @@ def send_notifications(user_ids, course_key: str, app_name, notification_type, c
content_context=context,
content_url=content_url,
course_id=course_key,
web='web' in notification_preferences,
email='email' in notification_preferences,
)
)
generated_notification_audience.append(user_id)
@@ -170,7 +174,7 @@ def is_notification_valid(notification_type, context):
"""
try:
get_notification_content(notification_type, context)
except Exception: # pylint: disable=broad-except
except Exception: # pylint: disable=broad-except
return False
return True

View File

@@ -176,6 +176,8 @@ class SendNotificationsTest(ModuleStoreTestCase):
preference = CourseNotificationPreference.get_user_course_preference(self.user.id, self.course_1.id)
app_prefs = preference.notification_preference_config[app_name]
app_prefs['notification_types']['core']['web'] = False
app_prefs['notification_types']['core']['email'] = False
app_prefs['notification_types']['core']['push'] = False
preference.save()
send_notifications([self.user.id], str(self.course_1.id), app_name, notification_type, context, content_url)

View File

@@ -570,11 +570,10 @@ class UserNotificationChannelPreferenceAPITest(ModuleStoreTestCase):
if expected_status == status.HTTP_200_OK:
expected_data = self._expected_api_response()
expected_app_prefs = expected_data['notification_preference_config'][notification_app]
for notification_type_name, notification_type_preferences in expected_app_prefs[
'notification_types'].items():
non_editable_channels = expected_app_prefs['non_editable'].get(notification_type_name, [])
for notification_type, __ in expected_app_prefs['notification_types'].items():
non_editable_channels = expected_app_prefs['non_editable'].get(notification_type, [])
if notification_channel not in non_editable_channels:
expected_app_prefs['notification_types'][notification_type_name][notification_channel] = value
expected_app_prefs['notification_types'][notification_type][notification_channel] = value
expected_data = remove_notifications_with_visibility_settings(expected_data)
self.assertEqual(response.data, expected_data)
event_name, event_data = mock_emit.call_args[0]
@@ -584,6 +583,7 @@ class UserNotificationChannelPreferenceAPITest(ModuleStoreTestCase):
self.assertEqual(event_data['value'], value)
@ddt.ddt
class NotificationListAPIViewTest(APITestCase):
"""
Tests suit for the NotificationListAPIView.
@@ -663,6 +663,43 @@ class NotificationListAPIViewTest(APITestCase):
'<p><strong>test_user</strong> responded to your post <strong>This is a test post.</strong></p>'
)
@ddt.data(
([], 0),
(['web'], 1),
(['email'], 0),
(['web', 'email'], 1),
(['web', 'email', 'push'], 1),
)
@ddt.unpack
def test_list_notifications_with_channels(self, channels, expected_count):
"""
Test that the view can filter notifications by app name and channels.
"""
Notification.objects.create(
user=self.user,
app_name='discussion',
notification_type='new_response',
content_context={
'replier_name': 'test_user',
'post_title': 'This is a test post.',
},
web='web' in channels,
email='email' in channels
)
self.client.login(username=self.user.username, password=self.TEST_PASSWORD)
# Make a request to the view with the app_name query parameter set to 'app1'.
response = self.client.get(self.url + "?app_name=discussion")
# Assert that the response is successful.
self.assertEqual(response.status_code, 200)
# Assert that the response contains expected results i.e. channels contains web or is null.
data = response.data['results']
self.assertEqual(len(data), expected_count)
@mock.patch("eventtracking.tracker.emit")
def test_list_notifications_with_tray_opened_param(self, mock_emit):
"""

View File

@@ -330,18 +330,15 @@ class NotificationListAPIView(generics.ListAPIView):
if self.request.query_params.get('tray_opened'):
unseen_count = Notification.objects.filter(user_id=self.request.user, last_seen__isnull=True).count()
notification_tray_opened_event(self.request.user, unseen_count)
params = {
'user': self.request.user,
'created__gte': expiry_date,
'web': True
}
if app_name:
return Notification.objects.filter(
user=self.request.user,
app_name=app_name,
created__gte=expiry_date,
).order_by('-id')
else:
return Notification.objects.filter(
user=self.request.user,
created__gte=expiry_date,
).order_by('-id')
params['app_name'] = app_name
return Notification.objects.filter(**params).order_by('-id')
@allow_any_authenticated_user()