Commit cf5d276a by Sven Marnach

Emit events when users vote on forum posts.

parent 3f76da0b
......@@ -25,13 +25,7 @@ from discussion_api.permissions import (
get_initializable_thread_fields,
)
from discussion_api.serializers import CommentSerializer, ThreadSerializer, get_context
from django_comment_client.base.views import (
THREAD_CREATED_EVENT_NAME,
get_comment_created_event_data,
get_comment_created_event_name,
get_thread_created_event_data,
track_forum_event,
)
from django_comment_client.base.views import track_comment_created_event, track_thread_created_event
from django_comment_common.signals import (
thread_created,
thread_edited,
......@@ -566,13 +560,7 @@ def create_thread(request, thread_data):
api_thread = serializer.data
_do_extra_actions(api_thread, cc_thread, thread_data.keys(), actions_form, context)
track_forum_event(
request,
THREAD_CREATED_EVENT_NAME,
course,
cc_thread,
get_thread_created_event_data(cc_thread, followed=actions_form.cleaned_data["following"])
)
track_thread_created_event(request, course, cc_thread, actions_form.cleaned_data["following"])
return api_thread
......@@ -616,13 +604,7 @@ def create_comment(request, comment_data):
api_comment = serializer.data
_do_extra_actions(api_comment, cc_comment, comment_data.keys(), actions_form, context)
track_forum_event(
request,
get_comment_created_event_name(cc_comment),
context["course"],
cc_comment,
get_comment_created_event_data(cc_comment, cc_thread["commentable_id"], followed=False)
)
track_comment_created_event(request, context["course"], cc_comment, cc_thread["commentable_id"], followed=False)
return api_comment
......
......@@ -49,40 +49,7 @@ import lms.lib.comment_client as cc
log = logging.getLogger(__name__)
TRACKING_MAX_FORUM_BODY = 2000
THREAD_CREATED_EVENT_NAME = "edx.forum.thread.created"
RESPONSE_CREATED_EVENT_NAME = 'edx.forum.response.created'
COMMENT_CREATED_EVENT_NAME = 'edx.forum.comment.created'
def permitted(fn):
@functools.wraps(fn)
def wrapper(request, *args, **kwargs):
def fetch_content():
if "thread_id" in kwargs:
content = cc.Thread.find(kwargs["thread_id"]).to_dict()
elif "comment_id" in kwargs:
content = cc.Comment.find(kwargs["comment_id"]).to_dict()
elif "commentable_id" in kwargs:
content = cc.Commentable.find(kwargs["commentable_id"]).to_dict()
else:
content = None
return content
course_key = SlashSeparatedCourseKey.from_deprecated_string(kwargs['course_id'])
if check_permissions_by_view(request.user, course_key, fetch_content(), request.view_name):
return fn(request, *args, **kwargs)
else:
return JsonError("unauthorized", status=401)
return wrapper
def ajax_content_response(request, course_key, content):
user_info = cc.User.from_django_user(request.user).to_dict()
annotated_content_info = get_annotated_content_info(course_key, content, request.user, user_info)
return JsonResponse({
'content': prepare_content(content, course_key),
'annotated_content_info': annotated_content_info,
})
_EVENT_NAME_TEMPLATE = 'edx.forum.{obj_type}.{action_name}'
def track_forum_event(request, event_name, course, obj, data, id_map=None):
......@@ -100,16 +67,9 @@ def track_forum_event(request, event_name, course, obj, data, id_map=None):
if id_map is None:
id_map = get_cached_discussion_id_map(course, [commentable_id], user)
if commentable_id in id_map:
data['category_name'] = id_map[commentable_id]["title"]
data['category_id'] = commentable_id
if len(obj.body) > TRACKING_MAX_FORUM_BODY:
data['truncated'] = True
else:
data['truncated'] = False
data['body'] = obj.body[:TRACKING_MAX_FORUM_BODY]
data['url'] = request.META.get('HTTP_REFERER', '')
data['user_forums_roles'] = [
role.name for role in user.roles.filter(course_id=course.id)
......@@ -121,12 +81,18 @@ def track_forum_event(request, event_name, course, obj, data, id_map=None):
tracker.emit(event_name, data)
def get_thread_created_event_data(thread, followed):
"""
Get the event data payload for thread creation (excluding fields populated
by track_forum_event)
"""
return {
def track_created_event(request, event_name, course, obj, data):
if len(obj.body) > TRACKING_MAX_FORUM_BODY:
data['truncated'] = True
else:
data['truncated'] = False
data['body'] = obj.body[:TRACKING_MAX_FORUM_BODY]
track_forum_event(request, event_name, course, obj, data)
def track_thread_created_event(request, course, thread, followed):
event_name = _EVENT_NAME_TEMPLATE.format(obj_type='thread', action_name='created')
event_data = {
'commentable_id': thread.commentable_id,
'group_id': thread.get("group_id"),
'thread_type': thread.thread_type,
......@@ -139,29 +105,66 @@ def get_thread_created_event_data(thread, followed):
# However, the view does not contain that data, and including it will
# likely require changes elsewhere.
}
track_created_event(request, event_name, course, thread, event_data)
def get_comment_created_event_name(comment):
"""Get the appropriate event name for creating a response/comment"""
return COMMENT_CREATED_EVENT_NAME if comment.get("parent_id") else RESPONSE_CREATED_EVENT_NAME
def get_comment_created_event_data(comment, commentable_id, followed):
"""
Get the event data payload for comment creation (excluding fields populated
by track_forum_event)
"""
def track_comment_created_event(request, course, comment, commentable_id, followed):
obj_type = 'comment' if comment.get("parent_id") else 'response'
event_name = _EVENT_NAME_TEMPLATE.format(obj_type=obj_type, action_name='created')
event_data = {
'discussion': {'id': comment.thread_id},
'commentable_id': commentable_id,
'options': {'followed': followed},
}
parent_id = comment.get("parent_id")
parent_id = comment.get('parent_id')
if parent_id:
event_data['response'] = {'id': parent_id}
track_created_event(request, event_name, course, comment, event_data)
def track_voted_event(request, course, obj, vote_value, undo_vote=False):
if isinstance(obj, cc.Thread):
obj_type = 'thread'
else:
obj_type = 'response'
event_name = _EVENT_NAME_TEMPLATE.format(obj_type=obj_type, action_name='voted')
event_data = {
'commentable_id': obj.commentable_id,
'target_username': obj.get('username'),
'undo_vote': undo_vote,
'vote_value': vote_value,
}
track_forum_event(request, event_name, course, obj, event_data)
def permitted(fn):
@functools.wraps(fn)
def wrapper(request, *args, **kwargs):
def fetch_content():
if "thread_id" in kwargs:
content = cc.Thread.find(kwargs["thread_id"]).to_dict()
elif "comment_id" in kwargs:
content = cc.Comment.find(kwargs["comment_id"]).to_dict()
elif "commentable_id" in kwargs:
content = cc.Commentable.find(kwargs["commentable_id"]).to_dict()
else:
content = None
return content
course_key = SlashSeparatedCourseKey.from_deprecated_string(kwargs['course_id'])
if check_permissions_by_view(request.user, course_key, fetch_content(), request.view_name):
return fn(request, *args, **kwargs)
else:
return JsonError("unauthorized", status=401)
return wrapper
return event_data
def ajax_content_response(request, course_key, content):
user_info = cc.User.from_django_user(request.user).to_dict()
annotated_content_info = get_annotated_content_info(course_key, content, request.user, user_info)
return JsonResponse({
'content': prepare_content(content, course_key),
'annotated_content_info': annotated_content_info,
})
@require_POST
......@@ -234,12 +237,11 @@ def create_thread(request, course_id, commentable_id):
cc_user = cc.User.from_django_user(user)
cc_user.follow(thread)
event_data = get_thread_created_event_data(thread, follow)
data = thread.to_dict()
add_courseware_context([data], course, user)
track_forum_event(request, THREAD_CREATED_EVENT_NAME, course, thread, event_data)
track_thread_created_event(request, course, thread, follow)
if request.is_ajax():
return ajax_content_response(request, course_key, data)
......@@ -330,9 +332,7 @@ def _create_comment(request, course_key, thread_id=None, parent_id=None):
cc_user = cc.User.from_django_user(request.user)
cc_user.follow(comment.thread)
event_name = get_comment_created_event_name(comment)
event_data = get_comment_created_event_data(comment, comment.thread.commentable_id, followed)
track_forum_event(request, event_name, course, comment, event_data)
track_comment_created_event(request, course, comment, comment.thread.commentable_id, followed)
if request.is_ajax():
return ajax_content_response(request, course_key, comment.to_dict())
......@@ -456,6 +456,24 @@ def delete_comment(request, course_id, comment_id):
return JsonResponse(prepare_content(comment.to_dict(), course_key))
def _vote_or_unvote(request, course_id, obj, value='up', undo_vote=False):
"""
Vote or unvote for a thread or a response.
"""
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
course = get_course_with_access(request.user, 'load', course_key)
user = cc.User.from_django_user(request.user)
if undo_vote:
user.unvote(obj)
# TODO(smarnach): Determine the value of the vote that is undone. Currently, you can
# only cast upvotes in the user interface, so it is assumed that the vote value is 'up'.
# (People could theoretically downvote by handcrafting AJAX requests.)
else:
user.vote(obj, value)
track_voted_event(request, course, obj, value, undo_vote)
return JsonResponse(prepare_content(obj.to_dict(), course_key))
@require_POST
@login_required
@permitted
......@@ -463,13 +481,10 @@ def vote_for_comment(request, course_id, comment_id, value):
"""
given a course_id and comment_id,
"""
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
user = request.user
cc_user = cc.User.from_django_user(user)
comment = cc.Comment.find(comment_id)
cc_user.vote(comment, value)
comment_voted.send(sender=None, user=user, post=comment)
return JsonResponse(prepare_content(comment.to_dict(), course_key))
result = _vote_or_unvote(request, course_id, comment, value)
comment_voted.send(sender=None, user=request.user, post=comment)
return result
@require_POST
......@@ -480,11 +495,7 @@ def undo_vote_for_comment(request, course_id, comment_id):
given a course id and comment id, remove vote
ajax only
"""
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
user = cc.User.from_django_user(request.user)
comment = cc.Comment.find(comment_id)
user.unvote(comment)
return JsonResponse(prepare_content(comment.to_dict(), course_key))
return _vote_or_unvote(request, course_id, cc.Comment.find(comment_id), undo_vote=True)
@require_POST
......@@ -495,13 +506,21 @@ def vote_for_thread(request, course_id, thread_id, value):
given a course id and thread id vote for this thread
ajax only
"""
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
user = request.user
cc_user = cc.User.from_django_user(user)
thread = cc.Thread.find(thread_id)
cc_user.vote(thread, value)
thread_voted.send(sender=None, user=user, post=thread)
return JsonResponse(prepare_content(thread.to_dict(), course_key))
result = _vote_or_unvote(request, course_id, thread, value)
thread_voted.send(sender=None, user=request.user, post=thread)
return result
@require_POST
@login_required
@permitted
def undo_vote_for_thread(request, course_id, thread_id):
"""
given a course id and thread id, remove users vote for thread
ajax only
"""
return _vote_or_unvote(request, course_id, cc.Thread.find(thread_id), undo_vote=True)
@require_POST
......@@ -579,22 +598,6 @@ def un_flag_abuse_for_comment(request, course_id, comment_id):
@require_POST
@login_required
@permitted
def undo_vote_for_thread(request, course_id, thread_id):
"""
given a course id and thread id, remove users vote for thread
ajax only
"""
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
user = cc.User.from_django_user(request.user)
thread = cc.Thread.find(thread_id)
user.unvote(thread)
return JsonResponse(prepare_content(thread.to_dict(), course_key))
@require_POST
@login_required
@permitted
def pin_thread(request, course_id, thread_id):
"""
given a course id and thread id, pin this thread
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment