Skip to content

Commit

Permalink
feat: added notifications when response is endorsed or answered (#34082)
Browse files Browse the repository at this point in the history
* feat: added notification when response is endorsed or answered

* test: added and fixed test cases

* fix: fixed lint errors

* refactor: changed method name for readibility

* feat: added notification when my response is endorsed

* test: fixed failed test cases
  • Loading branch information
ayesha-waris authored Feb 1, 2024
1 parent abb4b0e commit 9f136a4
Show file tree
Hide file tree
Showing 8 changed files with 191 additions and 8 deletions.
16 changes: 16 additions & 0 deletions lms/djangoapps/discussion/rest_api/discussions_notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,22 @@ def _create_cohort_course_audience(self):
}
return {}

def send_response_endorsed_on_thread_notification(self):
"""
Sends a notification to the author of the thread
response on his thread has been endorsed
"""
context = {
"username": self.creator.username,
}
self._send_notification([self.thread.user_id], "response_endorsed_on_thread", context)

def send_response_endorsed_notification(self):
"""
Sends a notification to the author of the response
"""
self._send_notification([self.creator.id], "response_endorsed")

def send_new_thread_created_notification(self):
"""
Send notification based on notification_type
Expand Down
19 changes: 19 additions & 0 deletions lms/djangoapps/discussion/rest_api/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,22 @@ def send_response_notifications(thread_id, course_key_str, user_id, parent_id=No
notification_sender.send_new_response_notification()
notification_sender.send_new_comment_on_response_notification()
notification_sender.send_response_on_followed_post_notification()


@shared_task
@set_code_owner_attribute
def send_response_endorsed_notifications(thread_id, course_key_str, comment_author_id):
"""
Send notifications when a response is marked answered/ endorsed
"""
course_key = CourseKey.from_string(course_key_str)
if not ENABLE_NOTIFICATIONS.is_enabled(course_key):
return
thread = Thread(id=thread_id).retrieve()
comment_author = User.objects.get(id=comment_author_id)
course = get_course_with_access(comment_author, 'load', course_key, check_if_enrolled=True)
notification_sender = DiscussionNotificationSender(thread, course, comment_author)
#sends notification to author of thread
notification_sender.send_response_endorsed_on_thread_notification()
#sends notification to author of response
notification_sender.send_response_endorsed_notification()
96 changes: 95 additions & 1 deletion lms/djangoapps/discussion/rest_api/tests/test_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@
from common.djangoapps.student.models import CourseEnrollment
from common.djangoapps.student.tests.factories import StaffFactory, UserFactory
from lms.djangoapps.discussion.django_comment_client.tests.factories import RoleFactory
from lms.djangoapps.discussion.rest_api.tasks import send_response_notifications, send_thread_created_notification
from lms.djangoapps.discussion.rest_api.tasks import (
send_response_notifications,
send_thread_created_notification,
send_response_endorsed_notifications)
from lms.djangoapps.discussion.rest_api.tests.utils import ThreadMock, make_minimal_cs_thread
from openedx.core.djangoapps.course_groups.models import CohortMembership, CourseCohortsSettings
from openedx.core.djangoapps.course_groups.tests.helpers import CohortFactory
Expand Down Expand Up @@ -49,6 +52,7 @@ class TestNewThreadCreatedNotification(DiscussionAPIViewTestMixin, ModuleStoreTe
"""
Test cases related to new_discussion_post and new_question_post notification types
"""

def setUp(self):
"""
Setup test case
Expand Down Expand Up @@ -478,6 +482,7 @@ class TestSendCommentNotification(DiscussionAPIViewTestMixin, ModuleStoreTestCas
"""
Test case to send new_comment notification
"""

def setUp(self):
super().setUp()
httpretty.reset()
Expand Down Expand Up @@ -527,3 +532,92 @@ def test_new_comment_notification(self):
handler.assert_called_once()
context = handler.call_args[1]['notification_data'].context
self.assertEqual(context['author_name'], 'their')


@override_waffle_flag(ENABLE_NOTIFICATIONS, active=True)
class TestResponseEndorsedNotifications(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
"""
Test case to send response endorsed notifications
"""

def setUp(self):
super().setUp()
httpretty.reset()
httpretty.enable()

self.course = CourseFactory.create()
self.user_1 = UserFactory.create()
CourseEnrollment.enroll(self.user_1, self.course.id)
self.user_2 = UserFactory.create()
CourseEnrollment.enroll(self.user_2, self.course.id)

def test_basic(self):
"""
Left empty intentionally. This test case is inherited from DiscussionAPIViewTestMixin
"""

def test_not_authenticated(self):
"""
Left empty intentionally. This test case is inherited from DiscussionAPIViewTestMixin
"""

def test_response_endorsed_notifications(self):
"""
Tests nresponse endorsed notifications
"""

thread = ThreadMock(thread_id=1, creator=self.user_1, title='test thread')
response = ThreadMock(thread_id=2, creator=self.user_2, title='test response')
self.register_get_thread_response({
'id': thread.id,
'course_id': str(self.course.id),
'topic_id': 'abc',
"user_id": thread.user_id,
"username": thread.username,
"thread_type": 'discussion',
"title": thread.title,
})
self.register_get_comment_response({
'id': response.id,
'thread_id': thread.id,
'user_id': response.user_id
})
handler = mock.Mock()
USER_NOTIFICATION_REQUESTED.connect(handler)
send_response_endorsed_notifications(thread.id, str(self.course.id), self.user_2.id)
self.assertEqual(handler.call_count, 2)

#Test response endorsed on thread notification
notification_data = handler.call_args_list[0][1]['notification_data']
# Target only the thread author
self.assertEqual([int(user_id) for user_id in notification_data.user_ids], [int(thread.user_id)])
self.assertEqual(notification_data.notification_type, 'response_endorsed_on_thread')

expected_context = {
'replier_name': response.username,
'post_title': 'test thread',
'course_name': self.course.display_name,
'sender_id': int(response.user_id),
'username': response.username,
}
self.assertDictEqual(notification_data.context, expected_context)
self.assertEqual(notification_data.content_url, _get_mfe_url(self.course.id, thread.id))
self.assertEqual(notification_data.app_name, 'discussion')
self.assertEqual('response_endorsed_on_thread', notification_data.notification_type)

#Test response endorsed notification
notification_data = handler.call_args_list[1][1]['notification_data']
# Target only the response author
self.assertEqual([int(user_id) for user_id in notification_data.user_ids], [int(response.user_id)])
self.assertEqual(notification_data.notification_type, 'response_endorsed')

expected_context = {
'replier_name': response.username,
'post_title': 'test thread',
'course_name': self.course.display_name,
'sender_id': int(response.user_id),
}
self.assertDictEqual(notification_data.context, expected_context)
self.assertEqual(notification_data.content_url, _get_mfe_url(self.course.id, thread.id))
self.assertEqual(notification_data.app_name, 'discussion')
self.assertEqual('response_endorsed', notification_data.notification_type)
20 changes: 19 additions & 1 deletion lms/djangoapps/discussion/signals/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@
from xmodule.modulestore.django import SignalHandler, modulestore

from lms.djangoapps.discussion import tasks
from lms.djangoapps.discussion.rest_api.tasks import send_response_notifications, send_thread_created_notification
from lms.djangoapps.discussion.rest_api.tasks import (
send_response_notifications,
send_thread_created_notification,
send_response_endorsed_notifications
)
from openedx.core.djangoapps.django_comment_common import signals
from openedx.core.djangoapps.site_configuration.models import SiteConfiguration
from openedx.core.djangoapps.theming.helpers import get_current_site
Expand Down Expand Up @@ -178,3 +182,17 @@ def create_comment_created_notification(*args, **kwargs):
parent_id = comment.attributes['parent_id']
course_key_str = comment.attributes['course_id']
send_response_notifications.apply_async(args=[thread_id, course_key_str, user.id, parent_id])


@receiver(signals.comment_endorsed)
def create_response_endorsed_on_thread_notification(*args, **kwargs):
"""
Creates a notification for thread author when response on thread is endorsed
and another notification for response author when response is endorsed
"""
comment = kwargs['post']
comment_author_id = comment.attributes['user_id']
thread_id = comment.attributes['thread_id']
course_key_str = comment.attributes['course_id']

send_response_endorsed_notifications.apply_async(args=[thread_id, course_key_str, comment_author_id])
14 changes: 10 additions & 4 deletions lms/djangoapps/teams/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,8 +320,11 @@ def test_signals(self, signal_name, user_should_update):
user = getattr(self, user)
with patch('lms.djangoapps.discussion.rest_api.tasks.send_response_notifications.apply_async'):
with patch('lms.djangoapps.discussion.rest_api.tasks.send_thread_created_notification.apply_async'):
signal = self.SIGNALS[signal_name]
signal.send(sender=None, user=user, post=self.mock_comment())
with patch(
'lms.djangoapps.discussion.rest_api.tasks.send_response_endorsed_notifications.apply_async'
):
signal = self.SIGNALS[signal_name]
signal.send(sender=None, user=user, post=self.mock_comment())

@ddt.data('thread_voted', 'comment_voted')
def test_vote_others_post(self, signal_name):
Expand All @@ -339,5 +342,8 @@ def test_signals_course_context(self, signal_name):
with self.assert_last_activity_updated(False):
with patch('lms.djangoapps.discussion.rest_api.tasks.send_response_notifications.apply_async'):
with patch('lms.djangoapps.discussion.rest_api.tasks.send_thread_created_notification.apply_async'):
signal = self.SIGNALS[signal_name]
signal.send(sender=None, user=self.user, post=self.mock_comment(context='course'))
with patch(
'lms.djangoapps.discussion.rest_api.tasks.send_response_endorsed_notifications.apply_async'
):
signal = self.SIGNALS[signal_name]
signal.send(sender=None, user=self.user, post=self.mock_comment(context='course'))
28 changes: 28 additions & 0 deletions openedx/core/djangoapps/notifications/base_notification.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,34 @@
},
'email_template': '',
},
'response_endorsed_on_thread': {
'notification_app': 'discussion',
'name': 'response_endorsed_on_thread',
'is_core': True,
'info': '',
'non_editable': [],
'content_template': _('<{p}><{strong}>{username}</{strong}> response has been endorsed in your post '
'<{strong}>{post_title}</{strong}></{p}>'),
'content_context': {
'post_title': 'Post title',
'username': 'Response author name',
},
'email_template': '',
'filters': [FILTER_AUDIT_EXPIRED_USERS_WITH_NO_ROLE]
},
'response_endorsed': {
'notification_app': 'discussion',
'name': 'response_endorsed',
'is_core': True,
'info': '',
'non_editable': [],
'content_template': _('<{p}><Your response has been endorsed <{strong}>{post_title}</{strong}></{p}>'),
'content_context': {
'post_title': 'Post title',
},
'email_template': '',
'filters': [FILTER_AUDIT_EXPIRED_USERS_WITH_NO_ROLE]
}
}

COURSE_NOTIFICATION_APPS = {
Expand Down
2 changes: 1 addition & 1 deletion openedx/core/djangoapps/notifications/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
NOTIFICATION_CHANNELS = ['web', 'push', 'email']

# Update this version when there is a change to any course specific notification type or app.
COURSE_NOTIFICATION_CONFIG_VERSION = 5
COURSE_NOTIFICATION_CONFIG_VERSION = 6


def get_course_notification_preference_config():
Expand Down
4 changes: 3 additions & 1 deletion openedx/core/djangoapps/notifications/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,9 @@ def _expected_api_response(self, course=None):
'new_comment',
'new_response',
'response_on_followed_post',
'comment_on_followed_post'
'comment_on_followed_post',
'response_endorsed_on_thread',
'response_endorsed'
],
'notification_types': {
'core': {
Expand Down

0 comments on commit 9f136a4

Please sign in to comment.