Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
5 changes: 4 additions & 1 deletion cms/djangoapps/contentstore/signals/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,10 @@ def listen_for_course_publish(sender, course_key, **kwargs): # pylint: disable=
if CoursewareSearchIndexer.indexing_is_enabled() and CourseAboutSearchIndexer.indexing_is_enabled():
update_search_index.delay(course_key_str, datetime.now(UTC).isoformat())

update_discussions_settings_from_course_task.delay(course_key_str)
update_discussions_settings_from_course_task.apply_async(
args=[course_key_str],
countdown=settings.DISCUSSION_SETTINGS['COURSE_PUBLISH_TASK_DELAY'],
)

# Send to a signal for catalog info changes as well, but only once we know the transaction is committed.
transaction.on_commit(lambda: emit_catalog_info_changed_signal(course_key))
Expand Down
14 changes: 9 additions & 5 deletions lms/djangoapps/discussion/django_comment_client/base/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,8 @@ def call_view(
"group_id": self.student_cohort.id,
"closed": False,
"type": "thread",
"commentable_id": "non_team_dummy_id"
"commentable_id": "non_team_dummy_id",
"body": "test body",
}
)
request = RequestFactory().post("dummy_url", post_params or {})
Expand Down Expand Up @@ -411,7 +412,7 @@ def test_create_thread(self, mock_request):
self.create_thread_helper(mock_request)

@ddt.data(
(ModuleStoreEnum.Type.split, 3, 6, 38),
(ModuleStoreEnum.Type.split, 3, 6, 42),
)
@ddt.unpack
@count_queries
Expand Down Expand Up @@ -527,6 +528,7 @@ def test_delete_thread(self, mock_request):
self._set_mock_request_data(mock_request, {
"user_id": str(self.student.id),
"closed": False,
"body": "test body",
})
test_thread_id = "test_thread_id"
request = RequestFactory().post("dummy_url", {"id": test_thread_id})
Expand All @@ -545,6 +547,7 @@ def test_delete_comment(self, mock_request):
self._set_mock_request_data(mock_request, {
"user_id": str(self.student.id),
"closed": False,
"body": "test body",
})
test_comment_id = "test_comment_id"
request = RequestFactory().post("dummy_url", {"id": test_comment_id})
Expand Down Expand Up @@ -1594,7 +1597,8 @@ def test_delete_comment(self, user, comment_author, commentable_id, status_code,
"commentable_id": commentable_id,
"user_id": str(comment_author.id),
"username": comment_author.username,
"course_id": str(self.course.id)
"course_id": str(self.course.id),
"body": "test body",
})

