chore: rebase

This commit is contained in:
knguyen2
2024-02-01 16:07:12 +00:00
8 changed files with 191 additions and 8 deletions

View File

@@ -235,6 +235,22 @@ class DiscussionNotificationSender:
}
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

View File

@@ -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()

View File

@@ -13,7 +13,10 @@ from openedx_events.learning.signals import USER_NOTIFICATION_REQUESTED, COURSE_
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
@@ -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
@@ -478,6 +482,7 @@ class TestSendCommentNotification(DiscussionAPIViewTestMixin, ModuleStoreTestCas
"""
Test case to send new_comment notification
"""
def setUp(self):
super().setUp()
httpretty.reset()
@@ -527,3 +532,92 @@ class TestSendCommentNotification(DiscussionAPIViewTestMixin, ModuleStoreTestCas
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)

View File

@@ -15,7 +15,11 @@ from lms.djangoapps.discussion.toggles import ENABLE_REPORTED_CONTENT_NOTIFICATI
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
@@ -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])

View File

@@ -320,8 +320,11 @@ class TeamSignalsTest(EventTestMixin, SharedModuleStoreTestCase):
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):
@@ -339,5 +342,8 @@ class TeamSignalsTest(EventTestMixin, SharedModuleStoreTestCase):
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'))

View File

@@ -132,6 +132,34 @@ COURSE_NOTIFICATION_TYPES = {
},
'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 = {

View File

@@ -21,7 +21,7 @@ log = logging.getLogger(__name__)
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():

View File

@@ -236,7 +236,9 @@ class UserNotificationPreferenceAPITest(ModuleStoreTestCase):
'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': {