Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 2 additions & 1 deletion lms/djangoapps/discussion/rest_api/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from django.http import Http404
from django.urls import reverse
from django.utils.html import strip_tags
from edx_django_utils.monitoring import function_trace
from edx_django_utils.monitoring import function_trace, set_custom_attribute
from opaque_keys import InvalidKeyError
from opaque_keys.edx.locator import CourseKey
from pytz import UTC
Expand Down Expand Up @@ -1905,6 +1905,7 @@ def create_thread(request, thread_data):
)
serializer.save()
cc_thread = serializer.instance
set_custom_attribute("forum.entity_id", str(cc_thread.id))
Comment thread
santhosh-apphelix-2u marked this conversation as resolved.
# Use send_signal_after_commit() to ensure the signal is sent only after the transaction commits.
send_signal_after_commit(
lambda: thread_created.send(sender=None, user=user, post=cc_thread, notify_all_learners=notify_all_learners)
Expand Down
55 changes: 53 additions & 2 deletions lms/djangoapps/discussion/rest_api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from django.core.exceptions import BadRequest, ValidationError
from django.shortcuts import get_object_or_404
from drf_yasg import openapi
from edx_django_utils.monitoring import set_custom_attribute
from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication
from edx_rest_framework_extensions.auth.session.authentication import (
SessionAuthenticationAllowInactiveUser,
Expand Down Expand Up @@ -119,6 +120,22 @@

User = get_user_model()

def _discussion_error_type(exc):
"""Map common discussion exceptions to a stable Datadog error type."""
if isinstance(exc, PermissionError):
return "permission_denied"
if isinstance(exc, PermissionDenied):
return "permission_denied"
Comment thread
santhosh-apphelix-2u marked this conversation as resolved.
Comment thread
santhosh-apphelix-2u marked this conversation as resolved.
if isinstance(exc, InvalidKeyError):
return "validation_error"
if isinstance(exc, ValidationError):
return "validation_error"
if isinstance(exc, ParseError):
return "validation_error"
if isinstance(exc, UnsupportedMediaType):
return "validation_error"
return "backend_error"


@view_auth_classes()
class CourseView(DeveloperErrorViewMixin, APIView):
Expand Down Expand Up @@ -713,12 +730,30 @@ def create(self, request):
Implements the POST method for the list endpoint as described in the
class docstring.
"""
set_custom_attribute("forum.operation", "thread.create")
set_custom_attribute("forum.course_id", request.data.get("course_id", ""))
set_custom_attribute("forum.entity_type", "thread")
set_custom_attribute("forum.actor_id", str(getattr(request.user, "id", "")))

if request.data.get("type"):
set_custom_attribute("forum.thread_type", request.data.get("type"))
if request.data.get("topic_id"):
set_custom_attribute("forum.commentable_id", request.data.get("topic_id"))
if request.data.get("group_id") is not None:
set_custom_attribute("forum.group_id", str(request.data.get("group_id")))
Comment thread
santhosh-apphelix-2u marked this conversation as resolved.

try:
if not request.data.get("course_id"):
raise ValidationError({"course_id": ["This field is required."]})
course_key_str = request.data.get("course_id")
course_key = CourseKey.from_string(course_key_str)

if is_content_creation_rate_limited(request, course_key=course_key):
set_custom_attribute("forum.result", "error")
set_custom_attribute(
"forum.http_status", str(status.HTTP_429_TOO_MANY_REQUESTS)
)
set_custom_attribute("forum.error_type", "rate_limited")
return Response(
"Too many requests", status=status.HTTP_429_TOO_MANY_REQUESTS
)
Expand All @@ -729,7 +764,15 @@ class docstring.
raise ValidationError({"captcha_token": "This field is required."})

if not verify_recaptcha_token(captcha_token):
return Response({"error": "CAPTCHA verification failed."}, status=400)
set_custom_attribute("forum.result", "error")
set_custom_attribute(
"forum.http_status", str(status.HTTP_400_BAD_REQUEST)
)
set_custom_attribute("forum.error_type", "validation_error")
return Response(
{"error": "CAPTCHA verification failed."},
status=status.HTTP_400_BAD_REQUEST,
)

if (
ONLY_VERIFIED_USERS_CAN_POST.is_enabled(course_key)
Expand All @@ -741,7 +784,15 @@ class docstring.

data = request.data.copy()
data.pop("captcha_token", None)
return Response(create_thread(request, data))
response = Response(create_thread(request, data))
set_custom_attribute("forum.result", "success")
set_custom_attribute("forum.http_status", str(response.status_code))
return response
except Exception as exc:
set_custom_attribute("forum.result", "error")
set_custom_attribute("forum.http_status", str(status.HTTP_400_BAD_REQUEST))
set_custom_attribute("forum.error_type", _discussion_error_type(exc))
raise

def partial_update(self, request, thread_id):
"""
Expand Down
Loading