feat: update initialize endpoint and create assessments/feedback endpoint in ORA Staff Grader (#33632)

This commit is contained in:
Bryann Valderrama
2024-02-22 09:44:32 -05:00
committed by GitHub
parent 5007418803
commit 20570ff417
5 changed files with 225 additions and 3 deletions

View File

@@ -12,6 +12,10 @@ Other handlers return status OK even for an error, but contain error info in the
These are checked (usually by checking for a {"success":false} response) and raise errors, possibly with extra context.
"""
import json
from http import HTTPStatus
from rest_framework.request import Request
from lms.djangoapps.ora_staff_grader.errors import (
LockContestedError,
XBlockInternalError,
@@ -33,6 +37,42 @@ def get_submissions(request, usage_id):
return json.loads(response.content)
def get_assessments(request: Request, usage_id: str, handler_name: str, submission_uuid: str):
"""
Get a list of assessments according to the handler name from ORA XBlock.json_handler
* `list_assessments_to` handler
Lists all assessments given by a user (according to their submissionUUID)
in an ORA assignment. Assessments can be Self, Peer and Staff.
* `list_assessments_from` handler
Lists all assessments received by a user (according to their submissionUUID)
in an ORA assignment. Assessments can be Self, Peer and Staff.
Args:
request (Request): The request object.
usage_id (str): Usage ID of the XBlock for running the handler
handler_name (str): The name of the handler to call
submission_uuid (str): The ORA submission UUID
"""
data = {
"item_id": usage_id,
"submission_uuid": submission_uuid,
}
response = call_xblock_json_handler(request, usage_id, handler_name, data)
if response.status_code != HTTPStatus.OK:
raise XBlockInternalError(context={"handler": handler_name})
try:
return json.loads(response.content)
except json.JSONDecodeError as exc:
raise XBlockInternalError(
context={"handler": handler_name, "details": response.content}
) from exc
def get_submission_info(request, usage_id, submission_uuid):
"""
Get submission content from ORA 'get_submission_info' XBlock.json_handler

View File

@@ -135,11 +135,13 @@ class ScoreSerializer(serializers.Serializer):
class SubmissionMetadataSerializer(serializers.Serializer):
"""
Submission metadata for displaying submissions table in ESG
Submission metadata for displaying submissions table in Enhanced Staff Grader (ESG)
"""
submissionUUID = serializers.CharField(source="submissionUuid")
username = serializers.CharField(allow_null=True)
email = serializers.CharField(allow_null=True)
fullname = serializers.CharField(allow_null=True)
teamName = serializers.CharField(allow_null=True)
dateSubmitted = serializers.DateTimeField()
dateGraded = serializers.DateTimeField(allow_null=True)
@@ -159,6 +161,57 @@ class SubmissionMetadataSerializer(serializers.Serializer):
"gradeStatus",
"lockStatus",
"score",
"email",
"fullname",
]
read_only_fields = fields
class AssessmentScoresSerializer(serializers.Serializer):
"""
Serializer for score information associated with a specific assessment.
This serializer is included in the `AssessmentSerializer` as a `ListField`
"""
criterion_name = serializers.CharField()
score_earned = serializers.IntegerField()
score_type = serializers.CharField()
class Meta:
fields = [
"criterion_name",
"score_earned",
"score_type",
]
read_only_fields = fields
class AssessmentSerializer(serializers.Serializer):
"""
Serializer for the each assessment metadata in the response from the assessments
feedback endpoints (from/to) in Enhanced Staff Grader (ESG)
This serializer is included in the `AssessmentFeedbackSerializer` as a `ListField`
"""
assessment_id = serializers.CharField()
scorer_name = serializers.CharField(allow_null=True)
scorer_username = serializers.CharField(allow_null=True)
scorer_email = serializers.CharField(allow_null=True)
assessment_date = serializers.DateTimeField()
assessment_scores = serializers.ListField(child=AssessmentScoresSerializer())
problem_step = serializers.CharField(allow_null=True)
feedback = serializers.CharField(allow_null=True)
class Meta:
fields = [
"assessment_id",
"scorer_name",
"scorer_username",
"scorer_email",
"assessment_date",
"assessment_scores",
"problem_step",
"feedback",
]
read_only_fields = fields
@@ -190,6 +243,19 @@ class InitializeSerializer(serializers.Serializer):
return obj['isEnabled'] and not obj['oraMetadata'].teams_enabled
class AssessmentFeedbackSerializer(serializers.Serializer):
"""
Serializer for a list of assessments for the response from the assessments
feedback endpoints (from/to) in Enhanced Staff Grader (ESG)
"""
assessments = serializers.ListField(child=AssessmentSerializer())
class Meta:
fields = ["assessments"]
read_only_fields = fields
class UploadedFileSerializer(serializers.Serializer):
"""Serializer for a file uploaded as a part of a response"""