response = self.client.post(
Expand Down Expand Up @@ -1663,7 +1667,7 @@ def test_comment_actions(self, user, commentable_id, status_code, mock_request):
commentable_id = getattr(self, commentable_id)
self._setup_mock(
user, mock_request,
{"closed": False, "commentable_id": commentable_id, "thread_id": "dummy_thread"},
{"closed": False, "commentable_id": commentable_id, "thread_id": "dummy_thread", "body": 'dummy body'},
)
for action in ["upvote_comment", "downvote_comment", "un_flag_abuse_for_comment", "flag_abuse_for_comment"]:
response = self.client.post(
Expand All @@ -1684,7 +1688,7 @@ def test_threads_actions(self, user, commentable_id, status_code, mock_request):
commentable_id = getattr(self, commentable_id)
self._setup_mock(
user, mock_request,
{"closed": False, "commentable_id": commentable_id},
{"closed": False, "commentable_id": commentable_id, "body": "dummy body"},
)
for action in ["upvote_thread", "downvote_thread", "un_flag_abuse_for_thread", "flag_abuse_for_thread",
"follow_thread", "unfollow_thread"]:
Expand Down
129 changes: 125 additions & 4 deletions lms/djangoapps/discussion/django_comment_client/base/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,17 @@ def track_voted_event(request, course, obj, vote_value, undo_vote=False):
track_forum_event(request, event_name, course, obj, event_data)


def track_forum_search_event(request, course, search_event_data):
"""
Send analytics event for discussions related search.
"""
event_name = 'edx.forum.searched'

context = contexts.course_context_from_course_id(course.id)
with tracker.get_tracker().context(event_name, context):
tracker.emit(event_name, search_event_data)


def track_thread_viewed_event(request, course, thread):
"""
Send analytics event for a viewed thread.
Expand Down Expand Up @@ -263,6 +274,101 @@ def track_comment_deleted_event(request, course, comment):
track_forum_event(request, event_name, course, comment, event_data)


def track_thread_reported_event(request, course, thread):
"""
Send analytics event for a reported thread.
"""
event_name = _EVENT_NAME_TEMPLATE.format(obj_type='thread', action_name='reported')
event_data = {
'body': thread.body[:TRACKING_MAX_FORUM_BODY],
'truncated': len(thread.body) > TRACKING_MAX_FORUM_BODY,
'content_type': 'Post',
'commentable_id': thread.get('commentable_id', ''),
'thread_type': thread.get('thread_type', ''),
'group_id': thread.get('group_id', ''),
}
if hasattr(thread, 'username'):
event_data['target_username'] = thread.get('username', '')
add_truncated_title_to_event_data(event_data, thread.get('title', ''))
track_forum_event(request, event_name, course, thread, event_data)


def track_comment_reported_event(request, course, comment):
"""
Send analytics event for a reported response or comment.
"""
obj_type = 'comment' if comment.get('parent_id') else 'response'
event_name = _EVENT_NAME_TEMPLATE.format(obj_type=obj_type, action_name='reported')
event_data = {
'body': comment.body[:TRACKING_MAX_FORUM_BODY],
'truncated': len(comment.body) > TRACKING_MAX_FORUM_BODY,
'commentable_id': comment.get('commentable_id', ''),
'content_type': obj_type.capitalize(),
}
if hasattr(comment, 'username'):
event_data['target_username'] = comment.get('username', '')
track_forum_event(request, event_name, course, comment, event_data)


def track_thread_unreported_event(request, course, thread):
"""
Send analytics event for a unreported thread.
"""
event_name = _EVENT_NAME_TEMPLATE.format(obj_type='thread', action_name='unreported')
event_data = {
'body': thread.body[:TRACKING_MAX_FORUM_BODY],
'truncated': len(thread.body) > TRACKING_MAX_FORUM_BODY,
'content_type': 'Post',
'commentable_id': thread.get('commentable_id', ''),
'reported_status_cleared': not bool(thread.get('abuse_flaggers', [])),
'thread_type': thread.get('thread_type', ''),
'group_id': thread.get('group_id', ''),

}
if hasattr(thread, 'username'):
event_data['target_username'] = thread.get('username', '')
add_truncated_title_to_event_data(event_data, thread.get('title', ''))
track_forum_event(request, event_name, course, thread, event_data)


def track_comment_unreported_event(request, course, comment):
"""
Send analytics event for a unreported response or comment.
"""
obj_type = 'comment' if comment.get('parent_id') else 'response'
event_name = _EVENT_NAME_TEMPLATE.format(obj_type=obj_type, action_name='unreported')
event_data = {
'body': comment.body[:TRACKING_MAX_FORUM_BODY],
'truncated': len(comment.body) > TRACKING_MAX_FORUM_BODY,
'commentable_id': comment.get('commentable_id', ''),
'content_type': obj_type.capitalize(),
'reported_status_cleared': not bool(comment.get('abuse_flaggers', [])),
}
if hasattr(comment, 'username'):
event_data['target_username'] = comment.get('username', '')
track_forum_event(request, event_name, course, comment, event_data)


def track_discussion_reported_event(request, course, cc_content):
"""
Helper method for discussion reported events.
"""
if cc_content.type == 'thread':
track_thread_reported_event(request, course, cc_content)
else:
track_comment_reported_event(request, course, cc_content)


def track_discussion_unreported_event(request, course, cc_content):
"""
Helper method for discussion unreported events.
"""
if cc_content.type == 'thread':
track_thread_unreported_event(request, course, cc_content)
else:
track_comment_unreported_event(request, course, cc_content)


def permitted(func):
"""
View decorator to verify the user is authorized to access this endpoint.
Expand Down Expand Up @@ -418,11 +524,11 @@ def update_thread(request, course_id, thread_id):
user = request.user
# The following checks should avoid issues we've seen during deploys, where end users are hitting an updated server
# while their browser still has the old client code. This will avoid erasing present values in those cases.
course = get_course_with_access(user, 'load', course_key)
if "thread_type" in request.POST:
thread.thread_type = request.POST["thread_type"]
if "commentable_id" in request.POST:
commentable_id = request.POST["commentable_id"]
course = get_course_with_access(user, 'load', course_key)
if thread_context == "course" and not discussion_category_id_access(course, user, commentable_id):
return JsonError(_("Topic doesn't exist"))
else:
Expand All @@ -432,6 +538,7 @@ def update_thread(request, course_id, thread_id):

thread_edited.send(sender=None, user=user, post=thread)

track_thread_edited_event(request, course, thread, None)
if request.is_ajax():
return ajax_content_response(request, course_key, thread.to_dict())
else:
Expand Down Expand Up @@ -510,9 +617,12 @@ def delete_thread(request, course_id, thread_id):
this is ajax only
"""
course_key = CourseKey.from_string(course_id)
course = get_course_with_access(request.user, 'load', course_key)
thread = cc.Thread.find(thread_id)
thread.delete()
thread_deleted.send(sender=None, user=request.user, post=thread)

track_thread_deleted_event(request, course, thread)
return JsonResponse(prepare_content(thread.to_dict(), course_key))


Expand All @@ -525,6 +635,7 @@ def update_comment(request, course_id, comment_id):
handles static and ajax submissions
"""
course_key = CourseKey.from_string(course_id)
course = get_course_with_access(request.user, 'load', course_key)
comment = cc.Comment.find(comment_id)
if 'body' not in request.POST or not request.POST['body'].strip():
return JsonError(_("Body can't be empty"))
Expand All @@ -533,6 +644,7 @@ def update_comment(request, course_id, comment_id):

comment_edited.send(sender=None, user=request.user, post=comment)

track_comment_edited_event(request, course, comment, None)
if request.is_ajax():
return ajax_content_response(request, course_key, comment.to_dict())
else:
Expand Down Expand Up @@ -566,10 +678,13 @@ def openclose_thread(request, course_id, thread_id):
ajax only
"""
course_key = CourseKey.from_string(course_id)
course = get_course_with_access(request.user, 'load', course_key)
thread = cc.Thread.find(thread_id)
thread.closed = request.POST.get('closed', 'false').lower() == 'true'
close_thread = request.POST.get('closed', 'false').lower() == 'true'
thread.closed = close_thread
thread.save()

track_thread_lock_unlock_event(request, course, thread, None, close_thread)
return JsonResponse({
'content': prepare_content(thread.to_dict(), course_key),
'ability': get_ability(course_key, thread.to_dict(), request.user),
Expand Down Expand Up @@ -598,9 +713,11 @@ def delete_comment(request, course_id, comment_id):
ajax only
"""
course_key = CourseKey.from_string(course_id)
course = get_course_with_access(request.user, 'load', course_key)
comment = cc.Comment.find(comment_id)
comment.delete()
comment_deleted.send(sender=None, user=request.user, post=comment)
track_comment_deleted_event(request, course, comment)
return JsonResponse(prepare_content(comment.to_dict(), course_key))


Expand Down Expand Up @@ -681,9 +798,10 @@ def flag_abuse_for_thread(request, course_id, thread_id):
"""
course_key = CourseKey.from_string(course_id)
user = cc.User.from_django_user(request.user)
course = get_course_by_id(course_key)
thread = cc.Thread.find(thread_id)
thread.flagAbuse(user, thread)

track_discussion_reported_event(request, course, thread)
return JsonResponse(prepare_content(thread.to_dict(), course_key))


Expand All @@ -704,7 +822,7 @@ def un_flag_abuse_for_thread(request, course_id, thread_id):
has_access(request.user, 'staff', course)
)
thread.unFlagAbuse(user, thread, remove_all)

track_discussion_unreported_event(request, course, thread)
return JsonResponse(prepare_content(thread.to_dict(), course_key))


Expand All @@ -718,8 +836,10 @@ def flag_abuse_for_comment(request, course_id, comment_id):
"""
course_key = CourseKey.from_string(course_id)
user = cc.User.from_django_user(request.user)
course = get_course_by_id(course_key)
comment = cc.Comment.find(comment_id)
comment.flagAbuse(user, comment)
track_discussion_reported_event(request, course, comment)
return JsonResponse(prepare_content(comment.to_dict(), course_key))


Expand All @@ -740,6 +860,7 @@ def un_flag_abuse_for_comment(request, course_id, comment_id):
)
comment = cc.Comment.find(comment_id)
comment.unFlagAbuse(user, comment, remove_all)
track_discussion_unreported_event(request, course, comment)
return JsonResponse(prepare_content(comment.to_dict(), course_key))


Expand Down
Loading