Commit 1aee957a by Christina Roberts

Merge pull request #818 from edx/christina/full-grade

Full staff grading UI
parents 29cd3969 d8d093af
......@@ -265,7 +265,7 @@ def get_submission_to_assess(course_id, item_id, scorer_id):
'attempt_number': 1,
'submitted_at': datetime.datetime(2014, 1, 29, 23, 14, 52, 649284, tzinfo=<UTC>),
'created_at': datetime.datetime(2014, 1, 29, 17, 14, 52, 668850, tzinfo=<UTC>),
'answer': u'The answer is 42.'
'answer': { ... }
}
"""
......
......@@ -19,7 +19,7 @@
<div class="staff-info__content ui-staff__content">
<div class="staff-info__student ui-staff__content__section">
<div class="staff-info__student">
<div class="wrapper--input" class="staff-info__student__form">
<form class="openassessment_student_info_form">
<div class="form--error"></div>
......@@ -167,6 +167,7 @@
</div>
</div>
{% if staff_assessment_required %}
<div class="openassessment__staff-grading wrapper--staff-grading wrapper--ui-staff is--hidden">
<div class="staff-grading ui-staff">
<h2 class="staff-grading__title ui-staff__title">
......@@ -175,8 +176,10 @@
</h2>
<div class="staff-info__content ui-staff__content">
{% include "openassessmentblock/staff_area/oa_staff_grade_learners.html" with staff_assessment_ungraded=staff_assessment_ungraded %}
</div>
</div>
</div>
{% endif %}
</div>
{% load i18n %}
<div class="staff__grade__control ui-toggle-visibility is--collapsed">
<header class="staff__grade__header ui-toggle-visibility__control">
<h3 class="staff__grade__title">
<span class="wrapper--copy">
<button class="staff__grade__show-form">{% trans "Staff Assessment" %}</button>
</span>
</h3>
{% block title %}
<span class="staff__grade__status">
<span class="staff__grade__value">
<span class="copy">
{% blocktrans with ungraded=staff_assessment_ungraded|stringformat:"s" in_progress=staff_assessment_in_progress|stringformat:"s" %}
{{ ungraded }} Available and {{ in_progress }} Checked Out
{% endblocktrans %}
</span>
</span>
</span>
{% endblock %}
</header>
<div class="ui-staff__content__section staff__grade__content ui-toggle-visibility__content">
<div class="wrapper--input">
<div class="staff__grade__form--error"></div>
<div class="staff__grade__form"></div>
</div>
</div>
</div>
{% load i18n %}
{% spaceless %}
{% block body %}
<div class="staff__grade__form ui-toggle-visibility__content" data-submission-uuid="{{ submission.uuid }}">
<div class="wrapper--staff-assessment">
<div>
<p>{% trans "Give this learner a grade using the problem's rubric." %}</p>
</div>
<div>
<article class="staff-assessment">
<div class="staff-assessment__display">
<header class="staff-assessment__display__header">
<h4 class="staff-assessment__display__title">
{% blocktrans %}
Response for: {{ student_username }}
{% endblocktrans %}
</h4>
</header>
{% include "openassessmentblock/oa_submission_answer.html" with answer=submission.answer answer_text_label="The learner's response to the question above:" %}
{% include "openassessmentblock/oa_uploaded_file.html" with file_upload_type=file_upload_type file_url=staff_file_url header="Associated File" class_prefix="staff-assessment" show_warning="true" %}
</div>
<form class="staff-assessment__assessment" method="post">
{% include "openassessmentblock/oa_rubric.html" with rubric_type="staff" %}
</form>
</article>
</div>
<div>
<div class="message message--inline message--error message--error-server">
<h4 class="message__title">{% trans "We could not submit your assessment" %}</h4>
<div class="message__content"></div>
</div>
<ul class="list list--actions">
<li class="list--actions__item submit_assessment--action">
<button type="submit" class="action action--submit is--disabled">
<span class="copy">{% trans "Submit assessment" %}</span>
</button>
</li>
<li class="list--actions__item submit_assessment--action">
<button type="submit" class="action action--submit is--disabled continue_grading--action">
<span class="copy">{% trans "Submit assessment and continue grading" %}</span>
</button>
</li>
</ul>
<div class="staff-grade-error"></div>
</div>
</div>
</div>
{% endblock %}
{% endspaceless %}
......@@ -2,52 +2,49 @@
{% spaceless %}
{% block body %}
<div class="ui-toggle-visibility__content">
<div class="wrapper--staff-assessment">
<div class="step__instruction">
<p>{% trans "Override this learner's current grade using the problem's rubric." %}</p>
</div>
<div class="wrapper--staff-assessment">
<div class="step__instruction">
<p>{% trans "Override this learner's current grade using the problem's rubric." %}</p>
</div>
<div class="step__content">
<article class="staff-assessment">
<div class="staff-assessment__display">
<header class="staff-assessment__display__header">
<h3 class="staff-assessment__display__title">
{% blocktrans %}
Response for: {{ student_username }}
{% endblocktrans %}
</h3>
</header>
{% include "openassessmentblock/oa_submission_answer.html" with answer=submission.answer answer_text_label="The learner's response to the question above:" %}
{% include "openassessmentblock/oa_uploaded_file.html" with file_upload_type=file_upload_type file_url=staff_file_url header="Associated File" class_prefix="staff-assessment" show_warning="true" %}
</div>
<form class="staff-assessment__assessment" method="post">
{% include "openassessmentblock/oa_rubric.html" with rubric_type="staff" rubric_feedback_prompt="(Optional) What aspects of this response stood out to you? What did it do well? How could it improve?" rubric_feedback_default_text="I noticed that this response..." %}
</form>
</article>
</div>
<div class="step__content">
<article class="staff-assessment">
<div class="staff-assessment__display">
<header class="staff-assessment__display__header">
<h3 class="staff-assessment__display__title">
{% blocktrans %}
Response for: {{ student_username }}
{% endblocktrans %}
</h3>
</header>
{% include "openassessmentblock/oa_submission_answer.html" with answer=submission.answer answer_text_label="The learner's response to the question above:" %}
<div class="step__actions">
<div class="message message--inline message--error message--error-server">
<h3 class="message__title">{% trans "We could not submit your assessment" %}</h3>
<div class="message__content"></div>
{% include "openassessmentblock/oa_uploaded_file.html" with file_upload_type=file_upload_type file_url=staff_file_url header="Associated File" class_prefix="staff-assessment" show_warning="true" %}
</div>
<ul class="list list--actions">
<li class="list--actions__item">
<button type="submit" class="action action--submit is--disabled">
<span class="copy">{% trans "Submit your assessment" %}</span>
</button>
<form class="staff-assessment__assessment" method="post">
{% include "openassessmentblock/oa_rubric.html" with rubric_type="staff" %}
</form>
</article>
</div>
<div class="staff-override-error"></div>
</li>
</ul>
<div class="step__actions">
<div class="message message--inline message--error message--error-server">
<h3 class="message__title">{% trans "We could not submit your assessment" %}</h3>
<div class="message__content"></div>
</div>
<ul class="list list--actions">
<li class="list--actions__item">
<button type="submit" class="action action--submit is--disabled">
<span class="copy">{% trans "Submit assessment" %}</span>
</button>
<div class="staff-override-error"></div>
</li>
</ul>
</div>
</div>
{% endblock %}
{% endspaceless %}
......@@ -32,14 +32,16 @@
{% endblocktrans %}
</p>
{% else %}
{% include "openassessmentblock/oa_submission_answer.html" with answer=submission.answer answer_text_label="The learner's response to the question above:" %}
<div class="wrapper--content">
{% include "openassessmentblock/oa_submission_answer.html" with answer=submission.answer answer_text_label="The learner's response to the question above:" %}
{% if submission.file_url %}
<a href="{{ submission.file_url }}" class="submission--file">
{% trans "The file associated with this response." %}
</a>
<span>{% trans "Caution: This file was uploaded by another course learner and has not been verified, screened, approved, reviewed, or endorsed by edX. If you decide to access it, you do so at your own risk." %}</span>
{% endif %}
{% if submission.file_url %}
<a href="{{ submission.file_url }}" class="submission--file">
{% trans "The file associated with this response." %}
</a>
<span>{% trans "Caution: This file was uploaded by another course learner and has not been verified, screened, approved, reviewed, or endorsed by edX. If you decide to access it, you do so at your own risk." %}</span>
{% endif %}
</div>
{% endif %}
</div>
</div>
......@@ -265,7 +267,7 @@
<div class="staff-info__staff-override__content ui-toggle-visibility__content">
<div class="wrapper--input">
{% include "openassessmentblock/staff_area/oa_staff_assessment.html" %}
{% include "openassessmentblock/staff_area/oa_staff_override_assessment.html" %}
</div>
</div>
</div>
......
......@@ -24,6 +24,7 @@ from openassessment.assessment.api import self as self_api
from openassessment.assessment.api import ai as ai_api
from openassessment.fileupload import api as file_api
from openassessment.workflow import api as workflow_api
from openassessment.assessment.api import staff as staff_api
from openassessment.fileupload import exceptions as file_exceptions
......@@ -77,6 +78,7 @@ def require_course_staff(error_key, with_json_handler=False):
permission_errors = {
"STAFF_AREA": xblock._(u"You do not have permission to access the ORA staff area"),
"STUDENT_INFO": xblock._(u"You do not have permission to access ORA learner information."),
"STUDENT_GRADE": xblock._(u"You do not have permission to access ORA staff grading."),
}
if not xblock.is_course_staff and with_json_handler:
......@@ -164,7 +166,14 @@ class StaffAreaMixin(object):
})
# Include whether or not staff grading step is enabled.
context['staff_assessment_required'] = "staff-assessment" in self.assessment_steps
staff_assessment_required = "staff-assessment" in self.assessment_steps
context['staff_assessment_required'] = staff_assessment_required
if staff_assessment_required:
grading_stats = staff_api.get_staff_grading_statistics(
student_item["course_id"], student_item["item_id"]
)
context['staff_assessment_ungraded'] = grading_stats['ungraded']
context['staff_assessment_in_progress'] = grading_stats['in-progress']
return path, context
......@@ -226,7 +235,83 @@ class StaffAreaMixin(object):
return self.render_assessment(path, context)
except PeerAssessmentInternalError:
return self.render_error(self._(u"Error finding assessment workflow cancellation."))
return self.render_error(self._(u"Error getting learner information."))
@XBlock.handler
@require_course_staff("STUDENT_GRADE")
def render_staff_grade_form(self, data, suffix=''): # pylint: disable=W0613
"""
Renders a form to staff-grade the next available learner submission.
Must be course staff to render this view.
"""
try:
student_item_dict = self.get_student_item_dict()
course_id = student_item_dict.get('course_id')
item_id = student_item_dict.get('item_id')
staff_id = student_item_dict['student_id']
# Note that this will check out a submission for grading by the specified staff member.
# If no submissions are available for grading, will return None.
submission_to_assess = staff_api.get_submission_to_assess(course_id, item_id, staff_id)
if submission_to_assess is not None:
submission = submission_api.get_submission_and_student(submission_to_assess['uuid'])
if submission:
anonymous_student_id = submission['student_item']['student_id']
submission_context = self.get_student_submission_context(
self.get_username(anonymous_student_id), submission
)
path = 'openassessmentblock/staff_area/oa_staff_grade_learners_assessment.html'
return self.render_assessment(path, submission_context)
else:
return self.render_error(self._(u"Error loading the checked out learner response."))
else:
return self.render_error(self._(u"No other learner responses are available for grading at this time."))
except PeerAssessmentInternalError:
return self.render_error(self._(u"Error getting staff grade information."))
def get_student_submission_context(self, student_username, submission):
"""
Get a context dict for rendering a student submission and associated rubric (for staff grading).
Includes submission (populating submitted file information if relevant), rubric_criteria,
and student_username.
Args:
student_username (unicode): The username of the student to report.
submission (object): A submission, as returned by the submission_api.
Returns:
A context dict for rendering a student submission and associated rubric (for staff grading).
"""
if submission and 'file_key' in submission.get('answer', {}):
file_key = submission['answer']['file_key']
try:
submission['file_url'] = file_api.get_download_url(file_key)
except file_exceptions.FileUploadError:
# Log the error, but do not prevent the rest of the student info
# from being displayed.
msg = (
u"Could not retrieve image URL for staff debug page. "
u"The learner username is '{student_username}', and the file key is {file_key}"
).format(student_username=student_username, file_key=file_key)
logger.exception(msg)
context = {
'submission': create_submission_dict(submission, self.prompts) if submission else None,
'rubric_criteria': copy.deepcopy(self.rubric_criteria_with_labels),
'student_username': student_username,
}
if self.rubric_feedback_prompt is not None:
context["rubric_feedback_prompt"] = self.rubric_feedback_prompt
if self.rubric_feedback_default_text is not None:
context['rubric_feedback_default_text'] = self.rubric_feedback_default_text
return context
def get_student_info_path_and_context(self, student_username, expanded_view=None):
"""
......@@ -238,12 +323,11 @@ class StaffAreaMixin(object):
expanded_view (str): An optional view to be shown initially expanded.
The default is None meaning that all views are shown collapsed.
"""
submission_uuid = None
submission = None
assessment_steps = self.assessment_steps
anonymous_user_id = None
submissions = None
student_item = None
submissions = None
submission = None
submission_uuid = None
if student_username:
anonymous_user_id = self.get_anonymous_user_id(student_username, self.course_id)
......@@ -255,22 +339,12 @@ class StaffAreaMixin(object):
submissions = submission_api.get_submissions(student_item, 1)
if submissions:
submission_uuid = submissions[0]['uuid']
submission = submissions[0]
submission_uuid = submission['uuid']
if 'file_key' in submission.get('answer', {}):
file_key = submission['answer']['file_key']
context = self.get_student_submission_context(student_username, submission)
try:
submission['file_url'] = file_api.get_download_url(file_key)
except file_exceptions.FileUploadError:
# Log the error, but do not prevent the rest of the student info
# from being displayed.
msg = (
u"Could not retrieve image URL for staff debug page. "
u"The learner username is '{student_username}', and the file key is {file_key}"
).format(student_username=student_username, file_key=file_key)
logger.exception(msg)
assessment_steps = self.assessment_steps
example_based_assessment = None
self_assessment = None
......@@ -291,8 +365,7 @@ class StaffAreaMixin(object):
workflow_cancellation = self.get_workflow_cancellation_info(submission_uuid)
context = {
'submission': create_submission_dict(submission, self.prompts) if submission else None,
context.update({
'score': workflow.get('score'),
'workflow_status': workflow.get('status'),
'workflow_cancellation': workflow_cancellation,
......@@ -300,10 +373,8 @@ class StaffAreaMixin(object):
'submitted_assessments': submitted_assessments,
'self_assessment': self_assessment,
'example_based_assessment': example_based_assessment,
'rubric_criteria': copy.deepcopy(self.rubric_criteria_with_labels),
'student_username': student_username,
'expanded_view': expanded_view,
}
})
if peer_assessments or self_assessment or example_based_assessment:
max_scores = peer_api.get_rubric_max_scores(submission_uuid)
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -689,6 +689,8 @@
"template": "openassessmentblock/staff_area/oa_staff_area.html",
"context": {
"staff_assessment_required": true,
"staff_assessment_ungraded": 10,
"staff_assessment_in_progress": 2,
"status_counts": {
"self": 1,
"peer": 2,
......@@ -718,6 +720,40 @@
"output": "oa_staff_area_full_grading.html"
},
{
"template": "openassessmentblock/staff_area/oa_staff_area.html",
"context": {
"staff_assessment_required": true,
"staff_assessment_ungraded": 9,
"staff_assessment_in_progress": 0,
"status_counts": {
"self": 1,
"peer": 2,
"waiting": 3,
"done": 4
},
"num_submissions": 10,
"item_id": "test_item",
"step_dates": [
{
"step": "submission",
"start": "2014-01-01",
"due": "N/A"
},
{
"step": "peer",
"start": "2014-02-02",
"due": "N/A"
},
{
"step": "self",
"start": "2014-03-03",
"due": "2015-04-05"
}
]
},
"output": "oa_staff_area_full_grading_2.html"
},
{
"template": "openassessmentblock/staff_area/oa_student_info.html",
"context": {
"rubric_criteria": [
......@@ -1031,5 +1067,117 @@
]
},
"output": "oa_turbo_mode.html"
},
{
"template": "openassessmentblock/staff_area/oa_staff_grade_learners_assessment.html",
"context": {
"rubric_criteria": [
{
"name": "vocabulary",
"prompt": "vocabulary",
"order_num": 0,
"feedback": "optional",
"options": [
{
"order_num": 0,
"points": 0,
"name": "Bad"
},
{
"order_num": 1,
"points": 1,
"name": "Good"
}
]
},
{
"name": "grammar",
"prompt": "grammar",
"order_num": 1,
"options": [
{
"order_num": 0,
"points": 0,
"name": "Bad"
},
{
"order_num": 1,
"points": 1,
"name": "Good"
}
]
},
{
"name": "feedback_only",
"prompt": "Feedback only, no options!",
"order_num": 2,
"feedback": "required",
"options": []
}
],
"submission": {
"answer": {
"text": "testing response text"
}
},
"student_username": "mock_user"
},
"output": "oa_staff_grade_learners_assessment.html"
},
{
"template": "openassessmentblock/staff_area/oa_staff_grade_learners_assessment.html",
"context": {
"rubric_criteria": [
{
"name": "vocabulary",
"prompt": "vocabulary",
"order_num": 0,
"feedback": "optional",
"options": [
{
"order_num": 0,
"points": 0,
"name": "Bad"
},
{
"order_num": 1,
"points": 1,
"name": "Good"
}
]
},
{
"name": "grammar",
"prompt": "grammar",
"order_num": 1,
"options": [
{
"order_num": 0,
"points": 0,
"name": "Bad"
},
{
"order_num": 1,
"points": 1,
"name": "Good"
}
]
},
{
"name": "feedback_only",
"prompt": "Feedback only, no options!",
"order_num": 2,
"feedback": "required",
"options": []
}
],
"submission": {
"answer": {
"text": "testing response text"
}
},
"student_username": "mock_user_2"
},
"output": "oa_staff_grade_learners_assessment_2.html"
}
]
if(typeof OpenAssessment=="undefined"||!OpenAssessment){OpenAssessment={}}if(typeof window.gettext==="undefined"){window.gettext=function(text){return text}}if(typeof window.ngetgext==="undefined"){window.ngettext=function(singularText,pluralText,n){if(n>1){return pluralText}else{return singularText}}}if(typeof window.Logger==="undefined"){window.Logger={log:function(){}}}if(typeof window.MathJax==="undefined"){window.MathJax={Hub:{Typeset:function(){},Queue:function(){}}}}if(typeof OpenAssessment.Server==="undefined"||!OpenAssessment.Server){OpenAssessment.Server=function(runtime,element){this.runtime=runtime;this.element=element};var jsonContentType="application/json; charset=utf-8";OpenAssessment.Server.prototype={url:function(handler){return this.runtime.handlerUrl(this.element,handler)},render:function(component){var view=this;var url=this.url("render_"+component);return $.Deferred(function(defer){$.ajax({url:url,type:"POST",dataType:"html"}).done(function(data){defer.resolveWith(view,[data])}).fail(function(){defer.rejectWith(view,[gettext("This section could not be loaded.")])})}).promise()},renderLatex:function(element){element.filter(".allow--latex").each(function(){MathJax.Hub.Queue(["Typeset",MathJax.Hub,this])})},renderContinuedPeer:function(){var view=this;var url=this.url("render_peer_assessment");return $.Deferred(function(defer){$.ajax({url:url,type:"POST",dataType:"html",data:{continue_grading:true}}).done(function(data){defer.resolveWith(view,[data])}).fail(function(){defer.rejectWith(view,[gettext("This section could not be loaded.")])})}).promise()},studentInfo:function(studentUsername,options){var url=this.url("render_student_info");return $.Deferred(function(defer){$.ajax({url:url,type:"POST",dataType:"html",data:_.extend({student_username:studentUsername},options)}).done(function(data){defer.resolveWith(this,[data])}).fail(function(){defer.rejectWith(this,[gettext("This section could not be loaded.")])})}).promise()},submit:function(submission){var url=this.url("submit");return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:JSON.stringify({submission:submission}),contentType:jsonContentType}).done(function(data){var success=data[0];if(success){var studentId=data[1];var attemptNum=data[2];defer.resolveWith(this,[studentId,attemptNum])}else{var errorNum=data[1];var errorMsg=data[2];defer.rejectWith(this,[errorNum,errorMsg])}}).fail(function(){defer.rejectWith(this,["AJAX",gettext("This response could not be submitted.")])})}).promise()},save:function(submission){var url=this.url("save_submission");return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:JSON.stringify({submission:submission}),contentType:jsonContentType}).done(function(data){if(data.success){defer.resolve()}else{defer.rejectWith(this,[data.msg])}}).fail(function(){defer.rejectWith(this,[gettext("This response could not be saved.")])})}).promise()},submitFeedbackOnAssessment:function(text,options){var url=this.url("submit_feedback");var payload=JSON.stringify({feedback_text:text,feedback_options:options});return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:payload,contentType:jsonContentType}).done(function(data){if(data.success){defer.resolve()}else{defer.rejectWith(this,[data.msg])}}).fail(function(){defer.rejectWith(this,[gettext("This feedback could not be submitted.")])})}).promise()},submitAssessment:function(assessmentType,payload){var url=this.url(assessmentType);return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:JSON.stringify(payload),contentType:jsonContentType}).done(function(data){if(data.success){defer.resolve()}else{defer.rejectWith(this,[data.msg])}}).fail(function(){defer.rejectWith(this,[gettext("This assessment could not be submitted.")])})}).promise()},peerAssess:function(optionsSelected,criterionFeedback,overallFeedback,submissionID){return this.submitAssessment("peer_assess",{options_selected:optionsSelected,criterion_feedback:criterionFeedback,overall_feedback:overallFeedback,submission_uuid:submissionID})},selfAssess:function(optionsSelected,criterionFeedback,overallFeedback){return this.submitAssessment("self_assess",{options_selected:optionsSelected,criterion_feedback:criterionFeedback,overall_feedback:overallFeedback})},staffAssess:function(optionsSelected,criterionFeedback,overallFeedback,submissionID){return this.submitAssessment("staff_assess",{options_selected:optionsSelected,criterion_feedback:criterionFeedback,overall_feedback:overallFeedback,submission_uuid:submissionID})},trainingAssess:function(optionsSelected){var url=this.url("training_assess");var payload=JSON.stringify({options_selected:optionsSelected});return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:payload,contentType:jsonContentType}).done(function(data){if(data.success){defer.resolveWith(this,[data.corrections])}else{defer.rejectWith(this,[data.msg])}}).fail(function(){defer.rejectWith(this,[gettext("This assessment could not be submitted.")])})})},scheduleTraining:function(){var url=this.url("schedule_training");return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:'""',contentType:jsonContentType}).done(function(data){if(data.success){defer.resolveWith(this,[data.msg])}else{defer.rejectWith(this,[data.msg])}}).fail(function(){defer.rejectWith(this,[gettext("This assessment could not be submitted.")])})})},rescheduleUnfinishedTasks:function(){var url=this.url("reschedule_unfinished_tasks");return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:'""',contentType:jsonContentType}).done(function(data){if(data.success){defer.resolveWith(this,[data.msg])}else{defer.rejectWith(this,[data.msg])}}).fail(function(){defer.rejectWith(this,[gettext("One or more rescheduling tasks failed.")])})})},updateEditorContext:function(options){var url=this.url("update_editor_context");var payload=JSON.stringify({prompts:options.prompts,feedback_prompt:options.feedbackPrompt,feedback_default_text:options.feedback_default_text,title:options.title,submission_start:options.submissionStart,submission_due:options.submissionDue,criteria:options.criteria,assessments:options.assessments,editor_assessments_order:options.editorAssessmentsOrder,file_upload_type:options.fileUploadType,white_listed_file_types:options.fileTypeWhiteList,allow_latex:options.latexEnabled,leaderboard_show:options.leaderboardNum});return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:payload,contentType:jsonContentType}).done(function(data){if(data.success){defer.resolve()}else{defer.rejectWith(this,[data.msg])}}).fail(function(){defer.rejectWith(this,[gettext("This problem could not be saved.")])})}).promise()},checkReleased:function(){var url=this.url("check_released");var payload='""';return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:payload,contentType:jsonContentType}).done(function(data){if(data.success){defer.resolveWith(this,[data.is_released])}else{defer.rejectWith(this,[data.msg])}}).fail(function(){defer.rejectWith(this,[gettext("The server could not be contacted.")])})}).promise()},getUploadUrl:function(contentType,filename){var url=this.url("upload_url");return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:JSON.stringify({contentType:contentType,filename:filename}),contentType:jsonContentType}).done(function(data){if(data.success){defer.resolve(data.url)}else{defer.rejectWith(this,[data.msg])}}).fail(function(){defer.rejectWith(this,[gettext("Could not retrieve upload url.")])})}).promise()},getDownloadUrl:function(){var url=this.url("download_url");return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:JSON.stringify({}),contentType:jsonContentType}).done(function(data){if(data.success){defer.resolve(data.url)}else{defer.rejectWith(this,[data.msg])}}).fail(function(){defer.rejectWith(this,[gettext("Could not retrieve download url.")])})}).promise()},cancelSubmission:function(submissionID,comments){var url=this.url("cancel_submission");var payload=JSON.stringify({submission_uuid:submissionID,comments:comments});return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:payload,contentType:jsonContentType}).done(function(data){if(data.success){defer.resolveWith(this,[data.msg])}}).fail(function(){defer.rejectWith(this,[gettext("The submission could not be removed from the grading pool.")])})}).promise()}}}if(typeof OpenAssessment=="undefined"||!OpenAssessment){OpenAssessment={}}if(typeof window.gettext==="undefined"){window.gettext=function(text){return text}}if(typeof window.ngetgext==="undefined"){window.ngettext=function(singularText,pluralText,n){if(n>1){return pluralText}else{return singularText}}}if(typeof window.Logger==="undefined"){window.Logger={log:function(){}}}if(typeof window.MathJax==="undefined"){window.MathJax={Hub:{Typeset:function(){},Queue:function(){}}}}OpenAssessment.BaseView=function(runtime,element,server,data){this.runtime=runtime;this.element=element;this.server=server;this.fileUploader=new OpenAssessment.FileUploader;this.responseView=new OpenAssessment.ResponseView(this.element,this.server,this.fileUploader,this,data);this.trainingView=new OpenAssessment.StudentTrainingView(this.element,this.server,this);this.selfView=new OpenAssessment.SelfView(this.element,this.server,this);this.peerView=new OpenAssessment.PeerView(this.element,this.server,this);this.staffView=new OpenAssessment.StaffView(this.element,this.server,this);this.gradeView=new OpenAssessment.GradeView(this.element,this.server,this);this.leaderboardView=new OpenAssessment.LeaderboardView(this.element,this.server,this);this.messageView=new OpenAssessment.MessageView(this.element,this.server,this);this.staffAreaView=new OpenAssessment.StaffAreaView(this.element,this.server,this)};OpenAssessment.BaseView.prototype={scrollToTop:function(){if($.scrollTo instanceof Function){$(window).scrollTo($("#openassessment__steps",this.element),800,{offset:-50})}},setUpCollapseExpand:function(parentElement){parentElement.on("click",".ui-toggle-visibility__control",function(eventData){var sel=$(eventData.target).closest(".ui-toggle-visibility");sel.toggleClass("is--collapsed")})},load:function(){this.responseView.load();this.loadAssessmentModules();this.staffAreaView.load()},loadAssessmentModules:function(){this.trainingView.load();this.peerView.load();this.staffView.load();this.selfView.load();this.gradeView.load();this.leaderboardView.load()},loadMessageView:function(){this.messageView.load()},toggleActionError:function(type,message){var element=this.element;var container=null;if(type==="save"){container=".response__submission__actions"}else if(type==="submit"||type==="peer"||type==="self"||type==="student-training"){container=".step__actions"}else if(type==="feedback_assess"){container=".submission__feedback__actions"}else if(type==="upload"){container="#upload__error"}if(container===null){if(message!==null){console.log(message)}}else{$(container+" .message__content",element).html("<p>"+(message?_.escape(message):"")+"</p>");$(container,element).toggleClass("has--error",message!==null)}},showLoadError:function(stepName,errorMessage){if(!errorMessage){errorMessage=gettext("Unable to load")}var $container=$("#openassessment__"+stepName);$container.toggleClass("has--error",true);$container.find(".step__status__value i").removeClass().addClass("icon fa fa-exclamation-triangle");$container.find(".step__status__value .copy").html(_.escape(errorMessage))}};function OpenAssessmentBlock(runtime,element,data){var server=new OpenAssessment.Server(runtime,element);var view=new OpenAssessment.BaseView(runtime,element,server,data);view.load()}OpenAssessment.FileUploader=function(){this.upload=function(url,file){return $.Deferred(function(defer){$.ajax({url:url,type:"PUT",data:file,async:false,processData:false,contentType:file.type}).done(function(){Logger.log("openassessment.upload_file",{fileName:file.name,fileSize:file.size,fileType:file.type});defer.resolve()}).fail(function(data,textStatus){defer.rejectWith(this,[textStatus])})}).promise()}};OpenAssessment.GradeView=function(element,server,baseView){this.element=element;this.server=server;this.baseView=baseView};OpenAssessment.GradeView.prototype={load:function(){var view=this;var baseView=this.baseView;this.server.render("grade").done(function(html){$("#openassessment__grade",view.element).replaceWith(html);view.server.renderLatex($("#openassessment__grade",view.element));view.installHandlers()}).fail(function(errMsg){baseView.showLoadError("grade",errMsg)})},installHandlers:function(){var sel=$("#openassessment__grade",this.element);this.baseView.setUpCollapseExpand(sel);var view=this;sel.find(".feedback__submit").click(function(eventObject){eventObject.preventDefault();view.submitFeedbackOnAssessment()})},feedbackText:function(text){if(typeof text==="undefined"){return $("#feedback__remarks__value",this.element).val()}else{$("#feedback__remarks__value",this.element).val(text)}},feedbackOptions:function(options){var view=this;if(typeof options==="undefined"){return $.map($(".feedback__overall__value:checked",view.element),function(element){return $(element).val()})}else{$(".feedback__overall__value",this.element).prop("checked",false);$.each(options,function(index,opt){$("#feedback__overall__value--"+opt,view.element).prop("checked",true)})}},setHidden:function(selector,hidden){selector.toggleClass("is--hidden",hidden);selector.attr("aria-hidden",hidden?"true":"false")},isHidden:function(selector){return selector.hasClass("is--hidden")&&selector.attr("aria-hidden")==="true"},feedbackState:function(newState){var containerSel=$(".submission__feedback__content",this.element);var instructionsSel=containerSel.find(".submission__feedback__instructions");var fieldsSel=containerSel.find(".submission__feedback__fields");var actionsSel=containerSel.find(".submission__feedback__actions");var transitionSel=containerSel.find(".transition__status");var messageSel=containerSel.find(".message--complete");if(typeof newState==="undefined"){var isSubmitting=containerSel.hasClass("is--transitioning")&&containerSel.hasClass("is--submitting")&&!this.isHidden(transitionSel)&&this.isHidden(messageSel)&&this.isHidden(instructionsSel)&&this.isHidden(fieldsSel)&&this.isHidden(actionsSel);var hasSubmitted=containerSel.hasClass("is--submitted")&&this.isHidden(transitionSel)&&!this.isHidden(messageSel)&&this.isHidden(instructionsSel)&&this.isHidden(fieldsSel)&&this.isHidden(actionsSel);var isOpen=!containerSel.hasClass("is--submitted")&&!containerSel.hasClass("is--transitioning")&&!containerSel.hasClass("is--submitting")&&this.isHidden(transitionSel)&&this.isHidden(messageSel)&&!this.isHidden(instructionsSel)&&!this.isHidden(fieldsSel)&&!this.isHidden(actionsSel);if(isOpen){return"open"}else if(isSubmitting){return"submitting"}else if(hasSubmitted){return"submitted"}else{throw"Invalid feedback state"}}else{if(newState==="open"){containerSel.toggleClass("is--transitioning",false);containerSel.toggleClass("is--submitting",false);containerSel.toggleClass("is--submitted",false);this.setHidden(instructionsSel,false);this.setHidden(fieldsSel,false);this.setHidden(actionsSel,false);this.setHidden(transitionSel,true);this.setHidden(messageSel,true)}else if(newState==="submitting"){containerSel.toggleClass("is--transitioning",true);containerSel.toggleClass("is--submitting",true);containerSel.toggleClass("is--submitted",false);this.setHidden(instructionsSel,true);this.setHidden(fieldsSel,true);this.setHidden(actionsSel,true);this.setHidden(transitionSel,false);this.setHidden(messageSel,true)}else if(newState==="submitted"){containerSel.toggleClass("is--transitioning",false);containerSel.toggleClass("is--submitting",false);containerSel.toggleClass("is--submitted",true);this.setHidden(instructionsSel,true);this.setHidden(fieldsSel,true);this.setHidden(actionsSel,true);this.setHidden(transitionSel,true);this.setHidden(messageSel,false)}}},submitFeedbackOnAssessment:function(){var view=this;var baseView=this.baseView;$(".feedback__submit",this.element).toggleClass("is--disabled",true);view.feedbackState("submitting");this.server.submitFeedbackOnAssessment(this.feedbackText(),this.feedbackOptions()).done(function(){view.feedbackState("submitted")}).fail(function(errMsg){baseView.toggleActionError("feedback_assess",errMsg)})}};OpenAssessment.LeaderboardView=function(element,server,baseView){this.element=element;this.server=server;this.baseView=baseView};OpenAssessment.LeaderboardView.prototype={load:function(){var view=this;var baseView=this.baseView;this.server.render("leaderboard").done(function(html){$("#openassessment__leaderboard",view.element).replaceWith(html);view.server.renderLatex($("#openassessment__leaderboard",view.element))}).fail(function(errMsg){baseView.showLoadError("leaderboard",errMsg)})}};OpenAssessment.MessageView=function(element,server,baseView){this.element=element;this.server=server;this.baseView=baseView};OpenAssessment.MessageView.prototype={load:function(){var view=this;var baseView=this.baseView;this.server.render("message").done(function(html){$("#openassessment__message",view.element).replaceWith(html);view.server.renderLatex($("#openassessment__message",view.element))}).fail(function(errMsg){baseView.showLoadError("message",errMsg)})}};OpenAssessment.PeerView=function(element,server,baseView){this.element=element;this.server=server;this.baseView=baseView;this.rubric=null};OpenAssessment.PeerView.prototype={load:function(){var view=this;this.server.render("peer_assessment").done(function(html){$("#openassessment__peer-assessment",view.element).replaceWith(html);view.server.renderLatex($("#openassessment__peer-assessment",view.element));view.installHandlers(false)}).fail(function(){view.baseView.showLoadError("peer-assessment")});view.baseView.loadMessageView()},loadContinuedAssessment:function(){var view=this;view.continueAssessmentEnabled(false);this.server.renderContinuedPeer().done(function(html){$("#openassessment__peer-assessment",view.element).replaceWith(html);view.server.renderLatex($("#openassessment__peer-assessment",view.element));view.installHandlers(true)}).fail(function(){view.baseView.showLoadError("peer-assessment");view.continueAssessmentEnabled(true)})},continueAssessmentEnabled:function(enabled){var button=$("#peer-assessment__continue__grading",this.element);if(typeof enabled==="undefined"){return!button.hasClass("is--disabled")}else{button.toggleClass("is--disabled",!enabled)}},installHandlers:function(isContinuedAssessment){var sel=$("#openassessment__peer-assessment",this.element);var view=this;this.baseView.setUpCollapseExpand(sel);var rubricSelector=$("#peer-assessment--001__assessment",this.element);if(rubricSelector.size()>0){var rubricElement=rubricSelector.get(0);this.rubric=new OpenAssessment.Rubric(rubricElement)}if(this.rubric!==null){this.rubric.canSubmitCallback($.proxy(view.peerSubmitEnabled,view))}sel.find("#peer-assessment--001__assessment__submit").click(function(eventObject){eventObject.preventDefault();if(!isContinuedAssessment){view.peerAssess()}else{view.continuedPeerAssess()}});sel.find("#peer-assessment__continue__grading").click(function(eventObject){eventObject.preventDefault();view.loadContinuedAssessment()})},peerSubmitEnabled:function(enabled){var button=$("#peer-assessment--001__assessment__submit",this.element);if(typeof enabled==="undefined"){return!button.hasClass("is--disabled")}else{button.toggleClass("is--disabled",!enabled);return enabled}},peerAssess:function(){var view=this;var baseView=view.baseView;this.peerAssessRequest(function(){baseView.loadAssessmentModules();baseView.scrollToTop()})},continuedPeerAssess:function(){var view=this;var gradeView=this.baseView.gradeView;var baseView=view.baseView;view.peerAssessRequest(function(){view.loadContinuedAssessment();gradeView.load();baseView.scrollToTop()})},peerAssessRequest:function(successFunction){var view=this;var uuid=$("#openassessment__peer-assessment").data("submission-uuid");view.baseView.toggleActionError("peer",null);view.peerSubmitEnabled(false);this.server.peerAssess(this.rubric.optionsSelected(),this.rubric.criterionFeedback(),this.rubric.overallFeedback(),uuid).done(successFunction).fail(function(errMsg){view.baseView.toggleActionError("peer",errMsg);view.peerSubmitEnabled(true)})}};OpenAssessment.ResponseView=function(element,server,fileUploader,baseView,data){this.element=element;this.server=server;this.fileUploader=fileUploader;this.baseView=baseView;this.savedResponse=[];this.files=null;this.fileType=null;this.lastChangeTime=Date.now();this.errorOnLastSave=false;this.autoSaveTimerId=null;this.data=data;this.fileUploaded=false};OpenAssessment.ResponseView.prototype={AUTO_SAVE_POLL_INTERVAL:2e3,AUTO_SAVE_WAIT:3e4,MAX_FILE_SIZE:5242880,load:function(){var view=this;this.server.render("submission").done(function(html){$("#openassessment__response",view.element).replaceWith(html);view.server.renderLatex($("#openassessment__response",view.element));view.installHandlers();view.setAutoSaveEnabled(true)}).fail(function(){view.baseView.showLoadError("response")})},installHandlers:function(){var sel=$("#openassessment__response",this.element);var view=this;var uploadType="";if(sel.find(".submission__answer__display__file").length){uploadType=sel.find(".submission__answer__display__file").data("upload-type")}this.baseView.setUpCollapseExpand(sel);this.savedResponse=this.response();var handleChange=function(){view.handleResponseChanged()};sel.find(".submission__answer__part__text__value").on("change keyup drop paste",handleChange);var handlePrepareUpload=function(eventData){view.prepareUpload(eventData.target.files,uploadType)};sel.find("input[type=file]").on("change",handlePrepareUpload);sel.find("#submission__preview__item").hide();sel.find("#step--response__submit").click(function(eventObject){eventObject.preventDefault();view.submit()});sel.find("#submission__save").click(function(eventObject){eventObject.preventDefault();view.save()});sel.find("#submission__preview").click(function(eventObject){eventObject.preventDefault();var previewText=sel.find(".submission__answer__part__text__value").val();var previewContainer=sel.find("#preview_content");previewContainer.html(previewText.replace(/\r\n|\r|\n/g,"<br />"));sel.find("#submission__preview__item").show();MathJax.Hub.Queue(["Typeset",MathJax.Hub,previewContainer[0]])});sel.find("#file__upload").click(function(eventObject){eventObject.preventDefault();$(".submission__answer__display__file",view.element).removeClass("is--hidden");view.fileUpload()})},setAutoSaveEnabled:function(enabled){if(enabled){if(this.autoSaveTimerId===null){this.autoSaveTimerId=setInterval($.proxy(this.autoSave,this),this.AUTO_SAVE_POLL_INTERVAL)}}else{if(this.autoSaveTimerId!==null){clearInterval(this.autoSaveTimerId)}}},submitEnabled:function(enabled){var sel=$("#step--response__submit",this.element);if(typeof enabled==="undefined"){return!sel.hasClass("is--disabled")}else{sel.toggleClass("is--disabled",!enabled);return enabled}},saveEnabled:function(enabled){var sel=$("#submission__save",this.element);if(typeof enabled==="undefined"){return!sel.hasClass("is--disabled")}else{sel.toggleClass("is--disabled",!enabled)}},previewEnabled:function(enabled){var sel=$("#submission__preview",this.element);if(typeof enabled==="undefined"){return!sel.hasClass("is--disabled")}else{sel.toggleClass("is--disabled",!enabled)}},saveStatus:function(msg){var sel=$("#response__save_status h3",this.element);if(typeof msg==="undefined"){return sel.text()}else{var label=gettext("Status of Your Response");sel.html('<span class="sr">'+_.escape(label)+":"+"</span>\n"+msg)}},unsavedWarningEnabled:function(enabled){if(typeof enabled==="undefined"){return window.onbeforeunload!==null}else{if(enabled){window.onbeforeunload=function(){return gettext("If you leave this page without saving or submitting your response, you'll lose any work you've done on the response.")}}else{window.onbeforeunload=null}}},response:function(texts){var sel=$(".submission__answer__part__text__value",this.element);if(typeof texts==="undefined"){return sel.map(function(){return $.trim($(this).val())}).get()}else{sel.map(function(index){$(this).val(texts[index])})}},responseChanged:function(){var savedResponse=this.savedResponse;return this.response().some(function(element,index){return element!==savedResponse[index]})},autoSave:function(){var timeSinceLastChange=Date.now()-this.lastChangeTime;if(this.responseChanged()&&timeSinceLastChange>this.AUTO_SAVE_WAIT&&!this.errorOnLastSave){this.save()}},handleResponseChanged:function(){var isNotBlank=!this.response().every(function(element){return $.trim(element)===""});this.submitEnabled(isNotBlank);if(this.responseChanged()){this.saveEnabled(isNotBlank);this.previewEnabled(isNotBlank);this.saveStatus(gettext("This response has not been saved."));this.unsavedWarningEnabled(true)}this.lastChangeTime=Date.now()},save:function(){this.errorOnLastSave=false;this.saveStatus(gettext("Saving..."));this.baseView.toggleActionError("save",null);this.unsavedWarningEnabled(false);var view=this;var savedResponse=this.response();this.server.save(savedResponse).done(function(){view.savedResponse=savedResponse;var currentResponse=view.response();var currentResponseIsEmpty=currentResponse.every(function(element){return element===""});view.submitEnabled(!currentResponseIsEmpty);var currentResponseEqualsSaved=currentResponse.every(function(element,index){return element===savedResponse[index]});if(currentResponseEqualsSaved){view.saveEnabled(false);view.saveStatus(gettext("This response has been saved but not submitted."))}}).fail(function(errMsg){view.saveStatus(gettext("Error"));view.baseView.toggleActionError("save",errMsg);view.errorOnLastSave=true})},submit:function(){this.submitEnabled(false);var view=this;var baseView=this.baseView;var fileDefer=$.Deferred();if(view.files!==null&&!view.fileUploaded){var msg=gettext("Do you want to upload your file before submitting?");if(confirm(msg)){fileDefer=view.fileUpload()}else{view.submitEnabled(true);return}}else{fileDefer.resolve()}fileDefer.pipe(function(){return view.confirmSubmission().pipe(function(){var submission=view.response();baseView.toggleActionError("response",null);return view.server.submit(submission)})}).done($.proxy(view.moveToNextStep,view)).fail(function(errCode,errMsg){if(errCode==="ENOMULTI"){view.moveToNextStep()}else{if(errMsg){baseView.toggleActionError("submit",errMsg)}view.submitEnabled(true)}})},moveToNextStep:function(){this.load();this.baseView.loadAssessmentModules();this.unsavedWarningEnabled(false)},confirmSubmission:function(){var msg=gettext("You're about to submit your response for this assignment. After you submit this response, you can't change it or submit a new response.");return $.Deferred(function(defer){if(confirm(msg)){defer.resolve()}else{defer.reject()}})},prepareUpload:function(files,uploadType){this.files=null;this.fileType=files[0].type;var ext=files[0].name.split(".").pop().toLowerCase();if(files[0].size>this.MAX_FILE_SIZE){this.baseView.toggleActionError("upload",gettext("File size must be 5MB or less."))}else if(uploadType==="image"&&this.data.ALLOWED_IMAGE_MIME_TYPES.indexOf(this.fileType)===-1){this.baseView.toggleActionError("upload",gettext("You can upload files with these file types: ")+"JPG, PNG or GIF")}else if(uploadType==="pdf-and-image"&&this.data.ALLOWED_FILE_MIME_TYPES.indexOf(this.fileType)===-1){this.baseView.toggleActionError("upload",gettext("You can upload files with these file types: ")+"JPG, PNG, GIF or PDF")}else if(uploadType==="custom"&&this.data.FILE_TYPE_WHITE_LIST.indexOf(ext)===-1){this.baseView.toggleActionError("upload",gettext("You can upload files with these file types: ")+this.data.FILE_TYPE_WHITE_LIST.join(", "))}else if(this.data.FILE_EXT_BLACK_LIST.indexOf(ext)!==-1){this.baseView.toggleActionError("upload",gettext("File type is not allowed."))}else{this.baseView.toggleActionError("upload",null);this.files=files}$("#file__upload").toggleClass("is--disabled",this.files===null)},fileUpload:function(){var view=this;var fileUpload=$("#file__upload");fileUpload.addClass("is--disabled");var handleError=function(errMsg){view.baseView.toggleActionError("upload",errMsg);fileUpload.removeClass("is--disabled")};return this.server.getUploadUrl(view.fileType,view.files[0].name).done(function(url){var file=view.files[0];view.fileUploader.upload(url,file).done(function(){view.fileUrl();view.baseView.toggleActionError("upload",null);view.fileUploaded=true}).fail(handleError)}).fail(handleError)},fileUrl:function(){var view=this;var file=$("#submission__answer__file",view.element);view.server.getDownloadUrl().done(function(url){if(file.prop("tagName")==="IMG"){file.attr("src",url)}else{file.attr("href",url)}return url})}};OpenAssessment.Rubric=function(element){this.element=element};OpenAssessment.Rubric.prototype={criterionFeedback:function(criterionFeedback){var selector="textarea.answer__value";var feedback={};$(selector,this.element).each(function(index,sel){if(typeof criterionFeedback!=="undefined"){$(sel).val(criterionFeedback[sel.name]);feedback[sel.name]=criterionFeedback[sel.name]}else{feedback[sel.name]=$(sel).val()}});return feedback},overallFeedback:function(overallFeedback){var selector=".assessment__rubric__question--feedback__value";if(typeof overallFeedback==="undefined"){return $(selector,this.element).val()}else{$(selector,this.element).val(overallFeedback)}},optionsSelected:function(optionsSelected){var selector="input[type=radio]";if(typeof optionsSelected==="undefined"){var options={};$(selector+":checked",this.element).each(function(index,sel){options[sel.name]=sel.value});return options}else{$(selector,this.element).prop("checked",false);$(selector,this.element).each(function(index,sel){if(optionsSelected.hasOwnProperty(sel.name)){if(sel.value===optionsSelected[sel.name]){$(sel).prop("checked",true)}}})}},canSubmitCallback:function(callback){var rubric=this;callback(rubric.canSubmit());$(this.element).on("change keyup drop paste",function(){callback(rubric.canSubmit())})},canSubmit:function(){var numChecked=$("input[type=radio]:checked",this.element).length;var numAvailable=$(".field--radio.assessment__rubric__question.has--options",this.element).length;var completedRequiredComments=true;$("textarea[required]",this.element).each(function(){var trimmedText=$.trim($(this).val());if(trimmedText===""){completedRequiredComments=false}});return numChecked===numAvailable&&completedRequiredComments},showCorrections:function(corrections){var selector="input[type=radio]";var hasErrors=false;$(selector,this.element).each(function(index,sel){var listItem=$(sel).parents(".assessment__rubric__question");if(corrections.hasOwnProperty(sel.name)){hasErrors=true;listItem.find(".message--incorrect").removeClass("is--hidden");listItem.find(".message--correct").addClass("is--hidden")}else{listItem.find(".message--correct").removeClass("is--hidden");listItem.find(".message--incorrect").addClass("is--hidden")}});return hasErrors}};OpenAssessment.SelfView=function(element,server,baseView){this.element=element;this.server=server;this.baseView=baseView;this.rubric=null};OpenAssessment.SelfView.prototype={load:function(){var view=this;this.server.render("self_assessment").done(function(html){$("#openassessment__self-assessment",view.element).replaceWith(html);view.server.renderLatex($("#openassessment__self-assessment",view.element));view.installHandlers()}).fail(function(){view.showLoadError("self-assessment")})},installHandlers:function(){var view=this;var sel=$("#openassessment__self-assessment",view.element);this.baseView.setUpCollapseExpand(sel);var rubricSelector=$("#self-assessment--001__assessment",this.element);if(rubricSelector.size()>0){var rubricElement=rubricSelector.get(0);this.rubric=new OpenAssessment.Rubric(rubricElement)}if(this.rubric!==null){this.rubric.canSubmitCallback($.proxy(this.selfSubmitEnabled,this))}sel.find("#self-assessment--001__assessment__submit").click(function(eventObject){eventObject.preventDefault();
view.selfAssess()})},selfSubmitEnabled:function(enabled){var button=$("#self-assessment--001__assessment__submit",this.element);if(typeof enabled==="undefined"){return!button.hasClass("is--disabled")}else{button.toggleClass("is--disabled",!enabled);return enabled}},selfAssess:function(){var view=this;var baseView=this.baseView;baseView.toggleActionError("self",null);view.selfSubmitEnabled(false);this.server.selfAssess(this.rubric.optionsSelected(),this.rubric.criterionFeedback(),this.rubric.overallFeedback()).done(function(){baseView.loadAssessmentModules();baseView.scrollToTop()}).fail(function(errMsg){baseView.toggleActionError("self",errMsg);view.selfSubmitEnabled(true)})}};OpenAssessment.StaffView=function(element,server,baseView){this.element=element;this.server=server;this.baseView=baseView};OpenAssessment.StaffView.prototype={load:function(){var view=this;this.server.render("staff_assessment").done(function(html){$("#openassessment__staff-assessment",view.element).replaceWith(html)}).fail(function(){view.baseView.showLoadError("staff-assessment")})}};!function(OpenAssessment){"use strict";OpenAssessment.StaffAreaView=function(element,server,baseView){this.element=element;this.server=server;this.baseView=baseView};OpenAssessment.StaffAreaView.prototype={load:function(){var view=this;if($(".openassessment__staff-area",view.element).length>0){this.server.render("staff_area").done(function(html){$(".openassessment__staff-area",view.element).replaceWith(html);view.server.renderLatex($(".openassessment__staff-area",view.element));view.installHandlers()}).fail(function(){view.baseView.showLoadError("staff_area")})}},loadStudentInfo:function(options){var view=this;var $staffTools=$(".openassessment__staff-tools",this.element);var $form=$staffTools.find(".openassessment_student_info_form");var studentUsername=$staffTools.find(".openassessment__student_username").val();var showFormError=function(errorMessage){$form.find(".form--error").text(errorMessage)};var deferred=$.Deferred();$(".openassessment__student-info",view.element).text("");if(studentUsername.trim()){this.server.studentInfo(studentUsername,options).done(function(html){showFormError("");$(".openassessment__student-info",view.element).replaceWith(html);$staffTools.on("click",".action--submit-cancel-submission",function(eventObject){eventObject.preventDefault();view.cancelSubmission($(this).data("submission-uuid"))});var handleChange=function(eventData){view.handleCommentChanged(eventData)};$staffTools.find(".cancel_submission_comments").on("change keyup drop paste",handleChange);var $rubric=$(".staff-assessment__assessment",view.element);if($rubric.size()>0){var rubricElement=$rubric.get(0);var rubric=new OpenAssessment.Rubric(rubricElement);rubric.canSubmitCallback($.proxy(view.staffSubmitEnabled,view));$(".wrapper--staff-assessment .action--submit",view.element).click(function(eventObject){var target=$(eventObject.currentTarget),rootElement=target.closest(".openassessment__student-info"),submissionID=rootElement.data("submission-uuid");eventObject.preventDefault();view.submitStaffAssessment(submissionID,rubric)})}deferred.resolve()}).fail(function(){showFormError(gettext("Unexpected server error."));deferred.reject()})}else{showFormError(gettext("You must provide a learner name."));deferred.reject()}return deferred.promise()},installHandlers:function(){var view=this;var $staffArea=$(".openassessment__staff-area",this.element);var $staffTools=$(".openassessment__staff-tools",$staffArea);var $staffInfo=$(".openassessment__student-info",$staffArea);if($staffArea.length<=0){return}this.baseView.setUpCollapseExpand($staffTools,function(){});this.baseView.setUpCollapseExpand($staffInfo,function(){});$staffArea.find(".ui-staff__button").click(function(eventObject){var $button=$(eventObject.currentTarget),panelClass=$button.data("panel"),$panel=$staffArea.find("."+panelClass).first();if($button.hasClass("is--active")){$button.removeClass("is--active");$panel.addClass("is--hidden")}else{$staffArea.find(".ui-staff__button").removeClass("is--active");$button.addClass("is--active");$staffArea.find(".wrapper--ui-staff").addClass("is--hidden");$panel.removeClass("is--hidden")}});$staffArea.find(".ui-staff_close_button").click(function(eventObject){var $button=$(eventObject.currentTarget),$panel=$button.closest(".wrapper--ui-staff");$staffArea.find(".ui-staff__button").removeClass("is--active");$panel.addClass("is--hidden")});$staffTools.find(".openassessment_student_info_form").submit(function(eventObject){eventObject.preventDefault();view.loadStudentInfo()});$staffTools.find(".action--submit-username").click(function(eventObject){eventObject.preventDefault();view.loadStudentInfo()});$staffTools.find(".action--submit-training").click(function(eventObject){eventObject.preventDefault();view.scheduleTraining()});$staffTools.find(".action--submit-unfinished-tasks").click(function(eventObject){eventObject.preventDefault();view.rescheduleUnfinishedTasks()})},scheduleTraining:function(){var view=this;this.server.scheduleTraining().done(function(msg){$(".schedule_training_message",view.element).text(msg)}).fail(function(errMsg){$(".schedule_training_message",view.element).text(errMsg)})},rescheduleUnfinishedTasks:function(){var view=this;this.server.rescheduleUnfinishedTasks().done(function(msg){$(".reschedule_unfinished_tasks_message",view.element).text(msg)}).fail(function(errMsg){$(".reschedule_unfinished_tasks_message",view.element).text(errMsg)})},cancelSubmission:function(submissionUUID){this.cancelSubmissionEnabled(false);var view=this;var comments=$(".cancel_submission_comments",this.element).val();this.server.cancelSubmission(submissionUUID,comments).done(function(){view.loadStudentInfo({expanded_view:"final-grade"})}).fail(function(errorMessage){$(".cancel-submission-error").html(_.escape(errorMessage))})},cancelSubmissionEnabled:function(enabled){var $cancelButton=$(".action--submit-cancel-submission",this.element);if(typeof enabled==="undefined"){return!$cancelButton.hasClass("is--disabled")}else{$cancelButton.toggleClass("is--disabled",!enabled)}},comment:function(text){var $submissionComments=$(".cancel_submission_comments",this.element);if(typeof text==="undefined"){return $submissionComments.val()}else{$submissionComments.val(text)}},handleCommentChanged:function(){var isBlank=$.trim(this.comment())!=="";this.cancelSubmissionEnabled(isBlank)},staffSubmitEnabled:function(enabled){var button=$(".wrapper--staff-assessment .action--submit",this.element);if(typeof enabled==="undefined"){return!button.hasClass("is--disabled")}else{button.toggleClass("is--disabled",!enabled);return enabled}},submitStaffAssessment:function(submissionID,rubric){var view=this;var baseView=this.baseView;baseView.toggleActionError("staff",null);view.staffSubmitEnabled(false);this.server.staffAssess(rubric.optionsSelected(),rubric.criterionFeedback(),rubric.overallFeedback(),submissionID).done(function(){view.loadStudentInfo({expanded_view:"final-grade"})}).fail(function(errorMessage){$(".staff-override-error").html(_.escape(errorMessage));view.staffSubmitEnabled(true)})}}}(OpenAssessment);OpenAssessment.StudentTrainingView=function(element,server,baseView){this.element=element;this.server=server;this.baseView=baseView;this.rubric=null};OpenAssessment.StudentTrainingView.prototype={load:function(){var view=this;this.server.render("student_training").done(function(html){$("#openassessment__student-training",view.element).replaceWith(html);view.server.renderLatex($("#openassessment__student-training",view.element));view.installHandlers()}).fail(function(){view.baseView.showLoadError("student-training")})},installHandlers:function(){var sel=$("#openassessment__student-training",this.element);var view=this;this.baseView.setUpCollapseExpand(sel);var rubricSelector=$("#student-training--001__assessment",this.element);if(rubricSelector.size()>0){var rubricElement=rubricSelector.get(0);this.rubric=new OpenAssessment.Rubric(rubricElement)}if(this.rubric!==null){this.rubric.canSubmitCallback($.proxy(this.assessButtonEnabled,this))}sel.find("#student-training--001__assessment__submit").click(function(eventObject){eventObject.preventDefault();view.assess()})},assess:function(){this.assessButtonEnabled(false);var options={};if(this.rubric!==null){options=this.rubric.optionsSelected()}var view=this;var baseView=this.baseView;this.server.trainingAssess(options).done(function(corrections){var incorrect=$("#openassessment__student-training--incorrect",view.element);var instructions=$("#openassessment__student-training--instructions",view.element);if(!view.rubric.showCorrections(corrections)){view.load();baseView.loadAssessmentModules();incorrect.addClass("is--hidden");instructions.removeClass("is--hidden")}else{instructions.addClass("is--hidden");incorrect.removeClass("is--hidden")}baseView.scrollToTop()}).fail(function(errMsg){baseView.toggleActionError("student-training",errMsg);view.assessButtonEnabled(true)})},assessButtonEnabled:function(isEnabled){var button=$("#student-training--001__assessment__submit",this.element);if(typeof isEnabled==="undefined"){return!button.hasClass("is--disabled")}else{button.toggleClass("is--disabled",!isEnabled)}}};
\ No newline at end of file
if(typeof OpenAssessment=="undefined"||!OpenAssessment){OpenAssessment={}}if(typeof window.gettext==="undefined"){window.gettext=function(text){return text}}if(typeof window.ngetgext==="undefined"){window.ngettext=function(singularText,pluralText,n){if(n>1){return pluralText}else{return singularText}}}if(typeof window.Logger==="undefined"){window.Logger={log:function(){}}}if(typeof window.MathJax==="undefined"){window.MathJax={Hub:{Typeset:function(){},Queue:function(){}}}}if(typeof OpenAssessment.Server==="undefined"||!OpenAssessment.Server){OpenAssessment.Server=function(runtime,element){this.runtime=runtime;this.element=element};var jsonContentType="application/json; charset=utf-8";OpenAssessment.Server.prototype={url:function(handler){return this.runtime.handlerUrl(this.element,handler)},render:function(component){var view=this;var url=this.url("render_"+component);return $.Deferred(function(defer){$.ajax({url:url,type:"POST",dataType:"html"}).done(function(data){defer.resolveWith(view,[data])}).fail(function(){defer.rejectWith(view,[gettext("This section could not be loaded.")])})}).promise()},renderLatex:function(element){element.filter(".allow--latex").each(function(){MathJax.Hub.Queue(["Typeset",MathJax.Hub,this])})},renderContinuedPeer:function(){var view=this;var url=this.url("render_peer_assessment");return $.Deferred(function(defer){$.ajax({url:url,type:"POST",dataType:"html",data:{continue_grading:true}}).done(function(data){defer.resolveWith(view,[data])}).fail(function(){defer.rejectWith(view,[gettext("This section could not be loaded.")])})}).promise()},studentInfo:function(studentUsername,options){var url=this.url("render_student_info");return $.Deferred(function(defer){$.ajax({url:url,type:"POST",dataType:"html",data:_.extend({student_username:studentUsername},options)}).done(function(data){defer.resolveWith(this,[data])}).fail(function(){defer.rejectWith(this,[gettext("This section could not be loaded.")])})}).promise()},staffGradeForm:function(){var url=this.url("render_staff_grade_form");return $.Deferred(function(defer){$.ajax({url:url,type:"POST",dataType:"html"}).done(function(data){defer.resolveWith(this,[data])}).fail(function(){defer.rejectWith(this,[gettext("The staff assessment form could not be loaded.")])})}).promise()},submit:function(submission){var url=this.url("submit");return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:JSON.stringify({submission:submission}),contentType:jsonContentType}).done(function(data){var success=data[0];if(success){var studentId=data[1];var attemptNum=data[2];defer.resolveWith(this,[studentId,attemptNum])}else{var errorNum=data[1];var errorMsg=data[2];defer.rejectWith(this,[errorNum,errorMsg])}}).fail(function(){defer.rejectWith(this,["AJAX",gettext("This response could not be submitted.")])})}).promise()},save:function(submission){var url=this.url("save_submission");return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:JSON.stringify({submission:submission}),contentType:jsonContentType}).done(function(data){if(data.success){defer.resolve()}else{defer.rejectWith(this,[data.msg])}}).fail(function(){defer.rejectWith(this,[gettext("This response could not be saved.")])})}).promise()},submitFeedbackOnAssessment:function(text,options){var url=this.url("submit_feedback");var payload=JSON.stringify({feedback_text:text,feedback_options:options});return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:payload,contentType:jsonContentType}).done(function(data){if(data.success){defer.resolve()}else{defer.rejectWith(this,[data.msg])}}).fail(function(){defer.rejectWith(this,[gettext("This feedback could not be submitted.")])})}).promise()},submitAssessment:function(assessmentType,payload){var url=this.url(assessmentType);return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:JSON.stringify(payload),contentType:jsonContentType}).done(function(data){if(data.success){defer.resolve()}else{defer.rejectWith(this,[data.msg])}}).fail(function(){defer.rejectWith(this,[gettext("This assessment could not be submitted.")])})}).promise()},peerAssess:function(optionsSelected,criterionFeedback,overallFeedback,submissionID){return this.submitAssessment("peer_assess",{options_selected:optionsSelected,criterion_feedback:criterionFeedback,overall_feedback:overallFeedback,submission_uuid:submissionID})},selfAssess:function(optionsSelected,criterionFeedback,overallFeedback){return this.submitAssessment("self_assess",{options_selected:optionsSelected,criterion_feedback:criterionFeedback,overall_feedback:overallFeedback})},staffAssess:function(optionsSelected,criterionFeedback,overallFeedback,submissionID){return this.submitAssessment("staff_assess",{options_selected:optionsSelected,criterion_feedback:criterionFeedback,overall_feedback:overallFeedback,submission_uuid:submissionID})},trainingAssess:function(optionsSelected){var url=this.url("training_assess");var payload=JSON.stringify({options_selected:optionsSelected});return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:payload,contentType:jsonContentType}).done(function(data){if(data.success){defer.resolveWith(this,[data.corrections])}else{defer.rejectWith(this,[data.msg])}}).fail(function(){defer.rejectWith(this,[gettext("This assessment could not be submitted.")])})})},scheduleTraining:function(){var url=this.url("schedule_training");return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:'""',contentType:jsonContentType}).done(function(data){if(data.success){defer.resolveWith(this,[data.msg])}else{defer.rejectWith(this,[data.msg])}}).fail(function(){defer.rejectWith(this,[gettext("This assessment could not be submitted.")])})})},rescheduleUnfinishedTasks:function(){var url=this.url("reschedule_unfinished_tasks");return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:'""',contentType:jsonContentType}).done(function(data){if(data.success){defer.resolveWith(this,[data.msg])}else{defer.rejectWith(this,[data.msg])}}).fail(function(){defer.rejectWith(this,[gettext("One or more rescheduling tasks failed.")])})})},updateEditorContext:function(options){var url=this.url("update_editor_context");var payload=JSON.stringify({prompts:options.prompts,feedback_prompt:options.feedbackPrompt,feedback_default_text:options.feedback_default_text,title:options.title,submission_start:options.submissionStart,submission_due:options.submissionDue,criteria:options.criteria,assessments:options.assessments,editor_assessments_order:options.editorAssessmentsOrder,file_upload_type:options.fileUploadType,white_listed_file_types:options.fileTypeWhiteList,allow_latex:options.latexEnabled,leaderboard_show:options.leaderboardNum});return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:payload,contentType:jsonContentType}).done(function(data){if(data.success){defer.resolve()}else{defer.rejectWith(this,[data.msg])}}).fail(function(){defer.rejectWith(this,[gettext("This problem could not be saved.")])})}).promise()},checkReleased:function(){var url=this.url("check_released");var payload='""';return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:payload,contentType:jsonContentType}).done(function(data){if(data.success){defer.resolveWith(this,[data.is_released])}else{defer.rejectWith(this,[data.msg])}}).fail(function(){defer.rejectWith(this,[gettext("The server could not be contacted.")])})}).promise()},getUploadUrl:function(contentType,filename){var url=this.url("upload_url");return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:JSON.stringify({contentType:contentType,filename:filename}),contentType:jsonContentType}).done(function(data){if(data.success){defer.resolve(data.url)}else{defer.rejectWith(this,[data.msg])}}).fail(function(){defer.rejectWith(this,[gettext("Could not retrieve upload url.")])})}).promise()},getDownloadUrl:function(){var url=this.url("download_url");return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:JSON.stringify({}),contentType:jsonContentType}).done(function(data){if(data.success){defer.resolve(data.url)}else{defer.rejectWith(this,[data.msg])}}).fail(function(){defer.rejectWith(this,[gettext("Could not retrieve download url.")])})}).promise()},cancelSubmission:function(submissionID,comments){var url=this.url("cancel_submission");var payload=JSON.stringify({submission_uuid:submissionID,comments:comments});return $.Deferred(function(defer){$.ajax({type:"POST",url:url,data:payload,contentType:jsonContentType}).done(function(data){if(data.success){defer.resolveWith(this,[data.msg])}}).fail(function(){defer.rejectWith(this,[gettext("The submission could not be removed from the grading pool.")])})}).promise()}}}if(typeof OpenAssessment=="undefined"||!OpenAssessment){OpenAssessment={}}if(typeof window.gettext==="undefined"){window.gettext=function(text){return text}}if(typeof window.ngetgext==="undefined"){window.ngettext=function(singularText,pluralText,n){if(n>1){return pluralText}else{return singularText}}}if(typeof window.Logger==="undefined"){window.Logger={log:function(){}}}if(typeof window.MathJax==="undefined"){window.MathJax={Hub:{Typeset:function(){},Queue:function(){}}}}OpenAssessment.BaseView=function(runtime,element,server,data){this.runtime=runtime;this.element=element;this.server=server;this.fileUploader=new OpenAssessment.FileUploader;this.responseView=new OpenAssessment.ResponseView(this.element,this.server,this.fileUploader,this,data);this.trainingView=new OpenAssessment.StudentTrainingView(this.element,this.server,this);this.selfView=new OpenAssessment.SelfView(this.element,this.server,this);this.peerView=new OpenAssessment.PeerView(this.element,this.server,this);this.staffView=new OpenAssessment.StaffView(this.element,this.server,this);this.gradeView=new OpenAssessment.GradeView(this.element,this.server,this);this.leaderboardView=new OpenAssessment.LeaderboardView(this.element,this.server,this);this.messageView=new OpenAssessment.MessageView(this.element,this.server,this);this.staffAreaView=new OpenAssessment.StaffAreaView(this.element,this.server,this)};OpenAssessment.BaseView.prototype={scrollToTop:function(){if($.scrollTo instanceof Function){$(window).scrollTo($("#openassessment__steps",this.element),800,{offset:-50})}},setUpCollapseExpand:function(parentElement){parentElement.on("click",".ui-toggle-visibility__control",function(eventData){var sel=$(eventData.target).closest(".ui-toggle-visibility");sel.toggleClass("is--collapsed")})},load:function(){this.responseView.load();this.loadAssessmentModules();this.staffAreaView.load()},loadAssessmentModules:function(){this.trainingView.load();this.peerView.load();this.staffView.load();this.selfView.load();this.gradeView.load();this.leaderboardView.load()},loadMessageView:function(){this.messageView.load()},toggleActionError:function(type,message){var element=this.element;var container=null;if(type==="save"){container=".response__submission__actions"}else if(type==="submit"||type==="peer"||type==="self"||type==="student-training"){container=".step__actions"}else if(type==="feedback_assess"){container=".submission__feedback__actions"}else if(type==="upload"){container="#upload__error"}if(container===null){if(message!==null){console.log(message)}}else{$(container+" .message__content",element).html("<p>"+(message?_.escape(message):"")+"</p>");$(container,element).toggleClass("has--error",message!==null)}},showLoadError:function(stepName,errorMessage){if(!errorMessage){errorMessage=gettext("Unable to load")}var $container=$("#openassessment__"+stepName);$container.toggleClass("has--error",true);$container.find(".step__status__value i").removeClass().addClass("icon fa fa-exclamation-triangle");$container.find(".step__status__value .copy").html(_.escape(errorMessage))}};function OpenAssessmentBlock(runtime,element,data){var server=new OpenAssessment.Server(runtime,element);var view=new OpenAssessment.BaseView(runtime,element,server,data);view.load()}OpenAssessment.FileUploader=function(){this.upload=function(url,file){return $.Deferred(function(defer){$.ajax({url:url,type:"PUT",data:file,async:false,processData:false,contentType:file.type}).done(function(){Logger.log("openassessment.upload_file",{fileName:file.name,fileSize:file.size,fileType:file.type});defer.resolve()}).fail(function(data,textStatus){defer.rejectWith(this,[textStatus])})}).promise()}};OpenAssessment.GradeView=function(element,server,baseView){this.element=element;this.server=server;this.baseView=baseView};OpenAssessment.GradeView.prototype={load:function(){var view=this;var baseView=this.baseView;this.server.render("grade").done(function(html){$("#openassessment__grade",view.element).replaceWith(html);view.server.renderLatex($("#openassessment__grade",view.element));view.installHandlers()}).fail(function(errMsg){baseView.showLoadError("grade",errMsg)})},installHandlers:function(){var sel=$("#openassessment__grade",this.element);this.baseView.setUpCollapseExpand(sel);var view=this;sel.find(".feedback__submit").click(function(eventObject){eventObject.preventDefault();view.submitFeedbackOnAssessment()})},feedbackText:function(text){if(typeof text==="undefined"){return $("#feedback__remarks__value",this.element).val()}else{$("#feedback__remarks__value",this.element).val(text)}},feedbackOptions:function(options){var view=this;if(typeof options==="undefined"){return $.map($(".feedback__overall__value:checked",view.element),function(element){return $(element).val()})}else{$(".feedback__overall__value",this.element).prop("checked",false);$.each(options,function(index,opt){$("#feedback__overall__value--"+opt,view.element).prop("checked",true)})}},setHidden:function(selector,hidden){selector.toggleClass("is--hidden",hidden);selector.attr("aria-hidden",hidden?"true":"false")},isHidden:function(selector){return selector.hasClass("is--hidden")&&selector.attr("aria-hidden")==="true"},feedbackState:function(newState){var containerSel=$(".submission__feedback__content",this.element);var instructionsSel=containerSel.find(".submission__feedback__instructions");var fieldsSel=containerSel.find(".submission__feedback__fields");var actionsSel=containerSel.find(".submission__feedback__actions");var transitionSel=containerSel.find(".transition__status");var messageSel=containerSel.find(".message--complete");if(typeof newState==="undefined"){var isSubmitting=containerSel.hasClass("is--transitioning")&&containerSel.hasClass("is--submitting")&&!this.isHidden(transitionSel)&&this.isHidden(messageSel)&&this.isHidden(instructionsSel)&&this.isHidden(fieldsSel)&&this.isHidden(actionsSel);var hasSubmitted=containerSel.hasClass("is--submitted")&&this.isHidden(transitionSel)&&!this.isHidden(messageSel)&&this.isHidden(instructionsSel)&&this.isHidden(fieldsSel)&&this.isHidden(actionsSel);var isOpen=!containerSel.hasClass("is--submitted")&&!containerSel.hasClass("is--transitioning")&&!containerSel.hasClass("is--submitting")&&this.isHidden(transitionSel)&&this.isHidden(messageSel)&&!this.isHidden(instructionsSel)&&!this.isHidden(fieldsSel)&&!this.isHidden(actionsSel);if(isOpen){return"open"}else if(isSubmitting){return"submitting"}else if(hasSubmitted){return"submitted"}else{throw"Invalid feedback state"}}else{if(newState==="open"){containerSel.toggleClass("is--transitioning",false);containerSel.toggleClass("is--submitting",false);containerSel.toggleClass("is--submitted",false);this.setHidden(instructionsSel,false);this.setHidden(fieldsSel,false);this.setHidden(actionsSel,false);this.setHidden(transitionSel,true);this.setHidden(messageSel,true)}else if(newState==="submitting"){containerSel.toggleClass("is--transitioning",true);containerSel.toggleClass("is--submitting",true);containerSel.toggleClass("is--submitted",false);this.setHidden(instructionsSel,true);this.setHidden(fieldsSel,true);this.setHidden(actionsSel,true);this.setHidden(transitionSel,false);this.setHidden(messageSel,true)}else if(newState==="submitted"){containerSel.toggleClass("is--transitioning",false);containerSel.toggleClass("is--submitting",false);containerSel.toggleClass("is--submitted",true);this.setHidden(instructionsSel,true);this.setHidden(fieldsSel,true);this.setHidden(actionsSel,true);this.setHidden(transitionSel,true);this.setHidden(messageSel,false)}}},submitFeedbackOnAssessment:function(){var view=this;var baseView=this.baseView;$(".feedback__submit",this.element).toggleClass("is--disabled",true);view.feedbackState("submitting");this.server.submitFeedbackOnAssessment(this.feedbackText(),this.feedbackOptions()).done(function(){view.feedbackState("submitted")}).fail(function(errMsg){baseView.toggleActionError("feedback_assess",errMsg)})}};OpenAssessment.LeaderboardView=function(element,server,baseView){this.element=element;this.server=server;this.baseView=baseView};OpenAssessment.LeaderboardView.prototype={load:function(){var view=this;var baseView=this.baseView;this.server.render("leaderboard").done(function(html){$("#openassessment__leaderboard",view.element).replaceWith(html);view.server.renderLatex($("#openassessment__leaderboard",view.element))}).fail(function(errMsg){baseView.showLoadError("leaderboard",errMsg)})}};OpenAssessment.MessageView=function(element,server,baseView){this.element=element;this.server=server;this.baseView=baseView};OpenAssessment.MessageView.prototype={load:function(){var view=this;var baseView=this.baseView;this.server.render("message").done(function(html){$("#openassessment__message",view.element).replaceWith(html);view.server.renderLatex($("#openassessment__message",view.element))}).fail(function(errMsg){baseView.showLoadError("message",errMsg)})}};OpenAssessment.PeerView=function(element,server,baseView){this.element=element;this.server=server;this.baseView=baseView;this.rubric=null};OpenAssessment.PeerView.prototype={load:function(){var view=this;this.server.render("peer_assessment").done(function(html){$("#openassessment__peer-assessment",view.element).replaceWith(html);view.server.renderLatex($("#openassessment__peer-assessment",view.element));view.installHandlers(false)}).fail(function(){view.baseView.showLoadError("peer-assessment")});view.baseView.loadMessageView()},loadContinuedAssessment:function(){var view=this;view.continueAssessmentEnabled(false);this.server.renderContinuedPeer().done(function(html){$("#openassessment__peer-assessment",view.element).replaceWith(html);view.server.renderLatex($("#openassessment__peer-assessment",view.element));view.installHandlers(true)}).fail(function(){view.baseView.showLoadError("peer-assessment");view.continueAssessmentEnabled(true)})},continueAssessmentEnabled:function(enabled){var button=$("#peer-assessment__continue__grading",this.element);if(typeof enabled==="undefined"){return!button.hasClass("is--disabled")}else{button.toggleClass("is--disabled",!enabled)}},installHandlers:function(isContinuedAssessment){var sel=$("#openassessment__peer-assessment",this.element);var view=this;this.baseView.setUpCollapseExpand(sel);var rubricSelector=$("#peer-assessment--001__assessment",this.element);if(rubricSelector.size()>0){var rubricElement=rubricSelector.get(0);this.rubric=new OpenAssessment.Rubric(rubricElement)}if(this.rubric!==null){this.rubric.canSubmitCallback($.proxy(view.peerSubmitEnabled,view))}sel.find("#peer-assessment--001__assessment__submit").click(function(eventObject){eventObject.preventDefault();if(!isContinuedAssessment){view.peerAssess()}else{view.continuedPeerAssess()}});sel.find("#peer-assessment__continue__grading").click(function(eventObject){eventObject.preventDefault();view.loadContinuedAssessment()})},peerSubmitEnabled:function(enabled){var button=$("#peer-assessment--001__assessment__submit",this.element);if(typeof enabled==="undefined"){return!button.hasClass("is--disabled")}else{button.toggleClass("is--disabled",!enabled);return enabled}},peerAssess:function(){var view=this;var baseView=view.baseView;this.peerAssessRequest(function(){baseView.loadAssessmentModules();baseView.scrollToTop()})},continuedPeerAssess:function(){var view=this;var gradeView=this.baseView.gradeView;var baseView=view.baseView;view.peerAssessRequest(function(){view.loadContinuedAssessment();gradeView.load();baseView.scrollToTop()})},peerAssessRequest:function(successFunction){var view=this;var uuid=$("#openassessment__peer-assessment").data("submission-uuid");view.baseView.toggleActionError("peer",null);view.peerSubmitEnabled(false);this.server.peerAssess(this.rubric.optionsSelected(),this.rubric.criterionFeedback(),this.rubric.overallFeedback(),uuid).done(successFunction).fail(function(errMsg){view.baseView.toggleActionError("peer",errMsg);view.peerSubmitEnabled(true)})}};OpenAssessment.ResponseView=function(element,server,fileUploader,baseView,data){this.element=element;this.server=server;this.fileUploader=fileUploader;this.baseView=baseView;this.savedResponse=[];this.files=null;this.fileType=null;this.lastChangeTime=Date.now();this.errorOnLastSave=false;this.autoSaveTimerId=null;this.data=data;this.fileUploaded=false};OpenAssessment.ResponseView.prototype={AUTO_SAVE_POLL_INTERVAL:2e3,AUTO_SAVE_WAIT:3e4,MAX_FILE_SIZE:5242880,load:function(){var view=this;this.server.render("submission").done(function(html){$("#openassessment__response",view.element).replaceWith(html);view.server.renderLatex($("#openassessment__response",view.element));view.installHandlers();view.setAutoSaveEnabled(true)}).fail(function(){view.baseView.showLoadError("response")})},installHandlers:function(){var sel=$("#openassessment__response",this.element);var view=this;var uploadType="";if(sel.find(".submission__answer__display__file").length){uploadType=sel.find(".submission__answer__display__file").data("upload-type")}this.baseView.setUpCollapseExpand(sel);this.savedResponse=this.response();var handleChange=function(){view.handleResponseChanged()};sel.find(".submission__answer__part__text__value").on("change keyup drop paste",handleChange);var handlePrepareUpload=function(eventData){view.prepareUpload(eventData.target.files,uploadType)};sel.find("input[type=file]").on("change",handlePrepareUpload);sel.find("#submission__preview__item").hide();sel.find("#step--response__submit").click(function(eventObject){eventObject.preventDefault();view.submit()});sel.find("#submission__save").click(function(eventObject){eventObject.preventDefault();view.save()});sel.find("#submission__preview").click(function(eventObject){eventObject.preventDefault();var previewText=sel.find(".submission__answer__part__text__value").val();var previewContainer=sel.find("#preview_content");previewContainer.html(previewText.replace(/\r\n|\r|\n/g,"<br />"));sel.find("#submission__preview__item").show();MathJax.Hub.Queue(["Typeset",MathJax.Hub,previewContainer[0]])});sel.find("#file__upload").click(function(eventObject){eventObject.preventDefault();$(".submission__answer__display__file",view.element).removeClass("is--hidden");view.fileUpload()})},setAutoSaveEnabled:function(enabled){if(enabled){if(this.autoSaveTimerId===null){this.autoSaveTimerId=setInterval($.proxy(this.autoSave,this),this.AUTO_SAVE_POLL_INTERVAL)}}else{if(this.autoSaveTimerId!==null){clearInterval(this.autoSaveTimerId)}}},submitEnabled:function(enabled){var sel=$("#step--response__submit",this.element);if(typeof enabled==="undefined"){return!sel.hasClass("is--disabled")}else{sel.toggleClass("is--disabled",!enabled);return enabled}},saveEnabled:function(enabled){var sel=$("#submission__save",this.element);if(typeof enabled==="undefined"){return!sel.hasClass("is--disabled")}else{sel.toggleClass("is--disabled",!enabled)}},previewEnabled:function(enabled){var sel=$("#submission__preview",this.element);if(typeof enabled==="undefined"){return!sel.hasClass("is--disabled")}else{sel.toggleClass("is--disabled",!enabled)}},saveStatus:function(msg){var sel=$("#response__save_status h3",this.element);if(typeof msg==="undefined"){return sel.text()}else{var label=gettext("Status of Your Response");sel.html('<span class="sr">'+_.escape(label)+":"+"</span>\n"+msg)}},unsavedWarningEnabled:function(enabled){if(typeof enabled==="undefined"){return window.onbeforeunload!==null}else{if(enabled){window.onbeforeunload=function(){return gettext("If you leave this page without saving or submitting your response, you'll lose any work you've done on the response.")}}else{window.onbeforeunload=null}}},response:function(texts){var sel=$(".submission__answer__part__text__value",this.element);if(typeof texts==="undefined"){return sel.map(function(){return $.trim($(this).val())}).get()}else{sel.map(function(index){$(this).val(texts[index])})}},responseChanged:function(){var savedResponse=this.savedResponse;return this.response().some(function(element,index){return element!==savedResponse[index]})},autoSave:function(){var timeSinceLastChange=Date.now()-this.lastChangeTime;if(this.responseChanged()&&timeSinceLastChange>this.AUTO_SAVE_WAIT&&!this.errorOnLastSave){this.save()}},handleResponseChanged:function(){var isNotBlank=!this.response().every(function(element){return $.trim(element)===""});this.submitEnabled(isNotBlank);if(this.responseChanged()){this.saveEnabled(isNotBlank);this.previewEnabled(isNotBlank);this.saveStatus(gettext("This response has not been saved."));this.unsavedWarningEnabled(true)}this.lastChangeTime=Date.now()},save:function(){this.errorOnLastSave=false;this.saveStatus(gettext("Saving..."));this.baseView.toggleActionError("save",null);this.unsavedWarningEnabled(false);var view=this;var savedResponse=this.response();this.server.save(savedResponse).done(function(){view.savedResponse=savedResponse;var currentResponse=view.response();var currentResponseIsEmpty=currentResponse.every(function(element){return element===""});view.submitEnabled(!currentResponseIsEmpty);var currentResponseEqualsSaved=currentResponse.every(function(element,index){return element===savedResponse[index]});if(currentResponseEqualsSaved){view.saveEnabled(false);view.saveStatus(gettext("This response has been saved but not submitted."))}}).fail(function(errMsg){view.saveStatus(gettext("Error"));view.baseView.toggleActionError("save",errMsg);view.errorOnLastSave=true})},submit:function(){this.submitEnabled(false);var view=this;var baseView=this.baseView;var fileDefer=$.Deferred();if(view.files!==null&&!view.fileUploaded){var msg=gettext("Do you want to upload your file before submitting?");if(confirm(msg)){fileDefer=view.fileUpload()}else{view.submitEnabled(true);return}}else{fileDefer.resolve()}fileDefer.pipe(function(){return view.confirmSubmission().pipe(function(){var submission=view.response();baseView.toggleActionError("response",null);return view.server.submit(submission)})}).done($.proxy(view.moveToNextStep,view)).fail(function(errCode,errMsg){if(errCode==="ENOMULTI"){view.moveToNextStep()}else{if(errMsg){baseView.toggleActionError("submit",errMsg)}view.submitEnabled(true)}})},moveToNextStep:function(){this.load();this.baseView.loadAssessmentModules();this.unsavedWarningEnabled(false)},confirmSubmission:function(){var msg=gettext("You're about to submit your response for this assignment. After you submit this response, you can't change it or submit a new response.");return $.Deferred(function(defer){if(confirm(msg)){defer.resolve()}else{defer.reject()}})},prepareUpload:function(files,uploadType){this.files=null;this.fileType=files[0].type;var ext=files[0].name.split(".").pop().toLowerCase();if(files[0].size>this.MAX_FILE_SIZE){this.baseView.toggleActionError("upload",gettext("File size must be 5MB or less."))}else if(uploadType==="image"&&this.data.ALLOWED_IMAGE_MIME_TYPES.indexOf(this.fileType)===-1){this.baseView.toggleActionError("upload",gettext("You can upload files with these file types: ")+"JPG, PNG or GIF")}else if(uploadType==="pdf-and-image"&&this.data.ALLOWED_FILE_MIME_TYPES.indexOf(this.fileType)===-1){this.baseView.toggleActionError("upload",gettext("You can upload files with these file types: ")+"JPG, PNG, GIF or PDF")}else if(uploadType==="custom"&&this.data.FILE_TYPE_WHITE_LIST.indexOf(ext)===-1){this.baseView.toggleActionError("upload",gettext("You can upload files with these file types: ")+this.data.FILE_TYPE_WHITE_LIST.join(", "))}else if(this.data.FILE_EXT_BLACK_LIST.indexOf(ext)!==-1){this.baseView.toggleActionError("upload",gettext("File type is not allowed."))}else{this.baseView.toggleActionError("upload",null);this.files=files}$("#file__upload").toggleClass("is--disabled",this.files===null)},fileUpload:function(){var view=this;var fileUpload=$("#file__upload");fileUpload.addClass("is--disabled");var handleError=function(errMsg){view.baseView.toggleActionError("upload",errMsg);fileUpload.removeClass("is--disabled")};return this.server.getUploadUrl(view.fileType,view.files[0].name).done(function(url){var file=view.files[0];view.fileUploader.upload(url,file).done(function(){view.fileUrl();view.baseView.toggleActionError("upload",null);view.fileUploaded=true}).fail(handleError)}).fail(handleError)},fileUrl:function(){var view=this;var file=$("#submission__answer__file",view.element);view.server.getDownloadUrl().done(function(url){if(file.prop("tagName")==="IMG"){file.attr("src",url)}else{file.attr("href",url)}return url})}};OpenAssessment.Rubric=function(element){this.element=element};OpenAssessment.Rubric.prototype={criterionFeedback:function(criterionFeedback){var selector="textarea.answer__value";var feedback={};$(selector,this.element).each(function(index,sel){if(typeof criterionFeedback!=="undefined"){$(sel).val(criterionFeedback[sel.name]);feedback[sel.name]=criterionFeedback[sel.name]}else{feedback[sel.name]=$(sel).val()}});return feedback},overallFeedback:function(overallFeedback){var selector=".assessment__rubric__question--feedback__value";if(typeof overallFeedback==="undefined"){return $(selector,this.element).val()}else{$(selector,this.element).val(overallFeedback)}},optionsSelected:function(optionsSelected){var selector="input[type=radio]";if(typeof optionsSelected==="undefined"){var options={};$(selector+":checked",this.element).each(function(index,sel){options[sel.name]=sel.value});return options}else{$(selector,this.element).prop("checked",false);$(selector,this.element).each(function(index,sel){if(optionsSelected.hasOwnProperty(sel.name)){if(sel.value===optionsSelected[sel.name]){$(sel).prop("checked",true)}}})}},canSubmitCallback:function(callback){var rubric=this;callback(rubric.canSubmit());$(this.element).on("change keyup drop paste",function(){callback(rubric.canSubmit())})},canSubmit:function(){var numChecked=$("input[type=radio]:checked",this.element).length;var numAvailable=$(".field--radio.assessment__rubric__question.has--options",this.element).length;var completedRequiredComments=true;$("textarea[required]",this.element).each(function(){var trimmedText=$.trim($(this).val());if(trimmedText===""){completedRequiredComments=false}});return numChecked===numAvailable&&completedRequiredComments},showCorrections:function(corrections){var selector="input[type=radio]";var hasErrors=false;$(selector,this.element).each(function(index,sel){var listItem=$(sel).parents(".assessment__rubric__question");if(corrections.hasOwnProperty(sel.name)){hasErrors=true;listItem.find(".message--incorrect").removeClass("is--hidden");listItem.find(".message--correct").addClass("is--hidden")}else{listItem.find(".message--correct").removeClass("is--hidden");listItem.find(".message--incorrect").addClass("is--hidden")}});return hasErrors}};OpenAssessment.SelfView=function(element,server,baseView){this.element=element;this.server=server;this.baseView=baseView;this.rubric=null};OpenAssessment.SelfView.prototype={load:function(){var view=this;this.server.render("self_assessment").done(function(html){$("#openassessment__self-assessment",view.element).replaceWith(html);view.server.renderLatex($("#openassessment__self-assessment",view.element));view.installHandlers()}).fail(function(){view.showLoadError("self-assessment")})},installHandlers:function(){var view=this;var sel=$("#openassessment__self-assessment",view.element);this.baseView.setUpCollapseExpand(sel);var rubricSelector=$("#self-assessment--001__assessment",this.element);
if(rubricSelector.size()>0){var rubricElement=rubricSelector.get(0);this.rubric=new OpenAssessment.Rubric(rubricElement)}if(this.rubric!==null){this.rubric.canSubmitCallback($.proxy(this.selfSubmitEnabled,this))}sel.find("#self-assessment--001__assessment__submit").click(function(eventObject){eventObject.preventDefault();view.selfAssess()})},selfSubmitEnabled:function(enabled){var button=$("#self-assessment--001__assessment__submit",this.element);if(typeof enabled==="undefined"){return!button.hasClass("is--disabled")}else{button.toggleClass("is--disabled",!enabled);return enabled}},selfAssess:function(){var view=this;var baseView=this.baseView;baseView.toggleActionError("self",null);view.selfSubmitEnabled(false);this.server.selfAssess(this.rubric.optionsSelected(),this.rubric.criterionFeedback(),this.rubric.overallFeedback()).done(function(){baseView.loadAssessmentModules();baseView.scrollToTop()}).fail(function(errMsg){baseView.toggleActionError("self",errMsg);view.selfSubmitEnabled(true)})}};OpenAssessment.StaffView=function(element,server,baseView){this.element=element;this.server=server;this.baseView=baseView};OpenAssessment.StaffView.prototype={load:function(){var view=this;this.server.render("staff_assessment").done(function(html){$("#openassessment__staff-assessment",view.element).replaceWith(html)}).fail(function(){view.baseView.showLoadError("staff-assessment")})}};!function(OpenAssessment){"use strict";OpenAssessment.StaffAreaView=function(element,server,baseView){this.element=element;this.server=server;this.baseView=baseView};OpenAssessment.StaffAreaView.prototype={load:function(onSuccessCallback){var view=this;if($(".openassessment__staff-area",view.element).length>0){this.server.render("staff_area").done(function(html){$(".openassessment__staff-area",view.element).replaceWith(html);view.server.renderLatex($(".openassessment__staff-area",view.element));view.installHandlers();if(onSuccessCallback){onSuccessCallback()}}).fail(function(){view.baseView.showLoadError("staff_area")})}},loadStudentInfo:function(options){var view=this;var $manageLearnersTab=$(".openassessment__staff-tools",this.element);var $form=$manageLearnersTab.find(".openassessment_student_info_form");var studentUsername=$manageLearnersTab.find(".openassessment__student_username").val();var showFormError=function(errorMessage){$form.find(".form--error").text(errorMessage)};var deferred=$.Deferred();$(".openassessment__student-info",view.element).text("");if(studentUsername.trim()){this.server.studentInfo(studentUsername,options).done(function(html){showFormError("");$(".openassessment__student-info",view.element).replaceWith(html);$manageLearnersTab.on("click",".action--submit-cancel-submission",function(eventObject){eventObject.preventDefault();view.cancelSubmission($(this).data("submission-uuid"))});var handleChange=function(eventData){view.handleCommentChanged(eventData)};$manageLearnersTab.find(".cancel_submission_comments").on("change keyup drop paste",handleChange);var $rubric=$manageLearnersTab.find(".staff-assessment__assessment");if($rubric.size()>0){var rubricElement=$rubric.get(0);var rubric=new OpenAssessment.Rubric(rubricElement);rubric.canSubmitCallback($.proxy(view.staffSubmitEnabled,view,$manageLearnersTab));$manageLearnersTab.find(".wrapper--staff-assessment .action--submit",view.element).click(function(eventObject){var target=$(eventObject.currentTarget),rootElement=target.closest(".openassessment__student-info"),submissionID=rootElement.data("submission-uuid");eventObject.preventDefault();view.submitStaffOverride(submissionID,rubric,$manageLearnersTab)})}deferred.resolve()}).fail(function(){showFormError(gettext("Unexpected server error."));deferred.reject()})}else{showFormError(gettext("You must provide a learner name."));deferred.reject()}return deferred.promise()},loadStaffGradeForm:function(){var view=this;var $staffGradeTab=$(".openassessment__staff-grading",this.element);var isCollapsed=$staffGradeTab.find(".staff__grade__control").hasClass("is--collapsed");var deferred=$.Deferred();var showFormError=function(errorMessage){$staffGradeTab.find(".staff__grade__form--error").text(errorMessage)};if(isCollapsed&&!this.staffGradeFormLoaded){this.staffGradeFormLoaded=true;this.server.staffGradeForm().done(function(html){showFormError("");$staffGradeTab.find(".staff__grade__form").replaceWith(html);var $rubric=$staffGradeTab.find(".staff-assessment__assessment");if($rubric.size()>0){var rubricElement=$rubric.get(0);var rubric=new OpenAssessment.Rubric(rubricElement);rubric.canSubmitCallback($.proxy(view.staffSubmitEnabled,view,$staffGradeTab));$staffGradeTab.find(".wrapper--staff-assessment .action--submit").click(function(eventObject){var submissionID=$staffGradeTab.find(".staff__grade__form").data("submission-uuid");eventObject.preventDefault();view.submitStaffGrade(submissionID,rubric,$staffGradeTab,$(eventObject.currentTarget).hasClass("continue_grading--action"))})}deferred.resolve()}).fail(function(){showFormError(gettext("Unexpected server error."));view.staffGradeFormLoaded=false;deferred.reject()})}return deferred.promise()},installHandlers:function(){var view=this;var $staffArea=$(".openassessment__staff-area",this.element);var $staffTools=$(".openassessment__staff-tools",$staffArea);var $staffInfo=$(".openassessment__student-info",$staffArea);var $staffGradeTool=$(".openassessment__staff-grading",$staffArea);if($staffArea.length<=0){return}this.baseView.setUpCollapseExpand($staffTools,function(){});this.baseView.setUpCollapseExpand($staffInfo,function(){});this.baseView.setUpCollapseExpand($staffGradeTool,function(){});$staffArea.find(".ui-staff__button").click(function(eventObject){var $button=$(eventObject.currentTarget),panelClass=$button.data("panel"),$panel=$staffArea.find("."+panelClass).first();if($button.hasClass("is--active")){$button.removeClass("is--active");$panel.addClass("is--hidden")}else{$staffArea.find(".ui-staff__button").removeClass("is--active");$button.addClass("is--active");$staffArea.find(".wrapper--ui-staff").addClass("is--hidden");$panel.removeClass("is--hidden")}});$staffArea.find(".ui-staff_close_button").click(function(eventObject){var $button=$(eventObject.currentTarget),$panel=$button.closest(".wrapper--ui-staff");$staffArea.find(".ui-staff__button").removeClass("is--active");$panel.addClass("is--hidden")});$staffTools.find(".openassessment_student_info_form").submit(function(eventObject){eventObject.preventDefault();view.loadStudentInfo()});$staffTools.find(".action--submit-username").click(function(eventObject){eventObject.preventDefault();view.loadStudentInfo()});$staffTools.find(".action--submit-training").click(function(eventObject){eventObject.preventDefault();view.scheduleTraining()});$staffTools.find(".action--submit-unfinished-tasks").click(function(eventObject){eventObject.preventDefault();view.rescheduleUnfinishedTasks()});$staffGradeTool.find(".staff__grade__show-form").click(function(){view.loadStaffGradeForm()})},scheduleTraining:function(){var view=this;this.server.scheduleTraining().done(function(msg){$(".schedule_training_message",view.element).text(msg)}).fail(function(errMsg){$(".schedule_training_message",view.element).text(errMsg)})},rescheduleUnfinishedTasks:function(){var view=this;this.server.rescheduleUnfinishedTasks().done(function(msg){$(".reschedule_unfinished_tasks_message",view.element).text(msg)}).fail(function(errMsg){$(".reschedule_unfinished_tasks_message",view.element).text(errMsg)})},cancelSubmission:function(submissionUUID){this.cancelSubmissionEnabled(false);var view=this;var comments=$(".cancel_submission_comments",this.element).val();this.server.cancelSubmission(submissionUUID,comments).done(function(){view.loadStudentInfo({expanded_view:"final-grade"})}).fail(function(errorMessage){$(".cancel-submission-error").html(_.escape(errorMessage))})},cancelSubmissionEnabled:function(enabled){var $cancelButton=$(".action--submit-cancel-submission",this.element);if(typeof enabled==="undefined"){return!$cancelButton.hasClass("is--disabled")}else{$cancelButton.toggleClass("is--disabled",!enabled)}},comment:function(text){var $submissionComments=$(".cancel_submission_comments",this.element);if(typeof text==="undefined"){return $submissionComments.val()}else{$submissionComments.val(text)}},handleCommentChanged:function(){var isBlank=$.trim(this.comment())!=="";this.cancelSubmissionEnabled(isBlank)},staffSubmitEnabled:function(scope,enabled){var button=scope.find(".wrapper--staff-assessment .action--submit");if(typeof enabled==="undefined"){return!button.hasClass("is--disabled")}else{button.toggleClass("is--disabled",!enabled);return enabled}},submitStaffOverride:function(submissionID,rubric,scope){var view=this;var successCallback=function(){view.loadStudentInfo({expanded_view:"final-grade"})};this.callStaffAssess(submissionID,rubric,scope,successCallback,".staff-override-error")},submitStaffGrade:function(submissionID,rubric,scope,continueGrading){var view=this;var successCallback=function(){view.staffGradeFormLoaded=false;var showFullGradeTab=function(){$(".button-staff-grading").click();if(continueGrading){$(".staff__grade__show-form",view.element).click()}};view.load(showFullGradeTab)};this.callStaffAssess(submissionID,rubric,scope,successCallback,".staff-grade-error")},callStaffAssess:function(submissionID,rubric,scope,successCallback,errorSelector){var view=this;view.staffSubmitEnabled(scope,false);this.server.staffAssess(rubric.optionsSelected(),rubric.criterionFeedback(),rubric.overallFeedback(),submissionID).done(successCallback).fail(function(errorMessage){scope.find(errorSelector).html(_.escape(errorMessage));view.staffSubmitEnabled(scope,true)})}}}(OpenAssessment);OpenAssessment.StudentTrainingView=function(element,server,baseView){this.element=element;this.server=server;this.baseView=baseView;this.rubric=null};OpenAssessment.StudentTrainingView.prototype={load:function(){var view=this;this.server.render("student_training").done(function(html){$("#openassessment__student-training",view.element).replaceWith(html);view.server.renderLatex($("#openassessment__student-training",view.element));view.installHandlers()}).fail(function(){view.baseView.showLoadError("student-training")})},installHandlers:function(){var sel=$("#openassessment__student-training",this.element);var view=this;this.baseView.setUpCollapseExpand(sel);var rubricSelector=$("#student-training--001__assessment",this.element);if(rubricSelector.size()>0){var rubricElement=rubricSelector.get(0);this.rubric=new OpenAssessment.Rubric(rubricElement)}if(this.rubric!==null){this.rubric.canSubmitCallback($.proxy(this.assessButtonEnabled,this))}sel.find("#student-training--001__assessment__submit").click(function(eventObject){eventObject.preventDefault();view.assess()})},assess:function(){this.assessButtonEnabled(false);var options={};if(this.rubric!==null){options=this.rubric.optionsSelected()}var view=this;var baseView=this.baseView;this.server.trainingAssess(options).done(function(corrections){var incorrect=$("#openassessment__student-training--incorrect",view.element);var instructions=$("#openassessment__student-training--instructions",view.element);if(!view.rubric.showCorrections(corrections)){view.load();baseView.loadAssessmentModules();incorrect.addClass("is--hidden");instructions.removeClass("is--hidden")}else{instructions.addClass("is--hidden");incorrect.removeClass("is--hidden")}baseView.scrollToTop()}).fail(function(errMsg){baseView.toggleActionError("student-training",errMsg);view.assessButtonEnabled(true)})},assessButtonEnabled:function(isEnabled){var button=$("#student-training--001__assessment__submit",this.element);if(typeof isEnabled==="undefined"){return!button.hasClass("is--disabled")}else{button.toggleClass("is--disabled",!isEnabled)}}};
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -20,26 +20,28 @@ describe('OpenAssessment.StaffAreaView', function() {
var StubServer = function() {
this.studentTemplate = 'oa_student_info.html';
this.staffAreaTemplate = 'oa_staff_area.html';
this.staffGradeFormTemplate = 'oa_staff_grade_learners_assessment.html';
// Remember which fragments have been loaded
this.fragmentsLoaded = [];
// Render the template for the staff info view
this.render = function(component) {
this.mockLoadTemplate = function(template) {
var server = this;
this.fragmentsLoaded.push(component);
return $.Deferred(function(defer) {
var fragment = readFixtures(server.staffAreaTemplate);
var fragment = readFixtures(template);
defer.resolveWith(server, [fragment]);
});
};
this.studentInfo = function() {
// Render the template for the staff info view
this.render = function(component) {
var server = this;
return $.Deferred(function(defer) {
var fragment = readFixtures(server.studentTemplate);
defer.resolveWith(server, [fragment]);
});
this.fragmentsLoaded.push(component);
return this.mockLoadTemplate(server.staffAreaTemplate);
};
this.studentInfo = function() {
return this.mockLoadTemplate(server.studentTemplate);
};
this.scheduleTraining = function() {
......@@ -64,8 +66,11 @@ describe('OpenAssessment.StaffAreaView', function() {
return successPromise;
};
this.data = {};
this.staffGradeForm = function() {
return this.mockLoadTemplate(server.staffGradeFormTemplate);
};
this.data = {};
};
// Stubs
......@@ -74,6 +79,7 @@ describe('OpenAssessment.StaffAreaView', function() {
/**
* Create a staff area view.
*
* @param {dict} serverResponse An optional fake response from the server.
* @param {string} staffAreaTemplate - An optional template to use.
* @returns {OpenAssessment.StaffAreaView} The staff area view.
......@@ -95,7 +101,8 @@ describe('OpenAssessment.StaffAreaView', function() {
/**
* Initialize the staff area view, then check whether it makes
* an AJAX call to populate itself.
* @param shouldCall True if an AJAX call should be made.
*
* @param {bool} shouldCall - True if an AJAX call should be made.
*/
var assertStaffAreaAjaxCall = function(shouldCall) {
createStaffArea();
......@@ -247,7 +254,7 @@ describe('OpenAssessment.StaffAreaView', function() {
});
});
describe('Staff Tools', function() {
describe('Manage Individual Learners', function() {
var chooseStudent = function(view, studentName) {
var studentNameField = $('.openassessment__student_username', view.element),
submitButton = $('.action--submit-username', view.element);
......@@ -278,6 +285,15 @@ describe('OpenAssessment.StaffAreaView', function() {
.toBe('You must provide a learner name.');
});
it('shows an error message when failing to load the student info', function() {
var staffArea = createStaffArea();
server.studentInfo = failWith(server);
chooseStudent(staffArea, 'testStudent');
expect($('.openassessment_student_info_form .form--error', staffArea.element).first().text().trim()).toBe(
'Unexpected server error.'
);
});
describe('Submission Management', function() {
it('updates submission cancellation button when comments changes', function() {
// Prevent the server's response from resolving,
......@@ -348,9 +364,12 @@ describe('OpenAssessment.StaffAreaView', function() {
});
};
var getAssessment = function(staffArea) {
return $('.openassessment__staff-tools .wrapper--staff-assessment', staffArea.element);
};
var submitAssessment = function(staffArea) {
var $assessment = $('.wrapper--staff-assessment', staffArea.element),
$submitButton = $('.action--submit', $assessment);
var $submitButton = $('.action--submit', getAssessment(staffArea.element));
$submitButton.click();
};
......@@ -358,7 +377,7 @@ describe('OpenAssessment.StaffAreaView', function() {
var staffArea = createStaffArea(),
$assessment, $submitButton;
chooseStudent(staffArea, 'testStudent');
$assessment = $('.wrapper--staff-assessment', staffArea.element);
$assessment = getAssessment(staffArea.element);
$submitButton = $('.action--submit', $assessment);
expect($submitButton).toHaveClass('is--disabled');
fillAssessment($assessment);
......@@ -378,7 +397,7 @@ describe('OpenAssessment.StaffAreaView', function() {
);
// Fill in and submit the assessment
$assessment = $('.wrapper--staff-assessment', staffArea.element);
$assessment = getAssessment(staffArea.element);
fillAssessment($assessment);
server.studentTemplate = 'oa_staff_graded_submission.html';
submitAssessment(staffArea);
......@@ -396,11 +415,10 @@ describe('OpenAssessment.StaffAreaView', function() {
serverErrorMessage = 'Mock server error',
$assessment;
chooseStudent(staffArea, 'testStudent');
$assessment = $('.wrapper--staff-assessment', staffArea.element);
$assessment = getAssessment(staffArea.element);
fillAssessment($assessment);
// Submit the assessment but return a server error message
staffArea.comment('Cancellation reason.');
server.staffAssess = failWith(server, serverErrorMessage);
submitAssessment(staffArea);
......@@ -410,7 +428,7 @@ describe('OpenAssessment.StaffAreaView', function() {
});
});
describe('Staff Info', function() {
describe('View Assignment Statistics', function() {
beforeEach(function() {
loadFixtures('oa_base_course_staff.html');
});
......@@ -427,4 +445,130 @@ describe('OpenAssessment.StaffAreaView', function() {
expect($staffInfoPanel).toHaveClass('is--hidden');
});
});
describe('Grade Available Responses', function() {
var showInstructorAssessmentForm = function(staffArea) {
$('.staff__grade__show-form', staffArea.element).click();
};
var fillAssessment = function($assessment) {
$('#staff__assessment__rubric__question--2__feedback', $assessment).val('Text response');
$('.question__answers', $assessment).each(function() {
$('input[type="radio"]', this).first().click();
});
};
var getAssessment = function(staffArea) {
return $('.openassessment__staff-grading .wrapper--staff-assessment', staffArea.element);
};
var submitAssessment = function(staffArea) {
var $submitButton = $('.action--submit', getAssessment(staffArea.element));
$submitButton.click();
};
beforeEach(function() {
loadFixtures('oa_base_course_staff.html');
});
it('enables both submit buttons when all required fields are specified', function() {
var staffArea = createStaffArea({}, 'oa_staff_area_full_grading.html'),
$assessment, $submitButtons;
showInstructorAssessmentForm(staffArea);
$assessment = getAssessment(staffArea.element);
$submitButtons = $('.action--submit', $assessment);
expect($submitButtons.length).toBe(2);
expect($submitButtons).toHaveClass('is--disabled');
fillAssessment($assessment);
expect($submitButtons).not.toHaveClass('is--disabled');
});
it('can submit a staff grade', function() {
var staffArea = createStaffArea({}, 'oa_staff_area_full_grading.html'),
$assessment, $gradeSection;
showInstructorAssessmentForm(staffArea);
$assessment = getAssessment(staffArea.element);
// Verify that the submission is shown for the first user
expect($('.staff-assessment__display__title', $assessment).text().trim()).toBe(
'Response for: mock_user'
);
// Fill in and submit the assessment
fillAssessment($assessment);
server.staffGradeFormTemplate = 'oa_staff_grade_learners_assessment_2.html';
submitAssessment(staffArea);
// Verify that the assessment form has been removed
expect($('.staff__grade__form', staffArea.element).html().trim()).toBe('');
});
it('can submit a staff grade and receive another submission', function() {
var staffArea = createStaffArea({}, 'oa_staff_area_full_grading.html'),
$assessment;
showInstructorAssessmentForm(staffArea);
// Verify that the submission is shown for the first user
expect($('.staff-assessment__display__title', staffArea.element).text().trim()).toBe(
'Response for: mock_user'
);
// Fill in and click the button to submit and request another submission
$assessment = getAssessment(staffArea.element);
fillAssessment($assessment);
server.staffGradeFormTemplate = 'oa_staff_grade_learners_assessment_2.html';
$('.continue_grading--action', $assessment).click();
// Verify that the submission is shown for the second learner
expect($('.staff-assessment__display__title', staffArea.element).text().trim()).toBe(
'Response for: mock_user_2'
);
});
it('shows an error message when failing to load the staff grade form', function() {
var staffArea = createStaffArea({}, 'oa_staff_area_full_grading.html'),
$assessment, $submitButtons;
server.staffGradeForm = failWith(server);
showInstructorAssessmentForm(staffArea);
expect($('.staff__grade__form--error', staffArea.element).first().text().trim()).toBe(
'Unexpected server error.'
);
});
it('shows an error message when a staff grade request fails', function() {
var staffArea = createStaffArea({}, 'oa_staff_area_full_grading.html'),
serverErrorMessage = 'Mock server error',
$assessment;
showInstructorAssessmentForm(staffArea);
$assessment = getAssessment(staffArea.element);
fillAssessment($assessment);
// Submit the assessment but return a server error message
server.staffAssess = failWith(server, serverErrorMessage);
submitAssessment(staffArea);
// Verify that the error message is shown
expect($('.staff-grade-error', staffArea.element).first().text().trim()).toBe(serverErrorMessage);
});
it('shows the number of ungraded and checked out submissions', function() {
var staffArea = createStaffArea({}, 'oa_staff_area_full_grading.html'),
$assessment;
expect($('.staff__grade__value').text().trim()).toBe("10 Available and 2 Checked Out");
showInstructorAssessmentForm(staffArea);
// Render a different staff area teamplate the next time around so counts can update.
server.staffAreaTemplate = 'oa_staff_area_full_grading_2.html';
// Fill in assessment and make sure the counts re-render.
$assessment = getAssessment(staffArea.element);
fillAssessment($assessment);
server.staffGradeFormTemplate = 'oa_staff_grade_learners_assessment_2.html';
submitAssessment(staffArea);
expect($('.staff__grade__value').text().trim()).toBe("9 Available and 0 Checked Out");
});
});
});
......@@ -22,8 +22,12 @@
/**
* Load the staff area.
*
* @param {function} onSuccessCallback an optional callback to be executed when the
* server successfully returns the staff area HTML. This callback will be the last thing
* executed, after rendering and installing click handlers.
*/
load: function() {
load: function(onSuccessCallback) {
var view = this;
// If we're course staff, the base template should contain a section
......@@ -35,6 +39,9 @@
$('.openassessment__staff-area', view.element).replaceWith(html);
view.server.renderLatex($('.openassessment__staff-area', view.element));
view.installHandlers();
if (onSuccessCallback) {
onSuccessCallback();
}
}).fail(function() {
view.baseView.showLoadError('staff_area');
});
......@@ -47,14 +54,14 @@
* to the given student's current workflow.
*
* @param {object} options An optional set of options to render the section.
* @returns {promise} A promise representing the successful oading
* @returns {promise} A promise representing the successful loading
* of the student info section.
*/
loadStudentInfo: function(options) {
var view = this;
var $staffTools = $('.openassessment__staff-tools', this.element);
var $form = $staffTools.find('.openassessment_student_info_form');
var studentUsername = $staffTools.find('.openassessment__student_username').val();
var $manageLearnersTab = $('.openassessment__staff-tools', this.element);
var $form = $manageLearnersTab.find('.openassessment_student_info_form');
var studentUsername = $manageLearnersTab.find('.openassessment__student_username').val();
var showFormError = function(errorMessage) {
$form.find('.form--error').text(errorMessage);
};
......@@ -72,34 +79,33 @@
$('.openassessment__student-info', view.element).replaceWith(html);
// Install key handler for cancel submission button.
$staffTools.on('click', '.action--submit-cancel-submission', function(eventObject) {
$manageLearnersTab.on('click', '.action--submit-cancel-submission', function(eventObject) {
eventObject.preventDefault();
view.cancelSubmission($(this).data('submission-uuid'));
});
// Install change handler for textarea (to enable cancel submission button)
var handleChange = function(eventData) { view.handleCommentChanged(eventData); };
$staffTools.find('.cancel_submission_comments')
.on('change keyup drop paste', handleChange);
$manageLearnersTab.find('.cancel_submission_comments').on('change keyup drop paste', handleChange);
// Initialize the rubric
var $rubric = $('.staff-assessment__assessment', view.element);
var $rubric = $manageLearnersTab.find('.staff-assessment__assessment');
if ($rubric.size() > 0) {
var rubricElement = $rubric.get(0);
var rubric = new OpenAssessment.Rubric(rubricElement);
// Install a change handler for rubric options to enable/disable the submit button
rubric.canSubmitCallback($.proxy(view.staffSubmitEnabled, view));
rubric.canSubmitCallback($.proxy(view.staffSubmitEnabled, view, $manageLearnersTab));
// Install a click handler for the submit button
$('.wrapper--staff-assessment .action--submit', view.element).click(
$manageLearnersTab.find('.wrapper--staff-assessment .action--submit', view.element).click(
function(eventObject) {
var target = $(eventObject.currentTarget),
rootElement = target.closest('.openassessment__student-info'),
submissionID = rootElement.data('submission-uuid');
eventObject.preventDefault();
view.submitStaffAssessment(submissionID, rubric);
view.submitStaffOverride(submissionID, rubric, $manageLearnersTab);
}
);
}
......@@ -116,6 +122,59 @@
},
/**
* Upon request, loads the staff grade/assessment section of the staff area.
* This allows staff grading when staff assessment is a required step.
*
* @returns {promise} A promise representing the successful loading
* of the staff grade (assessment) section.
*/
loadStaffGradeForm: function() {
var view = this;
var $staffGradeTab = $('.openassessment__staff-grading', this.element);
var isCollapsed = $staffGradeTab.find('.staff__grade__control').hasClass("is--collapsed");
var deferred = $.Deferred();
var showFormError = function(errorMessage) {
$staffGradeTab.find('.staff__grade__form--error').text(errorMessage);
};
if (isCollapsed && !this.staffGradeFormLoaded) {
this.staffGradeFormLoaded = true;
this.server.staffGradeForm().done(function(html) {
showFormError('');
// Load the HTML and install event handlers
$staffGradeTab.find('.staff__grade__form').replaceWith(html);
var $rubric = $staffGradeTab.find('.staff-assessment__assessment');
if ($rubric.size() > 0) {
var rubricElement = $rubric.get(0);
var rubric = new OpenAssessment.Rubric(rubricElement);
// Install a change handler for rubric options to enable/disable the submit button
rubric.canSubmitCallback($.proxy(view.staffSubmitEnabled, view, $staffGradeTab));
// Install a click handler for the submit buttons
$staffGradeTab.find('.wrapper--staff-assessment .action--submit').click(
function(eventObject) {
var submissionID = $staffGradeTab.find('.staff__grade__form').data('submission-uuid');
eventObject.preventDefault();
view.submitStaffGrade(submissionID, rubric, $staffGradeTab,
$(eventObject.currentTarget).hasClass('continue_grading--action')
);
}
);
}
deferred.resolve();
}).fail(function() {
showFormError(gettext('Unexpected server error.'));
view.staffGradeFormLoaded = false;
deferred.reject();
});
}
return deferred.promise();
},
/**
* Install event handlers for the view.
*/
installHandlers: function() {
......@@ -123,6 +182,7 @@
var $staffArea = $('.openassessment__staff-area', this.element);
var $staffTools = $('.openassessment__staff-tools', $staffArea);
var $staffInfo = $('.openassessment__student-info', $staffArea);
var $staffGradeTool = $('.openassessment__staff-grading', $staffArea);
if ($staffArea.length <= 0) {
return;
......@@ -130,6 +190,7 @@
this.baseView.setUpCollapseExpand($staffTools, function() {});
this.baseView.setUpCollapseExpand($staffInfo, function() {});
this.baseView.setUpCollapseExpand($staffGradeTool, function() {});
// Install a click handler for the staff button panel
$staffArea.find('.ui-staff__button').click(
......@@ -190,6 +251,13 @@
view.rescheduleUnfinishedTasks();
}
);
// Install a click handler for showing the staff grading form.
$staffGradeTool.find('.staff__grade__show-form').click(
function() {
view.loadStaffGradeForm();
}
);
},
/**
......@@ -294,13 +362,15 @@
},
/**
* Enable/disable the staff assessment submit button.
* Enable/disable submit button(s) for staff grading or staff override.
*
* @param {element} scope An ancestor element for the submit button (to allow for shared
* classes in different form).
* @param {boolean} enabled If specified, sets the state of the button.
* @returns {boolean} Whether the button is enabled
*/
staffSubmitEnabled: function(enabled) {
var button = $('.wrapper--staff-assessment .action--submit', this.element);
staffSubmitEnabled: function(scope, enabled) {
var button = scope.find('.wrapper--staff-assessment .action--submit');
if (typeof enabled === 'undefined') {
return !button.hasClass('is--disabled');
} else {
......@@ -310,30 +380,71 @@
},
/**
* Submit the staff assessment.
* Submit the staff assessment override.
*
* @param {string} submissionID The ID of the submission to be submitted.
* @param {element} rubric The rubric element to be assessed.
* @param {element} scope An ancestor element for the submit button (to allow for shared
* classes in different form).
*/
submitStaffAssessment: function(submissionID, rubric) {
// Send the assessment to the server
submitStaffOverride: function(submissionID, rubric, scope) {
var view = this;
var baseView = this.baseView;
baseView.toggleActionError('staff', null);
view.staffSubmitEnabled(false);
this.server.staffAssess(
rubric.optionsSelected(), rubric.criterionFeedback(), rubric.overallFeedback(), submissionID
).done(function() {
var successCallback = function() {
// Note: we ignore any message returned from the server and instead
// re-render the student info with the "Learner's Final Grade"
// section expanded. This section will show the learner's
// final grade and in the future should include details of
// the staff override itself.
view.loadStudentInfo({expanded_view: 'final-grade'});
}).fail(function(errorMessage) {
$('.staff-override-error').html(_.escape(errorMessage));
view.staffSubmitEnabled(true);
};
this.callStaffAssess(submissionID, rubric, scope, successCallback, '.staff-override-error');
},
/**
* Submit the staff grade, and check out another learner for grading if continueGrading is true.
*
* @param {string} submissionID The ID of the submission to be submitted.
* @param {element} rubric The rubric element to be assessed.
* @param {element} scope An ancestor element for the submit button (to allow for shared
* classes in different form).
* @param {boolean} continueGrading If true, another learner will be marked as "In Progress",
* and a new grading form will be rendered with the learner's answer.
*/
submitStaffGrade: function(submissionID, rubric, scope, continueGrading) {
var view = this;
var successCallback = function() {
view.staffGradeFormLoaded = false;
var showFullGradeTab = function() {
// Need to show the staff grade component again, unfortunately requiring a global selector.
$('.button-staff-grading').click();
if (continueGrading) {
$('.staff__grade__show-form', view.element).click();
}
};
view.load(showFullGradeTab);
};
this.callStaffAssess(submissionID, rubric, scope, successCallback, '.staff-grade-error');
},
/**
* Make the server call to submit the staff assessment.
*
* @param {string} submissionID The ID of the submission to be submitted.
* @param {element} rubric The rubric element to be assessed.
* @param {element} scope An ancestor element for the submit button (to allow for shared
* classes in different form).
* @param {function} successCallback A function to execute on success.
* @param {string} errorSelector a CSS class selector for displaying error messages.
*/
callStaffAssess: function(submissionID, rubric, scope, successCallback, errorSelector) {
var view = this;
view.staffSubmitEnabled(scope, false);
this.server.staffAssess(
rubric.optionsSelected(), rubric.criterionFeedback(), rubric.overallFeedback(), submissionID
).done(successCallback).fail(function(errorMessage) {
scope.find(errorSelector).html(_.escape(errorMessage));
view.staffSubmitEnabled(scope, true);
});
}
};
......
......@@ -116,6 +116,27 @@ if (typeof OpenAssessment.Server === "undefined" || !OpenAssessment.Server) {
},
/**
* Renders the next submission for staff grading.
*
* @returns {promise} A JQuery promise, which resolves with the HTML of the rendered section
* fails with an error message.
*/
staffGradeForm: function() {
var url = this.url('render_staff_grade_form');
return $.Deferred(function(defer) {
$.ajax({
url: url,
type: "POST",
dataType: "html"
}).done(function(data) {
defer.resolveWith(this, [data]);
}).fail(function() {
defer.rejectWith(this, [gettext('The staff assessment form could not be loaded.')]);
});
}).promise();
},
/**
* Send a submission to the XBlock.
*
* @param {string} submission The text of the student's submission.
......
......@@ -4,8 +4,7 @@
// NOTES:
// * staff-centric UI used for reporting/debugging
.wrapper--xblock {
.openassessment {
// --------------------
// general: staff UI
......@@ -81,6 +80,12 @@
.ui-staff__content__section {
padding-bottom: ($baseline-v/2);
@extend %wipe-last-child;
.wrapper--input,
.wrapper--content {
padding: $baseline-v ($baseline-h/2);
background-color: $bg-content;
}
}
// --------------------
......@@ -105,9 +110,18 @@
}
}
// UI - status (table)
.staff-info__status, .staff-info__classifierset {
.openassessment_student_info_form {
margin-bottom: ($baseline-v/2);
.list--actions__item {
@include text-align(left);
.action--submit-username {
@extend %btn--secondary;
margin-left: 0;
}
}
}
.staff-info__status__table, .staff-info__classifierset__table {
......@@ -155,29 +169,186 @@
}
}
}
// staff assessments
.wrapper--staff-assessment {
margin-top: ($baseline-v/2);
padding-top: ($baseline-v/2);
border-top: 1px solid $color-decorative-tertiary;
}
.staff-assessment__display {
@extend %ui-subsection;
}
.staff-assessment__display__header {
@include clearfix();
span {
@extend %t-strong; // FIX: needed due to DOM structure
}
// UI - cancel submission (action)
.staff-info__workflow-cancellation {
.staff-assessment__display__title {
@extend %t-heading;
margin-bottom: ($baseline-v/2);
color: $heading-secondary-color;
}
}
.staff-info__cancel-submission__content {
.staff-assessment__display__response {
@extend %ui-subsection-content;
@extend %copy-3;
@extend %ui-content-longanswer;
@extend %ui-well;
color: $copy-color;
}
.comments__label {
color: $copy-secondary-color;
// assessment form
.staff-assessment__assessment {
// fields
.assessment__fields {
margin-bottom: $baseline-v;
}
// rubric question
.assessment__rubric__question {
@extend %ui-rubric-question;
}
.cancel_submission_comments {
width: 100%;
min-height: ($baseline-v*5);
text-align: left;
// rubric options
.question__answers {
@extend %ui-rubric-answers;
}
.list--actions {
.action--submit {
margin: ($baseline-v/2) 0;
// general feedback question
.assessment__rubric__question--feedback {
.wrapper--input {
margin-top: $baseline-v;
}
.question__title__copy {
@include margin-left(0);
white-space: pre-wrap;
}
textarea {
@extend %ui-content-longanswer;
min-height: ($baseline-v*5);
}
}
}
// Styling for staff grade tab ("Grade Available Responses").
.ui-staff {
.staff__grade__control {
border-top: ($baseline-v/4) solid $color-decorative-tertiary;
background: $bg-content;
background-color: $bg-content;
.staff__grade__title {
@include text-align(left);
@include float(none);
padding: 0 $baseline-v;
display: block;
width: 100%;
.staff__grade__show-form {
@extend %btn-reset;
@extend %t-superheading;
@include fontSize($f-size-medium);
background-color: $bg-content;
text-transform: none;
letter-spacing: normal;
padding: 0;
}
}
.staff__grade__status {
display: inline-block;
padding: 0 $baseline-v;
@include media($bp-dm) {
margin-top: 0;
@include float(right);
position: relative;
top: -($baseline-v*2);
}
@include media($bp-dl) {
margin-top: 0;
@include float(right);
position: relative;
top: -($baseline-v*2);
}
@include media($bp-dx) {
margin-top: 0;
@include float(right);
position: relative;
top: -($baseline-v*2);
}
.staff__grade__value {
border-radius: ($baseline-v/10);
padding: ($baseline-v/4) ($baseline-h/4);
background: $color-decorative-tertiary;
line-height: 0;
@include media($bp-ds) {
display: block;
}
@include media($bp-dm) {
display: block;
}
@include media($bp-dl) {
display: block;
}
@include media($bp-dx) {
display: block;
}
}
.copy {
@extend %t-score;
color: $heading-color;
}
}
.submit_assessment--action {
display: inline;
}
.wrapper--input {
padding-top: 0;
}
}
// Override the default color for h3 (for elements that can be toggled).
.ui-toggle-visibility .ui-toggle-visibility__control .staff__grade__title {
color: $action-primary-color;
}
}
// UI - cancel submission (action)
.staff-info__workflow-cancellation {
.staff-info__cancel-submission__content {
.comments__label {
color: $copy-secondary-color;
}
.cancel_submission_comments {
width: 100%;
min-height: ($baseline-v*5);
text-align: left;
}
}
}
}
......@@ -44,23 +44,14 @@ $link-hover: $edx-blue-l1 !default; // from our Pattern Library http://ux.edx.or
padding: 10px;
.ui-staff__button {
margin-left: ($baseline-v/2);
@extend %btn-reset;
padding: ($baseline-v/4) ($baseline-v/2);
border-radius: ($baseline-v/4);
text-transform: uppercase;
color: $lighter-base-font-color;
background-color: $shadow-l2;
// Remove button styling
font-family: "Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;
font-size: 12px;
-webkit-appearance: none;
background-image: none;
text-shadow: none;
box-shadow: none;
border: none;
border-image: none;
&.is--active {
color: white;
background-color: $edx-pink;
......@@ -103,14 +94,6 @@ $link-hover: $edx-blue-l1 !default; // from our Pattern Library http://ux.edx.or
}
}
.staff-info__status {
.action--submit {
@extend %btn--secondary;
@extend %action-2;
@include margin(($baseline-v/2), ($baseline-v/2), ($baseline-v/2), ($baseline-v/2));
}
}
.staff-info__student {
.label {
color: $heading-staff-color;
......@@ -121,12 +104,6 @@ $link-hover: $edx-blue-l1 !default; // from our Pattern Library http://ux.edx.or
}
}
.action--submit {
@extend %btn--secondary;
@extend %action-2;
@include margin(($baseline-v/2), ($baseline-v/2), ($baseline-v/2), ($baseline-v/2));
}
.title {
@extend %hd-2;
color: $heading-staff-color;
......@@ -161,89 +138,9 @@ $link-hover: $edx-blue-l1 !default; // from our Pattern Library http://ux.edx.or
}
}
.staff-info__cancel-submission__content,
.staff-info__staff-override__content {
padding: $baseline-v ($baseline-h/2);
background-color: white;
}
.value {
width: $max-width/2;
}
// staff assessments
.wrapper--staff-assessment {
margin-top: ($baseline-v/2);
padding-top: ($baseline-v/2);
border-top: 1px solid $color-decorative-tertiary;
.action--submit {
@extend .action--submit;
}
}
.staff-assessment__display {
@extend %ui-subsection;
}
.staff-assessment__display__header {
@include clearfix();
span {
@extend %t-strong; // FIX: needed due to DOM structure
}
.staff-assessment__display__title {
@extend %t-heading;
margin-bottom: ($baseline-v/2);
color: $heading-secondary-color;
}
}
.staff-assessment__display__response {
@extend %ui-subsection-content;
@extend %copy-3;
@extend %ui-content-longanswer;
@extend %ui-well;
color: $copy-color;
}
// assessment form
.staff-assessment__assessment {
// fields
.assessment__fields {
margin-bottom: $baseline-v;
}
// rubric question
.assessment__rubric__question {
@extend %ui-rubric-question;
}
// rubric options
.question__answers {
@extend %ui-rubric-answers;
}
// general feedback question
.assessment__rubric__question--feedback {
.wrapper--input {
margin-top: $baseline-v;
}
.question__title__copy {
@include margin-left(0);
white-space: pre-wrap;
}
textarea {
@extend %ui-content-longanswer;
min-height: ($baseline-v*5);
}
}
}
}
// --------------------
......@@ -1211,14 +1108,7 @@ $link-hover: $edx-blue-l1 !default; // from our Pattern Library http://ux.edx.or
max-width: 100%;
}
// Developer SASS for Continued Grading.
.openassessment__steps__step {
.action--continue--grading {
@extend .action--submit;
}
}
#openassessment__leaderboard{
#openassessment__leaderboard {
font-family: "Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;
.step__counter, .step__counter:before {
......
......@@ -196,11 +196,11 @@
.staff-info__workflow-cancellation {
margin-bottom: ($baseline-v) !important;
}
}
.staff-info__student {
// 'p' elements in LMS have a color set on them.
.student__answer__display__content p {
color: inherit;
.wrapper--staff-assessment {
// 'p' elements in LMS have a color set on them.
.student__answer__display__content p {
color: inherit;
}
}
}
......@@ -29,6 +29,56 @@
padding: 0;
}
// --------------------
// overall actions
// --------------------
.list--actions {
margin-bottom: ($baseline-v/2);
@include text-align(center);
@include media($bp-ds) {
@include text-align(right);
}
@include media($bp-dm) {
@include text-align(right);
}
@include media($bp-dl) {
@include text-align(right);
}
@include media($bp-dx) {
@include text-align(right);
}
// STATE: actions has an error
&.has--error {
.message {
margin-bottom: $baseline-v;
@include text-align(left);
}
}
.action--submit {
@extend %btn--primary;
@extend %action-2;
margin-left: ($baseline-v/2);
.copy, .icon {
display: inline;
}
.icon {
@extend %icon-2;
}
.fa-caret-right:before {
@include transform(rotate(bidi-rotate-angle(0deg)));
}
}
}
// --------------------
// steps
......@@ -235,55 +285,6 @@
}
}
// step actions
.step__actions {
margin-bottom: ($baseline-v/2);
@include text-align(center);
@include media($bp-ds) {
@include text-align(right);
}
@include media($bp-dm) {
@include text-align(right);
}
@include media($bp-dl) {
@include text-align(right);
}
@include media($bp-dx) {
@include text-align(right);
}
.action--submit {
@extend %btn--primary;
@extend %action-2;
.copy, .icon {
display: inline;
}
.icon {
@extend %icon-2;
}
.fa-caret-right:before {
@include transform(rotate(bidi-rotate-angle(0deg)));
}
}
// STATE: actions has an error
&.has--error {
.message {
margin-bottom: $baseline-v;
@include text-align(left);
}
}
}
// STATE: step is loading
&.is--loading {
......@@ -540,6 +541,10 @@
display: inline-block;
vertical-align: middle;
@include margin-left(($baseline-h/4));
.list--actions {
@include text-align(left);
}
}
.response__submission__status__title {
......@@ -549,11 +554,14 @@
.response__submission__actions {
.list--actions__item {
@include text-align(left);
}
.action--save {
@extend %btn--secondary;
@extend %action-2;
display: block;
@include text-align(center);
margin-bottom: ($baseline-v/2);
min-width: 215px;
......@@ -1125,7 +1133,7 @@
@extend %ui-subsection-content;
padding-top: 0;
.list--actions {
.list--actions {
padding: 0;
}
......
......@@ -106,6 +106,16 @@
border-radius: ($baseline-v/10);
}
%btn-reset {
font-family: "Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;
font-size: 12px;
-webkit-appearance: none;
background-image: none;
text-shadow: none;
box-shadow: none;
border: none;
border-image: none;
}
// buttons - primary (assuming dark bg/light copy)
%btn--primary {
......
<openassessment>
<title>Open Assessment Test</title>
<prompts>
<prompt>
<description>Given the state of the world today, what do you think should be done to combat poverty? Please answer in a short essay of 200-300 words.</description>
</prompt>
<prompt>
<description>Given the state of the world today, what do you think should be done to combat pollution?</description>
</prompt>
</prompts>
<rubric>
<criterion>
<name>Concise</name>
<prompt>How concise is it?</prompt>
<option points="0">
<name>Neal Stephenson (late)</name>
<explanation>Neal Stephenson explanation</explanation>
</option>
<option points="1">
<name>HP Lovecraft</name>
<explanation>HP Lovecraft explanation</explanation>
</option>
<option points="3">
<name>Robert Heinlein</name>
<explanation>Robert Heinlein explanation</explanation>
</option>
<option points="4">
<name>Neal Stephenson (early)</name>
<explanation>Neal Stephenson (early) explanation</explanation>
</option>
<option points="5">
<name>Earnest Hemingway</name>
<explanation>Earnest Hemingway</explanation>
</option>
</criterion>
<criterion>
<name>Clear-headed</name>
<prompt>How clear is the thinking?</prompt>
<option points="0">
<name>Yogi Berra</name>
<explanation>Yogi Berra explanation</explanation>
</option>
<option points="1">
<name>Hunter S. Thompson</name>
<explanation>Hunter S. Thompson explanation</explanation>
</option>
<option points="2">
<name>Robert Heinlein</name>
<explanation>Robert Heinlein explanation</explanation>
</option>
<option points="3">
<name>Isaac Asimov</name>
<explanation>Isaac Asimov explanation</explanation>
</option>
<option points="10">
<name>Spock</name>
<explanation>Spock explanation</explanation>
</option>
</criterion>
<criterion>
<name>Form</name>
<prompt>Lastly, how is its form? Punctuation, grammar, and spelling all count.</prompt>
<option points="0">
<name>lolcats</name>
<explanation>lolcats explanation</explanation>
</option>
<option points="1">
<name>Facebook</name>
<explanation>Facebook explanation</explanation>
</option>
<option points="2">
<name>Reddit</name>
<explanation>Reddit explanation</explanation>
</option>
<option points="3">
<name>metafilter</name>
<explanation>metafilter explanation</explanation>
</option>
<option points="4">
<name>Usenet, 1996</name>
<explanation>Usenet, 1996 explanation</explanation>
</option>
<option points="5">
<name>The Elements of Style</name>
<explanation>The Elements of Style explanation</explanation>
</option>
</criterion>
</rubric>
<assessments>
<assessment name="staff-assessment" />
</assessments>
</openassessment>
......@@ -608,6 +608,88 @@ class TestCourseStaff(XBlockHandlerTestCase):
self.assertIn("The learner submission has been removed from peer", resp['msg'])
self.assertEqual(True, resp['success'])
@scenario('data/staff_grade_scenario.xml', user_id='Bob')
def test_staff_assessment_counts(self, xblock):
"""
Verify the staff assessment counts (ungraded and checked out)
as shown in the staff grading tool when staff assessment is required.
"""
_, context = xblock.get_staff_path_and_context()
self._verify_staff_assessment_context(context, True, 0, 0)
# Simulate that we are course staff
xblock.xmodule_runtime = self._create_mock_runtime(
xblock.scope_ids.usage_id, True, False, "Bob"
)
bob_item = STUDENT_ITEM.copy()
bob_item["item_id"] = xblock.scope_ids.usage_id
# Create a submission for Bob, and corresponding workflow.
self._create_submission(bob_item, {'text': "Bob Answer"}, [])
# Verify the count as shown in the staff grading tool.
_, context = xblock.get_staff_path_and_context()
self._verify_staff_assessment_context(context, True, 1, 0)
# Check out the assessment for grading and ensure that the count changes.
self.request(xblock, 'render_staff_grade_form', json.dumps({}))
_, context = xblock.get_staff_path_and_context()
self._verify_staff_assessment_context(context, True, 0, 1)
@scenario('data/example_based_assessment.xml', user_id='Bob')
def test_staff_assessment_counts_not_required(self, xblock):
"""
Verify the staff assessment counts (ungraded and checked out) are
not present in the context when staff assessment is not required.
"""
xblock.xmodule_runtime = self._create_mock_runtime(
xblock.scope_ids.usage_id, True, True, "Bob"
)
_, context = xblock.get_staff_path_and_context()
self._verify_staff_assessment_context(context, False)
@scenario('data/staff_grade_scenario.xml', user_id='Bob')
def test_staff_assessment_form(self, xblock):
"""
Smoke test that the staff assessment form renders when staff assessment
is required.
"""
permission_denied = "You do not have permission to access ORA staff grading."
no_submissions_available = "No other learner responses are available for grading at this time."
submission_text = "Grade me, please!"
resp = self.request(xblock, 'render_staff_grade_form', json.dumps({})).decode('utf-8')
self.assertIn(permission_denied, resp)
self.assertNotIn(no_submissions_available, resp)
# Simulate that we are course staff
xblock.xmodule_runtime = self._create_mock_runtime(
xblock.scope_ids.usage_id, True, False, "Bob"
)
resp = self.request(xblock, 'render_staff_grade_form', json.dumps({})).decode('utf-8')
self.assertNotIn(permission_denied, resp)
self.assertIn(no_submissions_available, resp)
self.assertNotIn(submission_text, resp)
bob_item = STUDENT_ITEM.copy()
bob_item["item_id"] = xblock.scope_ids.usage_id
# Create a submission for Bob, and corresponding workflow.
self._create_submission(bob_item, {'text': submission_text}, [])
resp = self.request(xblock, 'render_staff_grade_form', json.dumps({})).decode('utf-8')
self.assertNotIn(no_submissions_available, resp)
self.assertIn(submission_text, resp)
def _verify_staff_assessment_context(self, context, required, ungraded=None, in_progress=None):
self.assertEquals(required, context['staff_assessment_required'])
if not required:
self.assertNotIn('staff_assessment_ungraded', context)
self.assertNotIn('staff_assessment_in_progress', context)
else:
self.assertEqual(ungraded, context['staff_assessment_ungraded'])
self.assertEqual(in_progress, context['staff_assessment_in_progress'])
def _create_mock_runtime(
self,
item_id,
......
......@@ -98,10 +98,10 @@ class StaffAreaA11yTest(OpenAssessmentA11yTest):
"""
Test the accessibility of the staff area.
This is testing a problem with "self assessment only".
This is testing a problem with "staff assessment only".
"""
def setUp(self):
super(StaffAreaA11yTest, self).setUp('self_only', staff=True)
super(StaffAreaA11yTest, self).setUp('staff_only', staff=True)
self.staff_area_page = StaffAreaPage(self.browser, self.problem_loc)
def test_staff_tools_panel(self):
......@@ -120,12 +120,26 @@ class StaffAreaA11yTest(OpenAssessmentA11yTest):
self.staff_area_page.click_staff_toolbar_button("staff-info")
self._check_a11y(self.staff_area_page)
def test_staff_grading_panel(self):
"""
Check the accessibility of the "Staff Grading" panel
"""
self.submission_page.visit().submit_response(self.SUBMISSION)
self.assertTrue(self.submission_page.has_submitted)
self.staff_area_page.visit()
self.staff_area_page.expand_staff_grading_section()
self._check_a11y(self.staff_area_page)
def test_learner_info(self):
"""
Check the accessibility of the learner information sections of the "Staff Tools" panel.
"""
# Create an assessment for a user.
username = self.do_self_assessment()
self.auto_auth_page.visit()
username = self.auto_auth_page.get_username()
self.submission_page.visit().submit_response(self.SUBMISSION)
self.assertTrue(self.submission_page.has_submitted)
self.staff_area_page.visit()
......@@ -139,24 +153,14 @@ class StaffAreaA11yTest(OpenAssessmentA11yTest):
"""
Check the accessibility of the Staff Grade section, as shown to the learner.
"""
self.auto_auth_page.visit()
username = self.auto_auth_page.get_username()
self.submission_page.visit().submit_response(self.SUBMISSION)
self.assertTrue(self.submission_page.has_submitted)
# Submit a staff override
self.staff_area_page.visit()
self.staff_area_page.show_learner(username)
self.staff_area_page.expand_learner_report_sections()
self.staff_area_page.assess("staff", self.STAFF_OVERRIDE_OPTIONS_SELECTED)
self.do_staff_assessment(options_selected=self.STAFF_OVERRIDE_OPTIONS_SELECTED)
# Refresh the page, and learner completes a self-assessment.
# Then verify accessibility of the Staff Grade section (marked Complete).
# Refresh the page, then verify accessibility of the Staff Grade section (marked Complete).
self.browser.refresh()
self.self_asmnt_page.wait_for_page().wait_for_response()
self.self_asmnt_page.assess("self", self.OPTIONS_SELECTED).wait_for_complete()
self.assertTrue(self.self_asmnt_page.is_complete)
self._verify_staff_grade_section(self.STAFF_OVERRIDE_EXISTS, None)
self._verify_staff_grade_section(self.STAFF_GRADE_EXISTS, None)
self._check_a11y(self.staff_asmnt_page)
......@@ -169,7 +173,7 @@ class FullWorkflowA11yTest(OpenAssessmentA11yTest, FullWorkflowMixin):
"""
def setUp(self):
super(FullWorkflowA11yTest, self).setUp('full_workflow', staff=True)
super(FullWorkflowA11yTest, self).setUp('full_workflow_staff_override', staff=True)
self.staff_area_page = StaffAreaPage(self.browser, self.problem_loc)
def test_training_peer_self_staff_override(self):
......
......@@ -462,6 +462,13 @@ class StaffAreaPage(OpenAssessmentPage, AssessmentMixin):
panels = self.q(css=self._bounded_selector(".wrapper--ui-staff"))
return [panel.get_attribute('class') for panel in panels if u'is--hidden' not in panel.get_attribute('class')]
def is_button_visible(self, button_name):
"""
Returns True if button_name is visible, else False
"""
button = self.q(css=self._bounded_selector(".button-{button_name}".format(button_name=button_name)))
return button.is_present()
def click_staff_toolbar_button(self, button_name):
"""
Presses the button to show the panel with the specified name.
......@@ -491,6 +498,50 @@ class StaffAreaPage(OpenAssessmentPage, AssessmentMixin):
submit_button.first.click()
self.wait_for_element_visibility(".staff-info__student__report", "Student report is present")
def expand_staff_grading_section(self):
"""
Clicks the staff grade control to expand staff grading section for use in staff required workflows.
"""
self.click_staff_toolbar_button("staff-grading")
self.q(css=self._bounded_selector(".staff__grade__show-form")).first.click()
self.wait_for_element_visibility("#staff__assessment__rubric__question--0__0", "staff grading is present")
@property
def available_checked_out_numbers(self):
"""
Gets "N available and M checked out" information from staff grading sections.
Returns tuple of (N, M)
"""
if not 'GRADE AVAILABLE RESPONSES' in self.selected_button_names:
self.expand_staff_grading_section()
raw_string = self.q(css=self._bounded_selector(".staff__grade__value")).text[0]
ret = tuple(int(s) for s in raw_string.split() if s.isdigit())
if len(ret) != 2:
raise PageConfigurationError("Unable to parse available and checked out numbers")
return ret
def verify_available_checked_out_numbers(self, expected_value):
"""
Waits until the expected value for available and checked out numbers appears. If it does not appear, fails the test.
expected_value should be a tuple as described in the available_checked_out_numbers property above.
"""
EmptyPromise(
lambda: self.available_checked_out_numbers == expected_value,
"Expected avaiable and checked out values present"
).fulfill()
def submissions_available(self):
"""
Utility method to check if there are any more learner responses to grade in the staff grading section.
"""
found = self.q(
css=self._bounded_selector(".staff__grade__content")
)
if found.text[0] == "No other learner responses are available for grading at this time.":
return False
return True
@property
def learner_report_text(self):
"""
......@@ -557,11 +608,24 @@ class StaffAreaPage(OpenAssessmentPage, AssessmentMixin):
css=self._bounded_selector(".staff-info__student__response .ui-toggle-visibility__content")
).text[0]
def submit_assessment(self):
def staff_assess(self, options_selected, continue_after=False):
for criterion_num, option_num in enumerate(options_selected):
sel = "#staff__assessment__rubric__question--{criterion_num}__{option_num}".format(
assessment_type="staff",
criterion_num=criterion_num,
option_num=option_num
)
self.q(css=self._bounded_selector(sel)).first.click()
self.submit_assessment(continue_after)
def submit_assessment(self, continue_after=False):
"""
Submit a staff assessment of the problem.
"""
self.submit(button_css=".wrapper--staff-assessment .action--submit")
filter_text = "Submit assessment"
if continue_after:
filter_text += " and continue grading"
self.q(css=self._bounded_selector("button.action--submit")).filter(text=filter_text).first.click()
def cancel_submission(self):
"""
......
......@@ -35,7 +35,7 @@ def retry(tries=4, delay=3, backoff=2):
return func(*args, **kwargs)
except (BrokenPromise, AssertionError) as ex:
if attempt_num >= (tries - 1):
raise ex
raise
else:
print "Test failed with {err}, retrying in {sec} seconds...".format(err=ex, sec=_delay)
time.sleep(_delay)
......@@ -66,9 +66,12 @@ class OpenAssessmentTest(WebAppTest):
'file_upload':
u'courses/{test_course_id}/courseware/'
u'57a3f9d51d424f6cb922f0d69cba868d/bb563abc989340d8806920902f267ca3/'.format(test_course_id=TEST_COURSE_ID),
'full_workflow':
'full_workflow_staff_override':
u'courses/{test_course_id}/courseware/'
u'676026889c884ac1827688750871c825/181ea9ff144c4766be44eb8cb360e34f/'.format(test_course_id=TEST_COURSE_ID),
'full_workflow_staff_required':
u'courses/{test_course_id}/courseware/'
u'8d9584d242b44343bc270ea5ef04ab03/0b0dcc728abe45138c650732af178afb/'.format(test_course_id=TEST_COURSE_ID),
}
SUBMISSION = u"This is a test submission."
......@@ -76,7 +79,7 @@ class OpenAssessmentTest(WebAppTest):
OPTIONS_SELECTED = [1, 2]
STAFF_OVERRIDE_OPTIONS_SELECTED = [0, 1]
STAFF_OVERRIDE_SCORE = 1
STAFF_OVERRIDE_EXISTS = "COMPLETE"
STAFF_GRADE_EXISTS = "COMPLETE"
STAFF_OVERRIDE_LEARNER_STEPS_NOT_COMPLETE = "YOU MUST COMPLETE THE STEPS ABOVE TO VIEW YOUR GRADE"
STAFF_AREA_SCORE = "Final grade: {} out of 8"
STAFF_OVERRIDE_STAFF_AREA_NOT_COMPLETE = "The problem has not been completed."
......@@ -212,10 +215,32 @@ class OpenAssessmentTest(WebAppTest):
self.staff_area_page.visit()
self.staff_area_page.show_learner(username)
self.staff_area_page.expand_learner_report_sections()
self.staff_area_page.assess("staff", self.STAFF_OVERRIDE_OPTIONS_SELECTED)
self.staff_area_page.staff_assess(self.STAFF_OVERRIDE_OPTIONS_SELECTED)
self.staff_area_page.verify_learner_final_score(final_score)
def do_staff_assessment(self, number_to_assess=0, options_selected=OPTIONS_SELECTED):
"""
Use staff tools to assess available responses.
Args:
number_to_assess: the number of submissions to assess. If not provided (or 0),
will grade all available submissions.
"""
self.staff_area_page.visit()
self.staff_area_page.expand_staff_grading_section()
start_numbers = self.staff_area_page.available_checked_out_numbers
assessed = 0
while number_to_assess == 0 or assessed < number_to_assess:
continue_after = False if number_to_assess-1 == assessed else True
self.staff_area_page.staff_assess(options_selected, continue_after)
assessed += 1
new_numbers = (start_numbers[0]-assessed, start_numbers[1])
self.staff_area_page.verify_available_checked_out_numbers(new_numbers)
if not continue_after or not self.staff_area_page.submissions_available():
break
class SelfAssessmentTest(OpenAssessmentTest):
"""
Test the self-assessment flow.
......@@ -275,15 +300,19 @@ class StaffAssessmentTest(OpenAssessmentTest):
# Verify staff grade section appears as expected
self._verify_staff_grade_section("NOT AVAILABLE", "WAITING FOR A STAFF GRADE")
# TODO: as part of @cahren's work on TNL-3493, change this section to do a proper full staff grade
# The override is a temporary hack for acceptance test advancement.
# Perform staff assessment
self.staff_area_page = StaffAreaPage(self.browser, self.problem_loc)
self.do_staff_override(username)
self.do_staff_assessment()
# Switch back to original user, verify staff grade section appears as expected
# Verify staff grade section appears as expected
self.staff_asmnt_page.visit()
self.staff_asmnt_page.verify_status_value("COMPLETE")
self._verify_staff_grade_section(self.STAFF_GRADE_EXISTS, None)
self.assertEqual(self.EXPECTED_SCORE, self.grade_page.wait_for_page().score)
# Verify that staff scores can be overriden
self.do_staff_override(username)
self.browser.refresh()
self.assertEqual(self.STAFF_OVERRIDE_SCORE, self.grade_page.wait_for_page().score)
class PeerAssessmentTest(OpenAssessmentTest):
"""
......@@ -355,7 +384,7 @@ class PeerAssessmentTestStaffOverride(OpenAssessmentTest):
# Refresh the page so the learner sees the Staff Grade section.
self.browser.refresh()
self._verify_staff_grade_section(self.STAFF_OVERRIDE_EXISTS, self.STAFF_OVERRIDE_LEARNER_STEPS_NOT_COMPLETE)
self._verify_staff_grade_section(self.STAFF_GRADE_EXISTS, self.STAFF_OVERRIDE_LEARNER_STEPS_NOT_COMPLETE)
# Verify no final grade yet.
self.assertIsNone(self.grade_page.wait_for_page().score)
......@@ -365,7 +394,7 @@ class PeerAssessmentTestStaffOverride(OpenAssessmentTest):
# Staff grade section is now marked complete, even though no students have submitted
# assessments for this particular student (no longer required since staff grade exists).
self._verify_staff_grade_section(self.STAFF_OVERRIDE_EXISTS, None)
self._verify_staff_grade_section(self.STAFF_GRADE_EXISTS, None)
# Verify the staff override grade
self.assertEqual(self.STAFF_OVERRIDE_SCORE, self.grade_page.wait_for_page().score)
......@@ -611,18 +640,22 @@ class StaffAreaTest(OpenAssessmentTest):
# Refresh the page so the learner sees the Staff Grade section.
self.browser.refresh()
self._verify_staff_grade_section(self.STAFF_OVERRIDE_EXISTS, self.STAFF_OVERRIDE_LEARNER_STEPS_NOT_COMPLETE)
self._verify_staff_grade_section(self.STAFF_GRADE_EXISTS, self.STAFF_OVERRIDE_LEARNER_STEPS_NOT_COMPLETE)
# Verify no final grade yet.
self.assertIsNone(self.grade_page.wait_for_page().score)
# Verify required staff grading section not available
self.staff_area_page = StaffAreaPage(self.browser, self.problem_loc)
self.assertFalse(self.staff_area_page.is_button_visible('staff-grading'))
# Learner does required self-assessment
self.self_asmnt_page.wait_for_page().wait_for_response()
self.assertIn(self.SUBMISSION, self.self_asmnt_page.response_text)
self.self_asmnt_page.assess("self", self.OPTIONS_SELECTED).wait_for_complete()
self.assertTrue(self.self_asmnt_page.is_complete)
self._verify_staff_grade_section(self.STAFF_OVERRIDE_EXISTS, None)
self._verify_staff_grade_section(self.STAFF_GRADE_EXISTS, None)
# Verify the staff override grade
self.assertEqual(self.STAFF_OVERRIDE_SCORE, self.grade_page.wait_for_page().score)
......@@ -762,25 +795,26 @@ class FullWorkflowMixin(object):
max_attempts: the maximum number of times an additional peer grading should be done
"""
count = 0
while self.grade_page.wait_for_page().score is None and count < (max_attempts + 1):
while not self.peer_asmnt_page.is_complete and count < (max_attempts + 1):
count += 1
self.do_submission_training_self_assessment("extra_{}@looping.com".format(count), None)
self.do_peer_assessment(options=self.PEER_ASSESSMENT)
self.login_user(learner)
self.grade_page.visit()
self.assertIsNotNone(
self.grade_page.wait_for_page().score,
self.assertTrue(
self.peer_asmnt_page.is_complete,
"Learner still not graded after {} additional attempts".format(max_attempts)
)
class FullWorkflowTest(OpenAssessmentTest, FullWorkflowMixin):
class FullWorkflowBaseTest(OpenAssessmentTest, FullWorkflowMixin):
"""
Tests of complete workflows, combining multiple required steps together.
Base class for common functionality in full workflow tests.
"""
def setUp(self):
super(FullWorkflowTest, self).setUp('full_workflow', staff=True)
def setUp(self, problem_type):
super(FullWorkflowBaseTest, self).setUp(problem_type, staff=True)
self.staff_area_page = StaffAreaPage(self.browser, self.problem_loc)
def verify_grade_entries(self, expected_entries):
......@@ -797,21 +831,13 @@ class FullWorkflowTest(OpenAssessmentTest, FullWorkflowMixin):
self.assertEqual(expected_entry[0], self.grade_page.grade_entry(0, index))
self.assertEqual(expected_entry[1], self.grade_page.grade_entry(1, index))
@retry()
@attr('acceptance')
def test_staff_override_at_end(self):
def do_train_self_peer(self, peer_to_grade=True):
"""
Scenario: complete workflow with staff override at the very end
Common functionality for executing training, self, and peer assessment steps.
Given that I have created a submission, completed training, and done a self assessment
And a second learner has also created a submission, training, and self assessment
Then I can assess a learner
And when another learner assesses me
Then I see my score based on the peer assessment
And when a staff member overrides the score
Then I see the staff override score
And all fields in the staff area tool are correct
Args:
peer_to_grade: boolean, defaults to True. Set to False to have learner complete their required steps,
but no peers to submit a grade for learner in return.
"""
# Create a learner with submission, training, and self assessment completed.
learner = self.do_submission_training_self_assessment(self.LEARNER_EMAIL, self.LEARNER_PASSWORD)
......@@ -819,7 +845,8 @@ class FullWorkflowTest(OpenAssessmentTest, FullWorkflowMixin):
# Now create a second learner so that learner 1 has someone to assess.
# The second learner does all the steps as well (submission, training, self assessment, peer assessment).
self.do_submission_training_self_assessment("learner2@foo.com", None)
self.do_peer_assessment(options=self.PEER_ASSESSMENT)
if peer_to_grade:
self.do_peer_assessment(options=self.PEER_ASSESSMENT)
# Go back to the first learner to complete her workflow.
self.login_user(learner)
......@@ -828,7 +855,34 @@ class FullWorkflowTest(OpenAssessmentTest, FullWorkflowMixin):
self.do_peer_assessment(options=self.SUBMITTED_ASSESSMENT)
# Continue grading by other students if necessary to ensure learner has a peer grade.
self.verify_submission_has_peer_grade(learner)
if peer_to_grade:
self.verify_submission_has_peer_grade(learner)
return learner
class FullWorkflowOverrideTest(FullWorkflowBaseTest):
"""
Tests of complete workflows, combining multiple required steps together.
"""
def setUp(self):
super(FullWorkflowOverrideTest, self).setUp("full_workflow_staff_override")
@retry()
@attr('acceptance')
def test_staff_override_at_end(self):
"""
Scenario: complete workflow with staff override at the very end
Given that I have created a submission, completed training, and done a self assessment
And a second learner has also created a submission, training, and self assessment
Then I can assess a learner
And when another learner assesses me
Then I see my score based on the peer assessment
And when a staff member overrides the score
Then I see the staff override score
And all fields in the staff area tool are correct
"""
learner = self.do_train_self_peer()
# At this point, the learner sees the peer assessment score (0).
self.assertEqual(self.PEER_ASSESSMENT_SCORE, self.grade_page.wait_for_page().score)
......@@ -846,7 +900,7 @@ class FullWorkflowTest(OpenAssessmentTest, FullWorkflowMixin):
self.do_staff_override(learner)
self.browser.refresh()
self._verify_staff_grade_section(self.STAFF_OVERRIDE_EXISTS, None)
self._verify_staff_grade_section(self.STAFF_GRADE_EXISTS, None)
self.assertEqual(self.STAFF_OVERRIDE_SCORE, self.grade_page.wait_for_page().score)
self.verify_staff_area_fields(
learner, self.STAFF_AREA_PEER_ASSESSMENT, self.STAFF_AREA_SUBMITTED, self.STAFF_AREA_SELF_ASSESSMENT
......@@ -889,7 +943,7 @@ class FullWorkflowTest(OpenAssessmentTest, FullWorkflowMixin):
# Refresh the page so the learner sees the Staff Grade section.
self.browser.refresh()
self._verify_staff_grade_section(self.STAFF_OVERRIDE_EXISTS, self.STAFF_OVERRIDE_LEARNER_STEPS_NOT_COMPLETE)
self._verify_staff_grade_section(self.STAFF_GRADE_EXISTS, self.STAFF_OVERRIDE_LEARNER_STEPS_NOT_COMPLETE)
# Now create a second learner so that "learner" has someone to assess.
self.do_submission("learner2@foo.com", None)
......@@ -903,7 +957,7 @@ class FullWorkflowTest(OpenAssessmentTest, FullWorkflowMixin):
self.submit_self_assessment(self.SELF_ASSESSMENT)
# Verify staff grade still not available, as learner has not done peer assessment.
self._verify_staff_grade_section(self.STAFF_OVERRIDE_EXISTS, self.STAFF_OVERRIDE_LEARNER_STEPS_NOT_COMPLETE)
self._verify_staff_grade_section(self.STAFF_GRADE_EXISTS, self.STAFF_OVERRIDE_LEARNER_STEPS_NOT_COMPLETE)
self.assertIsNone(self.grade_page.wait_for_page().score)
self.verify_staff_area_fields(learner, [], [], self.STAFF_AREA_SELF_ASSESSMENT)
self.staff_area_page.verify_learner_final_score(self.STAFF_OVERRIDE_STAFF_AREA_NOT_COMPLETE)
......@@ -912,7 +966,7 @@ class FullWorkflowTest(OpenAssessmentTest, FullWorkflowMixin):
self.do_peer_assessment(options=self.SUBMITTED_ASSESSMENT)
# Grade is now visible to the learner (even though no student has graded the learner).
self._verify_staff_grade_section(self.STAFF_OVERRIDE_EXISTS, None)
self._verify_staff_grade_section(self.STAFF_GRADE_EXISTS, None)
self.assertEqual(self.STAFF_OVERRIDE_SCORE, self.grade_page.wait_for_page().score)
self.verify_staff_area_fields(learner, [], self.STAFF_AREA_SUBMITTED, self.STAFF_AREA_SELF_ASSESSMENT)
self.staff_area_page.verify_learner_final_score(self.STAFF_AREA_SCORE.format(self.STAFF_OVERRIDE_SCORE))
......@@ -923,6 +977,55 @@ class FullWorkflowTest(OpenAssessmentTest, FullWorkflowMixin):
])
@ddt.ddt
class FullWorkflowRequiredTest(FullWorkflowBaseTest):
"""
Tests of complete workflows, combining multiple required steps together.
"""
def setUp(self):
super(FullWorkflowRequiredTest, self).setUp("full_workflow_staff_required")
@retry()
@attr('acceptance')
@ddt.data(True, False)
def test_train_self_peer_staff(self, peer_grades_me):
"""
Scenario: complete workflow that included staff required step.
Given that I have created a submission, completed training, and done a self assessment
And a second learner has also created a submission, training, and self assessment
Then I can assess a learner
And when another learner assesses me
And a staff member submits a score
Then I see the staff score
And all fields in the staff area tool are correct
"""
# Using ddt booleans to confirm behavior independent of whether I receive a peer score or not
learner = self.do_train_self_peer(peer_grades_me)
# Ensure grade is not present, since staff assessment has not been made
self.assertIsNone(self.grade_page.wait_for_page().score)
# Now do a staff assessment.
self.do_staff_assessment(options_selected=self.STAFF_OVERRIDE_OPTIONS_SELECTED)
# As an add-on, let's make sure that both submissions (the learner's, and the additional one created
# in do_train_self_peer() above) were assessed using staff-grading's "submit and keep going"
self.assertFalse(self.staff_area_page.submissions_available())
# At this point, the learner sees the score (1).
self.browser.refresh()
self._verify_staff_grade_section(self.STAFF_GRADE_EXISTS, None)
self.assertEqual(self.STAFF_OVERRIDE_SCORE, self.grade_page.wait_for_page().score)
# Note that PEER ASSESSMENT isn't shown here - it gets cut off the page in this case
# See TNL-3930 for details and possible fix.
self.verify_grade_entries([
[(u"STAFF GRADE - 0 POINTS", u"Poor"), (u"STAFF GRADE - 1 POINT", u"Fair")],
[(u"YOUR SELF ASSESSMENT", u"Good"), (u"YOUR SELF ASSESSMENT", u"Excellent")],
])
if __name__ == "__main__":
# Configure the screenshot directory
......
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