View File

@@ -241,6 +241,8 @@ class TestSubmissionMetadataSerializer(TestCase):
"submissionUuid": "a",
"username": "foo",
"teamName": "",
'email': "jondoes@example.com",
'fullname': "",
"dateSubmitted": "1969-07-16 13:32:00",
"dateGraded": "None",
"gradedBy": "",
@@ -251,6 +253,8 @@ class TestSubmissionMetadataSerializer(TestCase):
"b": {
"submissionUuid": "b",
"username": "",
'email': "jondoes@example.com",
'fullname': "Jhon Does",
"teamName": "bar",
"dateSubmitted": "1969-07-20 20:17:40",
"dateGraded": "None",
@@ -262,6 +266,8 @@ class TestSubmissionMetadataSerializer(TestCase):
"c": {
"submissionUuid": "c",
"username": "baz",
'email': "jondoes@example.com",
'fullname': "Jhon Does",
"teamName": "",
"dateSubmitted": "1969-07-21 21:35:00",
"dateGraded": "1969-07-24 16:44:00",
@@ -293,6 +299,8 @@ class TestSubmissionMetadataSerializer(TestCase):
submission = {
"submissionUuid": "empty-score",
"username": "WOPR",
'email': "jhondoes@example.com",
'fullname': "",
"dateSubmitted": "1983-06-03 00:00:00",
"dateGraded": None,
"gradedBy": None,
@@ -304,6 +312,8 @@ class TestSubmissionMetadataSerializer(TestCase):
expected_output = {
"submissionUUID": "empty-score",
"username": "WOPR",
'email': "jhondoes@example.com",
'fullname': "",
"teamName": None,
"dateSubmitted": "1983-06-03 00:00:00",
"dateGraded": None,

View File

@@ -3,7 +3,7 @@ URLs for Enhanced Staff Grader (ESG) backend-for-frontend (BFF)
"""
from django.urls import include
from django.urls import path
from rest_framework.routers import DefaultRouter
from lms.djangoapps.ora_staff_grader.views import (
InitializeView,
@@ -13,12 +13,15 @@ from lms.djangoapps.ora_staff_grader.views import (
SubmissionLockView,
SubmissionStatusFetchView,
UpdateGradeView,
AssessmentFeedbackView,
)
urlpatterns = []
app_name = "ora-staff-grader"
router = DefaultRouter()
router.register("assessments/feedback", AssessmentFeedbackView, basename="assessment-feedback")
urlpatterns += [
path("mock/", include("lms.djangoapps.ora_staff_grader.mock.urls")),
path("initialize", InitializeView.as_view(), name="initialize"),
@@ -33,3 +36,5 @@ urlpatterns += [
path("submission/grade", UpdateGradeView.as_view(), name="update-grade"),
path("submission", SubmissionFetchView.as_view(), name="fetch-submission"),
]
urlpatterns += router.urls

View File

@@ -15,9 +15,12 @@ from edx_rest_framework_extensions.auth.session.authentication import (
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import UsageKey
from openassessment.xblock.config_mixin import WAFFLE_NAMESPACE, ENHANCED_STAFF_GRADER
from rest_framework.decorators import action
from rest_framework.generics import RetrieveAPIView
from rest_framework.permissions import IsAuthenticated
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.viewsets import ViewSet
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.exceptions import ItemNotFoundError
@@ -43,11 +46,13 @@ from lms.djangoapps.ora_staff_grader.ora_api import (
get_assessment_info,
get_submission_info,
get_submissions,
get_assessments,
submit_grade,
)
from lms.djangoapps.ora_staff_grader.serializers import (
FileListSerializer,
InitializeSerializer,
AssessmentFeedbackSerializer,
LockStatusSerializer,
StaffAssessSerializer,
SubmissionFetchSerializer,
@@ -147,6 +152,102 @@ class InitializeView(StaffGraderBaseView):
return UnknownErrorResponse()
class AssessmentFeedbackView(StaffGraderBaseView, ViewSet):
"""
View for fetching assessment feedback for a submission.
**Methods**
* (GET) `api/ora_staff_grader/assessments/feedback/from`
List all assessments received by a user (according to
their submissionUUID) in an ORA assignment.
* (GET) `api/ora_staff_grader/assessments/feedback/to`
List all assessments given by a user (according to
their submissionUUID) in an ORA assignment.
**Query Params**:
* `oraLocation` (str): ORA location for XBlock handling
* `submissionUUID` (str): The ORA submission UUID
**Response**:
{
assessments (List[dict]): [
{
"assessment_id: (str) Assessment id
"scorer_name: (str) Scorer name
"scorer_username: (str) Scorer username
"scorer_email: (str) Scorer email
"assessment_date: (str) Assessment date
"assessment_scores (List[dict]) [
{
"criterion_name: (str) Criterion name
"score_earned: (int) Score earned
"score_type: (str) Score type
}
]
"problem_step (str) Problem step (Self, Peer, or Staff)
"feedback: (str) Feedback of the assessment
}
]
}
**Errors**:
* `MissingParamResponse` (HTTP 400) for missing params
* `BadOraLocationResponse` (HTTP 400) for bad ORA location
* `XBlockInternalError` (HTTP 500) for an issue with ORA
* `UnknownError` (HTTP 500) for other errors
"""
@action(methods=["get"], detail=False, url_path="from")
@require_params([PARAM_ORA_LOCATION, PARAM_SUBMISSION_ID])
def get_from(self, request: Request, ora_location: str, submission_uuid: str, *args, **kwargs):
return self._get_assessments(request, ora_location, "list_assessments_from", submission_uuid)
@action(methods=["get"], detail=False, url_path="to")
@require_params([PARAM_ORA_LOCATION, PARAM_SUBMISSION_ID])
def get_to(self, request: Request, ora_location: str, submission_uuid: str, *args, **kwargs):
return self._get_assessments(request, ora_location, "list_assessments_to", submission_uuid)
def _get_assessments(
self, request: Request, ora_location: str, handler_name: str, submission_uuid: str
):
"""
Fetches assessment data using the given handler name.
Args:
request (Request): The Django request object.
ora_location (str): The ORA location for XBlock handling.
handler_name (str): The name of the XBlock handler to use.
submission_uuid (str): The ORA submission UUID.
Returns:
A Django response object containing serialized assessment data or an error response.
"""
try:
assessments_data = {
"assessments": get_assessments(
request, ora_location, handler_name, submission_uuid
)
}
response_data = AssessmentFeedbackSerializer(assessments_data).data
return Response(response_data)
except (InvalidKeyError, ItemNotFoundError):
log.error(f"Bad ORA location provided: {ora_location}")
return BadOraLocationResponse()
except XBlockInternalError as ex:
log.error(ex)
return InternalErrorResponse(context=ex.context)
except Exception as ex:
log.exception(ex)
return UnknownErrorResponse()
class SubmissionFetchView(StaffGraderBaseView):
"""
GET submission contents and assessment info, if any