Skip to content

Commit

Permalink
feat: add tracking events for notifications app
Browse files Browse the repository at this point in the history
  • Loading branch information
SaadYousaf authored and saadyousafarbi committed Jul 13, 2023
1 parent f2d30d9 commit 51c826f
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 8 deletions.
105 changes: 105 additions & 0 deletions openedx/core/djangoapps/notifications/events.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
""" Events for notification app. """

from eventtracking import tracker
from common.djangoapps.track import contexts


NOTIFICATION_PREFERENCES_VIEWED = 'edx.notifications.preferences.viewed'
NOTIFICATION_GENERATED = 'edx.notifications.generated'
NOTIFICATION_READ = 'edx.notifications.read'
NOTIFICATION_PREFERENCES_UPDATED = 'edx.notifications.preferences.updated'


def get_user_forums_roles(user, course_id):
"""
Get the user's roles in the course forums.
"""
if course_id:
return list(user.roles.filter(course_id=course_id).values_list('name', flat=True))
return []


def get_user_course_roles(user, course_id):
"""
Get the user's roles in the course.
"""
if course_id:
return list(user.courseaccessrole_set.filter(course_id=course_id).values_list('role', flat=True))
return []


def notification_event_context(user, course_id, notification):
return {
'user_id': str(user.id),
'course_id': str(course_id),
'notification_type': notification.notification_type,
'notification_app': notification.app_name,
'notification_metadata': {
'notification_id': notification.id,
'notification_content': notification.content,
},
'user_forum_roles': get_user_forums_roles(user, course_id),
'user_course_roles': get_user_course_roles(user, course_id),
}


def notification_preferences_viewed_event(request, course_id):
"""
Emit an event when a user views their notification preferences.
"""
context = contexts.course_context_from_course_id(course_id)
with tracker.get_tracker().context(NOTIFICATION_PREFERENCES_VIEWED, context):
tracker.emit(
NOTIFICATION_PREFERENCES_VIEWED,
{
'user_id': str(request.user.id),
'course_id': str(course_id),
'user_forum_roles': get_user_forums_roles(request.user, course_id),
'user_course_roles': get_user_course_roles(request.user, course_id),
}
)


def notification_generated_event(user, notification):
"""
Emit an event when a notification is generated.
"""
context = contexts.course_context_from_course_id(notification.course_id)
with tracker.get_tracker().context(NOTIFICATION_GENERATED, context):
tracker.emit(
NOTIFICATION_GENERATED,
notification_event_context(user, notification.course_id, notification)
)


def notification_read_event(user, notification):
"""
Emit an event when a notification is read.
"""
context = contexts.course_context_from_course_id(notification.course_id)
with tracker.get_tracker().context(NOTIFICATION_READ, context):
tracker.emit(
NOTIFICATION_READ,
notification_event_context(user, notification.course_id, notification)
)


def notification_preference_update_event(user, course_id, updated_preference):
"""
Emit an event when a notification preference is updated.
"""
context = contexts.course_context_from_course_id(course_id)
with tracker.get_tracker().context(NOTIFICATION_PREFERENCES_UPDATED, context):
tracker.emit(
NOTIFICATION_PREFERENCES_UPDATED,
{
'user_id': str(user.id),
'course_id': str(course_id),
'user_forum_roles': get_user_forums_roles(user, course_id),
'user_course_roles': get_user_course_roles(user, course_id),
'notification_app': updated_preference.get('notification_app', ''),
'notification_type': updated_preference.get('notification_type', ''),
'notification_channel': updated_preference.get('notification_channel', ''),
'value': updated_preference.get('value', ''),
}
)
7 changes: 5 additions & 2 deletions openedx/core/djangoapps/notifications/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
Notification,
get_course_notification_preference_config_version
)
from openedx.core.djangoapps.notifications.events import notification_generated_event

logger = get_task_logger(__name__)

