diff --git a/lms/djangoapps/discussion/django_comment_client/base/tests.py b/lms/djangoapps/discussion/django_comment_client/base/tests.py index d450998a60c6..6ad1c8bb4a9a 100644 --- a/lms/djangoapps/discussion/django_comment_client/base/tests.py +++ b/lms/djangoapps/discussion/django_comment_client/base/tests.py @@ -1859,6 +1859,34 @@ def test_thread_voted_event(self, view_name, obj_id_name, obj_type, mock_request assert event['undo_vote'] == undo assert event['vote_value'] == 'up' + @ddt.data('follow_thread', 'unfollow_thread',) + @patch('eventtracking.tracker.emit') + @patch('openedx.core.djangoapps.django_comment_common.comment_client.utils.requests.request', autospec=True) + def test_thread_followed_event(self, view_name, mock_request, mock_emit): + self._set_mock_request_data(mock_request, { + 'closed': False, + 'commentable_id': 'test_commentable_id', + 'username': 'test_user', + }) + request = RequestFactory().post('dummy_url', {}) + request.user = self.student + request.view_name = view_name + view_function = getattr(views, view_name) + kwargs = dict(course_id=str(self.course.id)) + kwargs['thread_id'] = 'thread_id' + view_function(request, **kwargs) + + assert mock_emit.called + event_name, event_data = mock_emit.call_args[0] + action_name = 'followed' if view_name == 'follow_thread' else 'unfollowed' + expected_action_value = True if view_name == 'follow_thread' else False + assert event_name == f'edx.forum.thread.{action_name}' + assert event_data['commentable_id'] == 'test_commentable_id' + assert event_data['id'] == 'thread_id' + assert event_data['followed'] == expected_action_value + assert event_data['user_forums_roles'] == ['Student'] + assert event_data['user_course_roles'] == ['Wizard'] + class UsersEndpointTestCase(ForumsEnableMixin, SharedModuleStoreTestCase, MockRequestSetupMixin): diff --git a/lms/djangoapps/discussion/django_comment_client/base/views.py b/lms/djangoapps/discussion/django_comment_client/base/views.py index 5ad3dae8992b..1ec41e31551e 100644 --- a/lms/djangoapps/discussion/django_comment_client/base/views.py +++ b/lms/djangoapps/discussion/django_comment_client/base/views.py @@ -134,6 +134,20 @@ def track_thread_created_event(request, course, thread, followed, from_mfe_sideb track_created_event(request, event_name, course, thread, event_data) +def track_thread_followed_event(request, course, thread, followed): + """ + Send analytics event for a newly followed/unfollowed thread. + """ + action_name = 'followed' if followed else 'unfollowed' + event_name = _EVENT_NAME_TEMPLATE.format(obj_type='thread', action_name=action_name) + event_data = { + 'commentable_id': thread.commentable_id, + 'id': thread.id, + 'followed': followed, + } + track_forum_event(request, event_name, course, thread, event_data) + + def track_comment_created_event(request, course, comment, commentable_id, followed, from_mfe_sidebar=False): """ Send analytics event for a newly created response or comment. @@ -938,9 +952,12 @@ def un_pin_thread(request, course_id, thread_id): @permitted def follow_thread(request, course_id, thread_id): # lint-amnesty, pylint: disable=missing-function-docstring, unused-argument user = cc.User.from_django_user(request.user) + course_key = CourseKey.from_string(course_id) + course = get_course_by_id(course_key) thread = cc.Thread.find(thread_id) user.follow(thread) thread_followed.send(sender=None, user=request.user, post=thread) + track_thread_followed_event(request, course, thread, True) return JsonResponse({}) @@ -966,10 +983,13 @@ def unfollow_thread(request, course_id, thread_id): # lint-amnesty, pylint: dis given a course id and thread id, stop following this thread ajax only """ + course_key = CourseKey.from_string(course_id) + course = get_course_by_id(course_key) user = cc.User.from_django_user(request.user) thread = cc.Thread.find(thread_id) user.unfollow(thread) thread_unfollowed.send(sender=None, user=request.user, post=thread) + track_thread_followed_event(request, course, thread, False) return JsonResponse({}) diff --git a/lms/djangoapps/discussion/rest_api/api.py b/lms/djangoapps/discussion/rest_api/api.py index f79cd663718b..54878d69ca37 100644 --- a/lms/djangoapps/discussion/rest_api/api.py +++ b/lms/djangoapps/discussion/rest_api/api.py @@ -93,7 +93,7 @@ track_voted_event, track_discussion_reported_event, track_discussion_unreported_event, - track_forum_search_event + track_forum_search_event, track_thread_followed_event ) from ..django_comment_client.utils import ( get_group_id_for_user, @@ -1333,7 +1333,7 @@ def _do_extra_actions(api_content, cc_content, request_fields, actions_form, con if field in request_fields and field in api_content and form_value != api_content[field]: api_content[field] = form_value if field == "following": - _handle_following_field(form_value, context["cc_requester"], cc_content) + _handle_following_field(form_value, context["cc_requester"], cc_content, request) elif field == "abuse_flagged": _handle_abuse_flagged_field(form_value, context["cc_requester"], cc_content, request) elif field == "voted": @@ -1346,12 +1346,15 @@ def _do_extra_actions(api_content, cc_content, request_fields, actions_form, con raise ValidationError({field: ["Invalid Key"]}) -def _handle_following_field(form_value, user, cc_content): +def _handle_following_field(form_value, user, cc_content, request): """follow/unfollow thread for the user""" + course_key = CourseKey.from_string(cc_content.course_id) + course = get_course_with_access(request.user, 'load', course_key) if form_value: user.follow(cc_content) else: user.unfollow(cc_content) + track_thread_followed_event(request, course, cc_content, form_value) def _handle_abuse_flagged_field(form_value, user, cc_content, request): diff --git a/lms/djangoapps/discussion/rest_api/tests/test_api.py b/lms/djangoapps/discussion/rest_api/tests/test_api.py index 1193384bfbd0..98c27feb58c1 100644 --- a/lms/djangoapps/discussion/rest_api/tests/test_api.py +++ b/lms/djangoapps/discussion/rest_api/tests/test_api.py @@ -2709,7 +2709,8 @@ def test_author_only_fields(self, role_name): @ddt.data(*itertools.product([True, False], [True, False])) @ddt.unpack - def test_following(self, old_following, new_following): + @mock.patch("eventtracking.tracker.emit") + def test_following(self, old_following, new_following, mock_emit): """ Test attempts to edit the "following" field. @@ -2739,6 +2740,13 @@ def test_following(self, old_following, new_following): ) request_data.pop("request_id", None) assert request_data == {'source_type': ['thread'], 'source_id': ['test_thread']} + event_name, event_data = mock_emit.call_args[0] + expected_event_action = 'followed' if new_following else 'unfollowed' + assert event_name == f'edx.forum.thread.{expected_event_action}' + assert event_data['commentable_id'] == 'original_topic' + assert event_data['id'] == 'test_thread' + assert event_data['followed'] == new_following + assert event_data['user_forums_roles'] == ['Student'] @ddt.data(*itertools.product([True, False], [True, False])) @ddt.unpack