From 4f8a99805dc0dc901fdb9fba0a095b3fa2707153 Mon Sep 17 00:00:00 2001 From: Ahtisham Shahid Date: Mon, 22 Apr 2024 18:14:26 +0500 Subject: [PATCH] feat: added new ora notification (#34464) --- .../notifications/base_notification.py | 29 +++++++++ .../djangoapps/notifications/config/waffle.py | 11 ++++ .../core/djangoapps/notifications/handlers.py | 7 ++- .../core/djangoapps/notifications/models.py | 2 +- .../notifications/tests/test_views.py | 60 +++++++++++++++++-- .../core/djangoapps/notifications/utils.py | 39 +++++++----- 6 files changed, 124 insertions(+), 24 deletions(-) diff --git a/openedx/core/djangoapps/notifications/base_notification.py b/openedx/core/djangoapps/notifications/base_notification.py index 4384a2e20779..3f080aab8136 100644 --- a/openedx/core/djangoapps/notifications/base_notification.py +++ b/openedx/core/djangoapps/notifications/base_notification.py @@ -4,6 +4,7 @@ from django.utils.translation import gettext_lazy as _ from .email_notifications import EmailCadence +from common.djangoapps.student.roles import CourseInstructorRole, CourseStaffRole from .utils import find_app_in_normalized_apps, find_pref_in_normalized_prefs from ..django_comment_common.models import FORUM_ROLE_ADMINISTRATOR, FORUM_ROLE_MODERATOR, FORUM_ROLE_COMMUNITY_TA @@ -185,6 +186,25 @@ 'email_template': '', 'filters': [FILTER_AUDIT_EXPIRED_USERS_WITH_NO_ROLE] }, + 'ora_staff_notification': { + 'notification_app': 'ora', + 'name': 'ora_staff_notification', + 'is_core': False, + 'info': '', + 'web': False, + 'email': False, + 'push': False, + 'email_cadence': EmailCadence.DAILY, + 'non_editable': [], + 'content_template': _('<{p}>You have a new open response submission awaiting for review for : ' + '<{strong}>{ora_name}'), + 'content_context': { + 'ora_name': 'Name of ORA in course', + }, + 'email_template': '', + 'filters': [FILTER_AUDIT_EXPIRED_USERS_WITH_NO_ROLE], + 'visible_to': [CourseStaffRole.ROLE, CourseInstructorRole.ROLE] + }, } COURSE_NOTIFICATION_APPS = { @@ -207,6 +227,15 @@ 'core_email_cadence': EmailCadence.DAILY, 'non_editable': [] }, + 'ora': { + 'enabled': True, + 'core_info': _('Notifications for Open response submissions.'), + 'core_web': True, + 'core_email': True, + 'core_push': True, + 'core_email_cadence': EmailCadence.DAILY, + 'non_editable': [] + }, } diff --git a/openedx/core/djangoapps/notifications/config/waffle.py b/openedx/core/djangoapps/notifications/config/waffle.py index 51c4df6e402b..9d54a0abb8fd 100644 --- a/openedx/core/djangoapps/notifications/config/waffle.py +++ b/openedx/core/djangoapps/notifications/config/waffle.py @@ -47,3 +47,14 @@ # .. toggle_target_removal_date: 2024-06-01 # .. toggle_tickets: INF-1145 ENABLE_COURSEWIDE_NOTIFICATIONS = CourseWaffleFlag(f"{WAFFLE_NAMESPACE}.enable_coursewide_notifications", __name__) + + +# .. toggle_name: notifications.enable_ora_staff_notifications +# .. toggle_implementation: CourseWaffleFlag +# .. toggle_default: False +# .. toggle_description: Waffle flag to enable ORA staff notifications +# .. toggle_use_cases: temporary, open_edx +# .. toggle_creation_date: 2024-04-04 +# .. toggle_target_removal_date: 2024-06-04 +# .. toggle_tickets: INF-1304 +ENABLE_ORA_STAFF_NOTIFICATION = CourseWaffleFlag(f"{WAFFLE_NAMESPACE}.enable_ora_staff_notifications", __name__) diff --git a/openedx/core/djangoapps/notifications/handlers.py b/openedx/core/djangoapps/notifications/handlers.py index dcd5f1ec1ee9..ec56558cc549 100644 --- a/openedx/core/djangoapps/notifications/handlers.py +++ b/openedx/core/djangoapps/notifications/handlers.py @@ -21,7 +21,7 @@ CohortAudienceFilter, CourseRoleAudienceFilter, ) -from openedx.core.djangoapps.notifications.config.waffle import ENABLE_NOTIFICATIONS +from openedx.core.djangoapps.notifications.config.waffle import ENABLE_NOTIFICATIONS, ENABLE_ORA_STAFF_NOTIFICATION from openedx.core.djangoapps.notifications.models import CourseNotificationPreference log = logging.getLogger(__name__) @@ -108,6 +108,11 @@ def generate_course_notifications(signal, sender, course_notification_data, meta """ Watches for COURSE_NOTIFICATION_REQUESTED signal and calls send_notifications task """ + if ( + course_notification_data.notification_type == 'ora_staff_notification' + and not ENABLE_ORA_STAFF_NOTIFICATION.is_enabled(course_notification_data['course_key']) + ): + return from openedx.core.djangoapps.notifications.tasks import send_notifications course_notification_data = course_notification_data.__dict__ diff --git a/openedx/core/djangoapps/notifications/models.py b/openedx/core/djangoapps/notifications/models.py index 111397a513f9..dea3169ef497 100644 --- a/openedx/core/djangoapps/notifications/models.py +++ b/openedx/core/djangoapps/notifications/models.py @@ -23,7 +23,7 @@ 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 = 8 +COURSE_NOTIFICATION_CONFIG_VERSION = 9 def get_course_notification_preference_config(): diff --git a/openedx/core/djangoapps/notifications/tests/test_views.py b/openedx/core/djangoapps/notifications/tests/test_views.py index a34408ed75f2..8dcb335f5fe5 100644 --- a/openedx/core/djangoapps/notifications/tests/test_views.py +++ b/openedx/core/djangoapps/notifications/tests/test_views.py @@ -16,6 +16,7 @@ from rest_framework.test import APIClient, APITestCase from common.djangoapps.student.models import CourseEnrollment +from common.djangoapps.student.roles import CourseStaffRole from common.djangoapps.student.tests.factories import UserFactory from lms.djangoapps.discussion.django_comment_client.tests.factories import RoleFactory from lms.djangoapps.discussion.toggles import ENABLE_REPORTED_CONTENT_NOTIFICATIONS @@ -296,6 +297,27 @@ def _expected_api_response(self, course=None): } }, 'non_editable': {} + }, + 'ora': { + 'enabled': True, + 'core_notification_types': [], + 'notification_types': { + 'ora_staff_notification': { + 'web': False, + 'email': False, + 'push': False, + 'email_cadence': 'Daily', + 'info': '' + }, + 'core': { + 'web': True, + 'email': True, + 'push': True, + 'email_cadence': 'Daily', + 'info': 'Notifications for Open response submissions.' + } + }, + 'non_editable': {} } } } @@ -349,6 +371,8 @@ def test_get_user_notification_preference_with_visibility_settings(self, role, m """ Test get user notification preference. """ + if role: + CourseStaffRole(self.course.id).add_users(self.user) self.client.login(username=self.user.username, password=self.TEST_PASSWORD) role_instance = None @@ -576,6 +600,27 @@ def _expected_api_response(self, course=None): } }, 'non_editable': {} + }, + 'ora': { + 'enabled': True, + 'core_notification_types': [], + 'notification_types': { + 'ora_staff_notification': { + 'web': False, + 'email': False, + 'push': False, + 'email_cadence': 'Daily', + 'info': '' + }, + 'core': { + 'web': True, + 'email': True, + 'push': True, + 'email_cadence': 'Daily', + 'info': 'Notifications for Open response submissions.' + } + }, + 'non_editable': {} } } } @@ -881,7 +926,7 @@ def test_get_unseen_notifications_count_with_show_notifications_tray(self, show_ 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}) + 'App Name 1': 2, 'App Name 2': 1, 'App Name 3': 1, 'discussion': 0, 'updates': 0, 'ora': 0}) self.assertEqual(response.data['show_notifications_tray'], show_notifications_tray_enabled) def test_get_unseen_notifications_count_for_unauthenticated_user(self): @@ -902,7 +947,7 @@ def test_get_unseen_notifications_count_for_user_with_no_notifications(self): self.assertEqual(response.status_code, 200) self.assertEqual(response.data['count'], 0) - self.assertEqual(response.data['count_by_app_name'], {'discussion': 0, 'updates': 0}) + self.assertEqual(response.data['count_by_app_name'], {'discussion': 0, 'updates': 0, 'ora': 0}) def test_get_expiry_days_in_count_view(self): """ @@ -1065,8 +1110,11 @@ def remove_notifications_with_visibility_settings(expected_response): Remove notifications with visibility settings from the expected response. """ not_visible = get_notification_types_with_visibility_settings() - for notification_type, visibility_settings in not_visible.items(): - expected_response['notification_preference_config']['discussion']['notification_types'].pop( - notification_type - ) + for expected_response_app in expected_response['notification_preference_config']: + for notification_type, visibility_settings in not_visible.items(): + types = expected_response['notification_preference_config'][expected_response_app]['notification_types'] + if notification_type in types: + expected_response['notification_preference_config'][expected_response_app]['notification_types'].pop( + notification_type + ) return expected_response diff --git a/openedx/core/djangoapps/notifications/utils.py b/openedx/core/djangoapps/notifications/utils.py index e1e25901e134..5ce592bb413d 100644 --- a/openedx/core/djangoapps/notifications/utils.py +++ b/openedx/core/djangoapps/notifications/utils.py @@ -3,7 +3,7 @@ """ from typing import Dict, List -from common.djangoapps.student.models import CourseEnrollment +from common.djangoapps.student.models import CourseEnrollment, CourseAccessRole from lms.djangoapps.discussion.toggles import ENABLE_REPORTED_CONTENT_NOTIFICATIONS from openedx.core.djangoapps.django_comment_common.models import Role from openedx.core.lib.cache_utils import request_cached @@ -110,7 +110,8 @@ def get_notification_types_with_visibility_settings() -> Dict[str, List[str]]: def filter_out_visible_notifications( user_preferences: dict, notifications_with_visibility: Dict[str, List[str]], - user_forum_roles: List[str] + user_forum_roles: List[str], + user_course_roles: List[str] ) -> dict: """ Filter out notifications visible to forum roles from user preferences. @@ -119,21 +120,22 @@ def filter_out_visible_notifications( :param notifications_with_visibility: List of dictionaries with notification type names and corresponding visibility settings :param user_forum_roles: List of forum roles for the user + :param user_course_roles: List of course roles for the user :return: Updated user preferences dictionary """ - discussion_user_preferences = user_preferences.get('discussion', {}) - if 'notification_types' in discussion_user_preferences: - # Iterate over the types to remove and pop them from the dictionary - for notification_type, is_visible_to in notifications_with_visibility.items(): - is_visible = False - for role in is_visible_to: - if role in user_forum_roles: - is_visible = True - break - if is_visible: - continue - - discussion_user_preferences['notification_types'].pop(notification_type) + for user_preferences_app, app_config in user_preferences.items(): + if 'notification_types' in app_config: + # Iterate over the types to remove and pop them from the dictionary + for notification_type, is_visible_to in notifications_with_visibility.items(): + is_visible = False + for role in is_visible_to: + if role in user_forum_roles or role in user_course_roles: + is_visible = True + break + if is_visible: + continue + if notification_type in user_preferences[user_preferences_app]['notification_types']: + user_preferences[user_preferences_app]['notification_types'].pop(notification_type) return user_preferences @@ -148,10 +150,15 @@ def remove_preferences_with_no_access(preferences: dict, user) -> dict: user_preferences = preferences['notification_preference_config'] user_forum_roles = get_user_forum_roles(user.id, preferences['course_id']) notifications_with_visibility_settings = get_notification_types_with_visibility_settings() + user_course_roles = CourseAccessRole.objects.filter( + user=user, + course_id=preferences['course_id'] + ).values_list('role', flat=True) preferences['notification_preference_config'] = filter_out_visible_notifications( user_preferences, notifications_with_visibility_settings, - user_forum_roles + user_forum_roles, + user_course_roles ) return preferences