Skip to content
Snippets Groups Projects
Commit 4edbbbb6 authored by Simon Chen's avatar Simon Chen
Browse files

Update the gradebook functionality to allow grades update.

The override modal is now able to update grades for all users for each gradable unit in the course
parent e3d858c2
No related merge requests found
......@@ -36,6 +36,7 @@
</tbody>
</table>
</div>
<div class="grade-override-info-container alert alert-info pattern-library-shim" role="alert"></div>
<div class="grade-override-menu-buttons">
<button class="btn grade-override-modal-save"><%- strLib.save %></button>
<button class="btn grade-override-modal-close"><%- strLib.cancel %></button>
......
......@@ -12,6 +12,7 @@ from rest_framework.generics import GenericAPIView
from rest_framework.pagination import CursorPagination
from rest_framework.response import Response
from six import text_type
from util.date_utils import to_timestamp
from courseware.courses import get_course_with_access
from edx_rest_framework_extensions import permissions
......@@ -22,16 +23,23 @@ from lms.djangoapps.grades.config.waffle import waffle_flags, WRITABLE_GRADEBOOK
from lms.djangoapps.grades.constants import ScoreDatabaseTableEnum
from lms.djangoapps.grades.course_data import CourseData
from lms.djangoapps.grades.course_grade_factory import CourseGradeFactory
from lms.djangoapps.grades.events import SUBSECTION_GRADE_CALCULATED, subsection_grade_calculated
from lms.djangoapps.grades.models import PersistentSubsectionGrade, PersistentSubsectionGradeOverride
from lms.djangoapps.grades.signals import signals
from lms.djangoapps.grades.subsection_grade import CreateSubsectionGrade
from lms.djangoapps.grades.tasks import recalculate_subsection_grade_v3
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey, UsageKey
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.core.lib.api.authentication import OAuth2AuthenticationAllowInactiveUser
from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin
from student.models import CourseEnrollment
from track.event_transaction_utils import (
create_new_event_transaction_id,
get_event_transaction_id,
get_event_transaction_type,
set_event_transaction_type
)
log = logging.getLogger(__name__)
USER_MODEL = get_user_model()
......@@ -767,17 +775,27 @@ class GradebookBulkUpdateView(GradeViewMixin, GenericAPIView):
grade=subsection_grade_model,
defaults=self._clean_override_data(override_data),
)
signals.SUBSECTION_OVERRIDE_CHANGED.send(
sender=None,
user_id=subsection_grade_model.user_id,
course_id=text_type(subsection_grade_model.course_id),
usage_id=text_type(subsection_grade_model.usage_key),
only_if_higher=False,
modified=override.modified,
score_deleted=False,
score_db_table=ScoreDatabaseTableEnum.overrides,
force_update_subsections=True,
set_event_transaction_type(SUBSECTION_GRADE_CALCULATED)
create_new_event_transaction_id()
recalculate_subsection_grade_v3.apply(
kwargs=dict(
user_id=subsection_grade_model.user_id,
anonymous_user_id=None,
course_id=text_type(subsection_grade_model.course_id),
usage_id=text_type(subsection_grade_model.usage_key),
only_if_higher=False,
expected_modified_time=to_timestamp(override.modified),
score_deleted=False,
event_transaction_id=unicode(get_event_transaction_id()),
event_transaction_type=unicode(get_event_transaction_type()),
score_db_table=ScoreDatabaseTableEnum.overrides,
force_update_subsections=True,
)
)
# Emit events to let our tracking system to know we updated subsection grade
subsection_grade_calculated(subsection_grade_model)
def _clean_override_data(self, override_data):
"""
......
......@@ -71,11 +71,18 @@ Decisions
d. A status code of ``422`` will be returned for requests that contain any failed item. This allows a client
to easily tell if any item in their request payload was problematic and needs special handling. If all
requested items succeed, a ``202 (accepted)`` is returned. This status code was chosen because an
asynchronous celery task is enqueued for each subsection grade that needs to be updated.
e. We have to thread a ``force_update_subsections`` keyword argument through the Django signal invocation
that enqueues the subsection update task. This is because we may be creating a new subsection grade
with no score data available from either ``courseware.StudentModule`` records or from the `Submissions` API.
In this case, the only score data available exists in the grade override record, and the subsection ``update()``
call should be forced to read from this record.
requested items succeed, a ``202 (accepted)`` is returned. This status code was chosen because a
celery task is enqueued and waited for each subsection grade that needs to be updated.
e. We have to thread a ``force_update_subsections`` keyword argument into the subsection update task that
we enqueue. This is because we may be creating a new subsection grade with no score data available from
either ``courseware.StudentModule`` records or from the `Submissions` API. In this case, the only score
data available exists in the grade override record, and the subsection ``update()`` call should be forced
to read from this record.
f. We have to synchronously update each grade record for each user in this endpoint. This means the request
will be left open for longer period than we wanted. The reason is: the primary consumer gradebook UI
would need to display the updated grade result for all users, after update is complete. If we do update
asynchronously, the gradebook UI do not know how to update the table with new values, including agregations
for the user's course grade. This is the lowest effort change to address the UI display problem. We will
need to improve this mechanism as we continue to develop.
......@@ -16,26 +16,21 @@ function _templateLoader(templateName, staticPath, callback, errorCallback) {
}
function courseXblockUpdater(courseID, dataToSend, visibilityData, callback, errorCallback) {
var cleanData = {'users' : {}};
if (dataToSend instanceof Array)
for (var i = 0; i < dataToSend.length; i++) {
cleanData.users['id_' + dataToSend.userID] = {
'block_id' : dataToSend[i].blockID || '',
'grade' : dataToSend[i].grade || '',
'max_grade' : dataToSend[i].maxGrade || null,
'state' : dataToSend[i].state || '{}',
'user_id' : dataToSend[i].userID || ''
};
}
else if (dataToSend instanceof Object)
cleanData.users = dataToSend;
var postUrl = '/api/score/courses/' + courseID;
if (!_.isEmpty(visibilityData))
cleanData.visibility = visibilityData;
var cleanData = _.map(dataToSend, function (data) {
return {
user_id: data.user_id,
usage_id: data.block_id,
grade: {
earned_all_override: data.grade || 0,
possible_all_override: data.max_grade || 0,
earned_graded_override: data.grade || 0,
possible_graded_override: data.max_grade || 0
}
};
});
var postUrl = '/api/grades/v1/gradebook/' + courseID + '/bulk-update';
$('')
$.ajax({
url: postUrl,
method: 'POST',
......@@ -314,6 +309,7 @@ $(document).ready(function() {
isManualGrading = JSON.parse($(gradeOverrideObject).attr('data-manual-grading'));
$modal.find('.assignment-name-placeholder').text(assignmentName);
$modal.find('.block-id-placeholder').text(blockID);
$modal.find('.grade-override-info-container').hide();
if ( _.isEmpty(userAutoGrades) ) {
$tableWrapper.hide();
$manualGradeVisibilityWrapper.toggle(false);
......@@ -486,6 +482,18 @@ $(document).ready(function() {
});
}
function setInfoMessage(messageText){
var $messageField = $('.grade-override-modal').find('.grade-override-info-container');
if(messageText) {
$messageField.text(messageText);
$messageField.show();
}
else {
$messageField.empty();
$messageField.hide();
}
}
$(document).on('click', '.grade-override-modal-save', function() {
var visibilityData = {};
if (isManualGrading) {
......@@ -499,12 +507,14 @@ $(document).ready(function() {
return;
var validStatus = ValidateAdjustedGradesData();
if (validStatus) {
setInfoMessage(gettext('Update in progress, please wait...'));
courseXblockUpdater(
courseID,
adjustedGradesData,
visibilityData,
function(data){
gradebookOverrideModalReset();
setInfoMessage();
renderAllGradebook = false;
gradeBookData = [];
$gradesTableWrapper.empty();
......
......@@ -74,6 +74,10 @@
.grade-override-menu-buttons {
padding: 10px;
}
.grade-override-info-container{
margin: 10px;
font-size: $small-font-size;
}
}
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment