Commit 5a530dab by Andy Armstrong

First phase of staff re-grading UI

parent bf30363c
...@@ -40,7 +40,7 @@ ...@@ -40,7 +40,7 @@
</ol> </ol>
{% if show_staff_area %} {% if show_staff_area %}
<div id="openassessment__staff-area"></div> <div class="openassessment__staff-area"></div>
{% endif %} {% endif %}
</div> </div>
</div> </div>
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
> >
<h4 class="question__title ui-toggle-visibility__control"> <h4 class="question__title ui-toggle-visibility__control">
<i class="icon fa fa-caret-right"></i> <i class="icon fa fa-caret-right"></i>
<span class="ui-toggle-visibility__control__copy question__title__copy">{{ criterion.prompt }}</span> <span id="assessment__rubric__prompt--{{ criterion.order_num }}" class="ui-toggle-visibility__control__copy question__title__copy">{{ criterion.prompt }}</span>
<span class="label--required sr">* ({% trans "Required" %})</span> <span class="label--required sr">* ({% trans "Required" %})</span>
</h4> </h4>
...@@ -23,7 +23,8 @@ ...@@ -23,7 +23,8 @@
name="{{ criterion.name }}" name="{{ criterion.name }}"
id="assessment__rubric__question--{{ criterion.order_num }}__{{ option.order_num }}" id="assessment__rubric__question--{{ criterion.order_num }}__{{ option.order_num }}"
class="answer__value" class="answer__value"
value="{{ option.name }}" /> value="{{ option.name }}"
aria-labelledby="assessment__rubric__prompt--{{ criterion.order_num }}"/>
<label for="assessment__rubric__question--{{ criterion.order_num }}__{{ option.order_num }}" <label for="assessment__rubric__question--{{ criterion.order_num }}__{{ option.order_num }}"
class="answer__label" class="answer__label"
>{{ option.label }}</label> >{{ option.label }}</label>
......
...@@ -139,10 +139,10 @@ ...@@ -139,10 +139,10 @@
<ul class="list list--actions"> <ul class="list list--actions">
<li class="list--actions__item"> <li class="list--actions__item">
<a aria-role="button" href="#" id="step--response__submit" <button type="submit" id="step--response__submit"
class="action action--submit step--response__submit {{ submit_enabled|yesno:",is--disabled" }}"> class="action action--submit step--response__submit {{ submit_enabled|yesno:",is--disabled" }}">
<span class="copy">{% trans "Submit your response and move to the next step" %}</span> <span class="copy">{% trans "Submit your response and move to the next step" %}</span>
</a> </button>
</li> </li>
</ul> </ul>
</div> </div>
......
{% load i18n %} {% load i18n %}
{% load tz %} {% load tz %}
<div id="openassessment__staff-area" class="wrapper--staff-area"> <div class="openassessment__staff-area wrapper--staff-area">
<div id="openassessment__staff-toolbar" class="wrapper--staff-toolbar"> <div class="wrapper--staff-toolbar">
<button class="ui-staff__button button-staff-tools" data-panel="openassessment__staff-tools">{% trans "Staff Tools" %}</button> <button class="ui-staff__button button-staff-tools" data-panel="openassessment__staff-tools">{% trans "Staff Tools" %}</button>
<button class="ui-staff__button button-staff-info" data-panel="openassessment__staff-info">{% trans "Staff Info" %}</button> <button class="ui-staff__button button-staff-info" data-panel="openassessment__staff-info">{% trans "Staff Info" %}</button>
</div> </div>
<div id="openassessment__staff-tools" class="wrapper--staff-tools wrapper--ui-staff is--hidden"> <div class="openassessment__staff-tools wrapper--staff-tools wrapper--ui-staff is--hidden">
<div class="staff-info ui-staff"> <div class="staff-info ui-staff">
<h2 class="staff-info__title ui-staff__title"> <h2 class="staff-info__title ui-staff__title">
<span class="staff-info__title__copy">{% trans "Course Staff Tools" %}</span> <span class="staff-info__title__copy">{% trans "Course Staff Tools" %}</span>
...@@ -18,17 +18,20 @@ ...@@ -18,17 +18,20 @@
<div class="staff-info__student ui-staff__content__section"> <div class="staff-info__student ui-staff__content__section">
<div class="wrapper--input" class="staff-info__student__form"> <div class="wrapper--input" class="staff-info__student__form">
<form id="openassessment_student_info_form"> <form class="openassessment_student_info_form">
<label for="openassessment__student_username" class="label">{% trans "Enter an individual learner's username or email" %}</label> <div class="form--error"></div>
<input id="openassessment__student_username" type="text" class="value" maxlength="255"> <label class="label">{% trans "Enter an individual learner's username or email" %}
<input type="text" class="openassessment__student_username value" maxlength="255">
</label>
<ul class="list list--actions"> <ul class="list list--actions">
<li class="list--actions__item"> <li class="list--actions__item">
<a aria-role="button" href="" id="submit_student_username" class="action--submit"><span class="copy">{% trans "Submit" %}</span></a> <button class="action--submit action--submit-username"><span class="copy">{% trans "Submit" %}</span></button>
<div class="student-form-error"></div>
</li> </li>
</ul> </ul>
</form> </form>
</div> </div>
<div id="openassessment__student-info" class="staff-info__student__report"></div> <div class="openassessment__student-info staff-info__student__report"></div>
</div> </div>
{% if display_schedule_training %} {% if display_schedule_training %}
...@@ -65,22 +68,22 @@ ...@@ -65,22 +68,22 @@
</div> </div>
<div class="staff-info__status ui-staff__content__section"> <div class="staff-info__status ui-staff__content__section">
<a aria-role="button" href="" id="schedule_training" class="action--submit"><span class="copy">{% trans "Schedule Example-Based Assessment Training" %}</span></a> <button class="action--submit action--submit-training"><span class="copy">{% trans "Schedule Example-Based Assessment Training" %}</span></button>
<div id="schedule_training_message"></div> <div class="schedule_training_message"></div>
</div> </div>
{% endif %} {% endif %}
{% if display_reschedule_unfinished_tasks %} {% if display_reschedule_unfinished_tasks %}
<div class="staff-info__status ui-staff__content__section"> <div class="staff-info__status ui-staff__content__section">
<a aria-role="button" href="" id="reschedule_unfinished_tasks" class="action--submit"><span class="copy">{% trans "Reschedule All Unfinished Example-Based Assessment Grading Tasks" %}</span></a> <button class="action--submit action--submit-unfinished-tasks"><span class="copy">{% trans "Reschedule All Unfinished Example-Based Assessment Grading Tasks" %}</span></button>
<div id="reschedule_unfinished_tasks_message"></div> <div class="reschedule_unfinished_tasks_message"></div>
</div> </div>
{% endif %} {% endif %}
</div> </div>
</div> </div>
</div> </div>
<div id="openassessment__staff-info" class="wrapper--staff-info wrapper--ui-staff is--hidden"> <div class="openassessment__staff-info wrapper--staff-info wrapper--ui-staff is--hidden">
<div class="staff-info ui-staff"> <div class="staff-info ui-staff">
<h2 class="staff-info__title ui-staff__title"> <h2 class="staff-info__title ui-staff__title">
<span class="staff-info__title__copy">{% trans "Course Staff Information" %}</span> <span class="staff-info__title__copy">{% trans "Course Staff Information" %}</span>
......
{% load tz %}
{% load i18n %}
{% spaceless %}
{% block body %}
<div class="ui-toggle-visibility__content">
<div class="wrapper--staff-assessment">
<div class="step__instruction">
<p>{% trans "Allows you to override the current learner's grade using the problem's rubric." %}</p>
</div>
<div class="step__content">
<article class="staff-assessment">
<div class="staff-assessment__display">
<header class="staff-assessment__display__header">
<h3 class="staff-assessment__display__title">
{% blocktrans %}
Response for: {{ student_username }}
{% endblocktrans %}
</h3>
</header>
{% include "openassessmentblock/oa_submission_answer.html" with answer=submission.answer answer_text_label="The learner's response to the question above:" %}
{% include "openassessmentblock/oa_uploaded_file.html" with file_upload_type=file_upload_type file_url=staff_file_url header="Associated File" class_prefix="staff-assessment" show_warning="true" %}
</div>
<form class="staff-assessment__assessment" method="post">
{% include "openassessmentblock/oa_rubric.html" with rubric_feedback_prompt="(Optional) What aspects of this response stood out to you? What did it do well? How could it improve?" rubric_feedback_default_text="I noticed that this response..." %}
</form>
</article>
</div>
<div class="step__actions">
<div class="message message--inline message--error message--error-server">
<h3 class="message__title">{% trans "We could not submit your assessment" %}</h3>
<div class="message__content"></div>
</div>
<ul class="list list--actions">
<li class="list--actions__item">
<button type="submit" class="action action--submit is--disabled">
<span class="copy">{% trans "Submit your assessment" %}</span>
</button>
</li>
</ul>
</div>
</div>
</div>
{% endblock %}
{% endspaceless %}
{% load i18n %} {% load i18n %}
{% load tz %} {% load tz %}
<div id="openassessment__student-info" class="staff-info__student__report">
{% if submission %} <div class="openassessment__student-info staff-info__student__report"
data-submission-uuid="{{ submission.uuid }}">
{% if submission %}
<h2 class="title"> <h2 class="title">
<span class="label"> <span class="label">
{% blocktrans with learner=student_username %} {% blocktrans with learner=student_username %}
...@@ -10,142 +12,148 @@ ...@@ -10,142 +12,148 @@
</span> </span>
</h2> </h2>
<div class="staff-info__status ui-staff__content__section wrapper--ui--collapse"> <div class="staff-info__status ui-staff__content__section wrapper--ui--collapse staff-info__student__response">
<div class="ui-staff ui-toggle-visibility is--collapsed"> <div class="ui-staff ui-toggle-visibility is--collapsed">
<h2 class="staff-info__title ui-staff__title ui-toggle-visibility__control"> <h2 class="staff-info__title ui-staff__subtitle ui-toggle-visibility__control">
<i class="icon fa fa-caret-right"></i> <i class="icon fa fa-caret-right" aria-hidden="true"></i>
<span>{% trans "Learner Response" %}</span> <span>{% trans "Learner's Response" %}</span>
</h2> </h2>
<div class="ui-toggle-visibility__content"> <div class="ui-toggle-visibility__content">
<div class="student__answer__display__content"> {% if workflow_cancellation %}
{% if workflow_cancellation %} <p>
{% blocktrans with removed_by_username=workflow_cancellation.cancelled_by removed_datetime=workflow_cancellation.created_at|utc|date:"N j, Y H:i e" %} {% blocktrans with removed_by_username=workflow_cancellation.cancelled_by removed_datetime=workflow_cancellation.cancelled_at|utc|date:"F j, Y H:i e" %}
Learner submission removed by {{ removed_by_username }} on {{ removed_datetime }} Learner submission removed by {{ removed_by_username }} on {{ removed_datetime }}
{% endblocktrans %} {% endblocktrans %}
<br> </p>
<!-- Comments: Reason for Cancellation--> <!-- Comments: Reason for Cancellation-->
{% blocktrans with comments=workflow_cancellation.comments %} <p>
Comments: {{ comments }} {% blocktrans with comments=workflow_cancellation.comments %}
{% endblocktrans %} Comments: {{ comments }}
{% else %} {% endblocktrans %}
{% include "openassessmentblock/oa_submission_answer.html" with answer=submission.answer answer_text_label="The learner's response to the question above:" %} </p>
{% endif %} {% else %}
</div> {% include "openassessmentblock/oa_submission_answer.html" with answer=submission.answer answer_text_label="The learner's response to the question above:" %}
{% endif %}
{% if submission.file_url %} {% if submission.file_url %}
<a href="{{ submission.file_url }}" class="submission--file"> <a href="{{ submission.file_url }}" class="submission--file">
{% trans "The file associated with this response." %} {% trans "The file associated with this response." %}
</a> </a>
<span>{% trans "Caution: This file was uploaded by another course learner and has not been verified, screened, approved, reviewed or endorsed by edX. If you decide to access it, you do so at your own risk." %}</span> <span>{% trans "Caution: This file was uploaded by another course learner and has not been verified, screened, approved, reviewed, or endorsed by edX. If you decide to access it, you do so at your own risk." %}</span>
{% endif %} {% endif %}
</div>
</div> </div>
</div> </div>
</div>
{% if peer_assessments %} {% if peer_assessments != None %}
<div class="staff-info__status ui-staff__content__section wrapper--ui--collapse"> <div class="staff-info__status ui-staff__content__section wrapper--ui--collapse">
<div class="ui-staff ui-toggle-visibility is--collapsed"> <div class="ui-staff ui-toggle-visibility is--collapsed">
<h2 class="staff-info__title ui-staff__title ui-toggle-visibility__control"> <h2 class="staff-info__title ui-staff__subtitle ui-toggle-visibility__control">
<i class="icon fa fa-caret-right"></i> <i class="icon fa fa-caret-right" aria-hidden="true"></i>
<span>{% trans "Peer Assessments for This Learner" %}</span> <span>{% trans "Peer Assessments for This Learner" %}</span>
</h2> </h2>
<div class="ui-toggle-visibility__content"> <div class="ui-toggle-visibility__content">
{% for assessment in peer_assessments %} {% if peer_assessments %}
{% with peer_num=forloop.counter %} {% for assessment in peer_assessments %}
<h4 class="title--sub"> {% trans "Peer" %} {{ peer_num }}: </h4> {% with peer_num=forloop.counter %}
<table class="staff-info__status__table" summary="{% trans "Assessment" %}"> <h4 class="title--sub"> {% trans "Peer" %} {{ peer_num }}: </h4>
<thead> <table class="staff-info__status__table" summary="{% trans "Assessment" %}">
<tr> <thead>
<th abbr="Criterion" scope="col">{% trans "Criterion" %}</th> <tr>
<th abbr="Selected Option" scope="col">{% trans "Selected Option" %}</th> <th abbr="Criterion" scope="col">{% trans "Criterion" %}</th>
<th abbr="Feedback" scope="col">{% trans "Feedback" %}</th> <th abbr="Selected Option" scope="col">{% trans "Selected Option" %}</th>
<th abbr="Points" scope="col">{% trans "Points" %}</th> <th abbr="Points" scope="col">{% trans "Points" %}</th>
<th abbr="Points Possible" scope="col">{% trans "Points Possible" %}</th> <th abbr="Points Possible" scope="col">{% trans "Points Possible" %}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for criterion in rubric_criteria %} {% for criterion in rubric_criteria %}
{% for part in assessment.parts %} {% for part in self_assessment.parts %}
{% if part.option.criterion.name == criterion.name %} {% if part.option.criterion.name == criterion.name %}
<tr> <tr>
<td class="label">{{ criterion.label }}</td> <td class="label">{{ criterion.label }}</td>
<td class="value">{{ part.option.label }}</td> <td class="value">{{ part.option.label }}</td>
<td class="value">{{ part.feedback }}</td> <td class="value">{{ part.option.points }}</td>
<td class="value">{{ part.option.points }}</td> <td class="value">{{ criterion.total_value }}</td>
<td class="value">{{ criterion.total_value }}</td> </tr>
</tr> {% endif %}
{% endif %} {% endfor %}
{% endfor %} {% endfor %}
{% endfor %} </tbody>
</tbody> </table>
</table> <h4 class="title--sub">{% trans "Overall Feedback" %}</h4>
<h4 class="title--sub">{% trans "Overall Feedback" %}</h4> <div class="student__answer__display__content">
<div class="student__answer__display__content"> {{ assessment.feedback|linebreaks }}
{{ assessment.feedback|linebreaks }} </div>
</div>
{% endwith %} {% endwith %}
{% endfor %} {% endfor %}
{% else %}
<p>{% trans "This learner currently has no peer assessments." %}</p>
{% endif %}
</div> </div>
</div> </div>
</div> </div>
{% endif %} {% endif %}
{% if submitted_assessments %} {% if submitted_assessments != None %}
<div class="staff-info__status ui-staff__content__section wrapper--ui--collapse"> <div class="staff-info__status ui-staff__content__section wrapper--ui--collapse">
<div class="ui-staff ui-toggle-visibility is--collapsed"> <div class="ui-staff ui-toggle-visibility is--collapsed">
<h2 class="staff-info__title ui-staff__title ui-toggle-visibility__control"> <h2 class="staff-info__title ui-staff__subtitle ui-toggle-visibility__control">
<i class="icon fa fa-caret-right"></i> <i class="icon fa fa-caret-right" aria-hidden="true"></i>
<span>{% trans "Peer Assessments Completed by This Learner" %}</span> <span>{% trans "Peer Assessments Completed by This Learner" %}</span>
</h2> </h2>
<div class="ui-toggle-visibility__content"> <div class="ui-toggle-visibility__content">
{% for assessment in submitted_assessments %} {% if submitted_assessments %}
{% with peer_num=forloop.counter %} {% for assessment in submitted_assessments %}
<h4 class="title--sub">{% trans "Assessment" %} {{ peer_num }}:</h4> {% with peer_num=forloop.counter %}
<table class="staff-info__status__table" summary="{% trans "Assessment" %}"> <h4 class="title--sub">{% trans "Assessment" %} {{ peer_num }}:</h4>
<thead> <table class="staff-info__status__table" summary="{% trans "Assessment" %}">
<tr> <thead>
<th abbr="Criterion" scope="col">{% trans "Criterion" %}</th> <tr>
<th abbr="Selected Option" scope="col">{% trans "Selected Option" %}</th> <th abbr="Criterion" scope="col">{% trans "Criterion" %}</th>
<th abbr="Feedback" scope="col">{% trans "Feedback" %}</th> <th abbr="Selected Option" scope="col">{% trans "Selected Option" %}</th>
<th abbr="Points" scope="col">{% trans "Points" %}</th> <th abbr="Feedback" scope="col">{% trans "Feedback" %}</th>
<th abbr="Points Possible" scope="col">{% trans "Points Possible" %}</th> <th abbr="Points" scope="col">{% trans "Points" %}</th>
</tr> <th abbr="Points Possible" scope="col">{% trans "Points Possible" %}</th>
</thead> </tr>
</thead>
<tbody> <tbody>
{% for criterion in rubric_criteria %} {% for criterion in rubric_criteria %}
{% for part in assessment.parts %} {% for part in assessment.parts %}
{% if part.option.criterion.name == criterion.name %} {% if part.option.criterion.name == criterion.name %}
<tr> <tr>
<td class="label">{{ criterion.label }}</td> <td class="label">{{ criterion.label }}</td>
<td class="value">{{ part.option.label }}</td> <td class="value">{{ part.option.label }}</td>
<td class="value">{{ part.feedback }}</td> <td class="value">{{ part.feedback }}</td>
<td class="value">{{ part.option.points }}</td> <td class="value">{{ part.option.points }}</td>
<td class="value">{{ criterion.total_value }}</td> <td class="value">{{ criterion.total_value }}</td>
</tr> </tr>
{% endif %} {% endif %}
{% endfor %}
{% endfor %} {% endfor %}
{% endfor %} </tbody>
</tbody> </table>
</table> <h4 class="title--sub">{% trans "Overall Feedback" %}</h4>
<h4 class="title--sub">{% trans "Overall Feedback" %}</h4> <div class="student__answer__display__content">
<div class="student__answer__display__content"> {{ assessment.feedback|linebreaks }}
{{ assessment.feedback|linebreaks }} </div>
</div> {% endwith %}
{% endwith %} {% endfor %}
{% endfor %} {% else %}
<p>{% trans "This learner has not completed any peer assessments." %}</p>
{% endif %}
</div> </div>
</div> </div>
</div> </div>
{% endif %} {% endif %}
{% if self_assessment %} {% if self_assessment %}
<div class="staff-info__status ui-staff__content__section wrapper--ui--collapse"> <div class="staff-info__status ui-staff__content__section wrapper--ui--collapse">
<div class="ui-staff ui-toggle-visibility is--collapsed"> <div class="ui-staff ui-toggle-visibility is--collapsed">
<h2 class="staff-info__title ui-staff__title ui-toggle-visibility__control"> <h2 class="staff-info__title ui-staff__subtitle ui-toggle-visibility__control">
<i class="icon fa fa-caret-right"></i> <i class="icon fa fa-caret-right" aria-hidden="true"></i>
<span>{% trans "Learner's Self Assessment" %}</span> <span>{% trans "Learner's Self Assessment" %}</span>
</h2> </h2>
<div class="ui-toggle-visibility__content"> <div class="ui-toggle-visibility__content">
...@@ -177,102 +185,132 @@ ...@@ -177,102 +185,132 @@
</div> </div>
</div> </div>
</div> </div>
{% endif %} {% endif %}
{% if example_based_assessment %} {% if example_based_assessment %}
<div class="staff-info__status ui-staff__content__section wrapper--ui--collapse"> <div class="staff-info__status ui-staff__content__section wrapper--ui--collapse">
<div class="ui-staff ui-toggle-visibility is--collapsed"> <div class="ui-staff ui-toggle-visibility is--collapsed">
<h2 class="staff-info__title ui-staff__title ui-toggle-visibility__control"> <h2 class="staff-info__title ui-staff__subtitle ui-toggle-visibility__control">
<i class="icon fa fa-caret-right"></i> <i class="icon fa fa-caret-right" aria-hidden="true"></i>
<span>{% trans "Example-Based Assessment" %}</span> <span>{% trans "Example-Based Assessment" %}</span>
</h2> </h2>
<div class="ui-toggle-visibility__content"> <div class="ui-toggle-visibility__content">
<table class="staff-info__status__table" summary="{% trans "Example Based Assessment" %}"> <table class="staff-info__status__table" summary="{% trans "Example Based Assessment" %}">
<thead> <thead>
<tr> <tr>
<th abbr="Criterion" scope="col">{% trans "Criterion" %}</th> <th abbr="Criterion" scope="col">{% trans "Criterion" %}</th>
<th abbr="Selected Option" scope="col">{% trans "Selected Option" %}</th> <th abbr="Selected Option" scope="col">{% trans "Selected Option" %}</th>
<th abbr="Points" scope="col">{% trans "Points" %}</th> <th abbr="Points" scope="col">{% trans "Points" %}</th>
<th abbr="Points Possible" scope="col">{% trans "Points Possible" %}</th> <th abbr="Points Possible" scope="col">{% trans "Points Possible" %}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for criterion in rubric_criteria %} {% for criterion in rubric_criteria %}
{% for part in example_based_assessment.parts %} {% for part in example_based_assessment.parts %}
{% if part.option.criterion.name == criterion.name %} {% if part.option.criterion.name == criterion.name %}
<tr> <tr>
<td class="label">{{ criterion.label }}</td> <td class="label">{{ criterion.label }}</td>
<td class="value">{{ part.option.label }}</td> <td class="value">{{ part.option.label }}</td>
<td class="value">{{ part.option.points }}</td> <td class="value">{{ part.option.points }}</td>
<td class="value">{{ criterion.total_value }}</td> <td class="value">{{ criterion.total_value }}</td>
</tr> </tr>
{% endif %} {% endif %}
{% endfor %}
{% endfor %} {% endfor %}
</tbody> {% endfor %}
</table> </tbody>
</table>
</div>
</div>
</div>
{% endif %}
<div class="staff-info__status ui-staff__content__section wrapper--ui--collapse staff-info__student__grade">
<div class="ui-staff ui-toggle-visibility {% if expanded_view != 'final-grade' %}is--collapsed {% endif %}">
<h2 class="staff-info__title ui-staff__subtitle ui-toggle-visibility__control">
<i class="icon fa fa-caret-right" aria-hidden="true"></i>
<span>{% trans "Learner's Final Grade" %}</span>
</h2>
<div class="ui-toggle-visibility__content">
{% if workflow_status == "done" %}
<p>
{% 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 %}
Final grade: {{ points_earned }} out of {{ points_possible }}
{% endblocktrans %}
{% endwith %}
</p>
{% elif workflow_status == "waiting" %}
<p>{% trans "The submission is waiting for assessments." %}</p>
{% elif workflow_status == "cancelled" %}
<p>{% trans "The learner's submission has been removed from peer assessment. The learner receives a grade of zero unless you delete the learner's state for the problem to allow them to resubmit a response." %}</p>
{% elif workflow_status == None %}
<p>{% trans "The problem has not been started." %}</p>
{% else %}
<p>{% trans "The problem has not been completed." %}</p>
{% endif %}
</div>
</div>
</div>
{% if not workflow_cancellation %}
<div class="staff-info__staff-override ui-staff__content__section wrapper--ui--collapse">
<div class="ui-staff ui-toggle-visibility {% if expanded_view != 'staff-override' %}is--collapsed {% endif %}">
<h2 class="staff-info__title ui-staff__subtitle ui-toggle-visibility__control">
<i class="icon fa fa-caret-right" aria-hidden="true"></i>
<span>{% trans "Submit Assessment Grade Override" %}</span>
</h2>
<div class="staff-info__staff-override__content ui-toggle-visibility__content">
<div class="wrapper--input">
{% include "openassessmentblock/staff_area/oa_staff_assessment.html" %}
</div> </div>
</div> </div>
</div> </div>
{% endif %} </div>
{% if not workflow_cancellation %} <div class="staff-info__workflow-cancellation ui-staff__content__section wrapper--ui--collapse">
<div id="openassessment__staff-info__cancel__submission"
class="openassessment__staff-info__cancel__submission wrapper--ui--collapse">
<div class="ui-staff ui-toggle-visibility is--collapsed"> <div class="ui-staff ui-toggle-visibility is--collapsed">
<h2 class="staff-info__title ui-staff__title ui-toggle-visibility__control"> <h2 class="staff-info__title ui-staff__subtitle ui-toggle-visibility__control">
<i class="icon fa fa-caret-right"></i> <i class="icon fa fa-caret-right" aria-hidden="true"></i>
<span>{% trans "Remove submission from peer grading" %}</span> <span>{% trans "Remove Submission From Peer Grading" %}</span>
</h2> </h2>
<div class="staff-info__cancel-submission__content ui-toggle-visibility__content"> <div class="staff-info__cancel-submission__content ui-toggle-visibility__content">
<div class="ui-staff__content__section"> <div class="wrapper--input">
<div class="wrapper--input"> <form data-submission-uuid="{{ submission.uuid }}">
<form id="openassessment_staff_cancel_submission_form" <div class="step__message message message--warning">
data-submission-uuid="{{ submission.uuid }}"> <h3 class="message__title">{% trans "Caution: This Action Cannot Be Undone" %}</h3>
<div class="message__content">{% trans "Removing a learner's submission cannot be undone and should be done with caution." %}</div>
</div>
<ul class="list list--actions"> <ul class="list list--actions">
<li> <li>
<div class="has--warnings"> <label class="label">{% trans "Comments:" %}
<div class="warning"> <textarea class="cancel_submission_comments" value="" maxlength="10000"></textarea>
{% trans "Caution: Removing a learner's submission cannot be undone." %} </label>
</div> </li>
</div> </ul>
</li> <ul class="list list--actions">
<li> <li class="list--actions__item">
<label for="staff-info__cancel-submission__comments" <button data-submission-uuid="{{ submission.uuid }}"
class="label">{% trans "Comments:" %}</label> class="action--submit action--submit-cancel-submission is--disabled">
</li> <span class="copy">{% trans "Remove submission" %}</span>
<li> </button>
<textarea
id="staff-info__cancel-submission__comments"
class="cancel_submission_comments"
value=""
maxlength="10000"></textarea>
</li>
</ul>
<ul class="list list--actions">
<li class="list--actions__item">
<a data-submission-uuid="{{ submission.uuid }}" aria-role="button" href=""
id="submit_cancel_submission" class="action--submit is--disabled">
<span class="copy">{% trans "Remove submission" %}</span>
</a>
<div class="cancel-submission-error"></div> <div class="cancel-submission-error"></div>
</li> </li>
</ul> </ul>
</form> </form>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{% endif %} {% endif %}
</div> </div>
{% else %} {% else %}
{% trans "A response was not found for this learner." %} {% trans "A response was not found for this learner." %}
{% endif %} {% endif %}
</div> </div>
...@@ -6,14 +6,9 @@ import logging ...@@ -6,14 +6,9 @@ import logging
from django.db import DatabaseError from django.db import DatabaseError
from openassessment.assessment.api import peer as peer_api from openassessment.assessment.errors import PeerAssessmentError, PeerAssessmentInternalError
from openassessment.assessment.api import ai as ai_api
from openassessment.assessment.api import student_training as training_api
from openassessment.assessment.errors import (
PeerAssessmentError, StudentTrainingInternalError, AIError,
PeerAssessmentInternalError)
from submissions import api as sub_api from submissions import api as sub_api
from .models import AssessmentWorkflow, AssessmentWorkflowCancellation, AssessmentWorkflowStep from .models import AssessmentWorkflow, AssessmentWorkflowCancellation
from .serializers import AssessmentWorkflowSerializer, AssessmentWorkflowCancellationSerializer from .serializers import AssessmentWorkflowSerializer, AssessmentWorkflowCancellationSerializer
from .errors import ( from .errors import (
AssessmentWorkflowError, AssessmentWorkflowInternalError, AssessmentWorkflowError, AssessmentWorkflowInternalError,
......
...@@ -24,6 +24,7 @@ from openassessment.assessment.api import self as self_api ...@@ -24,6 +24,7 @@ from openassessment.assessment.api import self as self_api
from openassessment.assessment.api import ai as ai_api from openassessment.assessment.api import ai as ai_api
from openassessment.fileupload import api as file_api from openassessment.fileupload import api as file_api
from openassessment.workflow import api as workflow_api from openassessment.workflow import api as workflow_api
from openassessment.workflow.models import AssessmentWorkflowCancellation
from openassessment.fileupload import exceptions as file_exceptions from openassessment.fileupload import exceptions as file_exceptions
...@@ -112,7 +113,7 @@ class StaffAreaMixin(object): ...@@ -112,7 +113,7 @@ class StaffAreaMixin(object):
Gets the path and context for the staff section of the ORA XBlock. Gets the path and context for the staff section of the ORA XBlock.
""" """
context = {} context = {}
path = 'openassessmentblock/staff_area/staff_area.html' path = 'openassessmentblock/staff_area/oa_staff_area.html'
student_item = self.get_student_item_dict() student_item = self.get_student_item_dict()
...@@ -214,16 +215,20 @@ class StaffAreaMixin(object): ...@@ -214,16 +215,20 @@ class StaffAreaMixin(object):
""" """
try: try:
student_username = data.params.get('student_username', '') student_username = data.params.get('student_username', '')
path, context = self.get_student_info_path_and_context(student_username) expanded_view = data.params.get('expanded_view', [])
path, context = self.get_student_info_path_and_context(
student_username,
expanded_view=expanded_view
)
return self.render_assessment(path, context) return self.render_assessment(path, context)
except PeerAssessmentInternalError: except PeerAssessmentInternalError:
return self.render_error(self._(u"Error finding assessment workflow cancellation.")) return self.render_error(self._(u"Error finding assessment workflow cancellation."))
def get_student_info_path_and_context(self, student_username): def get_student_info_path_and_context(self, student_username, expanded_view=None):
""" """
Get the proper path and context for rendering the the student info Get the proper path and context for rendering the student info
section of the staff debug panel. section of the staff area.
Args: Args:
student_username (unicode): The username of the student to report. student_username (unicode): The username of the student to report.
...@@ -278,19 +283,31 @@ class StaffAreaMixin(object): ...@@ -278,19 +283,31 @@ class StaffAreaMixin(object):
if "example-based-assessment" in assessment_steps: if "example-based-assessment" in assessment_steps:
example_based_assessment = ai_api.get_latest_assessment(submission_uuid) example_based_assessment = ai_api.get_latest_assessment(submission_uuid)
workflow = self.get_workflow_info(submission_uuid=submission_uuid)
workflow_cancellation = workflow_api.get_assessment_workflow_cancellation(submission_uuid) workflow_cancellation = workflow_api.get_assessment_workflow_cancellation(submission_uuid)
if workflow_cancellation: if workflow_cancellation:
workflow_cancellation['cancelled_by'] = self.get_username(workflow_cancellation['cancelled_by_id']) workflow_cancellation['cancelled_by'] = self.get_username(workflow_cancellation['cancelled_by_id'])
# Get the date that the workflow was cancelled to use in preference to the serialized date string
cancellation_model = AssessmentWorkflowCancellation.get_latest_workflow_cancellation(submission_uuid)
workflow_cancelled_at = cancellation_model.created_at
else:
workflow_cancelled_at = None
context = { context = {
'submission': create_submission_dict(submission, self.prompts) if submission else None, 'submission': create_submission_dict(submission, self.prompts) if submission else None,
'score': workflow.get('score'),
'workflow_status': workflow.get('status'),
'workflow_cancellation': workflow_cancellation, 'workflow_cancellation': workflow_cancellation,
'workflow_cancelled_at': workflow_cancelled_at,
'peer_assessments': peer_assessments, 'peer_assessments': peer_assessments,
'submitted_assessments': submitted_assessments, 'submitted_assessments': submitted_assessments,
'self_assessment': self_assessment, 'self_assessment': self_assessment,
'example_based_assessment': example_based_assessment, 'example_based_assessment': example_based_assessment,
'rubric_criteria': copy.deepcopy(self.rubric_criteria_with_labels), 'rubric_criteria': copy.deepcopy(self.rubric_criteria_with_labels),
'student_username': student_username 'student_username': student_username,
'expanded_view': expanded_view,
} }
if peer_assessments or self_assessment or example_based_assessment: if peer_assessments or self_assessment or example_based_assessment:
...@@ -298,7 +315,7 @@ class StaffAreaMixin(object): ...@@ -298,7 +315,7 @@ class StaffAreaMixin(object):
for criterion in context["rubric_criteria"]: for criterion in context["rubric_criteria"]:
criterion["total_value"] = max_scores[criterion["name"]] criterion["total_value"] = max_scores[criterion["name"]]
path = 'openassessmentblock/staff_area/student_info.html' path = 'openassessmentblock/staff_area/oa_student_info.html'
return path, context return path, context
@XBlock.json_handler @XBlock.json_handler
......
...@@ -651,7 +651,7 @@ ...@@ -651,7 +651,7 @@
"output": "oa_edit_student_training.html" "output": "oa_edit_student_training.html"
}, },
{ {
"template": "openassessmentblock/staff_area/staff_area.html", "template": "openassessmentblock/staff_area/oa_staff_area.html",
"context": { "context": {
"status_counts": { "status_counts": {
"self": 1, "self": 1,
...@@ -682,7 +682,7 @@ ...@@ -682,7 +682,7 @@
"output": "oa_staff_area.html" "output": "oa_staff_area.html"
}, },
{ {
"template": "openassessmentblock/staff_area/student_info.html", "template": "openassessmentblock/staff_area/oa_student_info.html",
"context": { "context": {
"submission": { "submission": {
"image_url": "/test-url", "image_url": "/test-url",
......
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(singular_text,plural_text,n){if(n>1){return plural_text}else{return singular_text}}}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(student_username){var url=this.url("render_student_info");return $.Deferred(function(defer){$.ajax({url:url,type:"POST",dataType:"html",data:{student_username:student_username}}).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()},peerAssess:function(optionsSelected,criterionFeedback,overallFeedback,uuid){var url=this.url("peer_assess");var payload=JSON.stringify({options_selected:optionsSelected,criterion_feedback:criterionFeedback,overall_feedback:overallFeedback,submission_uuid:uuid});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 assessment could not be submitted.")])})}).promise()},selfAssess:function(optionsSelected,criterionFeedback,overallFeedback){var url=this.url("self_assess");var payload=JSON.stringify({options_selected:optionsSelected,criterion_feedback:criterionFeedback,overall_feedback:overallFeedback});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 assessment could not be submitted.")])})})},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(kwargs){var url=this.url("update_editor_context");var payload=JSON.stringify({prompts:kwargs.prompts,feedback_prompt:kwargs.feedbackPrompt,feedback_default_text:kwargs.feedback_default_text,title:kwargs.title,submission_start:kwargs.submissionStart,submission_due:kwargs.submissionDue,criteria:kwargs.criteria,assessments:kwargs.assessments,editor_assessments_order:kwargs.editorAssessmentsOrder,file_upload_type:kwargs.fileUploadType,white_listed_file_types:kwargs.fileTypeWhiteList,allow_latex:kwargs.latexEnabled,leaderboard_show:kwargs.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(submissionUUID,comments){var url=this.url("cancel_submission");var payload=JSON.stringify({submission_uuid:submissionUUID,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])}else{defer.rejectWith(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(singular_text,plural_text,n){if(n>1){return plural_text}else{return singular_text}}}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.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(parentSel){parentSel.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.selfView.load();this.gradeView.load();this.leaderboardView.load()},loadMessageView:function(){this.messageView.load()},toggleActionError:function(type,msg){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(msg!==null){console.log(msg)}}else{var msgHtml=msg===null?"":msg;$(container+" .message__content",element).html("<p>"+msgHtml+"</p>");$(container,element).toggleClass("has--error",msg!==null)}},showLoadError:function(step){var container="#openassessment__"+step;$(container).toggleClass("has--error",true);$(container+" .step__status__value i").removeClass().addClass("icon fa fa-exclamation-triangle");$(container+" .step__status__value .copy").html(gettext("Unable to Load"))}};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)}},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 preview_text=sel.find(".submission__answer__part__text__value").val();var preview_container=sel.find("#preview_content");preview_container.html(preview_text.replace(/\r\n|\r|\n/g,"<br />"));sel.find("#submission__preview__item").show();MathJax.Hub.Queue(["Typeset",MathJax.Hub,preview_container[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)}},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">'+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) 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(singular_text,plural_text,n){if(n>1){return plural_text}else{return singular_text}}}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(student_username,options){var url=this.url("render_student_info");return $.Deferred(function(defer){$.ajax({url:url,type:"POST",dataType:"html",data:_.extend({student_username:student_username},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])}else{defer.rejectWith(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(singular_text,plural_text,n){if(n>1){return plural_text}else{return singular_text}}}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.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(parentSel){parentSel.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.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{var msgHtml=message===null?"":message;$(container+" .message__content",element).html("<p>"+msgHtml+"</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(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)}},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 preview_text=sel.find(".submission__answer__part__text__value").val();var preview_container=sel.find("#preview_content");preview_container.html(preview_text.replace(/\r\n|\r|\n/g,"<br />"));sel.find("#submission__preview__item").show();MathJax.Hub.Queue(["Typeset",MathJax.Hub,preview_container[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)}},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">'+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);
}},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)})}};!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(){var view=this;var sel=$("#openassessment__staff-tools",this.element);var student_username=sel.find("#openassessment__student_username").val();this.server.studentInfo(student_username).done(function(html){$("#openassessment__student-info",view.element).replaceWith(html);var selCancelSub=$("#openassessment__staff-info__cancel__submission",view.element);selCancelSub.on("click","#submit_cancel_submission",function(eventObject){eventObject.preventDefault();view.cancelSubmission($(this).data("submission-uuid"))});var handleChange=function(eventData){view.handleCommentChanged(eventData)};selCancelSub.find("#staff-info__cancel-submission__comments").on("change keyup drop paste",handleChange)}).fail(function(){view.showLoadError("student_info")})},installHandlers:function(){var $staffArea=$("#openassessment__staff-area",this.element);var toolsElement=$("#openassessment__staff-tools",$staffArea);var infoElement=$("#openassessment__student-info",$staffArea);var view=this;if(toolsElement.length<=0){return}this.baseView.setUpCollapseExpand(toolsElement,function(){});this.baseView.setUpCollapseExpand(infoElement,function(){});$staffArea.find(".ui-staff__button").click(function(eventObject){var $button=$(eventObject.currentTarget),panelID=$button.data("panel"),$panel=$staffArea.find("#"+panelID).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")});toolsElement.find("#openassessment_student_info_form").submit(function(eventObject){eventObject.preventDefault();view.loadStudentInfo()});toolsElement.find("#submit_student_username").click(function(eventObject){eventObject.preventDefault();view.loadStudentInfo()});toolsElement.find("#schedule_training").click(function(eventObject){eventObject.preventDefault();view.scheduleTraining()});toolsElement.find("#reschedule_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 sel=$("#openassessment__student-info",this.element);var comments=sel.find("#staff-info__cancel-submission__comments").val();this.server.cancelSubmission(submissionUUID,comments).done(function(msg){$(".cancel-submission-error").html("");$("#openassessment__staff-info__cancel__submission",view.element).html(msg)}).fail(function(errMsg){$(".cancel-submission-error").html(errMsg)})},cancelSubmissionEnabled:function(enabled){var sel=$("#submit_cancel_submission",this.element);if(typeof enabled==="undefined"){return!sel.hasClass("is--disabled")}else{sel.toggleClass("is--disabled",!enabled)}},comment:function(text){var sel=$("#staff-info__cancel-submission__comments",this.element);if(typeof text==="undefined"){return sel.val()}else{sel.val(text)}},handleCommentChanged:function(){var isBlank=$.trim(this.comment())!=="";this.cancelSubmissionEnabled(isBlank)}}}(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)}}}; if(typeof enabled==="undefined"){return!button.hasClass("is--disabled")}else{button.toggleClass("is--disabled",!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)})}};!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 student_username=$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(student_username.trim()){this.server.studentInfo(student_username,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("A learner name must be provided."));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(msg){$(".cancel-submission-error").html("");view.loadStudentInfo({expanded_view:"final-grade"}).done(function(){$(".openassessment__staff-info__cancel__submission",view.element).html(msg)})}).fail(function(errMsg){$(".cancel-submission-error").html(errMsg)})},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)}},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){baseView.toggleActionError("staff",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 \ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -50,7 +50,8 @@ describe("OpenAssessment.BaseView", function() { ...@@ -50,7 +50,8 @@ describe("OpenAssessment.BaseView", function() {
// Create a new stub server // Create a new stub server
server = new StubServer(); server = new StubServer();
server.renderLatex = jasmine.createSpy('renderLatex') server.renderLatex = jasmine.createSpy('renderLatex');
// Create the object under test // Create the object under test
var el = $("#openassessment").get(0); var el = $("#openassessment").get(0);
view = new OpenAssessment.BaseView(runtime, el, server); view = new OpenAssessment.BaseView(runtime, el, server);
......
...@@ -26,16 +26,9 @@ describe("OpenAssessment.GradeView", function() { ...@@ -26,16 +26,9 @@ describe("OpenAssessment.GradeView", function() {
}; };
}; };
// Stub base view
var StubBaseView = function() {
this.showLoadError = function(msg) {};
this.toggleActionError = function(msg, step) {};
this.setUpCollapseExpand = function(sel) {};
};
// Stubs // Stubs
var baseView = null;
var server = null; var server = null;
var runtime = {};
// View under test // View under test
var view = null; var view = null;
...@@ -47,12 +40,10 @@ describe("OpenAssessment.GradeView", function() { ...@@ -47,12 +40,10 @@ describe("OpenAssessment.GradeView", function() {
// Create the stub server // Create the stub server
server = new StubServer(); server = new StubServer();
// Create the stub base view
baseView = new StubBaseView();
// Create and install the view // Create and install the view
var el = $('#openassessment-base').get(0); var gradeElement = $('#openassessment__grade').get(0);
view = new OpenAssessment.GradeView(el, server, baseView); var baseView = new OpenAssessment.BaseView(runtime, gradeElement, server, {});
view = new OpenAssessment.GradeView(gradeElement, server, baseView);
view.installHandlers(); view.installHandlers();
}); });
......
...@@ -12,11 +12,11 @@ describe("OpenAssessment.PeerView", function() { ...@@ -12,11 +12,11 @@ describe("OpenAssessment.PeerView", function() {
} }
).promise(); ).promise();
this.peerAssess = function(optionsSelected, feedback) { this.peerAssess = function() {
return successPromise; return successPromise;
}; };
this.render = function(step) { this.render = function() {
return successPromise; return successPromise;
}; };
...@@ -25,40 +25,28 @@ describe("OpenAssessment.PeerView", function() { ...@@ -25,40 +25,28 @@ describe("OpenAssessment.PeerView", function() {
}; };
}; };
// Stub base view
var StubBaseView = function() {
this.showLoadError = function(msg) {};
this.toggleActionError = function(msg, step) {};
this.setUpCollapseExpand = function(sel) {};
this.scrollToTop = function() {};
this.loadAssessmentModules = function() {};
this.loadMessageView = function() {};
};
// Stubs // Stubs
var baseView = null;
var server = null; var server = null;
var runtime = {};
// View under test var createPeerAssessmentView = function(template) {
var view = null; loadFixtures(template);
beforeEach(function() { var assessmentElement = $('#openassessment__peer-assessment').get(0);
// Load the DOM fixture var baseView = new OpenAssessment.BaseView(runtime, assessmentElement, server, {});
loadFixtures('oa_peer_assessment.html'); var view = new OpenAssessment.PeerView(assessmentElement, server, baseView);
view.installHandlers();
return view;
};
beforeEach(function() {
// Create a new stub server // Create a new stub server
server = new StubServer(); server = new StubServer();
server.renderLatex = jasmine.createSpy('renderLatex');
// Create the stub base view
baseView = new StubBaseView();
// Create the object under test
var el = $("#openassessment-base").get(0);
view = new OpenAssessment.PeerView(el, server, baseView);
view.installHandlers();
}); });
it("Sends a peer assessment to the server", function() { it("Sends a peer assessment to the server", function() {
var view = createPeerAssessmentView('oa_peer_assessment.html');
spyOn(server, 'peerAssess').and.callThrough(); spyOn(server, 'peerAssess').and.callThrough();
// Select options in the rubric // Select options in the rubric
...@@ -89,6 +77,7 @@ describe("OpenAssessment.PeerView", function() { ...@@ -89,6 +77,7 @@ describe("OpenAssessment.PeerView", function() {
}); });
it("Re-enables the peer assess button on error", function() { it("Re-enables the peer assess button on error", function() {
var view = createPeerAssessmentView('oa_peer_assessment.html');
// Simulate a server error // Simulate a server error
spyOn(server, 'peerAssess').and.callFake(function() { spyOn(server, 'peerAssess').and.callFake(function() {
expect(view.peerSubmitEnabled()).toBe(false); expect(view.peerSubmitEnabled()).toBe(false);
...@@ -103,8 +92,8 @@ describe("OpenAssessment.PeerView", function() { ...@@ -103,8 +92,8 @@ describe("OpenAssessment.PeerView", function() {
}); });
it("Re-enables the continued grading button on error", function() { it("Re-enables the continued grading button on error", function() {
jasmine.getFixtures().fixturesPath = 'base/fixtures'; var view = createPeerAssessmentView('oa_peer_complete.html');
loadFixtures('oa_peer_complete.html');
// Simulate a server error // Simulate a server error
spyOn(server, 'renderContinuedPeer').and.callFake(function() { spyOn(server, 'renderContinuedPeer').and.callFake(function() {
expect(view.continueAssessmentEnabled()).toBe(false); expect(view.continueAssessmentEnabled()).toBe(false);
......
...@@ -9,7 +9,7 @@ describe("OpenAssessment.ResponseView", function() { ...@@ -9,7 +9,7 @@ describe("OpenAssessment.ResponseView", function() {
'image/gif', 'image/gif',
'image/jpeg', 'image/jpeg',
'image/pjpeg', 'image/pjpeg',
'image/png', 'image/png'
]; ];
var ALLOWED_FILE_MIME_TYPES = [ var ALLOWED_FILE_MIME_TYPES = [
...@@ -17,7 +17,7 @@ describe("OpenAssessment.ResponseView", function() { ...@@ -17,7 +17,7 @@ describe("OpenAssessment.ResponseView", function() {
'image/gif', 'image/gif',
'image/jpeg', 'image/jpeg',
'image/pjpeg', 'image/pjpeg',
'image/png', 'image/png'
]; ];
var FILE_TYPE_WHITE_LIST = ['pdf', 'doc', 'docx', 'html']; var FILE_TYPE_WHITE_LIST = ['pdf', 'doc', 'docx', 'html'];
...@@ -71,7 +71,7 @@ describe("OpenAssessment.ResponseView", function() { ...@@ -71,7 +71,7 @@ describe("OpenAssessment.ResponseView", function() {
// Store the args we were passed so we can verify them // Store the args we were passed so we can verify them
this.uploadArgs = { this.uploadArgs = {
url: url, url: url,
data: data, data: data
}; };
// Return a promise indicating success or error // Return a promise indicating success or error
...@@ -79,18 +79,9 @@ describe("OpenAssessment.ResponseView", function() { ...@@ -79,18 +79,9 @@ describe("OpenAssessment.ResponseView", function() {
}; };
}; };
var StubBaseView = function() {
this.loadAssessmentModules = function() {};
this.peerView = { load: function() {} };
this.gradeView = { load: function() {} };
this.showLoadError = function() {};
this.toggleActionError = function() {};
this.setUpCollapseExpand = function() {};
};
// Stubs // Stubs
var baseView = null;
var server = null; var server = null;
var runtime = {};
var fileUploader = null; var fileUploader = null;
var data = null; var data = null;
...@@ -120,7 +111,6 @@ describe("OpenAssessment.ResponseView", function() { ...@@ -120,7 +111,6 @@ describe("OpenAssessment.ResponseView", function() {
server = new StubServer(); server = new StubServer();
server.renderLatex = jasmine.createSpy('renderLatex'); server.renderLatex = jasmine.createSpy('renderLatex');
fileUploader = new StubFileUploader(); fileUploader = new StubFileUploader();
baseView = new StubBaseView();
data = { data = {
"ALLOWED_IMAGE_MIME_TYPES": ALLOWED_IMAGE_MIME_TYPES, "ALLOWED_IMAGE_MIME_TYPES": ALLOWED_IMAGE_MIME_TYPES,
"ALLOWED_FILE_MIME_TYPES": ALLOWED_FILE_MIME_TYPES, "ALLOWED_FILE_MIME_TYPES": ALLOWED_FILE_MIME_TYPES,
...@@ -129,8 +119,9 @@ describe("OpenAssessment.ResponseView", function() { ...@@ -129,8 +119,9 @@ describe("OpenAssessment.ResponseView", function() {
}; };
// Create and install the view // Create and install the view
var el = $('#openassessment-base').get(0); var responseElement = $('#openassessment__response').get(0);
view = new OpenAssessment.ResponseView(el, server, fileUploader, baseView, data); var baseView = new OpenAssessment.BaseView(runtime, responseElement, server, {});
view = new OpenAssessment.ResponseView(responseElement, server, fileUploader, baseView, data);
view.installHandlers(); view.installHandlers();
// Stub the confirmation step // Stub the confirmation step
...@@ -296,14 +287,14 @@ describe("OpenAssessment.ResponseView", function() { ...@@ -296,14 +287,14 @@ describe("OpenAssessment.ResponseView", function() {
}).promise(); }).promise();
}); });
spyOn(view, 'load'); spyOn(view, 'load');
spyOn(baseView, 'loadAssessmentModules'); spyOn(view.baseView, 'loadAssessmentModules');
view.response(['Test response 1', 'Test response 2']); view.response(['Test response 1', 'Test response 2']);
view.submit(); view.submit();
// Expect the current and next step to have been reloaded // Expect the current and next step to have been reloaded
expect(view.load).toHaveBeenCalled(); expect(view.load).toHaveBeenCalled();
expect(baseView.loadAssessmentModules).toHaveBeenCalled(); expect(view.baseView.loadAssessmentModules).toHaveBeenCalled();
}); });
it("enables the unsaved work warning when the user changes the response text", function() { it("enables the unsaved work warning when the user changes the response text", function() {
...@@ -442,39 +433,45 @@ describe("OpenAssessment.ResponseView", function() { ...@@ -442,39 +433,45 @@ describe("OpenAssessment.ResponseView", function() {
}); });
it("selects too large of a file", function() { it("selects too large of a file", function() {
spyOn(baseView, 'toggleActionError').and.callThrough(); spyOn(view.baseView, 'toggleActionError').and.callThrough();
var files = [{type: 'image/jpeg', size: 6000000, name: 'huge-picture.jpg', data: ''}]; var files = [{type: 'image/jpeg', size: 6000000, name: 'huge-picture.jpg', data: ''}];
view.prepareUpload(files, 'image'); view.prepareUpload(files, 'image');
expect(baseView.toggleActionError).toHaveBeenCalledWith('upload', 'File size must be 5MB or less.'); expect(view.baseView.toggleActionError).toHaveBeenCalledWith('upload', 'File size must be 5MB or less.');
}); });
it("selects the wrong image file type", function() { it("selects the wrong image file type", function() {
spyOn(baseView, 'toggleActionError').and.callThrough(); spyOn(view.baseView, 'toggleActionError').and.callThrough();
var files = [{type: 'image/jpg', size: 1024, name: 'picture.exe', data: ''}]; var files = [{type: 'image/jpg', size: 1024, name: 'picture.exe', data: ''}];
view.prepareUpload(files, 'image'); view.prepareUpload(files, 'image');
expect(baseView.toggleActionError).toHaveBeenCalledWith('upload', 'You can upload files with these file types: JPG, PNG or GIF'); expect(view.baseView.toggleActionError).toHaveBeenCalledWith(
'upload', 'You can upload files with these file types: JPG, PNG or GIF'
);
}); });
it("selects the wrong pdf or image file type", function() { it("selects the wrong pdf or image file type", function() {
spyOn(baseView, 'toggleActionError').and.callThrough(); spyOn(view.baseView, 'toggleActionError').and.callThrough();
var files = [{type: 'application/exe', size: 1024, name: 'application.exe', data: ''}]; var files = [{type: 'application/exe', size: 1024, name: 'application.exe', data: ''}];
view.prepareUpload(files, 'pdf-and-image'); view.prepareUpload(files, 'pdf-and-image');
expect(baseView.toggleActionError).toHaveBeenCalledWith('upload', 'You can upload files with these file types: JPG, PNG, GIF or PDF'); expect(view.baseView.toggleActionError).toHaveBeenCalledWith(
'upload', 'You can upload files with these file types: JPG, PNG, GIF or PDF'
);
}); });
it("selects the wrong file extension", function() { it("selects the wrong file extension", function() {
spyOn(baseView, 'toggleActionError').and.callThrough(); spyOn(view.baseView, 'toggleActionError').and.callThrough();
var files = [{type: 'application/exe', size: 1024, name: 'application.exe', data: ''}]; var files = [{type: 'application/exe', size: 1024, name: 'application.exe', data: ''}];
view.prepareUpload(files, 'custom'); view.prepareUpload(files, 'custom');
expect(baseView.toggleActionError).toHaveBeenCalledWith('upload', 'You can upload files with these file types: pdf, doc, docx, html'); expect(view.baseView.toggleActionError).toHaveBeenCalledWith(
'upload', 'You can upload files with these file types: pdf, doc, docx, html'
);
}); });
it("submits a file with extension in the black list", function() { it("submits a file with extension in the black list", function() {
spyOn(baseView, 'toggleActionError').and.callThrough(); spyOn(view.baseView, 'toggleActionError').and.callThrough();
view.data.FILE_TYPE_WHITE_LIST = ['exe']; view.data.FILE_TYPE_WHITE_LIST = ['exe'];
var files = [{type: 'application/exe', size: 1024, name: 'application.exe', data: ''}]; var files = [{type: 'application/exe', size: 1024, name: 'application.exe', data: ''}];
view.prepareUpload(files, 'custom'); view.prepareUpload(files, 'custom');
expect(baseView.toggleActionError).toHaveBeenCalledWith('upload', 'File type is not allowed.'); expect(view.baseView.toggleActionError).toHaveBeenCalledWith('upload', 'File type is not allowed.');
}); });
it("uploads an image using a one-time URL", function() { it("uploads an image using a one-time URL", function() {
...@@ -504,7 +501,7 @@ describe("OpenAssessment.ResponseView", function() { ...@@ -504,7 +501,7 @@ describe("OpenAssessment.ResponseView", function() {
it("displays an error if a one-time file upload URL cannot be retrieved", function() { it("displays an error if a one-time file upload URL cannot be retrieved", function() {
// Configure the server to fail when retrieving the one-time URL // Configure the server to fail when retrieving the one-time URL
server.uploadUrlError = true; server.uploadUrlError = true;
spyOn(baseView, 'toggleActionError').and.callThrough(); spyOn(view.baseView, 'toggleActionError').and.callThrough();
// Attempt to upload a file // Attempt to upload a file
var files = [{type: 'image/jpeg', size: 1024, name: 'picture.jpg', data: ''}]; var files = [{type: 'image/jpeg', size: 1024, name: 'picture.jpg', data: ''}];
...@@ -512,13 +509,13 @@ describe("OpenAssessment.ResponseView", function() { ...@@ -512,13 +509,13 @@ describe("OpenAssessment.ResponseView", function() {
view.fileUpload(); view.fileUpload();
// Expect an error to be displayed // Expect an error to be displayed
expect(baseView.toggleActionError).toHaveBeenCalledWith('upload', 'ERROR'); expect(view.baseView.toggleActionError).toHaveBeenCalledWith('upload', 'ERROR');
}); });
it("displays an error if a file could not be uploaded", function() { it("displays an error if a file could not be uploaded", function() {
// Configure the file upload server to return an error // Configure the file upload server to return an error
fileUploader.uploadError = true; fileUploader.uploadError = true;
spyOn(baseView, 'toggleActionError').and.callThrough(); spyOn(view.baseView, 'toggleActionError').and.callThrough();
// Attempt to upload a file // Attempt to upload a file
var files = [{type: 'image/jpeg', size: 1024, name: 'picture.jpg', data: ''}]; var files = [{type: 'image/jpeg', size: 1024, name: 'picture.jpg', data: ''}];
...@@ -526,6 +523,6 @@ describe("OpenAssessment.ResponseView", function() { ...@@ -526,6 +523,6 @@ describe("OpenAssessment.ResponseView", function() {
view.fileUpload(); view.fileUpload();
// Expect an error to be displayed // Expect an error to be displayed
expect(baseView.toggleActionError).toHaveBeenCalledWith('upload', 'ERROR'); expect(view.baseView.toggleActionError).toHaveBeenCalledWith('upload', 'ERROR');
}); });
}); });
...@@ -21,17 +21,8 @@ describe("OpenAssessment.SelfView", function() { ...@@ -21,17 +21,8 @@ describe("OpenAssessment.SelfView", function() {
}; };
}; };
// Stub base view
var StubBaseView = function() {
this.showLoadError = function(msg) {};
this.toggleActionError = function(msg, step) {};
this.setUpCollapseExpand = function(sel) {};
this.loadAssessmentModules = function() {};
this.scrollToTop = function() {};
};
// Stubs // Stubs
var baseView = null; var runtime = {};
var server = null; var server = null;
// View under test // View under test
...@@ -43,13 +34,12 @@ describe("OpenAssessment.SelfView", function() { ...@@ -43,13 +34,12 @@ describe("OpenAssessment.SelfView", function() {
// Create a new stub server // Create a new stub server
server = new StubServer(); server = new StubServer();
server.renderLatex = jasmine.createSpy('renderLatex');
// Create the stub base view
baseView = new StubBaseView();
// Create the object under test // Create the object under test
var el = $("#openassessment").get(0); var assessmentElement = $("#openassessment__self-assessment").get(0);
view = new OpenAssessment.SelfView(el, server, baseView); var baseView = new OpenAssessment.BaseView(runtime, assessmentElement, server, {});
view = new OpenAssessment.SelfView(assessmentElement, server, baseView);
view.installHandlers(); view.installHandlers();
}); });
......
...@@ -20,6 +20,14 @@ describe('OpenAssessment.StaffAreaView', function() { ...@@ -20,6 +20,14 @@ describe('OpenAssessment.StaffAreaView', function() {
}); });
}; };
this.studentInfo = function() {
var server = this;
return $.Deferred(function(defer) {
var fragment = readFixtures('oa_student_info.html');
defer.resolveWith(server, [fragment]);
});
};
this.scheduleTraining = function() { this.scheduleTraining = function() {
var server = this; var server = this;
return $.Deferred(function(defer) { return $.Deferred(function(defer) {
...@@ -46,19 +54,9 @@ describe('OpenAssessment.StaffAreaView', function() { ...@@ -46,19 +54,9 @@ describe('OpenAssessment.StaffAreaView', function() {
}; };
// Stub base view
var StubBaseView = function() {
this.showLoadError = function() {};
this.toggleActionError = function() {};
this.setUpCollapseExpand = function() {};
this.scrollToTop = function() {};
this.loadAssessmentModules = function() {};
this.loadMessageView = function() {};
};
// Stubs // Stubs
var baseView = null;
var server = null; var server = null;
var runtime = {};
/** /**
* Create a staff area view. * Create a staff area view.
...@@ -69,8 +67,9 @@ describe('OpenAssessment.StaffAreaView', function() { ...@@ -69,8 +67,9 @@ describe('OpenAssessment.StaffAreaView', function() {
if (serverResponse) { if (serverResponse) {
server.data = serverResponse; server.data = serverResponse;
} }
var el = $('#openassessment').get(0); var assessmentElement = $('#openassessment').get(0);
var view = new OpenAssessment.StaffAreaView(el, server, baseView); var baseView = new OpenAssessment.BaseView(runtime, assessmentElement, server, {});
var view = new OpenAssessment.StaffAreaView(assessmentElement, server, baseView);
view.load(); view.load();
return view; return view;
}; };
...@@ -93,8 +92,6 @@ describe('OpenAssessment.StaffAreaView', function() { ...@@ -93,8 +92,6 @@ describe('OpenAssessment.StaffAreaView', function() {
// Create a new stub server // Create a new stub server
server = new StubServer(); server = new StubServer();
server.renderLatex = jasmine.createSpy('renderLatex'); server.renderLatex = jasmine.createSpy('renderLatex');
// Create the stub base view
baseView = new StubBaseView();
}); });
describe('Initial rendering', function() { describe('Initial rendering', function() {
...@@ -165,46 +162,64 @@ describe('OpenAssessment.StaffAreaView', function() { ...@@ -165,46 +162,64 @@ describe('OpenAssessment.StaffAreaView', function() {
}); });
}); });
describe('Submission Management', function() { describe('Student Info', function() {
it('updates submission cancellation button when comments changes', function() { var chooseStudent = function(view, studentName) {
// Prevent the server's response from resolving, var studentNameField = $('.openassessment__student_username', view.element),
// so we can see what happens before view gets re-rendered. submitButton = $('.action--submit-username', view.element);
spyOn(server, 'cancelSubmission').and.callFake(function() { studentNameField.val(studentName);
return $.Deferred(function() {}).promise(); submitButton.click();
}); };
// Load the fixture
loadFixtures('oa_student_info.html');
var view = createStaffArea();
// comments is blank --> cancel submission button disabled
view.comment('');
view.handleCommentChanged();
expect(view.cancelSubmissionEnabled()).toBe(false);
// Response is whitespace --> cancel submission button disabled beforeEach(function() {
view.comment(' \n \n '); loadFixtures('oa_base_course_staff.html');
view.handleCommentChanged(); appendLoadFixtures('oa_student_info.html');
expect(view.cancelSubmissionEnabled()).toBe(false); });
// Response is not blank --> cancel submission button enabled it('shows an error when clicking "Submit" with no student name chosen', function() {
view.comment('Cancellation reason.'); var staffArea = createStaffArea();
view.handleCommentChanged(); chooseStudent(staffArea, '');
expect(view.cancelSubmissionEnabled()).toBe(true); expect($('.openassessment_student_info_form .form--error', staffArea.element).text().trim())
.toBe('A learner name must be provided.');
}); });
it('submits the cancel submission comments to the server', function() { describe('Submission Management', function() {
spyOn(server, 'cancelSubmission').and.callThrough(); it('updates submission cancellation button when comments changes', function() {
// Prevent the server's response from resolving,
// so we can see what happens before view gets re-rendered.
spyOn(server, 'cancelSubmission').and.callFake(function() {
return $.Deferred(function() {}).promise();
});
var staffArea = createStaffArea();
chooseStudent(staffArea, 'testStudent');
// comments is blank --> cancel submission button disabled
staffArea.comment('');
staffArea.handleCommentChanged();
expect(staffArea.cancelSubmissionEnabled()).toBe(false);
// Response is whitespace --> cancel submission button disabled
staffArea.comment(' \n \n ');
staffArea.handleCommentChanged();
expect(staffArea.cancelSubmissionEnabled()).toBe(false);
// Response is not blank --> cancel submission button enabled
staffArea.comment('Cancellation reason.');
staffArea.handleCommentChanged();
expect(staffArea.cancelSubmissionEnabled()).toBe(true);
});
// Load the fixture it('submits the cancel submission comments to the server', function() {
loadFixtures('oa_student_info.html'); spyOn(server, 'cancelSubmission').and.callThrough();
var view = createStaffArea();
view.comment('Cancellation reason.'); var staffArea = createStaffArea();
view.cancelSubmission('Bob'); chooseStudent(staffArea, 'testStudent');
expect(server.cancelSubmission).toHaveBeenCalledWith('Bob', 'Cancellation reason.'); staffArea.comment('Cancellation reason.');
staffArea.cancelSubmission('Bob');
expect(server.cancelSubmission).toHaveBeenCalledWith('Bob', 'Cancellation reason.');
});
}); });
}); });
......
...@@ -27,18 +27,9 @@ describe("OpenAssessment.StudentTrainingView", function() { ...@@ -27,18 +27,9 @@ describe("OpenAssessment.StudentTrainingView", function() {
this.corrections = {}; this.corrections = {};
}; };
// Stub base view
var StubBaseView = function() {
this.showLoadError = function(msg) {};
this.toggleActionError = function(msg, step) {};
this.setUpCollapseExpand = function(sel) {};
this.scrollToTop = function() {};
this.loadAssessmentModules = function() {};
};
// Stubs // Stubs
var baseView = null;
var server = null; var server = null;
var runtime = {};
// View under test // View under test
var view = null; var view = null;
...@@ -50,12 +41,11 @@ describe("OpenAssessment.StudentTrainingView", function() { ...@@ -50,12 +41,11 @@ describe("OpenAssessment.StudentTrainingView", function() {
// Create a new stub server // Create a new stub server
server = new StubServer(); server = new StubServer();
server.renderLatex = jasmine.createSpy('renderLatex') server.renderLatex = jasmine.createSpy('renderLatex')
// Create the stub base view
baseView = new StubBaseView();
// Create the object under test // Create the object under test
var el = $("#openassessment-base").get(0); var trainingElement = $('#openassessment__student-training').get(0);
view = new OpenAssessment.StudentTrainingView(el, server, baseView); var baseView = new OpenAssessment.BaseView(runtime, trainingElement, server, {});
view = new OpenAssessment.StudentTrainingView(trainingElement, server, baseView);
view.installHandlers(); view.installHandlers();
}); });
...@@ -112,7 +102,7 @@ describe("OpenAssessment.StudentTrainingView", function() { ...@@ -112,7 +102,7 @@ describe("OpenAssessment.StudentTrainingView", function() {
// Simulate that the user answered the problem correctly, so there are no corrections // Simulate that the user answered the problem correctly, so there are no corrections
server.corrections = {}; server.corrections = {};
spyOn(server, 'trainingAssess').and.callThrough(); spyOn(server, 'trainingAssess').and.callThrough();
spyOn(baseView, 'loadAssessmentModules').and.callThrough(); spyOn(view.baseView, 'loadAssessmentModules').and.callThrough();
// Select rubric options // Select rubric options
var optionsSelected = {}; var optionsSelected = {};
...@@ -128,6 +118,6 @@ describe("OpenAssessment.StudentTrainingView", function() { ...@@ -128,6 +118,6 @@ describe("OpenAssessment.StudentTrainingView", function() {
expect(server.trainingAssess).toHaveBeenCalledWith(optionsSelected); expect(server.trainingAssess).toHaveBeenCalledWith(optionsSelected);
// Expect that the steps were reloaded // Expect that the steps were reloaded
expect(baseView.loadAssessmentModules).toHaveBeenCalled(); expect(view.baseView.loadAssessmentModules).toHaveBeenCalled();
}); });
}); });
...@@ -4,3 +4,4 @@ Common test configuration, loaded before any of the spec files. ...@@ -4,3 +4,4 @@ Common test configuration, loaded before any of the spec files.
// Set the fixture path // Set the fixture path
jasmine.getFixtures().fixturesPath = 'base/fixtures'; jasmine.getFixtures().fixturesPath = 'base/fixtures';
...@@ -43,13 +43,12 @@ OpenAssessment.BaseView.prototype = { ...@@ -43,13 +43,12 @@ OpenAssessment.BaseView.prototype = {
}, },
/** /**
Install click handlers to expand/collapse a section. * Install click handlers to expand/collapse a section.
*
Args: * @param {element} parentElement JQuery selector for the container element.
parentSel (JQuery selector): CSS selector for the container element. */
**/ setUpCollapseExpand: function(parentElement) {
setUpCollapseExpand: function(parentSel) { parentElement.on('click', '.ui-toggle-visibility__control', function(eventData) {
parentSel.on('click', '.ui-toggle-visibility__control', function(eventData) {
var sel = $(eventData.target).closest('.ui-toggle-visibility'); var sel = $(eventData.target).closest('.ui-toggle-visibility');
sel.toggleClass('is--collapsed'); sel.toggleClass('is--collapsed');
} }
...@@ -57,8 +56,8 @@ OpenAssessment.BaseView.prototype = { ...@@ -57,8 +56,8 @@ OpenAssessment.BaseView.prototype = {
}, },
/** /**
Asynchronously load each sub-view into the DOM. * Asynchronously load each sub-view into the DOM.
**/ */
load: function() { load: function() {
this.responseView.load(); this.responseView.load();
this.loadAssessmentModules(); this.loadAssessmentModules();
...@@ -66,9 +65,9 @@ OpenAssessment.BaseView.prototype = { ...@@ -66,9 +65,9 @@ OpenAssessment.BaseView.prototype = {
}, },
/** /**
Refresh the Assessment Modules. This should be called any time an action is * Refresh the Assessment Modules. This should be called any time an action is
performed by the user. * performed by the user.
**/ */
loadAssessmentModules: function() { loadAssessmentModules: function() {
this.trainingView.load(); this.trainingView.load();
this.peerView.load(); this.peerView.load();
...@@ -91,21 +90,20 @@ OpenAssessment.BaseView.prototype = { ...@@ -91,21 +90,20 @@ OpenAssessment.BaseView.prototype = {
}, },
/** /**
Refresh the message only (called by PeerView to update and avoid race condition) * Refresh the message only (called by PeerView to update and avoid race condition)
**/ */
loadMessageView: function() { loadMessageView: function() {
this.messageView.load(); this.messageView.load();
}, },
/** /**
Report an error to the user. * Report an error to the user.
*
Args: * @param {string} type The type of error. Options are "save", submit", "peer", and "self".
type (str): Which type of error. Options are "save", submit", "peer", and "self". * @param {string} message The error message to display, or if null hide the message.
msg (str or null): The error message to display. * Note: loading errors are never hidden once displayed.
If null, hide the error message (with one exception: loading errors are never hidden once displayed) */
**/ toggleActionError: function(type, message) {
toggleActionError: function(type, msg) {
var element = this.element; var element = this.element;
var container = null; var container = null;
if (type === 'save') { if (type === 'save') {
...@@ -123,29 +121,32 @@ OpenAssessment.BaseView.prototype = { ...@@ -123,29 +121,32 @@ OpenAssessment.BaseView.prototype = {
// If we don't have anywhere to put the message, just log it to the console // If we don't have anywhere to put the message, just log it to the console
if (container === null) { if (container === null) {
if (msg !== null) { console.log(msg); } if (message !== null) { console.log(message); }
} }
else { else {
// Insert the error message // Insert the error message
var msgHtml = (msg === null) ? "" : msg; var msgHtml = (message === null) ? "" : message;
$(container + " .message__content", element).html('<p>' + msgHtml + '</p>'); $(container + " .message__content", element).html('<p>' + msgHtml + '</p>');
// Toggle the error class // Toggle the error class
$(container, element).toggleClass('has--error', msg !== null); $(container, element).toggleClass('has--error', message !== null);
} }
}, },
/** /**
Report an error loading a step. * Report an error loading a step.
*
Args: * @param {string} stepName The step that could not be loaded.
step (str): the step that could not be loaded. * @param {string} errorMessage An optional error message to use instead of the default.
**/ */
showLoadError: function(step) { showLoadError: function(stepName, errorMessage) {
var container = '#openassessment__' + step; if (!errorMessage) {
$(container).toggleClass('has--error', true); errorMessage = gettext('Unable to load');
$(container + ' .step__status__value i').removeClass().addClass('icon fa fa-exclamation-triangle'); }
$(container + ' .step__status__value .copy').html(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(errorMessage);
} }
}; };
......
(function(OpenAssessment) { (function(OpenAssessment) {
'use strict'; 'use strict';
/** /**
* Interface for staff area view. * Interface for staff area view.
* *
...@@ -20,24 +21,22 @@ ...@@ -20,24 +21,22 @@
OpenAssessment.StaffAreaView.prototype = { OpenAssessment.StaffAreaView.prototype = {
/** /**
Load the staff area. * Load the staff area.
**/ */
load: function() { load: function() {
var view = this; var view = this;
// If we're course staff, the base template should contain a section // If we're course staff, the base template should contain a section
// for us to render the staff area into. If that doesn't exist, // for us to render the staff area into. If that doesn't exist,
// then we're not staff, so we don't need to send the AJAX request. // then we're not staff, so we don't need to send the AJAX request.
if ($('#openassessment__staff-area', view.element).length > 0) { if ($('.openassessment__staff-area', view.element).length > 0) {
this.server.render('staff_area').done( this.server.render('staff_area')
function(html) { .done(function(html) {
// Load the HTML and install event handlers // Load the HTML and install event handlers
$('#openassessment__staff-area', view.element).replaceWith(html); $('.openassessment__staff-area', view.element).replaceWith(html);
view.server.renderLatex($('#openassessment__staff-area', view.element)); view.server.renderLatex($('.openassessment__staff-area', view.element));
view.installHandlers(); view.installHandlers();
} }).fail(function() {
).fail(
function() {
view.baseView.showLoadError('staff_area'); view.baseView.showLoadError('staff_area');
} }
); );
...@@ -45,62 +44,103 @@ ...@@ -45,62 +44,103 @@
}, },
/** /**
Upon request, loads the student info section of the staff info view. This * Upon request, loads the student info section of the staff area.
allows viewing all the submissions and assessments associated to the given * This allows viewing all the submissions and assessments associated
student's current workflow. * to the given student's current workflow.
**/ *
loadStudentInfo: function() { * @param {object} options An optional set of options to render the section.
* @returns {promise} A promise representing the successful oading
* of the student info section.
*/
loadStudentInfo: function(options) {
var view = this; var view = this;
var sel = $('#openassessment__staff-tools', this.element); var $staffTools = $('.openassessment__staff-tools', this.element);
var studentUsername = sel.find('#openassessment__student_username').val(); var $form = $staffTools.find('.openassessment_student_info_form');
this.server.studentInfo(studentUsername).done( var studentUsername = $staffTools.find('.openassessment__student_username').val();
function(html) { var showFormError = function(errorMessage) {
// Load the HTML and install event handlers $form.find('.form--error').text(errorMessage);
$('#openassessment__student-info', view.element).replaceWith(html); };
var deferred = $.Deferred();
// Install key handler for new staff grade Save button.
var selCancelSub = $('#openassessment__staff-info__cancel__submission', view.element); // Clear any previous student information
selCancelSub.on('click', '#submit_cancel_submission', function(eventObject) { $('.openassessment__student-info', view.element).text('');
if (studentUsername.trim()) {
this.server.studentInfo(studentUsername, options)
.done(function(html) {
// Clear any error message
showFormError('');
// Load the HTML and install event handlers
$('.openassessment__student-info', view.element).replaceWith(html);
// Install key handler for cancel submission button.
$staffTools.on('click', '.action--submit-cancel-submission', function(eventObject) {
eventObject.preventDefault(); eventObject.preventDefault();
view.cancelSubmission($(this).data('submission-uuid')); view.cancelSubmission($(this).data('submission-uuid'));
} });
);
// Install change handler for textarea (to enable cancel submission button) // Install change handler for textarea (to enable cancel submission button)
var handleChange = function(eventData) { view.handleCommentChanged(eventData); }; var handleChange = function(eventData) { view.handleCommentChanged(eventData); };
selCancelSub.find('#staff-info__cancel-submission__comments') $staffTools.find('.cancel_submission_comments')
.on('change keyup drop paste', handleChange); .on('change keyup drop paste', handleChange);
} // Initialize the rubric
).fail( var $rubric = $('.staff-assessment__assessment', view.element);
function() { if ($rubric.size() > 0) {
view.showLoadError('student_info'); var rubricElement = $rubric.get(0);
} var rubric = new OpenAssessment.Rubric(rubricElement);
);
// Install a change handler for rubric options to enable/disable the submit button
rubric.canSubmitCallback($.proxy(view.staffSubmitEnabled, view));
// Install a click handler for the submit button
$('.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('A learner name must be provided.'));
deferred.reject();
}
return deferred.promise();
}, },
/** /**
Install event handlers for the view. * Install event handlers for the view.
**/ */
installHandlers: function() { installHandlers: function() {
var $staffArea = $('#openassessment__staff-area', this.element);
var toolsElement = $('#openassessment__staff-tools', $staffArea);
var infoElement = $('#openassessment__student-info', $staffArea);
var view = this; var view = this;
var $staffArea = $('.openassessment__staff-area', this.element);
var $staffTools = $('.openassessment__staff-tools', $staffArea);
var $staffInfo = $('.openassessment__student-info', $staffArea);
if (toolsElement.length <= 0) { if ($staffArea.length <= 0) {
return; return;
} }
this.baseView.setUpCollapseExpand(toolsElement, function() {}); this.baseView.setUpCollapseExpand($staffTools, function() {});
this.baseView.setUpCollapseExpand(infoElement, function() {}); this.baseView.setUpCollapseExpand($staffInfo, function() {});
// Install a click handler for the staff button panel // Install a click handler for the staff button panel
$staffArea.find('.ui-staff__button').click( $staffArea.find('.ui-staff__button').click(
function(eventObject) { function(eventObject) {
var $button = $(eventObject.currentTarget), var $button = $(eventObject.currentTarget),
panelID = $button.data('panel'), panelClass = $button.data('panel'),
$panel = $staffArea.find('#' + panelID).first(); $panel = $staffArea.find('.' + panelClass).first();
if ($button.hasClass('is--active')) { if ($button.hasClass('is--active')) {
$button.removeClass('is--active'); $button.removeClass('is--active');
$panel.addClass('is--hidden'); $panel.addClass('is--hidden');
...@@ -124,7 +164,7 @@ ...@@ -124,7 +164,7 @@
); );
// Install key handler for student id field // Install key handler for student id field
toolsElement.find('#openassessment_student_info_form').submit( $staffTools.find('.openassessment_student_info_form').submit(
function(eventObject) { function(eventObject) {
eventObject.preventDefault(); eventObject.preventDefault();
view.loadStudentInfo(); view.loadStudentInfo();
...@@ -132,7 +172,7 @@ ...@@ -132,7 +172,7 @@
); );
// Install a click handler for requesting student info // Install a click handler for requesting student info
toolsElement.find('#submit_student_username').click( $staffTools.find('.action--submit-username').click(
function(eventObject) { function(eventObject) {
eventObject.preventDefault(); eventObject.preventDefault();
view.loadStudentInfo(); view.loadStudentInfo();
...@@ -140,7 +180,7 @@ ...@@ -140,7 +180,7 @@
); );
// Install a click handler for scheduling AI classifier training // Install a click handler for scheduling AI classifier training
toolsElement.find('#schedule_training').click( $staffTools.find('.action--submit-training').click(
function(eventObject) { function(eventObject) {
eventObject.preventDefault(); eventObject.preventDefault();
view.scheduleTraining(); view.scheduleTraining();
...@@ -148,7 +188,7 @@ ...@@ -148,7 +188,7 @@
); );
// Install a click handler for rescheduling unfinished AI tasks for this problem // Install a click handler for rescheduling unfinished AI tasks for this problem
toolsElement.find('#reschedule_unfinished_tasks').click( $staffTools.find('.action--submit-unfinished-tasks').click(
function(eventObject) { function(eventObject) {
eventObject.preventDefault(); eventObject.preventDefault();
view.rescheduleUnfinishedTasks(); view.rescheduleUnfinishedTasks();
...@@ -157,108 +197,148 @@ ...@@ -157,108 +197,148 @@
}, },
/** /**
Sends a request to the server to schedule the training of classifiers for * Sends a request to the server to schedule the training
this problem's Example Based Assessments. * of classifiers for this problem's Example Based Assessments.
*/
**/
scheduleTraining: function() { scheduleTraining: function() {
var view = this; var view = this;
this.server.scheduleTraining().done( this.server.scheduleTraining()
function(msg) { .done(function(msg) {
$('#schedule_training_message', view.element).text(msg); $('.schedule_training_message', view.element).text(msg);
} }).fail(function(errMsg) {
).fail(function(errMsg) { $('.schedule_training_message', view.element).text(errMsg);
$('#schedule_training_message', view.element).text(errMsg); });
});
}, },
/** /**
Begins the process of rescheduling all unfinished grading tasks. This incdludes * Begins the process of rescheduling all unfinished grading tasks.
checking if the classifiers have been created, and grading any unfinished * This includes checking if the classifiers have been created,
student submissions. * and grading any unfinished student submissions.
*/
**/
rescheduleUnfinishedTasks: function() { rescheduleUnfinishedTasks: function() {
var view = this; var view = this;
this.server.rescheduleUnfinishedTasks().done( this.server.rescheduleUnfinishedTasks()
function(msg) { .done(function(msg) {
$('#reschedule_unfinished_tasks_message', view.element).text(msg); $('.reschedule_unfinished_tasks_message', view.element).text(msg);
} }).fail(function(errMsg) {
).fail(function(errMsg) { $('.reschedule_unfinished_tasks_message', view.element).text(errMsg);
$('#reschedule_unfinished_tasks_message', view.element).text(errMsg); });
});
}, },
/** /**
Upon request, cancel the submission from grading pool. * Upon request, cancel the submission from grading pool.
**/ */
cancelSubmission: function(submissionUUID) { cancelSubmission: function(submissionUUID) {
// Immediately disable the button to prevent multiple requests. // Immediately disable the button to prevent multiple requests.
this.cancelSubmissionEnabled(false); this.cancelSubmissionEnabled(false);
var view = this; var view = this;
var sel = $('#openassessment__student-info', this.element); var comments = $('.cancel_submission_comments', this.element).val();
var comments = sel.find('#staff-info__cancel-submission__comments').val(); this.server.cancelSubmission(submissionUUID, comments)
this.server.cancelSubmission(submissionUUID, comments).done( .done(function(msg) {
function(msg) {
$('.cancel-submission-error').html(''); $('.cancel-submission-error').html('');
$('#openassessment__staff-info__cancel__submission', view.element).html(msg); view.loadStudentInfo({expanded_view: 'final-grade'})
} .done(function() {
).fail(function(errMsg) { $('.openassessment__staff-info__cancel__submission', view.element).html(msg);
});
})
.fail(function(errMsg) {
$('.cancel-submission-error').html(errMsg); $('.cancel-submission-error').html(errMsg);
}); });
}, },
/** /**
Enable/disable the cancel submission button. * Enable/disable the cancel submission button.
Check whether the cancel submission button is enabled. *
* Check whether the cancel submission button is enabled.
Args: *
enabled (bool): If specified, set the state of the button. * Args:
* enabled (bool): If specified, set the state of the button.
Returns: *
bool: Whether the button is enabled. * Returns:
* bool: Whether the button is enabled.
Examples: *
>> view.submitEnabled(true); // enable the button * Examples:
>> view.submitEnabled(); // check whether the button is enabled * >> view.submitEnabled(true); // enable the button
>> true * >> view.submitEnabled(); // check whether the button is enabled
**/ * >> true
*/
cancelSubmissionEnabled: function(enabled) { cancelSubmissionEnabled: function(enabled) {
var sel = $('#submit_cancel_submission', this.element); var $cancelButton = $('.action--submit-cancel-submission', this.element);
if (typeof enabled === 'undefined') { if (typeof enabled === 'undefined') {
return !sel.hasClass('is--disabled'); return !$cancelButton.hasClass('is--disabled');
} else { } else {
sel.toggleClass('is--disabled', !enabled); $cancelButton.toggleClass('is--disabled', !enabled);
} }
}, },
/** /**
Set the comment text. * Set the comment text.
Retrieve the comment text. *
* Retrieve the comment text.
Args: *
text (string): reason to . * Args:
* text (string): reason to .
Returns: *
string: The current comment text. * Returns:
**/ * string: The current comment text.
*/
comment: function(text) { comment: function(text) {
var sel = $('#staff-info__cancel-submission__comments', this.element); var $submissionComments = $('.cancel_submission_comments', this.element);
if (typeof text === 'undefined') { if (typeof text === 'undefined') {
return sel.val(); return $submissionComments.val();
} else { } else {
sel.val(text); $submissionComments.val(text);
} }
}, },
/** /**
Enable/disable the cancel submission based on whether * Enable/disable the cancel submission based on whether
the user has entered a comment. * the user has entered a comment.
**/ */
handleCommentChanged: function() { handleCommentChanged: function() {
// Enable the cancel submission button only for non-blank comments // Enable the cancel submission button only for non-blank comments
var isBlank = ($.trim(this.comment()) !== ''); var isBlank = $.trim(this.comment()) !== '';
this.cancelSubmissionEnabled(isBlank); this.cancelSubmissionEnabled(isBlank);
},
/**
* Enable/disable the staff assessment submit button.
*
* @param {boolean} enabled If specified, sets the state of the button.
* @returns {boolean} Whether the button is enabled
*/
staffSubmitEnabled: function(enabled) {
var button = $('.wrapper--staff-assessment .action--submit', this.element);
if (typeof enabled === 'undefined') {
return !button.hasClass('is--disabled');
} else {
button.toggleClass('is--disabled', !enabled);
}
},
/**
* Submit the staff assessment.
*
* @param {string} submissionID The ID of the submission to be submitted.
* @param {element} rubric The rubric element to be assessed.
*/
submitStaffAssessment: function(submissionID, rubric) {
// Send the assessment to the server
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) {
baseView.toggleActionError('staff', errorMessage);
view.staffSubmitEnabled(true);
});
} }
}; };
})(OpenAssessment); })(OpenAssessment);
/** /**
Encapsulate interactions with OpenAssessment XBlock handlers. * Encapsulate interactions with OpenAssessment XBlock handlers.
**/ */
// Since the server is included by both LMS and Studio views, // Since the server is included by both LMS and Studio views,
// skip loading it the second time. // skip loading it the second time.
if (typeof OpenAssessment.Server === "undefined" || !OpenAssessment.Server) { if (typeof OpenAssessment.Server === "undefined" || !OpenAssessment.Server) {
/** /**
Interface for server-side XBlock handlers. * Interface for server-side XBlock handlers.
* @param runtime (Runtime): An XBlock runtime instance.
Args: * @param element (DOM element): The DOM element representing this XBlock.
runtime (Runtime): An XBlock runtime instance. * @constructor
element (DOM element): The DOM element representing this XBlock. */
Returns:
OpenAssessment.Server
**/
OpenAssessment.Server = function(runtime, element) { OpenAssessment.Server = function(runtime, element) {
this.runtime = runtime; this.runtime = runtime;
this.element = element; this.element = element;
...@@ -26,57 +22,45 @@ if (typeof OpenAssessment.Server === "undefined" || !OpenAssessment.Server) { ...@@ -26,57 +22,45 @@ if (typeof OpenAssessment.Server === "undefined" || !OpenAssessment.Server) {
OpenAssessment.Server.prototype = { OpenAssessment.Server.prototype = {
/** /**
Construct the URL for the handler, specific to one instance of the XBlock on the page. * Returns the URL for the handler, specific to one instance of the XBlock on the page.
*
Args: * @param {string} handler The name of the XBlock handler.
handler (string): The name of the XBlock handler. * @returns {*} The URL for the handler.
*/
Returns:
URL (string)
**/
url: function(handler) { url: function(handler) {
return this.runtime.handlerUrl(this.element, handler); return this.runtime.handlerUrl(this.element, handler);
}, },
/** /**
Render the XBlock. * Render the XBlock.
*
Args: * @param {string} component The component to render.
component (string): The component to render. * @returns {*} A JQuery promise, which resolves with the HTML of the rendered XBlock
* and fails with an error message.
Returns: */
A JQuery promise, which resolves with the HTML of the rendered XBlock
and fails with an error message.
Example:
server.render('submission').done(
function(html) { console.log(html); }
).fail(
function(err) { console.log(err); }
)
**/
render: function(component) { render: function(component) {
var view = this; var view = this;
var url = this.url('render_' + component); var url = this.url('render_' + component);
return $.Deferred(function(defer) { return $.Deferred(function(defer) {
$.ajax({ $.ajax({
url: url, url: url,
type: "POST", type: "POST",
dataType: "html" dataType: "html"
}).done(function(data) { })
defer.resolveWith(view, [data]); .done(function(data) {
}).fail(function() { defer.resolveWith(view, [data]);
defer.rejectWith(view, [gettext('This section could not be loaded.')]); })
}); .fail(function() {
defer.rejectWith(view, [gettext('This section could not be loaded.')]);
});
}).promise(); }).promise();
}, },
/** /**
Render Latex for all new DOM elements with class 'allow--latex'. * Render Latex for all new DOM elements with class 'allow--latex'.
*
Args: * @param element The element to modify.
element: The element to modify. */
**/
renderLatex: function(element) { renderLatex: function(element) {
element.filter(".allow--latex").each(function() { element.filter(".allow--latex").each(function() {
MathJax.Hub.Queue(['Typeset', MathJax.Hub, this]); MathJax.Hub.Queue(['Typeset', MathJax.Hub, this]);
...@@ -84,20 +68,12 @@ if (typeof OpenAssessment.Server === "undefined" || !OpenAssessment.Server) { ...@@ -84,20 +68,12 @@ if (typeof OpenAssessment.Server === "undefined" || !OpenAssessment.Server) {
}, },
/** /**
Render the Peer Assessment Section after a complete workflow, in order to * Render the Peer Assessment Section after a complete workflow, in order to
continue grading peers. * continue grading peers.
*
Returns: * @returns {promise} A JQuery promise, which resolves with the HTML of the rendered peer
A JQuery promise, which resolves with the HTML of the rendered peer * assessment section or fails with an error message.
assessment section or fails with an error message. */
Example:
server.render_continued_peer().done(
function(html) { console.log(html); }
).fail(
function(err) { console.log(err); }
)
**/
renderContinuedPeer: function() { renderContinuedPeer: function() {
var view = this; var view = this;
var url = this.url('render_peer_assessment'); var url = this.url('render_peer_assessment');
...@@ -116,16 +92,21 @@ if (typeof OpenAssessment.Server === "undefined" || !OpenAssessment.Server) { ...@@ -116,16 +92,21 @@ if (typeof OpenAssessment.Server === "undefined" || !OpenAssessment.Server) {
}, },
/** /**
Load the Student Info section in Staff Info. * Load the student information section inside the Staff Info section.
**/ *
studentInfo: function(studentUsername) { * @param {string} student_username The username for the student.
* @param {object} options An optional set of configuration options.
* @returns {promise} A JQuery promise, which resolves with the HTML of the rendered section
* fails with an error message.
*/
studentInfo: function(studentUsername, options) {
var url = this.url('render_student_info'); var url = this.url('render_student_info');
return $.Deferred(function(defer) { return $.Deferred(function(defer) {
$.ajax({ $.ajax({
url: url, url: url,
type: "POST", type: "POST",
dataType: "html", dataType: "html",
data: {student_username: studentUsername} data: _.extend({student_username: studentUsername}, options)
}).done(function(data) { }).done(function(data) {
defer.resolveWith(this, [data]); defer.resolveWith(this, [data]);
}).fail(function() { }).fail(function() {
...@@ -135,86 +116,74 @@ if (typeof OpenAssessment.Server === "undefined" || !OpenAssessment.Server) { ...@@ -135,86 +116,74 @@ if (typeof OpenAssessment.Server === "undefined" || !OpenAssessment.Server) {
}, },
/** /**
Send a submission to the XBlock. * Send a submission to the XBlock.
*
Args: * @param {string} submission The text of the student's submission.
submission (string): The text of the student's submission. * @returns {promise} A JQuery promise, which resolves with the student's ID
* and attempt number if the call was successful and fails with a status code
Returns: * and error message otherwise.
A JQuery promise, which resolves with the student's ID and attempt number */
if the call was successful and fails with an status code and error message otherwise.
**/
submit: function(submission) { submit: function(submission) {
var url = this.url('submit'); var url = this.url('submit');
return $.Deferred(function(defer) { return $.Deferred(function(defer) {
$.ajax({ $.ajax({
type: "POST", type: "POST",
url: url, url: url,
data: JSON.stringify({submission: submission}), data: JSON.stringify({submission: submission}),
contentType: jsonContentType contentType: jsonContentType
}).done(function(data) { })
var success = data[0]; .done(function(data) {
if (success) { var success = data[0];
var studentId = data[1]; if (success) {
var attemptNum = data[2]; var studentId = data[1];
defer.resolveWith(this, [studentId, attemptNum]); var attemptNum = data[2];
} defer.resolveWith(this, [studentId, attemptNum]);
else { }
var errorNum = data[1]; else {
var errorMsg = data[2]; var errorNum = data[1];
defer.rejectWith(this, [errorNum, errorMsg]); var errorMsg = data[2];
} defer.rejectWith(this, [errorNum, errorMsg]);
}).fail(function() { }
defer.rejectWith(this, ["AJAX", gettext("This response could not be submitted.")]); })
}); .fail(function() {
defer.rejectWith(this, ["AJAX", gettext("This response could not be submitted.")]);
});
}).promise(); }).promise();
}, },
/** /**
Save a response without submitting it. * Save a response without submitting it.
*
Args: * @param {string} submission The text of the student's response.
submission (string): The text of the student's response. * @returns {promise} A JQuery promise, which resolves with no arguments on success and
* fails with an error message.
Returns: */
A JQuery promise, which resolves with no arguments on success and
fails with an error message.
**/
save: function(submission) { save: function(submission) {
var url = this.url('save_submission'); var url = this.url('save_submission');
return $.Deferred(function(defer) { return $.Deferred(function(defer) {
$.ajax({ $.ajax({
type: "POST", type: "POST",
url: url, url: url,
data: JSON.stringify({submission: submission}), data: JSON.stringify({submission: submission}),
contentType: jsonContentType contentType: jsonContentType
}).done(function(data) { })
if (data.success) { defer.resolve(); } .done(function(data) {
else { defer.rejectWith(this, [data.msg]); } if (data.success) { defer.resolve(); }
}).fail(function() { else { defer.rejectWith(this, [data.msg]); }
defer.rejectWith(this, [gettext("This response could not be saved.")]); })
}); .fail(function() {
defer.rejectWith(this, [gettext("This response could not be saved.")]);
});
}).promise(); }).promise();
}, },
/** /**
* Send feedback on assessments to the XBlock. * Submit feedback on assessments to the XBlock.
* Args:
* text (string): Written feedback from the student.
* options (list of strings): One or more options the student selected.
* *
* Returns: * @param {string} text written feedback from the student.
* A JQuery promise, which resolves with no args if successful and * @param {Array.string} options one or more options the student selected.
* fails with an error message otherwise. * @returns {promise} A JQuery promise, which resolves with no args if successful and
* * fails with an error message otherwise.
* Example:
* server.submit_feedback(
* "Good feedback!", ["I liked the feedback I received"]
* ).done(function() {
* console.log("Success!");
* }).fail(function(errMsg) {
* console.log("Error: " + errMsg);
* });
*/ */
submitFeedbackOnAssessment: function(text, options) { submitFeedbackOnAssessment: function(text, options) {
var url = this.url('submit_feedback'); var url = this.url('submit_feedback');
...@@ -223,150 +192,137 @@ if (typeof OpenAssessment.Server === "undefined" || !OpenAssessment.Server) { ...@@ -223,150 +192,137 @@ if (typeof OpenAssessment.Server === "undefined" || !OpenAssessment.Server) {
'feedback_options': options 'feedback_options': options
}); });
return $.Deferred(function(defer) { return $.Deferred(function(defer) {
$.ajax({type: "POST", url: url, data: payload, contentType: jsonContentType}).done( $.ajax({
function(data) { type: "POST", url: url, data: payload, contentType: jsonContentType
if (data.success) { defer.resolve(); } }).done(function(data) {
else { defer.rejectWith(this, [data.msg]); } if (data.success) {
defer.resolve();
}
else {
defer.rejectWith(this, [data.msg]);
} }
).fail(function() { }).fail(function() {
defer.rejectWith(this, [gettext('This feedback could not be submitted.')]); defer.rejectWith(this, [gettext('This feedback could not be submitted.')]);
}); });
}).promise(); }).promise();
}, },
/** /**
Send a peer assessment to the XBlock. * Submits an assessment.
Args: *
optionsSelected (object literal): Keys are criteria names, * @param {string} assessmentType The type of assessment.
values are the option text the user selected for the criterion. * @param payload The assessment payload
criterionFeedback (object literal): Written feedback on a particular criterion, * @returns {promise} A promise which resolves with no arguments if successful,
where keys are criteria names and values are the feedback strings. * and which fails with an error message otherwise.
overallFeedback (string): Written feedback on the submission as a whole. */
submitAssessment: function(assessmentType, payload) {
Returns: var url = this.url(assessmentType);
A JQuery promise, which resolves with no args if successful
and fails with an error message otherise.
Example:
var options = { clarity: "Very clear", precision: "Somewhat precise" };
var criterionFeedback = { clarity: "The essay was very clear." };
var overallFeedback = "Good job!";
server.peerAssess(options, criterionFeedback, overallFeedback).done(
function() { console.log("Success!"); }
).fail(
function(errorMsg) { console.log(errorMsg); }
);
**/
peerAssess: function(optionsSelected, criterionFeedback, overallFeedback, uuid) {
var url = this.url('peer_assess');
var payload = JSON.stringify({
options_selected: optionsSelected,
criterion_feedback: criterionFeedback,
overall_feedback: overallFeedback,
submission_uuid: uuid
});
return $.Deferred(function(defer) { return $.Deferred(function(defer) {
$.ajax({type: "POST", url: url, data: payload, contentType: jsonContentType}).done( $.ajax({
function(data) { type: "POST", url: url, data: JSON.stringify(payload), contentType: jsonContentType
if (data.success) { }).done(function(data) {
defer.resolve(); if (data.success) {
} defer.resolve();
else {
defer.rejectWith(this, [data.msg]);
}
} }
).fail(function() { else {
defer.rejectWith(this, [data.msg]);
}
}).fail(function() {
defer.rejectWith(this, [gettext('This assessment could not be submitted.')]); defer.rejectWith(this, [gettext('This assessment could not be submitted.')]);
}); });
}).promise(); }).promise();
}, },
/** /**
Send a self-assessment to the XBlock. * Send a peer assessment to the XBlock.
*
Args: * @param optionsSelected The options selected as a dict,
optionsSelected (object literal): Keys are criteria names, * e.g. { clarity: "Very clear", precision: "Somewhat precise" }
values are the option text the user selected for the criterion. * @param criterionFeedback Feedback on the criterion,
var criterionFeedback = { clarity: "The essay was very clear." }; * e.g. { clarity: "The essay was very clear." }
var overallFeedback = "Good job!"; * @param {string} overallFeedback A string with the staff member's overall feedback.
* @param {string} submissionID The ID of the submission being assessed.
Returns: * @returns {promise} A promise which resolves with no arguments if successful,
A JQuery promise, which resolves with no args if successful * and which fails with an error message otherwise.
and fails with an error message otherwise. */
peerAssess: function(optionsSelected, criterionFeedback, overallFeedback, submissionID) {
return this.submitAssessment("peer_assess", {
options_selected: optionsSelected,
criterion_feedback: criterionFeedback,
overall_feedback: overallFeedback,
submission_uuid: submissionID
});
},
Example: /**
var options = { clarity: "Very clear", precision: "Somewhat precise" }; * Send a self assessment to the XBlock.
server.selfAssess(options).done( *
function() { console.log("Success!"); } * @param optionsSelected The options selected as a dict,
).fail( * e.g. { clarity: "Very clear", precision: "Somewhat precise" }
function(errorMsg) { console.log(errorMsg); } * @param criterionFeedback Feedback on the criterion,
); * e.g. { clarity: "The essay was very clear." }
**/ * @param {string} overallFeedback A string with the staff member's overall feedback.
* @returns {promise} A promise which resolves with no arguments if successful,
* and which fails with an error message otherwise.
*/
selfAssess: function(optionsSelected, criterionFeedback, overallFeedback) { selfAssess: function(optionsSelected, criterionFeedback, overallFeedback) {
var url = this.url('self_assess'); return this.submitAssessment("self_assess", {
var payload = JSON.stringify({
options_selected: optionsSelected, options_selected: optionsSelected,
criterion_feedback: criterionFeedback, criterion_feedback: criterionFeedback,
overall_feedback: overallFeedback overall_feedback: overallFeedback
}); });
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 assessment could not be submitted.')]);
});
});
}, },
/** /**
Assess an instructor-provided training example. * Send a staff assessment to the XBlock.
*
Args: * @param optionsSelected The options selected as a dict,
optionsSelected (object literal): Keys are criteria names, * e.g. { clarity: "Very clear", precision: "Somewhat precise" }
values are the option text the user selected for the criterion. * @param criterionFeedback Feedback on the criterion,
* e.g. { clarity: "The essay was very clear." }
Returns: * @param {string} overallFeedback A string with the staff member's overall feedback.
A JQuery promise, which resolves with a list of corrections if * @param {string} submissionID The ID of the submission being assessed.
successful and fails with an error message otherwise. * @returns {promise} A promise which resolves with no arguments if successful,
* and which fails with an error message otherwise.
*/
staffAssess: function(optionsSelected, criterionFeedback, overallFeedback, submissionID) {
return this.submitAssessment("staff_assess", {
options_selected: optionsSelected,
criterion_feedback: criterionFeedback,
overall_feedback: overallFeedback,
submission_uuid: submissionID
});
},
Example: /**
var options = { clarity: "Very clear", precision: "Somewhat precise" }; * Submit an instructor-provided training example.
server.trainingAssess(options).done( *
function(corrections) { console.log("Success!"); } * @param optionsSelected The options selected as a dict,
alert(corrections); * e.g. { clarity: "Very clear", precision: "Somewhat precise" }
).fail( * @returns {promise} A promise which resolves with a list of corrections if successful,
function(errorMsg) { console.log(errorMsg); } * and which fails with an error message otherwise.
); */
**/
trainingAssess: function(optionsSelected) { trainingAssess: function(optionsSelected) {
var url = this.url('training_assess'); var url = this.url('training_assess');
var payload = JSON.stringify({ var payload = JSON.stringify({
options_selected: optionsSelected options_selected: optionsSelected
}); });
return $.Deferred(function(defer) { return $.Deferred(function(defer) {
$.ajax({type: "POST", url: url, data: payload, contentType: jsonContentType}).done( $.ajax({type: "POST", url: url, data: payload, contentType: jsonContentType}).done(function(data) {
function(data) { if (data.success) {
if (data.success) { defer.resolveWith(this, [data.corrections]);
defer.resolveWith(this, [data.corrections]); }
} else {
else { defer.rejectWith(this, [data.msg]);
defer.rejectWith(this, [data.msg]);
}
} }
).fail(function() { }).fail(function() {
defer.rejectWith(this, [gettext('This assessment could not be submitted.')]); defer.rejectWith(this, [gettext('This assessment could not be submitted.')]);
}); });
}); });
}, },
/** /**
<<<<<<< HEAD
Schedules classifier training for Example Based Assessment for this Schedules classifier training for Example Based Assessment for this
Location. Location.
...@@ -385,140 +341,128 @@ if (typeof OpenAssessment.Server === "undefined" || !OpenAssessment.Server) { ...@@ -385,140 +341,128 @@ if (typeof OpenAssessment.Server === "undefined" || !OpenAssessment.Server) {
scheduleTraining: function() { scheduleTraining: function() {
var url = this.url('schedule_training'); var url = this.url('schedule_training');
return $.Deferred(function(defer) { return $.Deferred(function(defer) {
$.ajax({type: "POST", url: url, data: "\"\"", contentType: jsonContentType}).done( $.ajax({type: "POST", url: url, data: "\"\"", contentType: jsonContentType}).done(function(data) {
function(data) { if (data.success) {
if (data.success) { defer.resolveWith(this, [data.msg]);
defer.resolveWith(this, [data.msg]);
}
else {
defer.rejectWith(this, [data.msg]);
}
} }
).fail(function() { else {
defer.rejectWith(this, [data.msg]);
}
}).fail(function() {
defer.rejectWith(this, [gettext('This assessment could not be submitted.')]); defer.rejectWith(this, [gettext('This assessment could not be submitted.')]);
}); });
}); });
}, },
/** /**
Reschedules grading tasks for example based assessments * Reschedules grading tasks for example based assessments
*
Returns: * @returns {promise} a JQuery Promise which will resolve with a message indicating
JQuery Promise which will resolve with a message indicating success or failure of the scheduling * success or failure of the scheduling.
**/ */
rescheduleUnfinishedTasks: function() { rescheduleUnfinishedTasks: function() {
var url = this.url('reschedule_unfinished_tasks'); var url = this.url('reschedule_unfinished_tasks');
return $.Deferred(function(defer) { return $.Deferred(function(defer) {
$.ajax({type: "POST", url: url, data: "\"\"", contentType: jsonContentType}).done( $.ajax(
function(data) { {type: "POST", url: url, data: "\"\"", contentType: jsonContentType}
if (data.success) { ).done(function(data) {
defer.resolveWith(this, [data.msg]); if (data.success) {
} defer.resolveWith(this, [data.msg]);
else { }
defer.rejectWith(this, [data.msg]); else {
} defer.rejectWith(this, [data.msg]);
} }
).fail(function() { }).fail(function() {
defer.rejectWith(this, [gettext('One or more rescheduling tasks failed.')]); defer.rejectWith(this, [gettext('One or more rescheduling tasks failed.')]);
}); });
}); });
}, },
/** /**
Update the XBlock's XML definition on the server. * Update the XBlock's XML definition on the server.
*
Kwargs: * @param options An object with the following options:
title (string): The title of the problem. * title (string): The title of the problem.
prompt (string): The question prompt. * prompt (string): The question prompt.
feedbackPrompt (string): The directions to the student for giving overall feedback on a submission. * feedbackPrompt (string): The directions to the student for giving overall feedback on a submission.
feedback_default_text (string): The default feedback text used as the student's feedback response * feedback_default_text (string): The default feedback text used as the student's feedback response
submissionStart (ISO-formatted datetime string or null): The start date of the submission. * submissionStart (ISO-formatted datetime string or null): The start date of the submission.
submissionDue (ISO-formatted datetime string or null): The date the submission is due. * submissionDue (ISO-formatted datetime string or null): The date the submission is due.
criteria (list of object literals): The rubric criteria. * criteria (list of object literals): The rubric criteria.
assessments (list of object literals): The assessments the student will be evaluated on. * assessments (list of object literals): The assessments the student will be evaluated on.
fileUploadType (string): 'image' if image attachments are allowed, 'pdf-and-image' if pdf and * fileUploadType (string): 'image' if image attachments are allowed, 'pdf-and-image' if pdf and
image attachments are allowed, 'custom' if file type is restricted by a white list. * image attachments are allowed, 'custom' if file type is restricted by a white list.
fileTypeWhiteList (string): Comma separated file type white list * fileTypeWhiteList (string): Comma separated file type white list
latexEnabled: TRUE if latex rendering is enabled. * latexEnabled: TRUE if latex rendering is enabled.
leaderboardNum (int): The number of scores to show in the leaderboard. * leaderboardNum (int): The number of scores to show in the leaderboard.
*
Returns: * @returns {promise} A JQuery promise, which resolves with no arguments
A JQuery promise, which resolves with no arguments * and fails with an error message.
and fails with an error message. */
updateEditorContext: function(options) {
**/
updateEditorContext: function(kwargs) {
var url = this.url('update_editor_context'); var url = this.url('update_editor_context');
var payload = JSON.stringify({ var payload = JSON.stringify({
prompts: kwargs.prompts, prompts: options.prompts,
feedback_prompt: kwargs.feedbackPrompt, feedback_prompt: options.feedbackPrompt,
feedback_default_text: kwargs.feedback_default_text, feedback_default_text: options.feedback_default_text,
title: kwargs.title, title: options.title,
submission_start: kwargs.submissionStart, submission_start: options.submissionStart,
submission_due: kwargs.submissionDue, submission_due: options.submissionDue,
criteria: kwargs.criteria, criteria: options.criteria,
assessments: kwargs.assessments, assessments: options.assessments,
editor_assessments_order: kwargs.editorAssessmentsOrder, editor_assessments_order: options.editorAssessmentsOrder,
file_upload_type: kwargs.fileUploadType, file_upload_type: options.fileUploadType,
white_listed_file_types: kwargs.fileTypeWhiteList, white_listed_file_types: options.fileTypeWhiteList,
allow_latex: kwargs.latexEnabled, allow_latex: options.latexEnabled,
leaderboard_show: kwargs.leaderboardNum leaderboard_show: options.leaderboardNum
}); });
return $.Deferred(function(defer) { return $.Deferred(function(defer) {
$.ajax({ $.ajax({
type: "POST", url: url, data: payload, contentType: jsonContentType type: "POST", url: url, data: payload, contentType: jsonContentType
}).done(function(data) { })
if (data.success) { defer.resolve(); } .done(function(data) {
else { defer.rejectWith(this, [data.msg]); } if (data.success) { defer.resolve(); }
}).fail(function() { else { defer.rejectWith(this, [data.msg]); }
defer.rejectWith(this, [gettext('This problem could not be saved.')]); })
}); .fail(function() {
defer.rejectWith(this, [gettext('This problem could not be saved.')]);
});
}).promise(); }).promise();
}, },
/** /**
Check whether the XBlock has been released. * Check whether the XBlock has been released.
*
Returns: * @returns {promise} A JQuery promise, which resolves with a boolean indicating
A JQuery promise, which resolves with a boolean indicating * whether the XBlock has been released. On failure, the promise provides
whether the XBlock has been released. On failure, the promise * an error message.
provides an error message. */
Example:
server.checkReleased().done(
function(isReleased) {}
).fail(
function(errMsg) {}
)
**/
checkReleased: function() { checkReleased: function() {
var url = this.url('check_released'); var url = this.url('check_released');
var payload = "\"\""; var payload = "\"\"";
return $.Deferred(function(defer) { return $.Deferred(function(defer) {
$.ajax({ $.ajax({
type: "POST", url: url, data: payload, contentType: jsonContentType type: "POST", url: url, data: payload, contentType: jsonContentType
}).done(function(data) { })
if (data.success) { defer.resolveWith(this, [data.is_released]); } .done(function(data) {
else { defer.rejectWith(this, [data.msg]); } if (data.success) { defer.resolveWith(this, [data.is_released]); }
}).fail(function() { else { defer.rejectWith(this, [data.msg]); }
defer.rejectWith(this, [gettext("The server could not be contacted.")]); })
}); .fail(function() {
defer.rejectWith(this, [gettext("The server could not be contacted.")]);
});
}).promise(); }).promise();
}, },
/** /**
Get an upload url used to asynchronously post related files for the * Get an upload URL used to asynchronously post related files for the submission.
submission. *
* @param {string} contentType The Content Type for the file being uploaded.
Args: * @param {string} filename The name of the file to be uploaded.
contentType (str): The Content Type for the file being uploaded. * @returns {promise} A promise which resolves with a presigned upload URL from the
filename (str): The name of the file to be uploaded. * specified service used for uploading files on success, or with an error message
* upon failure.
Returns: */
A presigned upload URL from the specified service used for uploading
files.
**/
getUploadUrl: function(contentType, filename) { getUploadUrl: function(contentType, filename) {
var url = this.url('upload_url'); var url = this.url('upload_url');
return $.Deferred(function(defer) { return $.Deferred(function(defer) {
...@@ -537,12 +481,11 @@ if (typeof OpenAssessment.Server === "undefined" || !OpenAssessment.Server) { ...@@ -537,12 +481,11 @@ if (typeof OpenAssessment.Server === "undefined" || !OpenAssessment.Server) {
}, },
/** /**
Get a download url used to download related files for the submission. * Get a download url used to download related files for the submission.
*
Returns: * @returns {promise} A promise which resolves with a temporary download URL for
A temporary download URL for retrieving documents from s3. * retrieving documents from s3 on success, or with an error message upon failure.
*/
**/
getDownloadUrl: function() { getDownloadUrl: function() {
var url = this.url('download_url'); var url = this.url('download_url');
return $.Deferred(function(defer) { return $.Deferred(function(defer) {
...@@ -558,28 +501,26 @@ if (typeof OpenAssessment.Server === "undefined" || !OpenAssessment.Server) { ...@@ -558,28 +501,26 @@ if (typeof OpenAssessment.Server === "undefined" || !OpenAssessment.Server) {
}, },
/** /**
Cancel the submission from peer grading pool. * Cancel a submission from the peer grading pool.
Args: *
submissionUUID: ID for submission to be cancelled from pool. * @param submissionID The id of the submission to be canceled.
comments: reason to cancel the submission * @param comments The reason for canceling the submission.
         **/ * @returns {*}
cancelSubmission: function(submissionUUID, comments) { */
cancelSubmission: function (submissionID, comments) {
var url = this.url('cancel_submission'); var url = this.url('cancel_submission');
var payload = JSON.stringify({ var payload = JSON.stringify({
submission_uuid: submissionUUID, submission_uuid: submissionID,
comments: comments comments: comments
}); });
return $.Deferred(function(defer) { return $.Deferred(function(defer) {
$.ajax({type: "POST", url: url, data: payload, contentType: jsonContentType}).done( $.ajax({
function(data) { type: "POST", url: url, data: payload, contentType: jsonContentType
if (data.success) { }).done(function(data) {
defer.resolveWith(this, [data.msg]); if (data.success) {
} defer.resolveWith(this, [data.msg]);
else {
defer.rejectWith(this, [data.msg]);
}
} }
).fail(function() { }).fail(function() {
defer.rejectWith(this, [gettext('The submission could not be removed from the grading pool.')]); defer.rejectWith(this, [gettext('The submission could not be removed from the grading pool.')]);
}); });
}).promise(); }).promise();
......
...@@ -35,6 +35,16 @@ ...@@ -35,6 +35,16 @@
.ui-staff__content { .ui-staff__content {
margin-top: 0; margin-top: 0;
} }
.staff-info__cancel-submission__content,
.staff-info__staff-override__content {
padding: 0;
}
}
.ui-toggle-visibility__content {
@include margin-left(($baseline-h/4));
margin-bottom: ($baseline-v/2);
} }
} }
} }
...@@ -44,6 +54,21 @@ ...@@ -44,6 +54,21 @@
color: $copy-staff-color; color: $copy-staff-color;
} }
.ui-staff__subtitle {
@extend %t-subheading;
@extend %t-strong;
@include fontSize($f-size-medium);
// We want to keep the collapsible headers within the staff assessment block blue
// (because they are being displayed in the LMS color scheme). Unfortunately because of
// that we need to add an override just for ui-staff_subtitle collapsible items.
color: $heading-staff-color !important;
margin-bottom: ($baseline-v/2);
span {
font-weight: inherit;
}
}
.staff-info__title__copy { .staff-info__title__copy {
@extend %t-strong; @extend %t-strong;
} }
...@@ -54,10 +79,7 @@ ...@@ -54,10 +79,7 @@
} }
.ui-staff__content__section { .ui-staff__content__section {
padding-bottom: $baseline-v; padding-bottom: ($baseline-v/2);
border-bottom: 1px solid rgba($color-decorative-staff, 0.25);
margin-bottom: $baseline-v;
@extend %wipe-last-child; @extend %wipe-last-child;
} }
...@@ -110,7 +132,7 @@ ...@@ -110,7 +132,7 @@
} }
th, td { th, td {
border: 1px solid rgba($color-decorative-staff, 0.25); border: 1px solid rgba($copy-staff-color, 0.25);
padding: ($baseline-v/2) ($baseline-h/4); padding: ($baseline-v/2) ($baseline-h/4);
} }
...@@ -136,7 +158,7 @@ ...@@ -136,7 +158,7 @@
} }
// UI - cancel submission (action) // UI - cancel submission (action)
.openassessment__staff-info__cancel__submission { .staff-info__workflow-cancellation {
.staff-info__cancel-submission__content { .staff-info__cancel-submission__content {
......
...@@ -110,10 +110,15 @@ $link-hover: $edx-blue-l1 !default; // from our Pattern Library http://ux.edx.or ...@@ -110,10 +110,15 @@ $link-hover: $edx-blue-l1 !default; // from our Pattern Library http://ux.edx.or
@include margin(($baseline-v/2), ($baseline-v/2), ($baseline-v/2), ($baseline-v/2)); @include margin(($baseline-v/2), ($baseline-v/2), ($baseline-v/2), ($baseline-v/2));
} }
} }
.staff-info__student { .staff-info__student {
.label { .label {
color: $heading-staff-color; color: $heading-staff-color;
margin-bottom: ($baseline-v/2); margin-bottom: ($baseline-v/2);
input {
display: block;
}
} }
.action--submit { .action--submit {
...@@ -129,33 +134,115 @@ $link-hover: $edx-blue-l1 !default; // from our Pattern Library http://ux.edx.or ...@@ -129,33 +134,115 @@ $link-hover: $edx-blue-l1 !default; // from our Pattern Library http://ux.edx.or
} }
.title--sub { .title--sub {
@extend %hd-4;
color: $heading-staff-color; color: $heading-staff-color;
margin-top: ($baseline-v/2); margin-top: ($baseline-v/2);
margin-bottom: ($baseline-v/2); margin-bottom: ($baseline-v/2);
} }
.student__answer__display__content { .student__answer__display__content {
border: 1px solid rgba($color-decorative-staff, 0.25); border: 1px solid rgba($copy-staff-color, 0.25);
@include padding(($baseline-v/2), ($baseline-h/2), ($baseline-v/2), ($baseline-h/2)); padding: ($baseline-v/2) ($baseline-h/4);
margin-bottom: ($baseline-v/2); margin-bottom: ($baseline-v/2);
} }
.openassessment__student-info_list { .staff-info__student__report {
list-style-type: none; list-style-type: none;
.title {
@extend %t-strong;
margin-top: ($baseline-v/2);
border-top: 1px solid $heading-staff-color;
padding: ($baseline-v/2) ($baseline-h/2) ($baseline-v/2) 0;
span {
font-weight: inherit;
}
}
}
.staff-info__cancel-submission__content,
.staff-info__staff-override__content {
padding: $baseline-v ($baseline-h/2);
background-color: white;
} }
.value { .value {
width: $max-width/2; width: $max-width/2;
} }
/** // staff assessments
* The follow styles are bound for the "shame" file. This is done to override .wrapper--staff-assessment {
* LMS specific styles on HTML elements. margin-top: ($baseline-v/2);
*/ padding-top: ($baseline-v/2);
border-top: 1px solid $color-decorative-tertiary;
.action--submit {
@extend .action--submit;
}
}
.staff-assessment__display {
@extend %ui-subsection;
}
.staff-assessment__display__header {
@include clearfix();
span {
@extend %t-strong; // FIX: needed due to DOM structure
}
.staff-assessment__display__title {
@extend %t-heading;
margin-bottom: ($baseline-v/2);
color: $heading-secondary-color;
}
}
.staff-assessment__display__response {
@extend %ui-subsection-content;
@extend %copy-3;
@extend %ui-content-longanswer;
@extend %ui-well;
color: $copy-color;
}
// assessment form
.staff-assessment__assessment {
// fields
.assessment__fields {
margin-bottom: $baseline-v;
}
// rubric question
.assessment__rubric__question {
@extend %ui-rubric-question;
}
// 'p' elements in LMS have a color set on them. // rubric options
.student__answer__display__content p { .question__answers {
color: inherit; @extend %ui-rubric-answers;
}
// general feedback question
.assessment__rubric__question--feedback {
.wrapper--input {
margin-top: $baseline-v;
}
.question__title__copy {
@include margin-left(0);
white-space: pre-wrap;
}
textarea {
@extend %ui-content-longanswer;
min-height: ($baseline-v*5);
}
}
} }
} }
...@@ -1144,6 +1231,8 @@ $link-hover: $edx-blue-l1 !default; // from our Pattern Library http://ux.edx.or ...@@ -1144,6 +1231,8 @@ $link-hover: $edx-blue-l1 !default; // from our Pattern Library http://ux.edx.or
.self-assessment__display__title, .self-assessment__display__title,
.peer-assessment__display__header .peer-assessment__display__header
.peer-assessment__display__title, .peer-assessment__display__title,
.staff-assessment__display__header
.staff-assessment__display__title,
.submission__answer__display .submission__answer__display
.submission__answer__display__title{ .submission__answer__display__title{
margin: 10px 0; margin: 10px 0;
...@@ -1152,6 +1241,7 @@ $link-hover: $edx-blue-l1 !default; // from our Pattern Library http://ux.edx.or ...@@ -1152,6 +1241,7 @@ $link-hover: $edx-blue-l1 !default; // from our Pattern Library http://ux.edx.or
.self-assessment__display__image, .self-assessment__display__image,
.peer-assessment__display__image, .peer-assessment__display__image,
.staff-assessment__display__image,
.submission__answer__display__image{ .submission__answer__display__image{
@extend .submission__answer__display__content; @extend .submission__answer__display__content;
max-height: 400px; max-height: 400px;
......
...@@ -193,7 +193,14 @@ ...@@ -193,7 +193,14 @@
color: $copy-staff-color !important; color: $copy-staff-color !important;
} }
.openassessment__staff-info__cancel__submission { .staff-info__workflow-cancellation {
margin-bottom: ($baseline-v) !important; margin-bottom: ($baseline-v) !important;
} }
} }
.staff-info__student {
// 'p' elements in LMS have a color set on them.
.student__answer__display__content p {
color: inherit;
}
}
...@@ -6,7 +6,6 @@ import urllib ...@@ -6,7 +6,6 @@ import urllib
from mock import Mock, patch from mock import Mock, patch
from django.test.utils import override_settings from django.test.utils import override_settings
import ddt
from openassessment.assessment.api import peer as peer_api from openassessment.assessment.api import peer as peer_api
from openassessment.assessment.api import self as self_api from openassessment.assessment.api import self as self_api
from openassessment.assessment.api import ai as ai_api from openassessment.assessment.api import ai as ai_api
...@@ -17,7 +16,6 @@ from submissions import api as sub_api ...@@ -17,7 +16,6 @@ from submissions import api as sub_api
from openassessment.xblock.data_conversion import prepare_submission_for_serialization from openassessment.xblock.data_conversion import prepare_submission_for_serialization
from openassessment.xblock.test.base import scenario, XBlockHandlerTestCase from openassessment.xblock.test.base import scenario, XBlockHandlerTestCase
from xblock.core import XBlock
ALGORITHM_ID = 'fake' ALGORITHM_ID = 'fake'
...@@ -182,18 +180,14 @@ class TestCourseStaff(XBlockHandlerTestCase): ...@@ -182,18 +180,14 @@ class TestCourseStaff(XBlockHandlerTestCase):
bob_item = STUDENT_ITEM.copy() bob_item = STUDENT_ITEM.copy()
bob_item["item_id"] = xblock.scope_ids.usage_id bob_item["item_id"] = xblock.scope_ids.usage_id
# Create a submission for Bob, and corresponding workflow. # Create a submission for Bob, and corresponding workflow.
submission = sub_api.create_submission( submission = self._create_submission(
bob_item, prepare_submission_for_serialization(("Bob Answer 1", "Bob Answer 2")) bob_item, prepare_submission_for_serialization(("Bob Answer 1", "Bob Answer 2")), ['peer']
) )
peer_api.on_start(submission["uuid"])
workflow_api.create_workflow(submission["uuid"], ['peer'])
# Create a submission for Tim, and corresponding workflow. # Create a submission for Tim, and corresponding workflow.
tim_item = bob_item.copy() tim_item = bob_item.copy()
tim_item["student_id"] = "Tim" tim_item["student_id"] = "Tim"
tim_sub = sub_api.create_submission(tim_item, "Tim Answer") self._create_submission(tim_item, "Tim Answer", ['peer', 'self'])
peer_api.on_start(tim_sub["uuid"])
workflow_api.create_workflow(tim_sub["uuid"], ['peer', 'self'])
# Bob assesses Tim. # Bob assesses Tim.
peer_api.get_submission_to_assess(submission['uuid'], 1) peer_api.get_submission_to_assess(submission['uuid'], 1)
...@@ -209,7 +203,7 @@ class TestCourseStaff(XBlockHandlerTestCase): ...@@ -209,7 +203,7 @@ class TestCourseStaff(XBlockHandlerTestCase):
path, context = xblock.get_student_info_path_and_context("Bob") path, context = xblock.get_student_info_path_and_context("Bob")
self.assertEquals("Bob Answer 1", context['submission']['answer']['parts'][0]['text']) self.assertEquals("Bob Answer 1", context['submission']['answer']['parts'][0]['text'])
self.assertIsNone(context['self_assessment']) self.assertIsNone(context['self_assessment'])
self.assertEquals("openassessmentblock/staff_area/student_info.html", path) self.assertEquals("openassessmentblock/staff_area/oa_student_info.html", path)
@scenario('data/self_only_scenario.xml', user_id='Bob') @scenario('data/self_only_scenario.xml', user_id='Bob')
def test_staff_area_student_info_self_only(self, xblock): def test_staff_area_student_info_self_only(self, xblock):
...@@ -221,11 +215,9 @@ class TestCourseStaff(XBlockHandlerTestCase): ...@@ -221,11 +215,9 @@ class TestCourseStaff(XBlockHandlerTestCase):
bob_item = STUDENT_ITEM.copy() bob_item = STUDENT_ITEM.copy()
bob_item["item_id"] = xblock.scope_ids.usage_id bob_item["item_id"] = xblock.scope_ids.usage_id
# Create a submission for Bob, and corresponding workflow. # Create a submission for Bob, and corresponding workflow.
submission = sub_api.create_submission( submission = self._create_submission(
bob_item, prepare_submission_for_serialization(("Bob Answer 1", "Bob Answer 2")) bob_item, prepare_submission_for_serialization(("Bob Answer 1", "Bob Answer 2")), ['self']
) )
peer_api.on_start(submission["uuid"])
workflow_api.create_workflow(submission["uuid"], ['self'])
# Bob assesses himself. # Bob assesses himself.
self_api.create_assessment( self_api.create_assessment(
...@@ -240,7 +232,7 @@ class TestCourseStaff(XBlockHandlerTestCase): ...@@ -240,7 +232,7 @@ class TestCourseStaff(XBlockHandlerTestCase):
path, context = xblock.get_student_info_path_and_context("Bob") path, context = xblock.get_student_info_path_and_context("Bob")
self.assertEquals("Bob Answer 1", context['submission']['answer']['parts'][0]['text']) self.assertEquals("Bob Answer 1", context['submission']['answer']['parts'][0]['text'])
self.assertEquals([], context['peer_assessments']) self.assertEquals([], context['peer_assessments'])
self.assertEquals("openassessmentblock/staff_area/student_info.html", path) self.assertEquals("openassessmentblock/staff_area/oa_student_info.html", path)
@scenario('data/basic_scenario.xml', user_id='Bob') @scenario('data/basic_scenario.xml', user_id='Bob')
def test_staff_area_student_info_with_cancelled_submission(self, xblock): def test_staff_area_student_info_with_cancelled_submission(self, xblock):
...@@ -260,11 +252,9 @@ class TestCourseStaff(XBlockHandlerTestCase): ...@@ -260,11 +252,9 @@ class TestCourseStaff(XBlockHandlerTestCase):
bob_item = STUDENT_ITEM.copy() bob_item = STUDENT_ITEM.copy()
bob_item["item_id"] = xblock.scope_ids.usage_id bob_item["item_id"] = xblock.scope_ids.usage_id
# Create a submission for Bob, and corresponding workflow. # Create a submission for Bob, and corresponding workflow.
submission = sub_api.create_submission( submission = self._create_submission(
bob_item, prepare_submission_for_serialization(("Bob Answer 1", "Bob Answer 2")) bob_item, prepare_submission_for_serialization(("Bob Answer 1", "Bob Answer 2")), ['peer']
) )
peer_api.on_start(submission["uuid"])
workflow_api.create_workflow(submission["uuid"], ['peer'])
workflow_api.cancel_workflow( workflow_api.cancel_workflow(
submission_uuid=submission["uuid"], submission_uuid=submission["uuid"],
...@@ -276,7 +266,7 @@ class TestCourseStaff(XBlockHandlerTestCase): ...@@ -276,7 +266,7 @@ class TestCourseStaff(XBlockHandlerTestCase):
path, context = xblock.get_student_info_path_and_context("Bob") path, context = xblock.get_student_info_path_and_context("Bob")
self.assertEquals("Bob Answer 1", context['submission']['answer']['parts'][0]['text']) self.assertEquals("Bob Answer 1", context['submission']['answer']['parts'][0]['text'])
self.assertIsNotNone(context['workflow_cancellation']) self.assertIsNotNone(context['workflow_cancellation'])
self.assertEquals("openassessmentblock/staff_area/student_info.html", path) self.assertEquals("openassessmentblock/staff_area/oa_student_info.html", path)
@scenario('data/basic_scenario.xml', user_id='Bob') @scenario('data/basic_scenario.xml', user_id='Bob')
def test_cancelled_submission_peer_assessment_render_path(self, xblock): def test_cancelled_submission_peer_assessment_render_path(self, xblock):
...@@ -289,9 +279,7 @@ class TestCourseStaff(XBlockHandlerTestCase): ...@@ -289,9 +279,7 @@ class TestCourseStaff(XBlockHandlerTestCase):
bob_item = STUDENT_ITEM.copy() bob_item = STUDENT_ITEM.copy()
bob_item["item_id"] = xblock.scope_ids.usage_id bob_item["item_id"] = xblock.scope_ids.usage_id
# Create a submission for Bob, and corresponding workflow. # Create a submission for Bob, and corresponding workflow.
submission = sub_api.create_submission(bob_item, {'text': "Bob Answer"}) submission = self._create_submission(bob_item, {'text': "Bob Answer"}, ['peer'])
peer_api.on_start(submission["uuid"])
workflow_api.create_workflow(submission["uuid"], ['peer'])
requirements = { requirements = {
"peer": { "peer": {
...@@ -322,11 +310,11 @@ class TestCourseStaff(XBlockHandlerTestCase): ...@@ -322,11 +310,11 @@ class TestCourseStaff(XBlockHandlerTestCase):
bob_item = STUDENT_ITEM.copy() bob_item = STUDENT_ITEM.copy()
bob_item["item_id"] = xblock.scope_ids.usage_id bob_item["item_id"] = xblock.scope_ids.usage_id
# Create an image submission for Bob # Create an image submission for Bob, and corresponding workflow.
sub_api.create_submission(bob_item, { self._create_submission(bob_item, {
'text': "Bob Answer", 'text': "Bob Answer",
'file_key': "test_key" 'file_key': "test_key"
}) }, ['self'])
# Mock the file upload API to avoid hitting S3 # Mock the file upload API to avoid hitting S3
with patch("openassessment.xblock.staff_area_mixin.file_api") as file_api: with patch("openassessment.xblock.staff_area_mixin.file_api") as file_api:
...@@ -355,11 +343,11 @@ class TestCourseStaff(XBlockHandlerTestCase): ...@@ -355,11 +343,11 @@ class TestCourseStaff(XBlockHandlerTestCase):
bob_item = STUDENT_ITEM.copy() bob_item = STUDENT_ITEM.copy()
bob_item["item_id"] = xblock.scope_ids.usage_id bob_item["item_id"] = xblock.scope_ids.usage_id
# Create an image submission for Bob # Create an image submission for Bob, and corresponding workflow.
sub_api.create_submission(bob_item, { self._create_submission(bob_item, {
'text': "Bob Answer", 'text': "Bob Answer",
'file_key': "test_key" 'file_key': "test_key"
}) }, ['self'])
# Mock the file upload API to simulate an error # Mock the file upload API to simulate an error
with patch("openassessment.xblock.staff_area_mixin.file_api.get_download_url") as file_api_call: with patch("openassessment.xblock.staff_area_mixin.file_api.get_download_url") as file_api_call:
...@@ -401,16 +389,12 @@ class TestCourseStaff(XBlockHandlerTestCase): ...@@ -401,16 +389,12 @@ class TestCourseStaff(XBlockHandlerTestCase):
bob_item["item_id"] = xblock.scope_ids.usage_id bob_item["item_id"] = xblock.scope_ids.usage_id
# Create a submission for Bob, and corresponding workflow. # Create a submission for Bob, and corresponding workflow.
submission = sub_api.create_submission(bob_item, {'text': "Bob Answer"}) submission = self._create_submission(bob_item, {'text': "Bob Answer"}, ['peer', 'self'])
peer_api.on_start(submission["uuid"])
workflow_api.create_workflow(submission["uuid"], ['peer', 'self'])
# Create a submission for Tim, and corresponding workflow. # Create a submission for Tim, and corresponding workflow.
tim_item = bob_item.copy() tim_item = bob_item.copy()
tim_item["student_id"] = "Tim" tim_item["student_id"] = "Tim"
tim_sub = sub_api.create_submission(tim_item, "Tim Answer") self._create_submission(tim_item, "Tim Answer", ['peer', 'self'])
peer_api.on_start(tim_sub["uuid"])
workflow_api.create_workflow(tim_sub["uuid"], ['peer', 'self'])
# Bob assesses Tim. # Bob assesses Tim.
peer_api.get_submission_to_assess(submission['uuid'], 1) peer_api.get_submission_to_assess(submission['uuid'], 1)
...@@ -445,7 +429,7 @@ class TestCourseStaff(XBlockHandlerTestCase): ...@@ -445,7 +429,7 @@ class TestCourseStaff(XBlockHandlerTestCase):
xblock.scope_ids.usage_id, True, True, "Bob" xblock.scope_ids.usage_id, True, True, "Bob"
) )
path, context = xblock.get_staff_path_and_context() path, context = xblock.get_staff_path_and_context()
self.assertEquals('openassessmentblock/staff_area/staff_area.html', path) self.assertEquals('openassessmentblock/staff_area/oa_staff_area.html', path)
self.assertTrue(context['display_schedule_training']) self.assertTrue(context['display_schedule_training'])
@override_settings(ORA2_AI_ALGORITHMS=AI_ALGORITHMS) @override_settings(ORA2_AI_ALGORITHMS=AI_ALGORITHMS)
...@@ -466,7 +450,7 @@ class TestCourseStaff(XBlockHandlerTestCase): ...@@ -466,7 +450,7 @@ class TestCourseStaff(XBlockHandlerTestCase):
xblock.scope_ids.usage_id, True, False, "Bob" xblock.scope_ids.usage_id, True, False, "Bob"
) )
path, context = xblock.get_staff_path_and_context() path, context = xblock.get_staff_path_and_context()
self.assertEquals('openassessmentblock/staff_area/staff_area.html', path) self.assertEquals('openassessmentblock/staff_area/oa_staff_area.html', path)
self.assertFalse(context['display_schedule_training']) self.assertFalse(context['display_schedule_training'])
@scenario('data/basic_scenario.xml', user_id='Bob') @scenario('data/basic_scenario.xml', user_id='Bob')
...@@ -495,7 +479,7 @@ class TestCourseStaff(XBlockHandlerTestCase): ...@@ -495,7 +479,7 @@ class TestCourseStaff(XBlockHandlerTestCase):
xblock.scope_ids.usage_id, True, True, "Bob" xblock.scope_ids.usage_id, True, True, "Bob"
) )
path, context = xblock.get_staff_path_and_context() path, context = xblock.get_staff_path_and_context()
self.assertEquals('openassessmentblock/staff_area/staff_area.html', path) self.assertEquals('openassessmentblock/staff_area/oa_staff_area.html', path)
self.assertTrue(context['display_reschedule_unfinished_tasks']) self.assertTrue(context['display_reschedule_unfinished_tasks'])
@scenario('data/example_based_assessment.xml', user_id='Bob') @scenario('data/example_based_assessment.xml', user_id='Bob')
...@@ -609,9 +593,7 @@ class TestCourseStaff(XBlockHandlerTestCase): ...@@ -609,9 +593,7 @@ class TestCourseStaff(XBlockHandlerTestCase):
bob_item = STUDENT_ITEM.copy() bob_item = STUDENT_ITEM.copy()
bob_item["item_id"] = xblock.scope_ids.usage_id bob_item["item_id"] = xblock.scope_ids.usage_id
# Create a submission for Bob, and corresponding workflow. # Create a submission for Bob, and corresponding workflow.
submission = sub_api.create_submission(bob_item, {'text': "Bob Answer"}) submission = self._create_submission(bob_item, {'text': "Bob Answer"}, ['peer'])
peer_api.on_start(submission["uuid"])
workflow_api.create_workflow(submission["uuid"], ['peer'])
incorrect_submission_uuid = 'abc' incorrect_submission_uuid = 'abc'
params = {"submission_uuid": incorrect_submission_uuid, "comments": "Inappropriate language."} params = {"submission_uuid": incorrect_submission_uuid, "comments": "Inappropriate language."}
...@@ -648,3 +630,11 @@ class TestCourseStaff(XBlockHandlerTestCase): ...@@ -648,3 +630,11 @@ class TestCourseStaff(XBlockHandlerTestCase):
) )
) )
return mock_runtime return mock_runtime
def _create_submission(self, item, values, types):
""" Create a submission and corresponding workflow. """
submission = sub_api.create_submission(item, values)
peer_api.on_start(submission["uuid"])
workflow_api.create_workflow(submission["uuid"], types)
return submission
...@@ -110,21 +110,27 @@ class WorkflowMixin(object): ...@@ -110,21 +110,27 @@ class WorkflowMixin(object):
requirements = self.workflow_requirements() requirements = self.workflow_requirements()
workflow_api.update_from_assessments(submission_uuid, requirements) workflow_api.update_from_assessments(submission_uuid, requirements)
def get_workflow_info(self): def get_workflow_info(self, submission_uuid=None):
""" """
Retrieve a description of the student's progress in a workflow. Retrieve a description of the student's progress in a workflow.
Note that this *may* update the workflow status if it's changed. Note that this *may* update the workflow status if it's changed.
Keyword Arguments:
submission_uuid (str): The submission associated with the workflow to return.
Defaults to the submission created by the current student.
Returns: Returns:
dict dict
Raises: Raises:
AssessmentWorkflowError AssessmentWorkflowError
""" """
if not self.submission_uuid: if not submission_uuid:
return {} submission_uuid = self.submission_uuid
if not submission_uuid:
return {}
return workflow_api.get_workflow_for_submission( return workflow_api.get_workflow_for_submission(
self.submission_uuid, self.workflow_requirements() submission_uuid, self.workflow_requirements()
) )
def get_workflow_status_counts(self): def get_workflow_status_counts(self):
......
...@@ -25,7 +25,6 @@ class OpenAssessmentA11yTest(OpenAssessmentTest): ...@@ -25,7 +25,6 @@ class OpenAssessmentA11yTest(OpenAssessmentTest):
) )
page.a11y_audit.config.set_rules({ page.a11y_audit.config.set_rules({
"ignore": [ "ignore": [
"aria-valid-attr", # TODO: AC-199
"color-contrast", # TODO: AC-198 "color-contrast", # TODO: AC-198
"empty-heading", # TODO: AC-197 "empty-heading", # TODO: AC-197
"link-href", # TODO: AC-199 "link-href", # TODO: AC-199
...@@ -77,12 +76,14 @@ class StudentTrainingA11yTest(OpenAssessmentA11yTest): ...@@ -77,12 +76,14 @@ class StudentTrainingA11yTest(OpenAssessmentA11yTest):
class StaffAreaA11yTest(OpenAssessmentA11yTest): class StaffAreaA11yTest(OpenAssessmentA11yTest):
""" """
Test the accessibility of the staff area. Test the accessibility of the staff area.
This is testing a problem with "self assessment only".
""" """
def setUp(self): def setUp(self):
super(StaffAreaA11yTest, self).setUp('peer_only', staff=True) super(StaffAreaA11yTest, self).setUp('self_only', staff=True)
self.staff_area_page = StaffAreaPage(self.browser, self.problem_loc) self.staff_area_page = StaffAreaPage(self.browser, self.problem_loc)
def test_staff_tools_panel_a11y(self): def test_staff_tools_panel(self):
""" """
Check the accessibility of the "Staff Tools" panel Check the accessibility of the "Staff Tools" panel
""" """
...@@ -90,7 +91,7 @@ class StaffAreaA11yTest(OpenAssessmentA11yTest): ...@@ -90,7 +91,7 @@ class StaffAreaA11yTest(OpenAssessmentA11yTest):
self.staff_area_page.click_staff_toolbar_button("staff-tools") self.staff_area_page.click_staff_toolbar_button("staff-tools")
self._check_a11y(self.staff_area_page) self._check_a11y(self.staff_area_page)
def test_staff_info_panel_a11y(self): def test_staff_info_panel(self):
""" """
Check the accessibility of the "Staff Info" panel Check the accessibility of the "Staff Info" panel
""" """
...@@ -98,6 +99,20 @@ class StaffAreaA11yTest(OpenAssessmentA11yTest): ...@@ -98,6 +99,20 @@ class StaffAreaA11yTest(OpenAssessmentA11yTest):
self.staff_area_page.click_staff_toolbar_button("staff-info") self.staff_area_page.click_staff_toolbar_button("staff-info")
self._check_a11y(self.staff_area_page) self._check_a11y(self.staff_area_page)
def test_learner_info(self):
"""
Check the accessibility of the learner information sections of the "Staff Tools" panel.
"""
# Create an assessment for a user.
username = self.do_self_assessment()
self.staff_area_page.visit()
# Click on staff tools and search for the user.
self.staff_area_page.show_learner(username)
self._check_a11y(self.staff_area_page)
if __name__ == "__main__": if __name__ == "__main__":
......
...@@ -79,3 +79,14 @@ class AutoAuthPage(PageObject): ...@@ -79,3 +79,14 @@ class AutoAuthPage(PageObject):
message = self.q(css='BODY').text[0].strip() message = self.q(css='BODY').text[0].strip()
match = re.search(r' user_id ([^$]+)$', message) match = re.search(r' user_id ([^$]+)$', message)
return match.groups()[0] if match else None return match.groups()[0] if match else None
def get_username(self):
"""
Finds and returns the username
"""
message = self.q(css='BODY').text[0].strip()
match = re.search(r'Logged in user ([^$]+) with password ([^$]+) and user_id ([^$]+)$', message)
if not match:
return None
username_and_email = match.groups()[0]
return username_and_email.split(' ')[0]
...@@ -343,11 +343,11 @@ class GradePage(OpenAssessmentPage): ...@@ -343,11 +343,11 @@ class GradePage(OpenAssessmentPage):
class StaffAreaPage(OpenAssessmentPage): class StaffAreaPage(OpenAssessmentPage):
""" """
Page object representing the "submission" step in an ORA problem. Page object representing the tabbed staff area.
""" """
def is_browser_on_page(self): def is_browser_on_page(self):
return self.q(css="#openassessment__staff-area").is_present() return self.q(css=".openassessment__staff-area").is_present()
@property @property
def selected_button_names(self): def selected_button_names(self):
...@@ -360,10 +360,10 @@ class StaffAreaPage(OpenAssessmentPage): ...@@ -360,10 +360,10 @@ class StaffAreaPage(OpenAssessmentPage):
@property @property
def visible_staff_panels(self): def visible_staff_panels(self):
""" """
Returns the ids of the visible staff panels Returns the classes of the visible staff panels
""" """
panels = self.q(css=".wrapper--ui-staff") panels = self.q(css=".wrapper--ui-staff")
return [panel.get_attribute('id') for panel in panels if u'is--hidden' not in panel.get_attribute('class')] return [panel.get_attribute('class') for panel in panels if u'is--hidden' not in panel.get_attribute('class')]
def click_staff_toolbar_button(self, button_name): def click_staff_toolbar_button(self, button_name):
""" """
...@@ -379,3 +379,29 @@ class StaffAreaPage(OpenAssessmentPage): ...@@ -379,3 +379,29 @@ class StaffAreaPage(OpenAssessmentPage):
:return: :return:
""" """
self.q(css=".wrapper--{panel_name} .ui-staff_close_button".format(panel_name=panel_name)).click() self.q(css=".wrapper--{panel_name} .ui-staff_close_button".format(panel_name=panel_name)).click()
def show_learner(self, username):
"""
Clicks the staff tools panel and and searches for learner information about the given username.
"""
self.click_staff_toolbar_button("staff-tools")
self.wait_for_element_visibility("input.openassessment__student_username", "Input is present")
self.q(css="input.openassessment__student_username").fill(username)
submit_button = self.q(css=".action--submit-username")
submit_button.first.click()
self.wait_for_element_visibility(".staff-info__student__report", "Student report is present")
@property
def learner_report_text(self):
"""
Returns the text present in the learner report (useful for case where there is no response).
"""
return self.q(css=".staff-info__student__report").text[0]
@property
def learner_report_sections(self):
"""
Returns the titles of the collapsible learner report sections present on the page.
"""
sections = self.q(css=".ui-staff__subtitle")
return [section.text for section in sections]
...@@ -90,6 +90,27 @@ class OpenAssessmentTest(WebAppTest): ...@@ -90,6 +90,27 @@ class OpenAssessmentTest(WebAppTest):
self.student_training_page = AssessmentPage('student-training', self.browser, self.problem_loc) self.student_training_page = AssessmentPage('student-training', self.browser, self.problem_loc)
self.grade_page = GradePage(self.browser, self.problem_loc) self.grade_page = GradePage(self.browser, self.problem_loc)
def do_self_assessment(self):
"""
Submits a self assessment, verifies the grade, and returns the username of the student
for which the self assessment was submitted.
"""
self.auto_auth_page.visit()
username = self.auto_auth_page.get_username()
self.submission_page.visit().submit_response(self.SUBMISSION)
self.assertTrue(self.submission_page.has_submitted)
# Submit a self-assessment
self.self_asmnt_page.wait_for_page().wait_for_response()
self.assertIn(self.SUBMISSION, self.self_asmnt_page.response_text)
self.self_asmnt_page.assess(self.OPTIONS_SELECTED).wait_for_complete()
self.assertTrue(self.self_asmnt_page.is_complete)
# Verify the grade
self.assertEqual(self.grade_page.wait_for_page().score, self.EXPECTED_SCORE)
return username
class SelfAssessmentTest(OpenAssessmentTest): class SelfAssessmentTest(OpenAssessmentTest):
""" """
...@@ -103,18 +124,7 @@ class SelfAssessmentTest(OpenAssessmentTest): ...@@ -103,18 +124,7 @@ class SelfAssessmentTest(OpenAssessmentTest):
@attr('acceptance') @attr('acceptance')
def test_self_assessment(self): def test_self_assessment(self):
# Submit a response # Submit a response
self.auto_auth_page.visit() self.do_self_assessment()
self.submission_page.visit().submit_response(self.SUBMISSION)
self.assertTrue(self.submission_page.has_submitted)
# Submit a self-assessment
self.self_asmnt_page.wait_for_page().wait_for_response()
self.assertIn(self.SUBMISSION, self.self_asmnt_page.response_text)
self.self_asmnt_page.assess(self.OPTIONS_SELECTED).wait_for_complete()
self.assertTrue(self.self_asmnt_page.is_complete)
# Verify the grade
self.assertEqual(self.grade_page.wait_for_page().score, self.EXPECTED_SCORE)
# Check browser scrolled back to top of assessment # Check browser scrolled back to top of assessment
self.assertTrue(self.self_asmnt_page.is_on_top) self.assertTrue(self.self_asmnt_page.is_on_top)
...@@ -216,10 +226,12 @@ class StudentTrainingTest(OpenAssessmentTest): ...@@ -216,10 +226,12 @@ class StudentTrainingTest(OpenAssessmentTest):
class StaffAreaTest(OpenAssessmentTest): class StaffAreaTest(OpenAssessmentTest):
""" """
Test the staff area. Test the staff area.
This is testing a problem with "self assessment only".
""" """
def setUp(self): def setUp(self):
super(StaffAreaTest, self).setUp('peer_only', staff=True) super(StaffAreaTest, self).setUp('self_only', staff=True)
self.staff_area_page = StaffAreaPage(self.browser, self.problem_loc) self.staff_area_page = StaffAreaPage(self.browser, self.problem_loc)
@retry() @retry()
...@@ -275,9 +287,9 @@ class StaffAreaTest(OpenAssessmentTest): ...@@ -275,9 +287,9 @@ class StaffAreaTest(OpenAssessmentTest):
# Click on the button and verify that the panel has opened # Click on the button and verify that the panel has opened
self.staff_area_page.click_staff_toolbar_button(panel_name) self.staff_area_page.click_staff_toolbar_button(panel_name)
self.assertEqual(self.staff_area_page.selected_button_names, [button_label]) self.assertEqual(self.staff_area_page.selected_button_names, [button_label])
self.assertEqual( self.assertIn(
self.staff_area_page.visible_staff_panels, u'openassessment__{button_name}'.format(button_name=panel_name),
[u'openassessment__{button_name}'.format(button_name=panel_name)] self.staff_area_page.visible_staff_panels[0]
) )
# Click 'Close' and verify that the panel has been closed # Click 'Close' and verify that the panel has been closed
...@@ -285,6 +297,51 @@ class StaffAreaTest(OpenAssessmentTest): ...@@ -285,6 +297,51 @@ class StaffAreaTest(OpenAssessmentTest):
self.assertEqual(self.staff_area_page.selected_button_names, []) self.assertEqual(self.staff_area_page.selected_button_names, [])
self.assertEqual(self.staff_area_page.visible_staff_panels, []) self.assertEqual(self.staff_area_page.visible_staff_panels, [])
@retry()
@attr('acceptance')
def test_student_info(self):
"""
Scenario: staff tools shows learner response information
Given I am viewing the staff area of an ORA problem
When I search for a learner in staff tools
And the learner has submitted a response to an ORA problem with self-assessment
Then I see the correct learner information sections
"""
username = self.do_self_assessment()
self.staff_area_page.visit()
# Click on staff tools and search for user
self.staff_area_page.show_learner(username)
self.assertNotIn('A response was not found for this learner', self.staff_area_page.learner_report_text)
self.assertEqual(
[u'Learner Response', u"Learner's Self Assessment", u"Learner's Final Grade"],
self.staff_area_page.learner_report_sections
)
@retry()
@attr('acceptance')
def test_student_info_no_submission(self):
"""
Scenario: staff tools indicates if no submission has been received for a given learner
Given I am viewing the staff area of an ORA problem
When I search for a learner in staff tools
And the learner has not submitted a response to the ORA problem
Then I see a message indicating that the learner has not submitted a response
And there are no student information sections displayed
"""
self.auto_auth_page.visit()
self.staff_area_page.visit()
# Click on staff tools and search for user
self.staff_area_page.show_learner('no-submission-learner')
self.assertIn('A response was not found for this learner', self.staff_area_page.learner_report_text)
self.assertEqual([], self.staff_area_page.learner_report_sections)
class FileUploadTest(OpenAssessmentTest): class FileUploadTest(OpenAssessmentTest):
""" """
......
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