Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added notification when response is endorsed or answered #34082

Merged
merged 6 commits into from
Feb 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading