Commit 756e07c5 by Andy Armstrong

Add staff grade details to "Your Grade" section

TNL-3465
parent c10b1f61
{% load i18n %}
{% spaceless %}
{% if assessment %}
<li class="answer feedback">
{% if assessment.individual_assessments %}
{% for individual_assessment in assessment.individual_assessments %}
{% if individual_assessment.feedback %}
<h5 class="answer_title">
<span class="answer__source">
{% if individual_assessment.option %}
{% blocktrans with title=individual_assessment.title grade=individual_assessment.option.label start_tag='<span class="answer__source__value">'|safe end_tag="</span>"|safe %}
{{ start_tag }}{{ title }}{{ end_tag }} - {{ grade }}
{% endblocktrans %}
{% else %}
<span class="answer__source__value">
{{ individual_assessment.title }}
</span>
{% endif %}
</span>
</h5>
<div class="feedback__value">
<p class="feedback__value__raw">{{ individual_assessment.feedback }}</p>
</div>
{% endif %}
{% endfor %}
{% else %}
<h5 class="answer_title">
<span class="answer__source">
<span class="answer__source__value">{{ title }}</span>
</span>
</h5>
<div class="feedback__value">
<p class="feedback__value__raw">{{ assessment.feedback }}</p>
</div>
{% endif %}
</li>
{% endif %}
{% endspaceless %}
{% load i18n %}
{% spaceless %}
{% if assessment %}
<li class="answer">
<h5 class="answer__title">
<span class="answer__source">
{% if assessment.points != None %}
<span class="answer__source__value answer__source__value-with-points">
{% blocktrans with assessment_title=assessment.title count points=assessment.points %}
{{ assessment_title }} - {{ points }} point
{% plural %}
{{ assessment_title }} - {{ points }} points
{% endblocktrans %}
</span>
{% else %}
<span class="answer__source__value">{{ assessment.title }}</span>
{% endif %}
</span>
<span class="answer__value">
<span class="answer__value__label sr">{{ assessment.title }}</span>
<span class="answer__value__value">
{{ assessment.option.label }}
{% if assessment.option.explanation %}
<span class="ui-hint hint--top" data-hint="{{ assessment.option.explanation }}">
<i class="icon fa fa-info-circle" aria-hidden="true"
title="{% blocktrans with name=assessment.option.label %}More information about {{ name }}{% endblocktrans %}">
</i>
</span>
{% endif %}
</span>
</span>
</h5>
</li>
{% endif %}
{% endspaceless %}
{% load i18n %}
{% spaceless %}
<li id="openassessment__grade" class="openassessment__steps__step step--grade is--complete has--grade {% if allow_latex %}allow--latex{% endif %}">
<header class="step__header ui-toggle-visibility__control">
<h2 class="step__title">
<li id="openassessment__grade" class="openassessment__steps__step step--grade is--complete has--grade {% if allow_latex %}allow--latex{% endif %}">
<header class="step__header ui-toggle-visibility__control">
<h2 class="step__title">
<span class="wrapper--copy">
{% if score %}
<span class="step__label">{% trans "Your Grade" %}: </span>
<span class="grade__value">
<span class="step__label">{% trans "Your Grade" %}: </span>
<span class="grade__value">
<span class="grade__value__title">
{% with points_earned_string=score.points_earned|stringformat:"s" points_possible_string=score.points_possible|stringformat:"s" %}
{% blocktrans with points_earned='<span class="grade__value__earned">'|safe|add:points_earned_string|add:'</span>'|safe points_possible='<span class="grade__value__potential">'|safe|add:points_possible_string|add:'</span>'|safe %}
{{ points_earned }} out of {{ points_possible }}
{% endblocktrans %}
{% blocktrans with points_earned='<span class="grade__value__earned">'|safe|add:points_earned_string|add:'</span>'|safe points_possible='<span class="grade__value__potential">'|safe|add:points_possible_string|add:'</span>'|safe %}
{{ points_earned }} out of {{ points_possible }}
{% endblocktrans %}
{% endwith %}
</span>
</span>
{% else %}
<span class="step__label">{% trans "Your Grade" %}</span>
<span class="step__label">{% trans "Your Grade" %}</span>
{% endif %}
</span>
</h2>
</header>
<div class="ui-toggle-visibility__content">
<div class="wrapper--step__content">
<div class="step__content">
<article class="submission__answer__display step__content__section">
<h3 class="submission__answer__display__title">{% trans "Your Response" %}</h3>
{% include "openassessmentblock/oa_submission_answer.html" with answer=student_submission.answer answer_text_label="Your response to the question above:" %}
{% include "openassessmentblock/oa_uploaded_file.html" with file_upload_type=file_upload_type file_url=file_url header="Your Upload" class_prefix="submission__answer" %}
</article>
<article class="submission__peer-evaluations step__content__section">
<h3 class="submission__peer-evaluations__title">{% trans "Assessments of Your Response" %}</h3>
<ol class="list submission__peer-evaluations__questions">
{% for criterion in rubric_criteria %}
{% with criterion_num=forloop.counter %}
<li class="question question--{{ criterion_num }} ui-toggle-visibility">
<h4 class="question__title ui-toggle-visibility__control">
<i class="icon fa fa-caret-right" aria-hidden="true"></i>
<span class="question__title__copy">{{ criterion.label }}</span>
<span class="question__score">
<span class="label sr">{% trans "Overall Grade" %}</span>
<span class="question__score__value">{{ criterion.median_score }}</span>
<span class="label label--divider sr">out of</span>
<span class="question__score__potential">
{{ criterion.total_value }}
<span class="unit">{% trans "Points" %}</span>
</span>
</span>
</h4>
<ul class="question__answers ui-toggle-visibility__content has--hints">
{% for assessment in peer_assessments %}
{% with peer_num=forloop.counter %}
{% for part in assessment.parts %}
{% if part.option.criterion.name == criterion.name %}
<li class="answer peer-assessment--{{ peer_num }}"
id="question--{{ criterion_num }}__answer-{{ peer_num }}">
<h5 class="answer__title">
<span class="answer__source">
<span class="answer__source__label sr">{% trans "Assessor" %}: </span>
<span class="answer__source__value">
{% blocktrans with peer_num=peer_num%}Peer {{ peer_num }}{% endblocktrans %}
</span>
</span>
<span class="answer__value">
<span class="answer__value__label sr">{% trans "Peer's Assessment" %}: </span>
<span class="answer__value__value">
{{ part.option.label }}
<span class="ui-hint hint--top" data-hint="{{ part.option.explanation }}">
<i class="icon fa fa-info-circle" aria-hidden="true"
title="{% blocktrans with name=part.option.label %}More information about {{ name }}{% endblocktrans %}"></i>
</span>
</span>
</span>
</h5>
<span class="answer__score">
<span class="answer__score__label sr">{% trans "Grade Earned" %}: </span>
<span class="answer__score__value">{{ part.option.points }} {% trans "points" %}</span>
</span>
</li>
{% endif %}
{% endfor %}
{% endwith %}
{% endfor %}
{% for part in self_assessment.parts %}
{% if part.option.criterion.name == criterion.name %}
<li class="answer self-assessment"
id="question--{{ criterion_num }}__answer--self">
<h5 class="answer__title">
<span class="answer__source">
<span class="answer__source__value">{% trans "Your Self Assessment" %}</span>
</span>
<span class="answer__value">
<span class="answer__value__label sr">{% trans "Your Assessment" %}: </span>
<span class="answer__value__value">
{{ part.option.label }}
<span class="ui-hint hint--top" data-hint="{{ part.option.explanation }}">
<i class="icon fa fa-info-circle" aria-hidden="true"
title="{% blocktrans with name=part.option.label %}More information about {{ name }}{% endblocktrans %}"></i>
</h2>
</header>
<div class="ui-toggle-visibility__content">
<div class="wrapper--step__content">
<div class="step__content">
<article class="submission__answer__display step__content__section">
<h3 class="submission__answer__display__title">{% trans "Your Response" %}</h3>
{% include "openassessmentblock/oa_submission_answer.html" with answer=student_submission.answer answer_text_label="Your response to the question above:" %}
{% include "openassessmentblock/oa_uploaded_file.html" with file_upload_type=file_upload_type file_url=file_url header="Your Upload" class_prefix="submission__answer" %}
</article>
<article class="submission__peer-evaluations step__content__section">
<h3 class="submission__peer-evaluations__title">{% trans "Assessments of Your Response" %}</h3>
<ol class="list submission__peer-evaluations__questions">
{% for criterion in grade_details.criteria %}
{% with criterion_num=forloop.counter %}
<li class="question question--{{ criterion_num }} ui-toggle-visibility">
<h4 class="question__title ui-toggle-visibility__control">
<i class="icon fa fa-caret-right" aria-hidden="true"></i>
<span class="question__title__copy">{{ criterion.label }}</span>
<span class="question__score">
<span class="label sr">{% trans "Overall Grade" %}</span>
<span class="question__score__value">{{ criterion.median_score }}</span>
<span class="label label--divider sr">out of</span>
<span class="question__score__potential">
{{ criterion.total_value }}
<span class="unit">{% trans "Points" %}</span>
</span>
</span>
</span>
</h5>
</li>
{% endif %}
{% endfor %}
{% for part in example_based_assessment.parts %}
{% if part.option.criterion.name == criterion.name %}
<li class="answer example-based-assessment"
id="question--{{ criterion_num }}__answer--example-based">
<h5 class="answer__title">
<span class="answer__source">
<span class="answer__source__value">{% trans "Example-Based Assessment" %}</span>
</span>
<span class="answer__value">
<span class="answer__value__label sr">{% trans "Example-Based Assessment" %}: </span>
<span class="answer__value__value">
{{ part.option.label }}
<span class="ui-hint hint--top" data-hint="{{ part.option.explanation }}">
<i class="icon fa fa-info-circle" aria-hidden="true"
title="{% blocktrans with name=part.option.label %}More information about {{ name }}{% endblocktrans %}"></i>
</span>
</span>
</span>
</h5>
</li>
{% endif %}
{% endfor %}
{% if criterion.peer_feedback or criterion.self_feedback %}
<li class="answer--feedback ui-toggle-visibility {% if criterion.options %}is--collapsed{% endif %}">
{% if criterion.options %}
<h5 class="answer--feedback__title ui-toggle-visibility__control">
<i class="icon fa fa-caret-right" aria-hidden="true"></i>
{% if criterion.self_feedback %}
<span class="answer--feedback__title__copy">{% trans "Additional Comments" %} ({{ criterion.peer_feedback|length|add:'1' }})</span>
{% else %}
<span class="answer--feedback__title__copy">{% trans "Additional Comments" %} ({{ criterion.peer_feedback|length }})</span>
{% endif %}
</h5>
{% endif %}
<ul class="answer--feedback__content {% if criterion.options %}ui-toggle-visibility__content{% endif %}">
{% for feedback in criterion.peer_feedback %}
<li class="feedback feedback--{{ forloop.counter }}">
<h6 class="feedback__source">
{% trans "Peer" %} {{ forloop.counter }}
</h6>
<div class="feedback__value">
<p class="feedback__value__raw">{{ feedback }}</p>
</div>
</li>
{% endfor %}
{% if criterion.self_feedback %}
<li class="feedback feedback--{{ forloop.counter }}">
<h6 class="feedback__source">
{% trans "Your Assessment" %}
</h6>
<div class="feedback__value">
<p class="feedback__value__raw">{{ criterion.self_feedback }}</p>
</div>
</li>
</h4>
<ul class="question__answers ui-toggle-visibility__content has--hints">
{% for assessment in criterion.assessments %}
{% include "openassessmentblock/grade/oa_assessment_title.html" with assessment=assessment %}
{% endfor %}
</ul>
{% if criterion.has_feedback %}
<ul class="question__answers ui-toggle-visibility__content has--hints">
{% trans "Comments" as feedback_title %}
{% for assessment in criterion.assessments %}
{% include "openassessmentblock/grade/oa_assessment_feedback.html" with title=feedback_title assessment=assessment %}
{% endfor %}
</ul>
{% endif %}
</li>
{% endwith %}
{% endfor %}
{% if grade_details.additional_feedback %}
<li class="question question--feedback ui-toggle-visibility">
<h4 class="question__title ui-toggle-visibility__control">
<i class="icon fa fa-caret-right" aria-hidden="true"></i>
<span class="question__title__copy">{% trans "Additional comments on your response" %}</span>
</h4>
<ul class="question__answers ui-toggle-visibility__content">
{% for feedback in grade_details.additional_feedback %}
{% include "openassessmentblock/grade/oa_assessment_feedback.html" with title=feedback.title assessment=feedback %}
{% endfor %}
</ul>
</li>
{% endif %}
</ul>
</li>
{% endwith %}
{% endfor %}
{% if peer_assessments or self_assessment.feedback %}
<li class="question question--feedback ui-toggle-visibility">
<h4 class="question__title ui-toggle-visibility__control">
<i class="icon fa fa-caret-right" aria-hidden="true"></i>
<span class="question__title__copy">{% trans "Additional comments on your response" %}</span>
</h4>
<ul class="question__answers ui-toggle-visibility__content">
{% for assessment in peer_assessments %}
{% with peer_num=forloop.counter %}
{% if assessment.feedback %}
<li class="answer peer-evaluation--{{ peer_num }}" id="question--feedback__answer-{{ peer_num }}">
<h5 class="answer__title">
<span class="answer__source">
<span class="label sr">{% trans "Peer assessor" %}: </span>
<span class="value">{% blocktrans with peer_num=peer_num %}Peer {{ peer_num }}{% endblocktrans %}</span>
</span>
</h5>
<div class="answer__value">
<h6 class="label sr">{% trans "Peer's assessment" %}: </h6>
<p class="answer__value__raw">{{ assessment.feedback }}</p>
</div>
</li>
{% endif %}
{% endwith %}
{% endfor %}
{% if self_assessment.feedback %}
<li class="answer self-evaluation--0" id="question--feedback__answer-0">
<h5 class="answer__title">
<span class="answer__source">
<span class="label sr">{% trans "Self assessment" %}: </span>
<span class="value">{% trans "Self assessment" %}</span>
</span>
</h5>
<div class="answer__value">
<h6 class="label sr">{% trans "Your assessment" %}: </h6>
<p class="answer__value__raw">{{ self_assessment.feedback }}</p>
</div>
</li>
{% endif %}
</ul>
</li>
{% endif %}
</ol>
</article>
</ol>
</article>
{% if peer_assessments %}
<form id="submission__feedback" class="submission__feedback ui-toggle-visibility step__content__section is--collapsed" method="post">
<h3 class="submission__feedback__title ui-toggle-visibility__control">
<i class="icon fa fa-caret-right" aria-hidden="true"></i>
<span class="submission__feedback__title__copy">{% trans "Provide Feedback on Peer Assessments" %}</span>
</h3>
{% if peer_assessments %}
<form id="submission__feedback" class="submission__feedback ui-toggle-visibility step__content__section is--collapsed" method="post">
<h3 class="submission__feedback__title ui-toggle-visibility__control">
<i class="icon fa fa-caret-right" aria-hidden="true"></i>
<span class="submission__feedback__title__copy">{% trans "Provide Feedback on Peer Assessments" %}</span>
</h3>
<div class="ui-toggle-visibility__content">
<div class="ui-toggle-visibility__content">
<div class="submission__feedback__content {{ has_submitted_feedback|yesno:"is--submitted," }}">
<div class="submission__feedback__content {{ has_submitted_feedback|yesno:"is--submitted," }}">
<span class="transition__status is--hidden" aria-hidden="true">
<span class="wrapper--anim">
<i class="icon fa fa-spinner fa-spin" aria-hidden="true"></i>
......@@ -249,89 +106,88 @@
</span>
</span>
<div class="message message--complete {{ has_submitted_feedback|yesno:",is--hidden" }}"
{{ has_submitted_feedback|yesno:'aria-hidden=false,aria-hidden=true' }}>
<h3 class="message__title">{% trans "Your Feedback Has Been Submitted" %}</h3>
<div class="message__content">
<p>{% trans "Your feedback has been submitted. Course staff will be able to see this feedback when they review course records." %}</p>
<div class="message message--complete {{ has_submitted_feedback|yesno:",is--hidden" }}"
{{ has_submitted_feedback|yesno:'aria-hidden=false,aria-hidden=true' }}>
<h3 class="message__title">{% trans "Your Feedback Has Been Submitted" %}</h3>
<div class="message__content">
<p>{% trans "Your feedback has been submitted. Course staff will be able to see this feedback when they review course records." %}</p>
</div>
</div>
</div>
<div class="submission__feedback__instructions {{ has_submitted_feedback|yesno:"is--hidden," }}"
{{ has_submitted_feedback|yesno:'aria-hidden=true,aria-hidden=false' }}>
<p>{% trans "Course staff will be able to see any feedback that you provide here when they review course records." %}</p>
</div>
<ol class="list list--fields submission__feedback__fields {{ has_submitted_feedback|yesno:"is--hidden," }}"
{{ has_submitted_feedback|yesno:'aria-hidden=true,aria-hidden=false' }}>
<li class="field field-group field--radio feedback__overall" id="feedback__overall">
<h4 class="field-group__label">{% trans "Select the statements below that best reflect your experience with peer assessments" %}:</h4>
<ol class="list--options">
<li class="option option--useful">
<input type="checkbox"
name="feedback__overall__value"
id="feedback__overall__value--useful"
class="option__input feedback__overall__value"
value="These assessments were useful." />
<label class="option__label" for="feedback__overall__value--useful">{% trans "These assessments were useful." %}</label>
</li>
<li class="option option--notuseful">
<input type="checkbox"
name="feedback__overall__value"
id="feedback__overall__value--notuseful"
class="option__input feedback__overall__value"
value="These assessments were not useful." />
<label class="option__label" for="feedback__overall__value--notuseful">{% trans "These assessments were not useful." %}</label>
</li>
<li class="option option--disagree">
<input type="checkbox"
name="feedback__overall__value"
id="feedback__overall__value--disagree"
class="option__input feedback__overall__value"
value="I disagree with one or more of the peer assessments of my response." />
<label class="option__label" for="feedback__overall__value--disagree">{% trans "I disagree with one or more of the peer assessments of my response." %}</label>
</li>
<li class="option option--inappropriate">
<input type="checkbox"
name="feedback__overall__value"
id="feedback__overall__value--inappropriate"
class="option__input feedback__overall__value"
value="Some comments I received were inappropriate." />
<label class="option__label" for="feedback__overall__value--inappropriate">{% trans "Some comments I received were inappropriate." %}</label>
</li>
</ol>
</li>
<li class="field field--textarea feedback__remarks" id="feedback__remarks">
<label for="feedback__remarks__value">{% trans "Please provide any feedback on the grade or comments that you received from your peers." %}</label>
<textarea
id="feedback__remarks__value"
placeholder="{% trans "I feel the feedback I received was..." %}"
maxlength="100000"
>
{{ feedback_text }}
</textarea>
</li>
</ol>
<div class="submission__feedback__actions {{ has_submitted_feedback|yesno:"is--hidden," }}"
{{ has_submitted_feedback|yesno:'aria-hidden=true,aria-hidden=false' }}>
<div class="message message--inline message--error message--error-server">
<h3 class="message__title">{% trans "We could not submit your feedback" %}</h3>
<div class="message__content"></div>
<div class="submission__feedback__instructions {{ has_submitted_feedback|yesno:"is--hidden," }}"
{{ has_submitted_feedback|yesno:'aria-hidden=true,aria-hidden=false' }}>
<p>{% trans "Course staff will be able to see any feedback that you provide here when they review course records." %}</p>
</div>
<ul class="list list--actions submission__feedback__actions">
<li class="list--actions__item">
<button type="submit" id="feedback__submit" class="action action--submit feedback__submit">{% trans "Submit Feedback on Peer Assessments" %}</button>
<ol class="list list--fields submission__feedback__fields {{ has_submitted_feedback|yesno:"is--hidden," }}"
{{ has_submitted_feedback|yesno:'aria-hidden=true,aria-hidden=false' }}>
<li class="field field-group field--radio feedback__overall">
<h4 class="field-group__label">{% trans "Select the statements below that best reflect your experience with peer assessments" %}:</h4>
<ol class="list--options">
<li class="option option--useful">
<input type="checkbox"
name="feedback__overall__value"
id="feedback__overall__value--useful"
class="option__input feedback__overall__value"
value="These assessments were useful." />
<label class="option__label" for="feedback__overall__value--useful">{% trans "These assessments were useful." %}</label>
</li>
<li class="option option--notuseful">
<input type="checkbox"
name="feedback__overall__value"
id="feedback__overall__value--notuseful"
class="option__input feedback__overall__value"
value="These assessments were not useful." />
<label class="option__label" for="feedback__overall__value--notuseful">{% trans "These assessments were not useful." %}</label>
</li>
<li class="option option--disagree">
<input type="checkbox"
name="feedback__overall__value"
id="feedback__overall__value--disagree"
class="option__input feedback__overall__value"
value="I disagree with one or more of the peer assessments of my response." />
<label class="option__label" for="feedback__overall__value--disagree">{% trans "I disagree with one or more of the peer assessments of my response." %}</label>
</li>
<li class="option option--inappropriate">
<input type="checkbox"
name="feedback__overall__value"
id="feedback__overall__value--inappropriate"
class="option__input feedback__overall__value"
value="Some comments I received were inappropriate." />
<label class="option__label" for="feedback__overall__value--inappropriate">{% trans "Some comments I received were inappropriate." %}</label>
</li>
</ol>
</li>
</ul>
<li class="field field--textarea feedback__remarks" id="feedback__remarks">
<label for="feedback__remarks__value">{% trans "Provide feedback on the grade or comments that you received from your peers." %}</label>
<textarea
id="feedback__remarks__value"
placeholder="{% trans "I feel the feedback I received was..." %}"
maxlength="100000">
{{ feedback_text }}
</textarea>
</li>
</ol>
<div class="submission__feedback__actions {{ has_submitted_feedback|yesno:"is--hidden," }}"
{{ has_submitted_feedback|yesno:'aria-hidden=true,aria-hidden=false' }}>
<div class="message message--inline message--error message--error-server">
<h3 class="message__title">{% trans "We could not submit your feedback" %}</h3>
<div class="message__content"></div>
</div>
<ul class="list list--actions submission__feedback__actions">
<li class="list--actions__item">
<button type="submit" class="action action--submit feedback__submit">{% trans "Submit Feedback on Peer Assessments" %}</button>
</li>
</ul>
</div>
</div>
</div>
</div>
</form>
{% endif %}
</form>
{% endif %}
</div>
</div>
</div>
</div>
</li>
</li>
{% endspaceless %}
......@@ -5,11 +5,14 @@ import copy
from collections import defaultdict
from lazy import lazy
from django.utils.translation import ugettext as _
from xblock.core import XBlock
from openassessment.assessment.api import ai as ai_api
from openassessment.assessment.api import peer as peer_api
from openassessment.assessment.api import self as self_api
from openassessment.assessment.api import ai as ai_api
from openassessment.assessment.api import staff as staff_api
from openassessment.assessment.errors import SelfAssessmentError, PeerAssessmentError
from submissions import api as sub_api
......@@ -97,6 +100,7 @@ class GradeMixin(object):
assessment_steps = self.assessment_steps
submission_uuid = workflow['submission_uuid']
staff_assessment = None
example_based_assessment = None
self_assessment = None
feedback = None
......@@ -106,8 +110,8 @@ class GradeMixin(object):
if "peer-assessment" in assessment_steps:
feedback = peer_api.get_assessment_feedback(submission_uuid)
peer_assessments = [
self._assessment_grade_context(asmnt)
for asmnt in peer_api.get_assessments(submission_uuid)
self._assessment_grade_context(peer_assessment)
for peer_assessment in peer_api.get_assessments(submission_uuid)
]
has_submitted_feedback = feedback is not None
......@@ -121,6 +125,10 @@ class GradeMixin(object):
ai_api.get_latest_assessment(submission_uuid)
)
raw_staff_assessment = staff_api.get_latest_staff_assessment(submission_uuid)
if raw_staff_assessment:
staff_assessment = self._assessment_grade_context(raw_staff_assessment)
feedback_text = feedback.get('feedback', '') if feedback else ''
student_submission = sub_api.get_submission(submission_uuid)
......@@ -135,41 +143,21 @@ class GradeMixin(object):
context = {
'score': score,
'feedback_text': feedback_text,
'has_submitted_feedback': has_submitted_feedback,
'student_submission': create_submission_dict(student_submission, self.prompts),
'peer_assessments': peer_assessments,
'self_assessment': self_assessment,
'example_based_assessment': example_based_assessment,
'rubric_criteria': self._rubric_criteria_grade_context(peer_assessments, self_assessment),
'has_submitted_feedback': has_submitted_feedback,
'grade_details': self.grade_details(
submission_uuid,
peer_assessments=peer_assessments,
self_assessment=self_assessment,
example_based_assessment=example_based_assessment,
staff_assessment=staff_assessment,
),
'file_upload_type': self.file_upload_type,
'allow_latex': self.allow_latex,
'file_url': self.get_download_url_from_submission(student_submission)
}
# Update the scores we will display to the user
# Note that we are updating a *copy* of the rubric criteria stored in
# the XBlock field
max_scores = peer_api.get_rubric_max_scores(submission_uuid)
median_scores = None
if "peer-assessment" in assessment_steps:
median_scores = peer_api.get_assessment_median_scores(submission_uuid)
elif "self-assessment" in assessment_steps:
median_scores = self_api.get_assessment_scores_by_criteria(submission_uuid)
elif "example-based-assessment" in assessment_steps:
median_scores = ai_api.get_assessment_scores_by_criteria(submission_uuid)
if median_scores is not None and max_scores is not None:
for criterion in context["rubric_criteria"]:
# Although we prevent course authors from modifying criteria post-release,
# it's still possible for assessments created by course staff to
# have criteria that differ from the current problem definition.
# It's also possible to circumvent the post-release restriction
# if course authors directly import a course into Studio.
# If this happens, we simply leave the score blank so that the grade
# section can render without error.
criterion["median_score"] = median_scores.get(criterion["name"], '')
criterion["total_value"] = max_scores.get(criterion["name"], '')
return ('openassessmentblock/grade/oa_grade_complete.html', context)
def render_grade_incomplete(self, workflow):
......@@ -238,25 +226,25 @@ class GradeMixin(object):
)
return {'success': True, 'msg': self._(u"Feedback saved.")}
def _rubric_criteria_grade_context(self, peer_assessments, self_assessment):
def grade_details(
self, submission_uuid, peer_assessments, self_assessment, example_based_assessment, staff_assessment
):
"""
Sanitize the rubric criteria into a format that can be passed
into the grade complete Django template.
* Add per-criterion feedback from peer assessments to the rubric criteria.
* Filters out empty feedback.
* Assign a "label" for criteria/options if none is defined (backwards compatibility).
Returns details about the grade assigned to the submission.
Args:
submission_uuid (str): The id of the submission being graded.
peer_assessments (list of dict): Serialized assessment models from the peer API.
self_assessment (dict): Serialized assessment model from the self API
example_based_assessment (dict): Serialized assessment model from the example-based API
staff_assessment (dict): Serialized assessment model from the staff API
Returns:
list of criterion dictionaries
A dictionary with full details about the submission's grade.
Example:
[
{
{
criteria: [{
'label': 'Test name',
'name': 'f78ac7d4ca1e4134b0ba4b40ca212e72',
'prompt': 'Test prompt',
......@@ -266,32 +254,253 @@ class GradeMixin(object):
'Good job!',
'Excellent work!',
]
},
}],
additional_feedback: [{
}]
...
]
}
"""
criteria = copy.deepcopy(self.rubric_criteria_with_labels)
peer_criteria_feedback = defaultdict(list)
self_criteria_feedback = {}
for assessment in peer_assessments:
for part in assessment['parts']:
if part['feedback']:
part_criterion_name = part['criterion']['name']
peer_criteria_feedback[part_criterion_name].append(part['feedback'])
def has_feedback(assessments):
"""
Returns True if at least one assessment has feedback.
if self_assessment:
for part in self_assessment['parts']:
if part['feedback']:
part_criterion_name = part['criterion']['name']
self_criteria_feedback[part_criterion_name] = part['feedback']
Args:
assessments: A list of assessments
Returns:
Returns True if at least one assessment has feedback.
"""
return any(
assessment.get('feedback', None) or has_feedback(assessment.get('individual_assessments', []))
for assessment in assessments
)
max_scores = peer_api.get_rubric_max_scores(submission_uuid)
median_scores = None
assessment_steps = self.assessment_steps
if staff_assessment:
median_scores = staff_api.get_assessment_scores_by_criteria(submission_uuid)
elif "peer-assessment" in assessment_steps:
median_scores = peer_api.get_assessment_median_scores(submission_uuid)
elif "example-based-assessment" in assessment_steps:
median_scores = ai_api.get_assessment_scores_by_criteria(submission_uuid)
elif "self-assessment" in assessment_steps:
median_scores = self_api.get_assessment_scores_by_criteria(submission_uuid)
for criterion in criteria:
criterion_name = criterion['name']
criterion['peer_feedback'] = peer_criteria_feedback[criterion_name]
criterion['self_feedback'] = self_criteria_feedback.get(criterion_name)
return criteria
# Record assessment info for the current criterion
criterion['assessments'] = self._graded_assessments(
submission_uuid, criterion,
staff_assessment=staff_assessment,
peer_assessments=peer_assessments,
example_based_assessment=example_based_assessment,
self_assessment=self_assessment,
)
# Record whether there is any feedback provided in the assessments
criterion['has_feedback'] = has_feedback(criterion['assessments'])
# Although we prevent course authors from modifying criteria post-release,
# it's still possible for assessments created by course staff to
# have criteria that differ from the current problem definition.
# It's also possible to circumvent the post-release restriction
# if course authors directly import a course into Studio.
# If this happens, we simply leave the score blank so that the grade
# section can render without error.
criterion['median_score'] = median_scores.get(criterion_name, '')
criterion['total_value'] = max_scores.get(criterion_name, '')
return {
'criteria': criteria,
'additional_feedback': self._additional_feedback(
staff_assessment=staff_assessment,
peer_assessments=peer_assessments,
self_assessment= self_assessment,
),
}
def _graded_assessments(
self, submission_uuid, criterion, staff_assessment, peer_assessments,
example_based_assessment, self_assessment
):
"""
Returns an array of assessments with their associated grades.
"""
def _get_assessment_part(title, part_criterion_name, assessment):
"""
Returns the assessment part for the given criterion name.
"""
if assessment:
for part in assessment['parts']:
if part['criterion']['name'] == part_criterion_name:
part['title'] = title
return part
return None
# Fetch all the unique assessment parts
criterion_name = criterion['name']
staff_assessment_part = _get_assessment_part(_('Staff Grade'), criterion_name, staff_assessment)
if len(peer_assessments) > 0:
peer_assessment_part = {
'title': _('Peer Median Grade'),
'criterion': criterion,
'option': self._peer_median_option(submission_uuid, criterion),
'individual_assessments': [
_get_assessment_part(
_('Peer {peer_index}').format(peer_index=index + 1),
criterion_name,
peer_assessment
)
for index, peer_assessment in enumerate(peer_assessments)
],
}
else:
peer_assessment_part = None
example_based_assessment_part = _get_assessment_part(
_('Example-Based Grade'), criterion_name, example_based_assessment
)
self_assessment_part = _get_assessment_part(_('Your Self Assessment'), criterion_name, self_assessment)
# Now collect together all the assessments
assessments = []
if staff_assessment_part:
assessments.append(staff_assessment_part)
if peer_assessment_part:
assessments.append(peer_assessment_part)
if example_based_assessment_part:
assessments.append(example_based_assessment_part)
if self_assessment_part:
assessments.append(self_assessment_part)
# Include points only for the first assessment
if len(assessments) > 0:
first_assessment = assessments[0]
option = first_assessment['option']
if option:
first_assessment['points'] = option['points']
return assessments
def _peer_median_option(self, submission_uuid, criterion):
"""
Returns the option for the median peer grade.
Args:
submission_uuid (str): The id for the submission.
criterion (dict): The criterion in question.
Returns:
The option for the median peer grade.
"""
median_scores = peer_api.get_assessment_median_scores(submission_uuid)
median_score = median_scores.get(criterion['name'], None)
def median_options():
"""
Returns a list of options that should be shown to represent the median.
Some examples:
1. Options A=1, B=3, and C=5, a median score of 3 returns [B].
2. Options A=1, B=3, and C=5, a median score of 4 returns [B, C].
3. Options A=1, B=1, and C=3, a median score of 1 returns [A, B]
4. Options A=1, B=1, C=3, and D=3, a median score of 2 return [A, B, C, D]
5. Options A=1, B=3 and C=5, a median score of 6 returns [C]
Note: 5 should not happen as a median should never be out of range.
"""
last_score = None
median_options = []
# Sort the options first by name and then by points, so that if there
# are options with identical points they will sort alphabetically rather
# than randomly. Note that this depends upon sorted being a stable sort.
alphabetical_options = sorted(criterion['options'], key=lambda option: option['label'])
ordered_options = sorted(alphabetical_options, key=lambda option: option['points'])
for option in ordered_options:
current_score = option['points']
# If we have reached a new score, then decide what to do next
if current_score is not last_score:
# If the last score we saw was already larger than the median
# score, then we must have collected enough so return all
# the median options.
if last_score >= median_score:
return median_options
# If the current score is exactly the median or is less,
# then we don't need any previously collected scores.
if current_score <= median_score:
median_options = []
# Update the last score to be the current one
last_score = current_score
# Collect the current option in case it is applicable
median_options.append(option)
return median_options
# Calculate the full list of matching options for the median, and then:
# - If zero or one matches are found, then just return None or the single item.
# - If more than one match is found, return a dict with an aggregate label,
# - the median score, and no explanation (it is too verbose to show an aggregate).
options = median_options()
if len(options) == 0:
return None
if len(options) == 1:
return options[0]
return {
'label': u' / '.join([option['label'] for option in options]),
'points': median_score,
'explanation': None,
}
def _additional_feedback(self, staff_assessment, peer_assessments, self_assessment):
"""
Returns an array of additional feedback for the specified assessments.
Args:
staff_assessment: The staff assessment
peer_assessments: An array of peer assessments
self_assessment: The self assessment
Returns:
Returns an array of additional feedback per assessment.
"""
additional_feedback = []
if staff_assessment:
feedback = staff_assessment.get('feedback')
if feedback:
additional_feedback.append({
'title': _('Staff Comments'),
'feedback': feedback
})
if peer_assessments:
individual_feedback = []
for peer_index, peer_assessment in enumerate(peer_assessments):
individual_feedback.append({
'title': _('Peer {peer_index}').format(peer_index=peer_index + 1),
'feedback': peer_assessment.get('feedback')
})
if any(assessment_feedback['feedback'] for assessment_feedback in individual_feedback):
additional_feedback.append({
'title': _('Peer'),
'individual_assessments': individual_feedback
})
if self_assessment:
feedback = self_assessment.get('feedback')
if feedback:
additional_feedback.append({
'title': _('Your Comments'),
'feedback': feedback
})
return additional_feedback if additional_feedback else None
@lazy
def _criterion_and_option_labels(self):
......
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.
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(sel,hidden){sel.toggleClass("is--hidden",hidden);sel.attr("aria-hidden",hidden?"true":"false")},isHidden:function(sel){return sel.hasClass("is--hidden")&&sel.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();
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
/**
Interface for grade view.
Args:
element (DOM element): The DOM element representing the XBlock.
server (OpenAssessment.Server): The interface to the XBlock server.
baseView (OpenAssessment.BaseView): Container view.
Returns:
OpenAssessment.ResponseView
**/
* The GradeView class.
*
* @param {element} element - The DOM element representing the XBlock
* @param {OpenAssessment.Server} server - The interface to the XBlock server
* @param {OpenAssessment.BaseView} baseView - The container view.
* @constructor
*/
OpenAssessment.GradeView = function(element, server, baseView) {
this.element = element;
this.server = server;
......@@ -17,8 +14,8 @@ OpenAssessment.GradeView = function(element, server, baseView) {
OpenAssessment.GradeView.prototype = {
/**
Load the grade view.
**/
* Load the grade view.
*/
load: function() {
var view = this;
var baseView = this.baseView;
......@@ -35,8 +32,8 @@ OpenAssessment.GradeView.prototype = {
},
/**
Install event handlers for the view.
**/
* Install event handlers for the view.
*/
installHandlers: function() {
// Install a click handler for collapse/expand
var sel = $('#openassessment__grade', this.element);
......@@ -44,26 +41,18 @@ OpenAssessment.GradeView.prototype = {
// Install a click handler for assessment feedback
var view = this;
sel.find('#feedback__submit').click(function(eventObject) {
sel.find('.feedback__submit').click(function(eventObject) {
eventObject.preventDefault();
view.submitFeedbackOnAssessment();
});
},
/**
Get or set the text for feedback on assessments.
Args:
text (string or undefined): The text of the assessment to set (optional).
Returns:
string or undefined: The text of the feedback.
Example usage:
>>> view.feedbackText('I liked my assessment'); // Set the feedback text
>>> view.feedbackText(); // Retrieve the feedback text
'I liked my assessment'
**/
* Get or set the text for feedback on assessments.
*
* @param {string} text - The text of the assessment to set (optional).
* @returns {string} The text of the feedback
*/
feedbackText: function(text) {
if (typeof text === 'undefined') {
return $('#feedback__remarks__value', this.element).val();
......@@ -73,25 +62,11 @@ OpenAssessment.GradeView.prototype = {
},
/**
Get or set the options for feedback on assessments.
Args:
options (array of strings or undefined): List of options to check (optional).
Returns:
list of strings or undefined: The values of the options the user selected.
Example usage:
// Set the feedback options; all others will be unchecked
>>> view.feedbackOptions('notuseful', 'disagree');
// Retrieve the feedback options that are checked
>>> view.feedbackOptions();
[
'These assessments were not useful.',
'I disagree with the ways that my peers assessed me'
]
**/
* Get or set the options for feedback on assessments.
*
* @param {dict} options - List of options to check (optional).
* @returns {list} - The values of the options the user selected.
*/
feedbackOptions: function(options) {
var view = this;
if (typeof options === 'undefined') {
......@@ -111,60 +86,40 @@ OpenAssessment.GradeView.prototype = {
},
/**
Hide elements, including setting the aria-hidden attribute for screen readers.
Args:
sel (JQuery selector): The selector matching elements to hide.
hidden (boolean): Whether to hide or show the elements.
Returns:
undefined
**/
setHidden: function(sel, hidden) {
sel.toggleClass('is--hidden', hidden);
sel.attr('aria-hidden', hidden ? 'true' : 'false');
* Hide elements, including setting the aria-hidden attribute for screen readers.
*
* @param {JQuery.selector} selector - The selector matching the elements to hide.
* @param {boolean} hidden - Whether to hide or show the elements.
*/
setHidden: function(selector, hidden) {
selector.toggleClass('is--hidden', hidden);
selector.attr('aria-hidden', hidden ? 'true' : 'false');
},
/**
Check whether elements are hidden.
Args:
sel (JQuery selector): The selector matching elements to hide.
Returns:
boolean
**/
isHidden: function(sel) {
return sel.hasClass('is--hidden') && sel.attr('aria-hidden') === 'true';
* Check whether elements are hidden.
*
* @param {JQuery.selector} selector - The selector matching the elements to check.
* @returns {boolean} - True if all the elements are hidden, else false.
*/
isHidden: function(selector) {
return selector.hasClass('is--hidden') && selector.attr('aria-hidden') === 'true';
},
/**
Get or set the state of the feedback on assessment.
Each state corresponds to a particular configuration of attributes
in the DOM, which control what the user sees in the UI.
Valid states are:
'open': The user has not yet submitted feedback on assessments.
'submitting': The user has submitted feedback, but the server has not yet responded.
'submitted': The feedback was successfully submitted
Args:
newState (string or undefined): One of above states.
Returns:
string or undefined: The current state.
Throws:
'Invalid feedback state' if the DOM is not in one of the valid states.
Example usage:
>>> view.feedbackState();
'open'
>>> view.feedbackState('submitted');
>>> view.feedbackState();
'submitted'
**/
* Get or set the state of the feedback on assessment.
*
* Each state corresponds to a particular configuration of attributes
* in the DOM, which control what the user sees in the UI.
*
* Valid states are:
* 'open': The user has not yet submitted feedback on assessments.
* 'submitting': The user has submitted feedback, but the server has not yet responded.
* 'submitted': The feedback was successfully submitted.
*
* @param {string} newState - the new state to set for the feedback (optional).
* @returns {*} The current state.
*/
feedbackState: function(newState) {
var containerSel = $('.submission__feedback__content', this.element);
var instructionsSel = containerSel.find('.submission__feedback__instructions');
......@@ -234,15 +189,15 @@ OpenAssessment.GradeView.prototype = {
},
/**
Send assessment feedback to the server and update the view.
**/
* Send assessment feedback to the server and update the view.
*/
submitFeedbackOnAssessment: function() {
// Send the submission to the server
var view = this;
var baseView = this.baseView;
// Disable the submission button to prevent duplicate submissions
$("#feedback__submit", this.element).toggleClass('is--disabled', true);
$(".feedback__submit", this.element).toggleClass('is--disabled', true);
// Indicate to the user that we're starting to submit
view.feedbackState('submitting');
......
......@@ -963,6 +963,11 @@
@extend %t-titlecase;
display: block;
color: $heading-secondary-color;
.answer__source__value-with-points {
@extend %t-score;
color: $heading-primary-color;
}
}
.answer__value {
......
# -*- coding: utf-8 -*-
"""
Base class for handler-level testing of the XBlock.
"""
import copy
import mock
import os.path
import json
from functools import wraps
from submissions import api as submissions_api
from openassessment.workflow import api as workflow_api
from openassessment.assessment.api import peer as peer_api
from openassessment.assessment.api import self as self_api
from openassessment.test_utils import CacheResetTest, TransactionCacheResetTest
from workbench.runtime import WorkbenchRuntime
import webob
# Sample peer assessments
PEER_ASSESSMENTS = [
{
'options_selected': {u'𝓒𝓸𝓷𝓬𝓲𝓼𝓮': u'ﻉซƈﻉɭɭﻉกՇ', u'Form': u'Good'},
'criterion_feedback': {
u'𝓒𝓸𝓷𝓬𝓲𝓼𝓮': u'Peer 1: ฝﻉɭɭ ɗѻกﻉ!'
},
'overall_feedback': u'єאςєɭɭєภՇ ฬ๏гк!',
},
{
'options_selected': {u'𝓒𝓸𝓷𝓬𝓲𝓼𝓮': u'Ġööḋ', u'Form': u'Fair'},
'criterion_feedback': {
u'𝓒𝓸𝓷𝓬𝓲𝓼𝓮': u'Peer 2: ฝﻉɭɭ ɗѻกﻉ!',
u'Form': u'Peer 2: ƒαιя נσв'
},
'overall_feedback': u'Good job!',
},
]
# Sample self assessment
SELF_ASSESSMENT = {
'options_selected': {u'𝓒𝓸𝓷𝓬𝓲𝓼𝓮': u'ﻉซƈﻉɭɭﻉกՇ', u'Form': u'Fair'},
'criterion_feedback': {
u'𝓒𝓸𝓷𝓬𝓲𝓼𝓮': u'Peer 1: ฝﻉɭɭ ɗѻกﻉ!'
},
'overall_feedback': u'єאςєɭɭєภՇ ฬ๏гк!',
}
# A sample good staff assessment
STAFF_GOOD_ASSESSMENT = {
'options_selected': {u'𝓒𝓸𝓷𝓬𝓲𝓼𝓮': u'ﻉซƈﻉɭɭﻉกՇ', u'Form': u'Fair'},
'criterion_feedback': {
u'𝓒𝓸𝓷𝓬𝓲𝓼𝓮': u'Staff: ฝﻉɭɭ ɗѻกﻉ!',
u'Form': u'Staff: ƒαιя נσв'
},
'overall_feedback': u'Staff: good job!'
}
# A sample bad staff assessment
STAFF_BAD_ASSESSMENT = {
'options_selected': {u'𝓒𝓸𝓷𝓬𝓲𝓼𝓮': u'ק๏๏г', u'Form': u'Poor'},
'criterion_feedback': {
u'𝓒𝓸𝓷𝓬𝓲𝓼𝓮': u'Staff: ק๏๏г נσв',
u'Form': u'Staff: ק๏๏г נσв'
},
'overall_feedback': u'Staff: very poor'
}
def scenario(scenario_path, user_id=None):
"""
......@@ -172,3 +229,120 @@ class XBlockHandlerTransactionTestCase(XBlockHandlerTestCaseMixin, TransactionCa
use `XBlockHandlerTestCase` instead.
"""
pass
class SubmitAssessmentsMixin(object):
"""
A mixin for creating a submission and peer/self assessments so that the user can
receive a grade. This is useful for getting into the "waiting for peer assessment" state.
"""
maxDiff = None
PEERS = ['McNulty', 'Moreland']
SUBMISSION = (u'ՇﻉรՇ', u'รપ๒๓ٱรรٱѻก')
STEPS = ['peer', 'self']
def create_submission_and_assessments(
self, xblock, submission_text, peers, peer_assessments, self_assessment,
waiting_for_peer=False,
):
"""
Create a submission and peer/self assessments, so that the user can receive a grade.
Args:
xblock (OpenAssessmentBlock): The XBlock, loaded for the user who needs a grade.
submission_text (unicode): Text of the submission from the user.
peers (list of unicode): List of user IDs of peers who will assess the user.
peer_assessments (list of dict): List of assessment dictionaries for peer assessments.
self_assessment (dict): Dict of assessment for self-assessment.
Keyword Arguments:
waiting_for_peer (bool): If true, skip creation of peer assessments for the user's submission.
Returns:
the submission
"""
# Create a submission from the user
student_item = xblock.get_student_item_dict()
student_id = student_item['student_id']
submission = xblock.create_submission(student_item, submission_text)
# Create submissions and assessments from other users
scorer_submissions = []
for scorer_name, assessment in zip(peers, peer_assessments):
# Create a submission for each scorer for the same problem
scorer = copy.deepcopy(student_item)
scorer['student_id'] = scorer_name
scorer_sub = submissions_api.create_submission(scorer, {'text': submission_text})
workflow_api.create_workflow(scorer_sub['uuid'], self.STEPS)
submission = peer_api.get_submission_to_assess(scorer_sub['uuid'], len(peers))
# Store the scorer's submission so our user can assess it later
scorer_submissions.append(scorer_sub)
# Create an assessment of the user's submission
if not waiting_for_peer:
peer_api.create_assessment(
scorer_sub['uuid'], scorer_name,
assessment['options_selected'],
assessment['criterion_feedback'],
assessment['overall_feedback'],
{'criteria': xblock.rubric_criteria},
xblock.get_assessment_module('peer-assessment')['must_be_graded_by']
)
# Have our user make assessments (so she can get a score)
for assessment in peer_assessments:
peer_api.get_submission_to_assess(submission['uuid'], len(peers))
peer_api.create_assessment(
submission['uuid'],
student_id,
assessment['options_selected'],
assessment['criterion_feedback'],
assessment['overall_feedback'],
{'criteria': xblock.rubric_criteria},
xblock.get_assessment_module('peer-assessment')['must_be_graded_by']
)
# Have the user submit a self-assessment (so she can get a score)
if self_assessment is not None:
self_api.create_assessment(
submission['uuid'], student_id, self_assessment['options_selected'],
self_assessment['criterion_feedback'], self_assessment['overall_feedback'],
{'criteria': xblock.rubric_criteria}
)
return submission
def set_staff_access(self, xblock):
xblock.xmodule_runtime = mock.Mock(user_is_staff=True)
xblock.xmodule_runtime.anonymous_student_id = 'Bob'
@staticmethod
def set_mock_workflow_info(xblock, workflow_status, status_details, submission_uuid):
xblock.get_workflow_info = mock.Mock(return_value={
'status': workflow_status,
'status_details': status_details,
'submission_uuid': submission_uuid
})
def submit_staff_assessment(self, xblock, submission, assessment):
"""
Submits a staff assessment for the specified submission.
Args:
xblock: The XBlock being assessed.
submission: The submission being assessed.
assessment: The staff assessment.
"""
self.set_staff_access(xblock)
assessment = copy.deepcopy(assessment)
assessment['submission_uuid'] = submission['uuid']
resp = self.request(xblock, 'staff_assess', json.dumps(assessment), response_format='json')
self.assertTrue(resp['success'])
......@@ -12,7 +12,7 @@
<criterion feedback="optional">
<name>𝓒𝓸𝓷𝓬𝓲𝓼𝓮</name>
<prompt>How concise is it?</prompt>
<option points="3">
<option points="4">
<name>ﻉซƈﻉɭɭﻉกՇ</name>
<explanation>Extremely concise</explanation>
</option>
......@@ -32,7 +32,7 @@
<name>Good</name>
<explanation>Good</explanation>
</option>
<option points="2">
<option points="3">
<name>Fair</name>
<explanation>Fair</explanation>
</option>
......
......@@ -7,118 +7,14 @@ import ddt
import json
import mock
from django.test.utils import override_settings
from submissions import api as sub_api
from openassessment.workflow import api as workflow_api
from openassessment.assessment.api import peer as peer_api
from openassessment.assessment.api import self as self_api
from openassessment.xblock.openassessmentblock import OpenAssessmentBlock
from .base import XBlockHandlerTestCase, scenario
class SubmitAssessmentsMixin(object):
"""
A mixin for creating a submission and peer/self assessments so that the user can
receive a grade. This is useful for getting into the "waiting for peer assessment" state.
"""
PEERS = ['McNulty', 'Moreland']
ASSESSMENTS = [
{
'options_selected': {u'𝓒𝓸𝓷𝓬𝓲𝓼𝓮': u'ﻉซƈﻉɭɭﻉกՇ', u'Form': u'Fair'},
'criterion_feedback': {
u'𝓒𝓸𝓷𝓬𝓲𝓼𝓮': u'Peer 1: ฝﻉɭɭ ɗѻกﻉ!'
},
'overall_feedback': u'єאςєɭɭєภՇ ฬ๏гк!',
},
{
'options_selected': {u'𝓒𝓸𝓷𝓬𝓲𝓼𝓮': u'ﻉซƈﻉɭɭﻉกՇ', u'Form': u'Fair'},
'criterion_feedback': {
u'𝓒𝓸𝓷𝓬𝓲𝓼𝓮': u'Peer 2: ฝﻉɭɭ ɗѻกﻉ!',
u'Form': u'Peer 2: ƒαιя נσв'
},
'overall_feedback': u'Good job!',
},
]
SUBMISSION = (u'ՇﻉรՇ', u'รપ๒๓ٱรรٱѻก')
STEPS = ['peer', 'self']
def _create_submission_and_assessments(
self, xblock, submission_text, peers, peer_assessments, self_assessment,
waiting_for_peer=False,
):
"""
Create a submission and peer/self assessments, so that the user can receive a grade.
Args:
xblock (OpenAssessmentBlock): The XBlock, loaded for the user who needs a grade.
submission_text (unicode): Text of the submission from the user.
peers (list of unicode): List of user IDs of peers who will assess the user.
peer_assessments (list of dict): List of assessment dictionaries for peer assessments.
self_assessment (dict): Dict of assessment for self-assessment.
Keyword Arguments:
waiting_for_peer (bool): If true, skip creation of peer assessments for the user's submission.
Returns:
the submission
"""
# Create a submission from the user
student_item = xblock.get_student_item_dict()
student_id = student_item['student_id']
submission = xblock.create_submission(student_item, submission_text)
# Create submissions and assessments from other users
scorer_submissions = []
for scorer_name, assessment in zip(peers, peer_assessments):
# Create a submission for each scorer for the same problem
scorer = copy.deepcopy(student_item)
scorer['student_id'] = scorer_name
scorer_sub = sub_api.create_submission(scorer, {'text': submission_text})
workflow_api.create_workflow(scorer_sub['uuid'], self.STEPS)
submission = peer_api.get_submission_to_assess(scorer_sub['uuid'], len(peers))
# Store the scorer's submission so our user can assess it later
scorer_submissions.append(scorer_sub)
# Create an assessment of the user's submission
if not waiting_for_peer:
peer_api.create_assessment(
scorer_sub['uuid'], scorer_name,
assessment['options_selected'],
assessment['criterion_feedback'],
assessment['overall_feedback'],
{'criteria': xblock.rubric_criteria},
xblock.get_assessment_module('peer-assessment')['must_be_graded_by']
)
# Have our user make assessments (so she can get a score)
for assessment in peer_assessments:
peer_api.get_submission_to_assess(submission['uuid'], len(peers))
peer_api.create_assessment(
submission['uuid'],
student_id,
assessment['options_selected'],
assessment['criterion_feedback'],
assessment['overall_feedback'],
{'criteria': xblock.rubric_criteria},
xblock.get_assessment_module('peer-assessment')['must_be_graded_by']
)
# Have the user submit a self-assessment (so she can get a score)
if self_assessment is not None:
self_api.create_assessment(
submission['uuid'], student_id, self_assessment['options_selected'],
self_assessment['criterion_feedback'], self_assessment['overall_feedback'],
{'criteria': xblock.rubric_criteria}
)
return submission
from .base import (
scenario, SubmitAssessmentsMixin, XBlockHandlerTestCase,
PEER_ASSESSMENTS, SELF_ASSESSMENT, STAFF_GOOD_ASSESSMENT, STAFF_BAD_ASSESSMENT,
)
@ddt.ddt
......@@ -133,8 +29,8 @@ class TestGrade(XBlockHandlerTestCase, SubmitAssessmentsMixin):
@scenario('data/grade_scenario.xml', user_id='Greggs')
def test_render_grade(self, xblock):
# Submit, assess, and render the grade view
self._create_submission_and_assessments(
xblock, self.SUBMISSION, self.PEERS, self.ASSESSMENTS, self.ASSESSMENTS[0]
self.create_submission_and_assessments(
xblock, self.SUBMISSION, self.PEERS, PEER_ASSESSMENTS, SELF_ASSESSMENT
)
resp = self.request(xblock, 'render_grade', json.dumps(dict()))
......@@ -167,8 +63,8 @@ class TestGrade(XBlockHandlerTestCase, SubmitAssessmentsMixin):
@scenario('data/grade_scenario_self_only.xml', user_id='Greggs')
def test_render_grade_self_only(self, xblock):
# Submit, assess, and render the grade view
self._create_submission_and_assessments(
xblock, self.SUBMISSION, [], [], self.ASSESSMENTS[0],
self.create_submission_and_assessments(
xblock, self.SUBMISSION, [], [], SELF_ASSESSMENT,
waiting_for_peer=True
)
resp = self.request(xblock, 'render_grade', json.dumps(dict()))
......@@ -196,13 +92,13 @@ class TestGrade(XBlockHandlerTestCase, SubmitAssessmentsMixin):
@scenario('data/feedback_only_criterion_grade.xml', user_id='Greggs')
def test_render_grade_feedback_only_criterion(self, xblock):
# Add in per-criterion feedback for the feedback-only criterion
peer_assessments = copy.deepcopy(self.ASSESSMENTS)
peer_assessments = copy.deepcopy(PEER_ASSESSMENTS)
for asmnt in peer_assessments:
asmnt['criterion_feedback'] = {
u'𝖋𝖊𝖊𝖉𝖇𝖆𝖈𝖐 𝖔𝖓𝖑𝖞': u"Ṫḧïṡ ïṡ ṡöṁë ḟëëḋḅäċḳ."
}
self_assessment = copy.deepcopy(self.ASSESSMENTS[0])
self_assessment = copy.deepcopy(SELF_ASSESSMENT)
self_assessment['criterion_feedback'] = {
u'𝖋𝖊𝖊𝖉𝖇𝖆𝖈𝖐 𝖔𝖓𝖑𝖞': "Feedback here",
u'Form': 'lots of feedback yes"',
......@@ -210,7 +106,7 @@ class TestGrade(XBlockHandlerTestCase, SubmitAssessmentsMixin):
}
# Submit, assess, and render the grade view
self._create_submission_and_assessments(
self.create_submission_and_assessments(
xblock, self.SUBMISSION, self.PEERS, peer_assessments, self_assessment
)
......@@ -231,7 +127,7 @@ class TestGrade(XBlockHandlerTestCase, SubmitAssessmentsMixin):
self.request(xblock, 'schedule_training', json.dumps({}), response_format='json')
# Submit, assess, and render the grade view
self._create_submission_and_assessments(
self.create_submission_and_assessments(
xblock, self.SUBMISSION, [], [], None, waiting_for_peer=True
)
resp = self.request(xblock, 'render_grade', json.dumps(dict()))
......@@ -256,43 +152,136 @@ class TestGrade(XBlockHandlerTestCase, SubmitAssessmentsMixin):
self.assertNotIn('complete', resp.lower())
@scenario('data/feedback_per_criterion.xml', user_id='Bernard')
def test_render_grade_feedback_per_criterion(self, xblock):
def test_render_grade_feedback(self, xblock):
# Submit, assess, and render the grade view
self._create_submission_and_assessments(
xblock, self.SUBMISSION, self.PEERS, self.ASSESSMENTS, self.ASSESSMENTS[0]
submission = self.create_submission_and_assessments(
xblock, self.SUBMISSION, self.PEERS, PEER_ASSESSMENTS, SELF_ASSESSMENT
)
workflow_info = xblock.get_workflow_info()
# Verify that the context for the grade complete page contains the feedback
_, context = xblock.render_grade_complete(xblock.get_workflow_info())
criteria = context['rubric_criteria']
# Submit a staff assessment
self.submit_staff_assessment(xblock, submission, assessment=STAFF_GOOD_ASSESSMENT)
self.assertEqual(criteria[0]['peer_feedback'], [
u'Peer 2: ฝﻉɭɭ ɗѻกﻉ!',
# Get the grade details
_, context = xblock.render_grade_complete(workflow_info)
grade_details = context['grade_details']
# Verify feedback for the first criteria
first_criteria_assessments = grade_details['criteria'][0]['assessments']
self.assertEqual(
first_criteria_assessments[0]['feedback'],
u'Staff: ฝﻉɭɭ ɗѻกﻉ!'
)
self.assertEqual(
[assessment['feedback'] for assessment in first_criteria_assessments[1]['individual_assessments']],
[
u'Peer 2: ฝﻉɭɭ ɗѻกﻉ!',
u'Peer 1: ฝﻉɭɭ ɗѻกﻉ!',
]
)
self.assertEqual(
first_criteria_assessments[2]['feedback'],
u'Peer 1: ฝﻉɭɭ ɗѻกﻉ!'
)
# Verify the feedback for the second criteria
second_criteria_assessments = grade_details['criteria'][1]['assessments']
self.assertEqual(
second_criteria_assessments[0]['feedback'],
u'Staff: ƒαιя נσв'
)
self.assertEqual(
[assessment['feedback'] for assessment in second_criteria_assessments[1]['individual_assessments']],
[
u'Peer 2: ƒαιя נσв',
u'',
]
)
# Verify the additional feedback
additional_feedback = grade_details['additional_feedback']
self.assertEqual(
additional_feedback[0]['feedback'],
u'Staff: good job!'
)
self.assertEqual(
[assessment['feedback'] for assessment in additional_feedback[1]['individual_assessments']],
[
u'Good job!',
u'єאςєɭɭєภՇ ฬ๏гк!',
]
)
# Integration test: verify that all of the feedback makes it to the rendered template
html = self.request(xblock, 'render_grade', json.dumps(dict())).decode('utf-8')
for expected_text in [
u'Staff: ฝﻉɭɭ ɗѻกﻉ!',
u'Peer 1: ฝﻉɭɭ ɗѻกﻉ!',
])
self.assertEqual(criteria[0]['self_feedback'], u'Peer 1: ฝﻉɭɭ ɗѻกﻉ!')
self.assertEqual(criteria[1]['peer_feedback'], [u'Peer 2: ƒαιя נσв'])
# The order of the peers in the per-criterion feedback needs
# to match the order of the peer assessments
# We verify this by checking that the first peer assessment
# has the criteria feedback matching the first feedback
# for each criterion.
assessments = context['peer_assessments']
first_peer_feedback = [part['feedback'] for part in assessments[0]['parts']]
self.assertItemsEqual(first_peer_feedback, [u'Peer 2: ฝﻉɭɭ ɗѻกﻉ!', u'Peer 2: ƒαιя נσв'])
# Integration test: verify that the context makes it to the rendered template
resp = self.request(xblock, 'render_grade', json.dumps(dict()))
self.assertIn(u'Peer 1: ฝﻉɭɭ ɗѻกﻉ!', resp.decode('utf-8'))
self.assertIn(u'Peer 2: ฝﻉɭɭ ɗѻกﻉ!', resp.decode('utf-8'))
self.assertIn(u'Peer 2: ƒαιя נσв', resp.decode('utf-8'))
u'Peer 2: ฝﻉɭɭ ɗѻกﻉ!',
u'Staff: ƒαιя נσв',
u'Peer 2: ƒαιя נσв',
u'Staff: good job!',
u'Good job!',
u'єאςєɭɭєภՇ ฬ๏гк!',
]:
self.assertIn(expected_text, html)
@scenario('data/feedback_per_criterion.xml', user_id='Bernard')
def test_render_grade_details(self, xblock):
# Submit, assess, and render the grade view
self.create_submission_and_assessments(
xblock, self.SUBMISSION, self.PEERS, PEER_ASSESSMENTS, SELF_ASSESSMENT
)
# Get the grade details
_, context = xblock.render_grade_complete(xblock.get_workflow_info())
criteria = context['grade_details']['criteria']
# Verify that the median peer grades are correct
self.assertEquals(criteria[0]['assessments'][0]['option']['label'], u'Ġööḋ / ﻉซƈﻉɭɭﻉกՇ')
self.assertEquals(criteria[1]['assessments'][0]['option']['label'], u'Fair / Good')
self.assertEquals(criteria[0]['assessments'][0]['points'], 3)
self.assertEquals(criteria[1]['assessments'][0]['points'], 3)
# Verify that the self assessment grades are correct and have no points
self.assertEquals(criteria[0]['assessments'][1]['option']['label'], u'ﻉซƈﻉɭɭﻉกՇ')
self.assertEquals(criteria[1]['assessments'][1]['option']['label'], u'Fair')
self.assertIsNone(criteria[0]['assessments'][1].get('points', None))
self.assertIsNone(criteria[1]['assessments'][1].get('points', None))
@ddt.data(
(STAFF_GOOD_ASSESSMENT, [4, 3]),
(STAFF_BAD_ASSESSMENT, [1, 1]),
)
@ddt.unpack
@scenario('data/feedback_per_criterion.xml', user_id='Bernard')
def test_render_staff_grades(self, xblock, assessment, scores):
# Submit, assess, and render the grade view
submission = self.create_submission_and_assessments(
xblock, self.SUBMISSION, self.PEERS, PEER_ASSESSMENTS, SELF_ASSESSMENT
)
workflow_info = xblock.get_workflow_info()
# Submit a staff assessment
self.submit_staff_assessment(xblock, submission, assessment=assessment)
# Get the grade details
_, context = xblock.render_grade_complete(workflow_info)
grade_details = context['grade_details']
# Verify that the scores are correct
for criterion_index, criterion in enumerate(grade_details['criteria']):
for assessment_index, assessment in enumerate(criterion['assessments']):
if assessment_index == 0:
self.assertEquals(assessment['points'], scores[criterion_index])
else:
self.assertIsNone(assessment.get('points', None))
@scenario('data/grade_scenario.xml', user_id='Bob')
def test_assessment_does_not_match_rubric(self, xblock):
# Get to the grade complete section
self._create_submission_and_assessments(
xblock, self.SUBMISSION, self.PEERS, self.ASSESSMENTS, self.ASSESSMENTS[0]
self.create_submission_and_assessments(
xblock, self.SUBMISSION, self.PEERS, PEER_ASSESSMENTS, SELF_ASSESSMENT
)
# Change the problem definition so it no longer
......@@ -322,8 +311,8 @@ class TestGrade(XBlockHandlerTestCase, SubmitAssessmentsMixin):
self.request(xblock, 'schedule_training', json.dumps({}), response_format='json')
# Waiting to be assessed by a peer
self._create_submission_and_assessments(
xblock, self.SUBMISSION, self.PEERS, self.ASSESSMENTS, self.ASSESSMENTS[0],
self.create_submission_and_assessments(
xblock, self.SUBMISSION, self.PEERS, PEER_ASSESSMENTS, SELF_ASSESSMENT,
waiting_for_peer=data["waiting_for_peer"]
)
resp = self.request(xblock, 'render_grade', json.dumps(dict()))
......@@ -334,8 +323,8 @@ class TestGrade(XBlockHandlerTestCase, SubmitAssessmentsMixin):
@scenario('data/grade_incomplete_scenario.xml', user_id='Bunk')
def test_grade_incomplete_missing_self(self, xblock):
# Graded peers, but haven't completed self assessment
self._create_submission_and_assessments(
xblock, self.SUBMISSION, [self.PEERS[0]], [self.ASSESSMENTS[0]], None
self.create_submission_and_assessments(
xblock, self.SUBMISSION, [self.PEERS[0]], [PEER_ASSESSMENTS[0]], None
)
resp = self.request(xblock, 'render_grade', json.dumps(dict()))
......@@ -345,7 +334,7 @@ class TestGrade(XBlockHandlerTestCase, SubmitAssessmentsMixin):
@scenario('data/grade_incomplete_scenario.xml', user_id='Daniels')
def test_grade_incomplete_missing_peer(self, xblock):
# Have not yet completed peer assessment
self._create_submission_and_assessments(
self.create_submission_and_assessments(
xblock, self.SUBMISSION, [], [], None
)
resp = self.request(xblock, 'render_grade', json.dumps(dict()))
......@@ -356,8 +345,8 @@ class TestGrade(XBlockHandlerTestCase, SubmitAssessmentsMixin):
@scenario('data/grade_scenario.xml', user_id='Greggs')
def test_submit_feedback(self, xblock):
# Create submissions and assessments
self._create_submission_and_assessments(
xblock, self.SUBMISSION, self.PEERS, self.ASSESSMENTS, self.ASSESSMENTS[0]
self.create_submission_and_assessments(
xblock, self.SUBMISSION, self.PEERS, PEER_ASSESSMENTS, SELF_ASSESSMENT
)
# Submit feedback on the assessments
......@@ -379,8 +368,8 @@ class TestGrade(XBlockHandlerTestCase, SubmitAssessmentsMixin):
@scenario('data/grade_scenario.xml', user_id='Bob')
def test_submit_feedback_no_options(self, xblock):
# Create submissions and assessments
self._create_submission_and_assessments(
xblock, self.SUBMISSION, self.PEERS, self.ASSESSMENTS, self.ASSESSMENTS[0]
self.create_submission_and_assessments(
xblock, self.SUBMISSION, self.PEERS, PEER_ASSESSMENTS, SELF_ASSESSMENT
)
# Submit feedback on the assessments with no options specified
......@@ -399,8 +388,8 @@ class TestGrade(XBlockHandlerTestCase, SubmitAssessmentsMixin):
@scenario('data/grade_scenario.xml', user_id='Bob')
def test_submit_feedback_invalid_options(self, xblock):
# Create submissions and assessments
self._create_submission_and_assessments(
xblock, self.SUBMISSION, self.PEERS, self.ASSESSMENTS, self.ASSESSMENTS[0]
self.create_submission_and_assessments(
xblock, self.SUBMISSION, self.PEERS, PEER_ASSESSMENTS, SELF_ASSESSMENT
)
# Options should be a list, not a string
......@@ -423,8 +412,8 @@ class TestGrade(XBlockHandlerTestCase, SubmitAssessmentsMixin):
del option['label']
# Create a submission and assessments so we can get a grade
self._create_submission_and_assessments(
xblock, self.SUBMISSION, self.PEERS, self.ASSESSMENTS, self.ASSESSMENTS[0]
self.create_submission_and_assessments(
xblock, self.SUBMISSION, self.PEERS, PEER_ASSESSMENTS, SELF_ASSESSMENT
)
# Verify that criteria and options are assigned labels before
......@@ -434,17 +423,16 @@ class TestGrade(XBlockHandlerTestCase, SubmitAssessmentsMixin):
__, context = xblock.render_grade_complete(xblock.get_workflow_info())
criterion_labels = {}
option_labels = {}
for criterion in context['rubric_criteria']:
for criterion in context['grade_details']['criteria']:
self.assertEqual(criterion['label'], criterion['name'])
criterion_labels[criterion['name']] = criterion['label']
for option in criterion['options']:
self.assertEqual(option['label'], option['name'])
option_labels[(criterion['name'], option['name'])] = option['label']
# Verify that assessment part options are also assigned labels
for asmnt in context['peer_assessments'] + [context['self_assessment']]:
for part in asmnt['parts']:
expected_criterion_label = criterion_labels[part['criterion']['name']]
self.assertEqual(part['criterion']['label'], expected_criterion_label)
expected_option_label = option_labels[(part['criterion']['name'], part['option']['name'])]
self.assertEqual(part['option']['label'], expected_option_label)
# Verify that assessment part options are also assigned labels
for assessment in criterion['assessments']:
expected_criterion_label = criterion_labels[assessment['criterion']['name']]
self.assertEqual(assessment['criterion']['label'], expected_criterion_label)
expected_option_label = option_labels[(assessment['criterion']['name'], assessment['option']['name'])]
self.assertEqual(assessment['option']['label'], expected_option_label)
......@@ -5,24 +5,17 @@ Tests for staff assessment handlers in Open Assessment XBlock.
import json
import mock
import copy
from openassessment.assessment.api import staff as staff_api
from .base import XBlockHandlerTestCase, scenario
from .test_grade import SubmitAssessmentsMixin
class StaffAssessmentTestBase(XBlockHandlerTestCase):
maxDiff = None
from openassessment.assessment.api import staff as staff_api
SUBMISSION = (u'ՇﻉรՇ', u'รપ๒๓ٱรรٱѻก')
from .base import (
scenario, SubmitAssessmentsMixin, XBlockHandlerTestCase,
PEER_ASSESSMENTS, SELF_ASSESSMENT, STAFF_GOOD_ASSESSMENT,
)
ASSESSMENT = {
'options_selected': {u'𝓒𝓸𝓷𝓬𝓲𝓼𝓮': u'ﻉซƈﻉɭɭﻉกՇ', u'Form': u'Fair'},
'criterion_feedback': {},
'overall_feedback': ""
}
def set_staff_access(self, xblock):
xblock.xmodule_runtime = mock.Mock(user_is_staff=True)
xblock.xmodule_runtime.anonymous_student_id = 'Bob'
class StaffAssessmentTestBase(XBlockHandlerTestCase, SubmitAssessmentsMixin):
maxDiff = None
def _assert_path_and_context(self, xblock, expected_context):
path, context = xblock.staff_path_and_context()
......@@ -34,23 +27,8 @@ class StaffAssessmentTestBase(XBlockHandlerTestCase):
resp = self.request(xblock, 'render_staff_assessment', json.dumps({}))
self.assertGreater(len(resp), 0)
@staticmethod
def _set_mock_workflow_info(xblock, workflow_status, status_details, submission_uuid):
xblock.get_workflow_info = mock.Mock(return_value={
'status': workflow_status,
'status_details': status_details,
'submission_uuid': submission_uuid
})
def _submit_staff_assessment(self, xblock, submission):
# Submit a staff-assessment
self.set_staff_access(xblock)
self.ASSESSMENT['submission_uuid'] = submission['uuid']
resp = self.request(xblock, 'staff_assess', json.dumps(self.ASSESSMENT), response_format='json')
self.assertTrue(resp['success'])
class TestStaffAssessmentRender(StaffAssessmentTestBase, SubmitAssessmentsMixin):
class TestStaffAssessmentRender(StaffAssessmentTestBase):
@scenario('data/self_assessment_scenario.xml', user_id='Bob')
def test_staff_grade_templates(self, xblock):
......@@ -76,7 +54,7 @@ class TestStaffAssessmentRender(StaffAssessmentTestBase, SubmitAssessmentsMixin)
self._assert_path_and_context(xblock, unavailable_context)
# Submit a staff-assessment
self._submit_staff_assessment(xblock, submission)
self.submit_staff_assessment(xblock, submission, assessment=STAFF_GOOD_ASSESSMENT)
# Staff assessment exists, still waiting for self assessment.
self._assert_path_and_context(
......@@ -91,7 +69,7 @@ class TestStaffAssessmentRender(StaffAssessmentTestBase, SubmitAssessmentsMixin)
# Verify that once the required step (self assessment) is done, the staff grade is shown as complete.
status_details = {'peer': {'complete': True}}
self._set_mock_workflow_info(
self.set_mock_workflow_info(
xblock, workflow_status='done', status_details=status_details, submission_uuid=submission['uuid']
)
self._assert_path_and_context(
......@@ -104,7 +82,7 @@ class TestStaffAssessmentRender(StaffAssessmentTestBase, SubmitAssessmentsMixin)
)
# Verify that if the problem is cancelled, the staff grade reflects this.
self._set_mock_workflow_info(
self.set_mock_workflow_info(
xblock, workflow_status='cancelled', status_details=status_details, submission_uuid=submission['uuid']
)
self._assert_path_and_context(
......@@ -118,8 +96,8 @@ class TestStaffAssessmentRender(StaffAssessmentTestBase, SubmitAssessmentsMixin)
@scenario('data/grade_waiting_scenario.xml', user_id='Omar')
def test_staff_grade_templates_no_peer(self, xblock):
# Waiting to be assessed by a peer
submission = self._create_submission_and_assessments(
xblock, self.SUBMISSION, self.PEERS, self.ASSESSMENTS, self.ASSESSMENTS[0], waiting_for_peer=True
submission = self.create_submission_and_assessments(
xblock, self.SUBMISSION, self.PEERS, PEER_ASSESSMENTS, SELF_ASSESSMENT, waiting_for_peer=True
)
# Waiting for a peer assessment (though it is not used because staff grading is required),
......@@ -134,7 +112,7 @@ class TestStaffAssessmentRender(StaffAssessmentTestBase, SubmitAssessmentsMixin)
)
# Submit a staff-assessment. The student can now see the score even though no peer assessments have been done.
self._submit_staff_assessment(xblock, submission)
self.submit_staff_assessment(xblock, submission, assessment=STAFF_GOOD_ASSESSMENT)
self._assert_path_and_context(
xblock,
{
......@@ -155,7 +133,7 @@ class TestStaffAssessment(StaffAssessmentTestBase):
submission = xblock.create_submission(student_item, self.SUBMISSION)
# Submit a staff-assessment
self._submit_staff_assessment(xblock, submission)
self.submit_staff_assessment(xblock, submission, assessment=STAFF_GOOD_ASSESSMENT)
# Expect that a staff-assessment was created
assessment = staff_api.get_latest_staff_assessment(submission['uuid'])
......@@ -164,7 +142,7 @@ class TestStaffAssessment(StaffAssessmentTestBase):
self.assertEqual(assessment['points_possible'], 6)
self.assertEqual(assessment['scorer_id'], 'Bob')
self.assertEqual(assessment['score_type'], 'ST')
self.assertEqual(assessment['feedback'], u'')
self.assertEqual(assessment['feedback'], u'Staff: good job!')
parts = sorted(assessment['parts'])
self.assertEqual(len(parts), 2)
......@@ -187,7 +165,7 @@ class TestStaffAssessment(StaffAssessmentTestBase):
# Create a submission for the student
student_item = xblock.get_student_item_dict()
xblock.create_submission(student_item, self.SUBMISSION)
resp = self.request(xblock, 'staff_assess', json.dumps(self.ASSESSMENT))
resp = self.request(xblock, 'staff_assess', json.dumps(STAFF_GOOD_ASSESSMENT))
self.assertIn("You do not have permission", resp)
@scenario('data/self_assessment_scenario.xml', user_id='Bob')
......@@ -198,10 +176,10 @@ class TestStaffAssessment(StaffAssessmentTestBase):
submission = xblock.create_submission(student_item, self.SUBMISSION)
self.set_staff_access(xblock)
self.ASSESSMENT['submission_uuid'] = submission['uuid']
STAFF_GOOD_ASSESSMENT['submission_uuid'] = submission['uuid']
for key in self.ASSESSMENT:
assessment_copy = copy.copy(self.ASSESSMENT)
for key in STAFF_GOOD_ASSESSMENT:
assessment_copy = copy.copy(STAFF_GOOD_ASSESSMENT)
del assessment_copy[key]
resp = self.request(xblock, 'staff_assess', json.dumps(assessment_copy), response_format='json')
self.assertFalse(resp['success'])
......@@ -215,16 +193,16 @@ class TestStaffAssessment(StaffAssessmentTestBase):
submission = xblock.create_submission(student_item, self.SUBMISSION)
self.set_staff_access(xblock)
self.ASSESSMENT['submission_uuid'] = submission['uuid']
STAFF_GOOD_ASSESSMENT['submission_uuid'] = submission['uuid']
with mock.patch('openassessment.xblock.staff_assessment_mixin.staff_api') as mock_api:
# Simulate a error
mock_api.create_assessment.side_effect = staff_api.StaffAssessmentRequestError
resp = self.request(xblock, 'staff_assess', json.dumps(self.ASSESSMENT), response_format='json')
resp = self.request(xblock, 'staff_assess', json.dumps(STAFF_GOOD_ASSESSMENT), response_format='json')
self.assertFalse(resp['success'])
self.assertIn('msg', resp)
# Simulate a different error
mock_api.create_assessment.side_effect = staff_api.StaffAssessmentInternalError
resp = self.request(xblock, 'staff_assess', json.dumps(self.ASSESSMENT), response_format='json')
resp = self.request(xblock, 'staff_assess', json.dumps(STAFF_GOOD_ASSESSMENT), response_format='json')
self.assertFalse(resp['success'])
self.assertIn('msg', resp)
......@@ -780,6 +780,11 @@ class FullWorkflowTest(OpenAssessmentTest):
)
self.staff_area_page.verify_learner_final_score(self.PEER_ASSESSMENT_STAFF_AREA_SCORE)
self.verify_grade_entries([
[(u"PEER MEDIAN GRADE - 0 POINTS", u"Poor"), (u"PEER MEDIAN GRADE - 0 POINTS", u"Poor")],
[(u"YOUR SELF ASSESSMENT", u"Good"), (u"YOUR SELF ASSESSMENT", u"Excellent")]
])
# Now do a staff override, changing the score (to 1).
self.do_staff_override(learner)
......@@ -791,6 +796,13 @@ class FullWorkflowTest(OpenAssessmentTest):
)
self.staff_area_page.verify_learner_final_score(self.STAFF_AREA_SCORE.format(self.STAFF_OVERRIDE_SCORE))
self.verify_grade_entries([
[(u"STAFF GRADE - 0 POINTS", u"Poor"), (u"STAFF GRADE - 1 POINT", u"Fair")],
[(u"PEER MEDIAN GRADE", u"Poor"), (u"PEER MEDIAN GRADE", u"Poor")],
[(u"YOUR SELF ASSESSMENT", u"Good"), (u"YOUR SELF ASSESSMENT", u"Excellent")]
])
@retry()
@attr('acceptance')
def test_staff_override_at_beginning(self):
......@@ -848,6 +860,11 @@ class FullWorkflowTest(OpenAssessmentTest):
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))
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__":
......
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