Expand Down Expand Up @@ -100,14 +101,16 @@ def send_notifications(user_ids, course_key: str, app_name, notification_type, c
for preference in preferences:
preference = update_user_preference(preference, preference.user, course_key)
if preference and preference.get_web_config(app_name, notification_type):
notifications.append(Notification(
notification = Notification(
user_id=preference.user_id,
app_name=app_name,
notification_type=notification_type,
content_context=context,
content_url=content_url,
course_id=course_key,
))
)
notifications.append(notification)
notification_generated_event(preference.user, notification)
# send notification to users but use bulk_create
Notification.objects.bulk_create(notifications)

Expand Down
25 changes: 22 additions & 3 deletions openedx/core/djangoapps/notifications/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"""
import json
from datetime import datetime, timedelta
from unittest import mock

import ddt
from django.conf import settings
Expand Down Expand Up @@ -245,14 +246,17 @@ def test_get_user_notification_preference_without_login(self):
response = self.client.get(self.path)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)

def test_get_user_notification_preference(self):
@mock.patch("eventtracking.tracker.emit")
def test_get_user_notification_preference(self, mock_emit):
"""
Test get user notification preference.
"""
self.client.login(username=self.user.username, password='test')
response = self.client.get(self.path)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, self._expected_api_response())
event_name, event_data = mock_emit.call_args[0]
self.assertEqual(event_name, 'edx.notifications.preferences.viewed')

@ddt.data(
('discussion', None, None, True, status.HTTP_200_OK, 'app_update'),
Expand All @@ -269,8 +273,9 @@ def test_get_user_notification_preference(self):
('discussion', 'new_comment', 'invalid_notification_channel', False, status.HTTP_400_BAD_REQUEST, None),
)
@ddt.unpack
@mock.patch("eventtracking.tracker.emit")
def test_patch_user_notification_preference(
self, notification_app, notification_type, notification_channel, value, expected_status, update_type,
self, notification_app, notification_type, notification_channel, value, expected_status, update_type, mock_emit,
):
"""
Test update of user notification preference.
Expand Down Expand Up @@ -299,6 +304,14 @@ def test_patch_user_notification_preference(
'notification_types'][notification_type][notification_channel] = value
self.assertEqual(response.data, expected_data)

if expected_status == status.HTTP_200_OK:
event_name, event_data = mock_emit.call_args[0]
self.assertEqual(event_name, 'edx.notifications.preferences.updated')
self.assertEqual(event_data['notification_app'], notification_app)
self.assertEqual(event_data['notification_type'], notification_type or '')
self.assertEqual(event_data['notification_channel'], notification_channel or '')
self.assertEqual(event_data['value'], value)


class NotificationListAPIViewTest(APITestCase):
"""
Expand Down Expand Up @@ -591,7 +604,8 @@ def test_mark_all_notifications_read_with_invalid_app_name(self):
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(response.data, {'error': 'Invalid app_name or notification_id.'})

def test_mark_notification_read_with_notification_id(self):
@mock.patch("eventtracking.tracker.emit")
def test_mark_notification_read_with_notification_id(self, mock_emit):
# Create a PATCH request to mark notification as read for notification_id: 2
notification_id = 2
data = {'notification_id': notification_id}
Expand All @@ -602,6 +616,11 @@ def test_mark_notification_read_with_notification_id(self):
self.assertEqual(response.data, {'message': 'Notification marked read.'})
notifications = Notification.objects.filter(user=self.user, id=notification_id, last_read__isnull=False)
self.assertEqual(notifications.count(), 1)
event_name, event_data = mock_emit.call_args[0]
self.assertEqual(event_name, 'edx.notifications.read')
self.assertEqual(event_data.get('notification_metadata').get('notification_id'), notification_id)
self.assertEqual(event_data['notification_app'], 'discussion')
self.assertEqual(event_data['notification_type'], 'Type A')

def test_mark_notification_read_with_other_user_notification_id(self):
# Create a PATCH request to mark notification as read for notification_id: 2 through a different user
Expand Down
10 changes: 7 additions & 3 deletions openedx/core/djangoapps/notifications/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

from .base_notification import COURSE_NOTIFICATION_APPS
from .config.waffle import ENABLE_NOTIFICATIONS, SHOW_NOTIFICATIONS_TRAY
from .events import notification_preferences_viewed_event, notification_read_event, notification_preference_update_event
from .models import Notification
from .serializers import (
NotificationCourseEnrollmentSerializer,
Expand Down Expand Up @@ -163,6 +164,7 @@ def get(self, request, course_key_string):
course_id = CourseKey.from_string(course_key_string)
user_preference = CourseNotificationPreference.get_updated_user_course_preferences(request.user, course_id)
serializer = UserCourseNotificationPreferenceSerializer(user_preference)
notification_preferences_viewed_event(request, course_id)
return Response(serializer.data)

def patch(self, request, course_key_string):
Expand Down Expand Up @@ -191,11 +193,12 @@ def patch(self, request, course_key_string):
status=status.HTTP_409_CONFLICT,
)

preference_update_serializer = UserNotificationPreferenceUpdateSerializer(
preference_update = UserNotificationPreferenceUpdateSerializer(
user_course_notification_preference, data=request.data, partial=True
)
preference_update_serializer.is_valid(raise_exception=True)
updated_notification_preferences = preference_update_serializer.save()
preference_update.is_valid(raise_exception=True)
updated_notification_preferences = preference_update.save()
notification_preference_update_event(request.user, course_id, preference_update.validated_data)
serializer = UserCourseNotificationPreferenceSerializer(updated_notification_preferences)
return Response(serializer.data, status=status.HTTP_200_OK)

Expand Down Expand Up @@ -387,6 +390,7 @@ def patch(self, request, *args, **kwargs):
notification = get_object_or_404(Notification, pk=notification_id, user=request.user)
notification.last_read = read_at
notification.save()
notification_read_event(request.user, notification)
return Response({'message': _('Notification marked read.')}, status=status.HTTP_200_OK)

app_name = request.data.get('app_name', '')
Expand Down

0 comments on commit 51c826f

Please sign in to comment.