feat: add tracking events for notifications app
This commit is contained in:
105
openedx/core/djangoapps/notifications/events.py
Normal file
105
openedx/core/djangoapps/notifications/events.py
Normal file
@@ -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', ''),
|
||||
}
|
||||
)
|
||||
@@ -19,6 +19,7 @@ from openedx.core.djangoapps.notifications.models import (
|
||||
Notification,
|
||||
get_course_notification_preference_config_version
|
||||
)
|
||||
from openedx.core.djangoapps.notifications.events import notification_generated_event
|
||||
|
||||
logger = get_task_logger(__name__)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ Tests for the views in the notifications app.
|
||||
"""
|
||||
import json
|
||||
from datetime import datetime, timedelta
|
||||
from unittest import mock
|
||||
|
||||
import ddt
|
||||
from django.conf import settings
|
||||
@@ -245,7 +246,8 @@ class UserNotificationPreferenceAPITest(ModuleStoreTestCase):
|
||||
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.
|
||||
"""
|
||||
@@ -253,6 +255,8 @@ class UserNotificationPreferenceAPITest(ModuleStoreTestCase):
|
||||
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'),
|
||||
@@ -269,8 +273,9 @@ class UserNotificationPreferenceAPITest(ModuleStoreTestCase):
|
||||
('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.
|
||||
@@ -299,6 +304,14 @@ class UserNotificationPreferenceAPITest(ModuleStoreTestCase):
|
||||
'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):
|
||||
"""
|
||||
@@ -591,7 +604,8 @@ class NotificationReadAPIViewTestCase(APITestCase):
|
||||
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}
|
||||
@@ -602,6 +616,11 @@ class NotificationReadAPIViewTestCase(APITestCase):
|
||||
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
|
||||
|
||||
@@ -22,6 +22,7 @@ from openedx.core.djangoapps.notifications.models import (
|
||||
|
||||
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,
|
||||
@@ -163,6 +164,7 @@ class UserNotificationPreferenceView(APIView):
|
||||
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):
|
||||
@@ -191,11 +193,12 @@ class UserNotificationPreferenceView(APIView):
|
||||
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)
|
||||
|
||||
@@ -387,6 +390,7 @@ class NotificationReadAPIView(APIView):
|
||||
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', '')
|
||||
|
||||
Reference in New Issue
Block a user