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,47 +12,48 @@ ...@@ -10,47 +12,48 @@
</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 %}
{% blocktrans with removed_by_username=workflow_cancellation.cancelled_by removed_datetime=workflow_cancellation.created_at|utc|date:"N j, Y H:i e" %} <p>
{% 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-->
<p>
{% blocktrans with comments=workflow_cancellation.comments %} {% blocktrans with comments=workflow_cancellation.comments %}
Comments: {{ comments }} Comments: {{ comments }}
{% endblocktrans %} {% endblocktrans %}
</p>
{% else %} {% else %}
{% include "openassessmentblock/oa_submission_answer.html" with answer=submission.answer answer_text_label="The learner's response to the question above:" %} {% include "openassessmentblock/oa_submission_answer.html" with answer=submission.answer answer_text_label="The learner's response to the question above:" %}
{% endif %} {% endif %}
</div>
{% 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">
{% if peer_assessments %}
{% for assessment in peer_assessments %} {% for assessment in peer_assessments %}
{% with peer_num=forloop.counter %} {% with peer_num=forloop.counter %}
<h4 class="title--sub"> {% trans "Peer" %} {{ peer_num }}: </h4> <h4 class="title--sub"> {% trans "Peer" %} {{ peer_num }}: </h4>
...@@ -59,7 +62,6 @@ ...@@ -59,7 +62,6 @@
<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="Feedback" scope="col">{% trans "Feedback" %}</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>
...@@ -67,12 +69,11 @@ ...@@ -67,12 +69,11 @@
<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>
...@@ -87,19 +88,23 @@ ...@@ -87,19 +88,23 @@
</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">
{% if submitted_assessments %}
{% for assessment in submitted_assessments %} {% for assessment in submitted_assessments %}
{% with peer_num=forloop.counter %} {% with peer_num=forloop.counter %}
<h4 class="title--sub">{% trans "Assessment" %} {{ peer_num }}:</h4> <h4 class="title--sub">{% trans "Assessment" %} {{ peer_num }}:</h4>
...@@ -136,6 +141,9 @@ ...@@ -136,6 +141,9 @@
</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>
...@@ -144,8 +152,8 @@ ...@@ -144,8 +152,8 @@
{% 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">
...@@ -182,8 +190,8 @@ ...@@ -182,8 +190,8 @@
{% 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">
...@@ -217,48 +225,79 @@ ...@@ -217,48 +225,79 @@
</div> </div>
{% endif %} {% 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 %} {% if not workflow_cancellation %}
<div id="openassessment__staff-info__cancel__submission" <div class="staff-info__staff-override ui-staff__content__section wrapper--ui--collapse">
class="openassessment__staff-info__cancel__submission 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 class="staff-info__workflow-cancellation 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 "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>
</div>
</li>
<li>
<label for="staff-info__cancel-submission__comments"
class="label">{% trans "Comments:" %}</label>
</li>
<li>
<textarea
id="staff-info__cancel-submission__comments"
class="cancel_submission_comments"
value=""
maxlength="10000"></textarea>
</li> </li>
</ul> </ul>
<ul class="list list--actions"> <ul class="list list--actions">
<li class="list--actions__item"> <li class="list--actions__item">
<a data-submission-uuid="{{ submission.uuid }}" aria-role="button" href="" <button data-submission-uuid="{{ submission.uuid }}"
id="submit_cancel_submission" class="action--submit is--disabled"> class="action--submit action--submit-cancel-submission is--disabled">
<span class="copy">{% trans "Remove submission" %}</span> <span class="copy">{% trans "Remove submission" %}</span>
</a> </button>
<div class="cancel-submission-error"></div> <div class="cancel-submission-error"></div>
</li> </li>
...@@ -268,11 +307,10 @@ ...@@ -268,11 +307,10 @@
</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
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.Container=function(ContainerItem,kwargs){this.containerElement=kwargs.containerElement;this.templateElement=kwargs.templateElement;this.addButtonElement=kwargs.addButtonElement;this.removeButtonClass=kwargs.removeButtonClass;this.containerItemClass=kwargs.containerItemClass;this.notifier=kwargs.notifier;this.addRemoveEnabled=typeof kwargs.addRemoveEnabled==="undefined"||kwargs.addRemoveEnabled;var container=this;this.createContainerItem=function(element){return new ContainerItem(element,container.notifier)}};OpenAssessment.Container.prototype={addEventListeners:function(){var container=this;if(this.addRemoveEnabled){$(this.addButtonElement).click($.proxy(this.add,this));$("."+this.removeButtonClass,this.containerElement).click(function(eventData){var item=container.createContainerItem(eventData.target);container.remove(item)})}else{$(this.addButtonElement).addClass("is--disabled");$("."+this.removeButtonClass,this.containerElement).addClass("is--disabled")}$("."+this.containerItemClass,this.containerElement).each(function(index,element){var item=container.createContainerItem(element);item.addEventListeners()})},add:function(){$(this.templateElement).children().first().clone().removeAttr("id").toggleClass("is--hidden",false).toggleClass(this.containerItemClass,true).appendTo($(this.containerElement));var container=this;var containerItem=$("."+this.containerItemClass,this.containerElement).last();if(this.addRemoveEnabled){containerItem.find("."+this.removeButtonClass).click(function(eventData){var containerItem=container.createContainerItem(eventData.target);container.remove(containerItem)})}else{containerItem.find("."+this.removeButtonClass).addClass("is--disabled")}var handlerItem=container.createContainerItem(containerItem);handlerItem.addEventListeners();handlerItem.addHandler()},remove:function(item){var itemElement=$(item.element).closest("."+this.containerItemClass);var containerItem=this.createContainerItem(itemElement);containerItem.removeHandler();itemElement.remove()},getItemValues:function(){var values=[];var container=this;$("."+this.containerItemClass,this.containerElement).each(function(index,element){var containerItem=container.createContainerItem(element);var fieldValues=containerItem.getFieldValues();values.push(fieldValues)});return values},getItem:function(index){var element=$("."+this.containerItemClass,this.containerElement).get(index);return element!==undefined?this.createContainerItem(element):null},getAllItems:function(){var container=this;return $("."+this.containerItemClass,this.containerElement).map(function(){return container.createContainerItem(this)})}};OpenAssessment.ItemUtilities={createUniqueName:function(selector,nameAttribute){var index=0;while(index<=selector.length){if(selector.parent().find("*["+nameAttribute+"='"+index+"']").length===0){return index.toString()}index++}return index.toString()},refreshOptionString:function(element){var points=$(element).attr("data-points");var label=$(element).attr("data-label");var name=$(element).val();if(label===""){label=gettext("Unnamed Option")}var singularString=label+" - "+points+" point";var multipleString=label+" - "+points+" points";var finalLabel="";if(name===""){finalLabel=gettext("Not Selected")}else if(isNaN(points)){finalLabel=label}else{finalLabel=ngettext(singularString,multipleString,points)}$(element).text(finalLabel)}};OpenAssessment.Prompt=function(element,notifier){this.element=element;this.notifier=notifier};OpenAssessment.Prompt.prototype={getFieldValues:function(){var fields={description:this.description()};return fields},description:function(text){var sel=$(".openassessment_prompt_description",this.element);return OpenAssessment.Fields.stringField(sel,text)},addEventListeners:function(){},addHandler:function(){this.notifier.notificationFired("promptAdd",{index:this.element.index()})},removeHandler:function(){this.notifier.notificationFired("promptRemove",{index:this.element.index()})},updateHandler:function(){},validate:function(){return true},validationErrors:function(){return[]},clearValidationErrors:function(){}};OpenAssessment.RubricOption=function(element,notifier){this.element=element;this.notifier=notifier;this.pointsField=new OpenAssessment.IntField($(".openassessment_criterion_option_points",this.element),{min:0,max:999})};OpenAssessment.RubricOption.prototype={addEventListeners:function(){$(this.element).focusout($.proxy(this.updateHandler,this))},getFieldValues:function(){var fields={label:this.label(),points:this.points(),explanation:this.explanation()};var nameString=OpenAssessment.Fields.stringField($(".openassessment_criterion_option_name",this.element));if(nameString!==""){fields.name=nameString}return fields},label:function(label){var sel=$(".openassessment_criterion_option_label",this.element);return OpenAssessment.Fields.stringField(sel,label)},points:function(points){if(points!==undefined){this.pointsField.set(points)}return this.pointsField.get()},explanation:function(explanation){var sel=$(".openassessment_criterion_option_explanation",this.element);return OpenAssessment.Fields.stringField(sel,explanation)},addHandler:function(){var criterionElement=$(this.element).closest(".openassessment_criterion");var criterionName=$(criterionElement).data("criterion");var criterionLabel=$(".openassessment_criterion_label",criterionElement).val();var options=$(".openassessment_criterion_option",this.element.parent());var name=OpenAssessment.ItemUtilities.createUniqueName(options,"data-option");$(this.element).attr("data-criterion",criterionName).attr("data-option",name);$(".openassessment_criterion_option_name",this.element).attr("value",name);var fields=this.getFieldValues();this.notifier.notificationFired("optionAdd",{criterionName:criterionName,criterionLabel:criterionLabel,name:name,label:fields.label,points:fields.points})},removeHandler:function(){var criterionName=$(this.element).data("criterion");var optionName=$(this.element).data("option");this.notifier.notificationFired("optionRemove",{criterionName:criterionName,name:optionName})},updateHandler:function(){var fields=this.getFieldValues();var criterionName=$(this.element).data("criterion");var optionName=$(this.element).data("option");var optionLabel=fields.label;var optionPoints=fields.points;this.notifier.notificationFired("optionUpdated",{criterionName:criterionName,name:optionName,label:optionLabel,points:optionPoints})},validate:function(){return this.pointsField.validate()},validationErrors:function(){var hasError=this.pointsField.validationErrors().length>0;return hasError?["Option points are invalid"]:[]},clearValidationErrors:function(){this.pointsField.clearValidationErrors()}};OpenAssessment.RubricCriterion=function(element,notifier){this.element=element;this.notifier=notifier;this.labelSel=$(".openassessment_criterion_label",this.element);this.promptSel=$(".openassessment_criterion_prompt",this.element);this.optionContainer=new OpenAssessment.Container(OpenAssessment.RubricOption,{containerElement:$(".openassessment_criterion_option_list",this.element).get(0),templateElement:$("#openassessment_option_template").get(0),addButtonElement:$(".openassessment_criterion_add_option",this.element).get(0),removeButtonClass:"openassessment_criterion_option_remove_button",containerItemClass:"openassessment_criterion_option",notifier:this.notifier})};OpenAssessment.RubricCriterion.prototype={addEventListeners:function(){this.optionContainer.addEventListeners();$(this.element).focusout($.proxy(this.updateHandler,this))},getFieldValues:function(){var fields={label:this.label(),prompt:this.prompt(),feedback:this.feedback(),options:this.optionContainer.getItemValues()};var nameString=OpenAssessment.Fields.stringField($(".openassessment_criterion_name",this.element));if(nameString!==""){fields.name=nameString}return fields},label:function(label){return OpenAssessment.Fields.stringField(this.labelSel,label)},prompt:function(prompt){return OpenAssessment.Fields.stringField(this.promptSel,prompt)},feedback:function(){return $(".openassessment_criterion_feedback",this.element).val()},addOption:function(){this.optionContainer.add()},addHandler:function(){var criteria=$(".openassessment_criterion",this.element.parent());var name=OpenAssessment.ItemUtilities.createUniqueName(criteria,"data-criterion");$(this.element).attr("data-criterion",name);$(".openassessment_criterion_name",this.element).attr("value",name)},removeHandler:function(){var criterionName=$(this.element).data("criterion");this.notifier.notificationFired("criterionRemove",{criterionName:criterionName})},updateHandler:function(){var fields=this.getFieldValues();var criterionName=fields.name;var criterionLabel=fields.label;this.notifier.notificationFired("criterionUpdated",{criterionName:criterionName,criterionLabel:criterionLabel})},validate:function(){var isValid=this.prompt()!=="";if(!isValid){this.promptSel.addClass("openassessment_highlighted_field")}$.each(this.optionContainer.getAllItems(),function(){isValid=this.validate()&&isValid});return isValid},validationErrors:function(){var errors=[];if(this.promptSel.hasClass("openassessment_highlighted_field")){errors.push("Criterion prompt is invalid.")}$.each(this.optionContainer.getAllItems(),function(){errors=errors.concat(this.validationErrors())});return errors},clearValidationErrors:function(){this.promptSel.removeClass("openassessment_highlighted_field");$.each(this.optionContainer.getAllItems(),function(){this.clearValidationErrors()})}};OpenAssessment.TrainingExample=function(element){this.element=element;this.criteria=$(".openassessment_training_example_criterion_option",this.element);this.answer=$(".openassessment_training_example_essay_part textarea",this.element)};OpenAssessment.TrainingExample.prototype={getFieldValues:function(){var optionsSelected=this.criteria.map(function(){return{criterion:$(this).data("criterion"),option:$(this).prop("value")}}).get();return{answer:this.answer.map(function(){return $(this).prop("value")}).get(),options_selected:optionsSelected}},addHandler:function(){$(".openassessment_training_example_criterion_option",this.element).each(function(){$("option",this).each(function(){OpenAssessment.ItemUtilities.refreshOptionString($(this))})})},addEventListeners:function(){},removeHandler:function(){},updateHandler:function(){},validate:function(){var isValid=true;this.criteria.each(function(){var isOptionValid=$(this).prop("value")!=="";isValid=isOptionValid&&isValid;if(!isOptionValid){$(this).addClass("openassessment_highlighted_field")}});return isValid},validationErrors:function(){var errors=[];this.criteria.each(function(){var hasError=$(this).hasClass("openassessment_highlighted_field");if(hasError){errors.push("Student training example is invalid.")}});return errors},clearValidationErrors:function(){this.criteria.each(function(){$(this).removeClass("openassessment_highlighted_field")})}};OpenAssessment.StudioView=function(runtime,element,server,data){this.element=element;this.runtime=runtime;this.server=server;this.data=data;this.fixModalHeight();this.initializeTabs();this.alert=(new OpenAssessment.ValidationAlert).install();var studentTrainingListener=new OpenAssessment.StudentTrainingListener;this.promptsView=new OpenAssessment.EditPromptsView($("#oa_prompts_editor_wrapper",this.element).get(0),new OpenAssessment.Notifier([studentTrainingListener]));var studentTrainingView=new OpenAssessment.EditStudentTrainingView($("#oa_student_training_editor",this.element).get(0));var peerAssessmentView=new OpenAssessment.EditPeerAssessmentView($("#oa_peer_assessment_editor",this.element).get(0));var selfAssessmentView=new OpenAssessment.EditSelfAssessmentView($("#oa_self_assessment_editor",this.element).get(0));var exampleBasedAssessmentView=new OpenAssessment.EditExampleBasedAssessmentView($("#oa_ai_assessment_editor",this.element).get(0));var assessmentLookupDictionary={};assessmentLookupDictionary[studentTrainingView.getID()]=studentTrainingView;assessmentLookupDictionary[peerAssessmentView.getID()]=peerAssessmentView;assessmentLookupDictionary[selfAssessmentView.getID()]=selfAssessmentView;assessmentLookupDictionary[exampleBasedAssessmentView.getID()]=exampleBasedAssessmentView;this.settingsView=new OpenAssessment.EditSettingsView($("#oa_basic_settings_editor",this.element).get(0),assessmentLookupDictionary,data);this.rubricView=new OpenAssessment.EditRubricView($("#oa_rubric_editor_wrapper",this.element).get(0),new OpenAssessment.Notifier([studentTrainingListener]));$(".openassessment_save_button",this.element).click($.proxy(this.save,this));$(".openassessment_cancel_button",this.element).click($.proxy(this.cancel,this))};OpenAssessment.StudioView.prototype={fixModalHeight:function(){$(this.element).addClass("openassessment_full_height").parentsUntil(".modal-window").addClass("openassessment_full_height");$(this.element).closest(".modal-window").addClass("openassessment_modal_window")},initializeTabs:function(){if(typeof OpenAssessment.lastOpenEditingTab==="undefined"){OpenAssessment.lastOpenEditingTab=2}$(".openassessment_editor_content_and_tabs",this.element).tabs({active:OpenAssessment.lastOpenEditingTab})},saveTabState:function(){var tabElement=$(".openassessment_editor_content_and_tabs",this.element);OpenAssessment.lastOpenEditingTab=tabElement.tabs("option","active")},save:function(){var view=this;this.saveTabState();this.clearValidationErrors();if(!this.validate()){this.alert.setMessage(gettext("Couldn't Save This Assignment"),gettext("Please correct the outlined fields.")).show()}else{this.alert.hide();this.server.checkReleased().done(function(isReleased){if(isReleased){view.confirmPostReleaseUpdate($.proxy(view.updateEditorContext,view))}else{view.updateEditorContext()}}).fail(function(errMsg){view.showError(errMsg)})}},confirmPostReleaseUpdate:function(onConfirm){var msg=gettext("This problem has already been released. Any changes will apply only to future assessments.");if(confirm(msg)){onConfirm()}},updateEditorContext:function(){this.runtime.notify("save",{state:"start"});var view=this;this.server.updateEditorContext({prompts:view.promptsView.promptsDefinition(),feedbackPrompt:view.rubricView.feedbackPrompt(),feedback_default_text:view.rubricView.feedback_default_text(),criteria:view.rubricView.criteriaDefinition(),title:view.settingsView.displayName(),submissionStart:view.settingsView.submissionStart(),submissionDue:view.settingsView.submissionDue(),assessments:view.settingsView.assessmentsDescription(),fileUploadType:view.settingsView.fileUploadType(),fileTypeWhiteList:view.settingsView.fileTypeWhiteList(),latexEnabled:view.settingsView.latexEnabled(),leaderboardNum:view.settingsView.leaderboardNum(),editorAssessmentsOrder:view.settingsView.editorAssessmentsOrder()}).done(function(){view.runtime.notify("save",{state:"end"})}).fail(function(msg){view.showError(msg)})},cancel:function(){this.saveTabState();this.runtime.notify("cancel",{})},showError:function(errorMsg){this.runtime.notify("error",{msg:errorMsg})},validate:function(){var settingsValid=this.settingsView.validate();var rubricValid=this.rubricView.validate();var promptsValid=this.promptsView.validate();return settingsValid&&rubricValid&&promptsValid},validationErrors:function(){return this.settingsView.validationErrors().concat(this.rubricView.validationErrors().concat(this.promptsView.validationErrors()))},clearValidationErrors:function(){this.settingsView.clearValidationErrors();this.rubricView.clearValidationErrors();this.promptsView.clearValidationErrors()}};function OpenAssessmentEditor(runtime,element,data){var server=new OpenAssessment.Server(runtime,element);new OpenAssessment.StudioView(runtime,element,server,data)}OpenAssessment.EditPeerAssessmentView=function(element){this.element=element;this.name="peer-assessment";this.mustGradeField=new OpenAssessment.IntField($("#peer_assessment_must_grade",this.element),{min:0,max:99});this.mustBeGradedByField=new OpenAssessment.IntField($("#peer_assessment_graded_by",this.element),{min:0,max:99});new OpenAssessment.ToggleControl($("#include_peer_assessment",this.element),$("#peer_assessment_settings_editor",this.element),$("#peer_assessment_description_closed",this.element),new OpenAssessment.Notifier([new OpenAssessment.AssessmentToggleListener])).install();this.startDatetimeControl=new OpenAssessment.DatetimeControl(this.element,"#peer_assessment_start_date","#peer_assessment_start_time").install();this.dueDatetimeControl=new OpenAssessment.DatetimeControl(this.element,"#peer_assessment_due_date","#peer_assessment_due_time").install()};OpenAssessment.EditPeerAssessmentView.prototype={description:function(){return{must_grade:this.mustGradeNum(),must_be_graded_by:this.mustBeGradedByNum(),start:this.startDatetime(),due:this.dueDatetime()}},isEnabled:function(isEnabled){var sel=$("#include_peer_assessment",this.element);return OpenAssessment.Fields.booleanField(sel,isEnabled)},toggleEnabled:function(){$("#include_peer_assessment",this.element).click()},mustGradeNum:function(num){if(num!==undefined){this.mustGradeField.set(num)}return this.mustGradeField.get()},mustBeGradedByNum:function(num){if(num!==undefined){this.mustBeGradedByField.set(num)}return this.mustBeGradedByField.get()},startDatetime:function(dateString,timeString){return this.startDatetimeControl.datetime(dateString,timeString)},dueDatetime:function(dateString,timeString){return this.dueDatetimeControl.datetime(dateString,timeString)},getID:function(){return $(this.element).attr("id")},validate:function(){var startValid=this.startDatetimeControl.validate();var dueValid=this.dueDatetimeControl.validate();var mustGradeValid=this.mustGradeField.validate();var mustBeGradedByValid=this.mustBeGradedByField.validate();return startValid&&dueValid&&mustGradeValid&&mustBeGradedByValid},validationErrors:function(){var errors=[];if(this.startDatetimeControl.validationErrors().length>0){errors.push("Peer assessment start is invalid")}if(this.dueDatetimeControl.validationErrors().length>0){errors.push("Peer assessment due is invalid")}if(this.mustGradeField.validationErrors().length>0){errors.push("Peer assessment must grade is invalid")}if(this.mustBeGradedByField.validationErrors().length>0){errors.push("Peer assessment must be graded by is invalid")}return errors},clearValidationErrors:function(){this.startDatetimeControl.clearValidationErrors();this.dueDatetimeControl.clearValidationErrors();this.mustGradeField.clearValidationErrors();this.mustBeGradedByField.clearValidationErrors()}};OpenAssessment.EditSelfAssessmentView=function(element){this.element=element;this.name="self-assessment";new OpenAssessment.ToggleControl($("#include_self_assessment",this.element),$("#self_assessment_settings_editor",this.element),$("#self_assessment_description_closed",this.element),new OpenAssessment.Notifier([new OpenAssessment.AssessmentToggleListener])).install();this.startDatetimeControl=new OpenAssessment.DatetimeControl(this.element,"#self_assessment_start_date","#self_assessment_start_time").install();this.dueDatetimeControl=new OpenAssessment.DatetimeControl(this.element,"#self_assessment_due_date","#self_assessment_due_time").install()};OpenAssessment.EditSelfAssessmentView.prototype={description:function(){return{start:this.startDatetime(),due:this.dueDatetime()}},isEnabled:function(isEnabled){var sel=$("#include_self_assessment",this.element);return OpenAssessment.Fields.booleanField(sel,isEnabled)},toggleEnabled:function(){$("#include_self_assessment",this.element).click()},startDatetime:function(dateString,timeString){return this.startDatetimeControl.datetime(dateString,timeString)},dueDatetime:function(dateString,timeString){return this.dueDatetimeControl.datetime(dateString,timeString)},getID:function(){return $(this.element).attr("id")},validate:function(){var startValid=this.startDatetimeControl.validate();var dueValid=this.dueDatetimeControl.validate();return startValid&&dueValid},validationErrors:function(){var errors=[];if(this.startDatetimeControl.validationErrors().length>0){errors.push("Self assessment start is invalid")}if(this.dueDatetimeControl.validationErrors().length>0){errors.push("Self assessment due is invalid")}return errors},clearValidationErrors:function(){this.startDatetimeControl.clearValidationErrors();this.dueDatetimeControl.clearValidationErrors()}};OpenAssessment.EditStudentTrainingView=function(element){this.element=element;this.name="student-training";new OpenAssessment.ToggleControl($("#include_student_training",this.element),$("#student_training_settings_editor",this.element),$("#student_training_description_closed",this.element),new OpenAssessment.Notifier([new OpenAssessment.AssessmentToggleListener])).install();this.exampleContainer=new OpenAssessment.Container(OpenAssessment.TrainingExample,{containerElement:$("#openassessment_training_example_list",this.element).get(0),templateElement:$("#openassessment_training_example_template",this.element).get(0),addButtonElement:$(".openassessment_add_training_example",this.element).get(0),removeButtonClass:"openassessment_training_example_remove",containerItemClass:"openassessment_training_example"});this.exampleContainer.addEventListeners()};OpenAssessment.EditStudentTrainingView.prototype={description:function(){return{examples:this.exampleContainer.getItemValues()}},isEnabled:function(isEnabled){var sel=$("#include_student_training",this.element);return OpenAssessment.Fields.booleanField(sel,isEnabled)},toggleEnabled:function(){$("#include_student_training",this.element).click()},getID:function(){return $(this.element).attr("id")},validate:function(){var isValid=true;$.each(this.exampleContainer.getAllItems(),function(){isValid=this.validate()&&isValid});return isValid},validationErrors:function(){var errors=[];$.each(this.exampleContainer.getAllItems(),function(){errors=errors.concat(this.validationErrors())});return errors},clearValidationErrors:function(){$.each(this.exampleContainer.getAllItems(),function(){this.clearValidationErrors()})},addTrainingExample:function(){this.exampleContainer.add()}};OpenAssessment.EditExampleBasedAssessmentView=function(element){this.element=element;this.name="example-based-assessment";new OpenAssessment.ToggleControl($("#include_ai_assessment",this.element),$("#ai_assessment_settings_editor",this.element),$("#ai_assessment_description_closed",this.element),new OpenAssessment.Notifier([new OpenAssessment.AssessmentToggleListener])).install()};OpenAssessment.EditExampleBasedAssessmentView.prototype={description:function(){return{examples_xml:this.exampleDefinitions()}},isEnabled:function(isEnabled){var sel=$("#include_ai_assessment",this.element);return OpenAssessment.Fields.booleanField(sel,isEnabled)},toggleEnabled:function(){$("#include_ai_assessment",this.element).click()},exampleDefinitions:function(xml){var sel=$("#ai_training_examples",this.element);return OpenAssessment.Fields.stringField(sel,xml)},getID:function(){return $(this.element).attr("id")},validate:function(){return true},validationErrors:function(){return[]},clearValidationErrors:function(){}};OpenAssessment.Fields={stringField:function(sel,value){if(value!==undefined){sel.val(value) 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.Container=function(ContainerItem,kwargs){this.containerElement=kwargs.containerElement;this.templateElement=kwargs.templateElement;this.addButtonElement=kwargs.addButtonElement;this.removeButtonClass=kwargs.removeButtonClass;this.containerItemClass=kwargs.containerItemClass;this.notifier=kwargs.notifier;this.addRemoveEnabled=typeof kwargs.addRemoveEnabled==="undefined"||kwargs.addRemoveEnabled;var container=this;this.createContainerItem=function(element){return new ContainerItem(element,container.notifier)}};OpenAssessment.Container.prototype={addEventListeners:function(){var container=this;if(this.addRemoveEnabled){$(this.addButtonElement).click($.proxy(this.add,this));$("."+this.removeButtonClass,this.containerElement).click(function(eventData){var item=container.createContainerItem(eventData.target);container.remove(item)})}else{$(this.addButtonElement).addClass("is--disabled");$("."+this.removeButtonClass,this.containerElement).addClass("is--disabled")}$("."+this.containerItemClass,this.containerElement).each(function(index,element){var item=container.createContainerItem(element);item.addEventListeners()})},add:function(){$(this.templateElement).children().first().clone().removeAttr("id").toggleClass("is--hidden",false).toggleClass(this.containerItemClass,true).appendTo($(this.containerElement));var container=this;var containerItem=$("."+this.containerItemClass,this.containerElement).last();if(this.addRemoveEnabled){containerItem.find("."+this.removeButtonClass).click(function(eventData){var containerItem=container.createContainerItem(eventData.target);container.remove(containerItem)})}else{containerItem.find("."+this.removeButtonClass).addClass("is--disabled")}var handlerItem=container.createContainerItem(containerItem);handlerItem.addEventListeners();handlerItem.addHandler()},remove:function(item){var itemElement=$(item.element).closest("."+this.containerItemClass);var containerItem=this.createContainerItem(itemElement);containerItem.removeHandler();itemElement.remove()},getItemValues:function(){var values=[];var container=this;$("."+this.containerItemClass,this.containerElement).each(function(index,element){var containerItem=container.createContainerItem(element);var fieldValues=containerItem.getFieldValues();values.push(fieldValues)});return values},getItem:function(index){var element=$("."+this.containerItemClass,this.containerElement).get(index);return element!==undefined?this.createContainerItem(element):null},getAllItems:function(){var container=this;return $("."+this.containerItemClass,this.containerElement).map(function(){return container.createContainerItem(this)})}};OpenAssessment.ItemUtilities={createUniqueName:function(selector,nameAttribute){var index=0;while(index<=selector.length){if(selector.parent().find("*["+nameAttribute+"='"+index+"']").length===0){return index.toString()}index++}return index.toString()},refreshOptionString:function(element){var points=$(element).attr("data-points");var label=$(element).attr("data-label");var name=$(element).val();if(label===""){label=gettext("Unnamed Option")}var singularString=label+" - "+points+" point";var multipleString=label+" - "+points+" points";var finalLabel="";if(name===""){finalLabel=gettext("Not Selected")}else if(isNaN(points)){finalLabel=label}else{finalLabel=ngettext(singularString,multipleString,points)}$(element).text(finalLabel)}};OpenAssessment.Prompt=function(element,notifier){this.element=element;this.notifier=notifier};OpenAssessment.Prompt.prototype={getFieldValues:function(){var fields={description:this.description()};return fields},description:function(text){var sel=$(".openassessment_prompt_description",this.element);return OpenAssessment.Fields.stringField(sel,text)},addEventListeners:function(){},addHandler:function(){this.notifier.notificationFired("promptAdd",{index:this.element.index()})},removeHandler:function(){this.notifier.notificationFired("promptRemove",{index:this.element.index()})},updateHandler:function(){},validate:function(){return true},validationErrors:function(){return[]},clearValidationErrors:function(){}};OpenAssessment.RubricOption=function(element,notifier){this.element=element;this.notifier=notifier;this.pointsField=new OpenAssessment.IntField($(".openassessment_criterion_option_points",this.element),{min:0,max:999})};OpenAssessment.RubricOption.prototype={addEventListeners:function(){$(this.element).focusout($.proxy(this.updateHandler,this))},getFieldValues:function(){var fields={label:this.label(),points:this.points(),explanation:this.explanation()};var nameString=OpenAssessment.Fields.stringField($(".openassessment_criterion_option_name",this.element));if(nameString!==""){fields.name=nameString}return fields},label:function(label){var sel=$(".openassessment_criterion_option_label",this.element);return OpenAssessment.Fields.stringField(sel,label)},points:function(points){if(points!==undefined){this.pointsField.set(points)}return this.pointsField.get()},explanation:function(explanation){var sel=$(".openassessment_criterion_option_explanation",this.element);return OpenAssessment.Fields.stringField(sel,explanation)},addHandler:function(){var criterionElement=$(this.element).closest(".openassessment_criterion");var criterionName=$(criterionElement).data("criterion");var criterionLabel=$(".openassessment_criterion_label",criterionElement).val();var options=$(".openassessment_criterion_option",this.element.parent());var name=OpenAssessment.ItemUtilities.createUniqueName(options,"data-option");$(this.element).attr("data-criterion",criterionName).attr("data-option",name);$(".openassessment_criterion_option_name",this.element).attr("value",name);var fields=this.getFieldValues();this.notifier.notificationFired("optionAdd",{criterionName:criterionName,criterionLabel:criterionLabel,name:name,label:fields.label,points:fields.points})},removeHandler:function(){var criterionName=$(this.element).data("criterion");var optionName=$(this.element).data("option");this.notifier.notificationFired("optionRemove",{criterionName:criterionName,name:optionName})},updateHandler:function(){var fields=this.getFieldValues();var criterionName=$(this.element).data("criterion");var optionName=$(this.element).data("option");var optionLabel=fields.label;var optionPoints=fields.points;this.notifier.notificationFired("optionUpdated",{criterionName:criterionName,name:optionName,label:optionLabel,points:optionPoints})},validate:function(){return this.pointsField.validate()},validationErrors:function(){var hasError=this.pointsField.validationErrors().length>0;return hasError?["Option points are invalid"]:[]},clearValidationErrors:function(){this.pointsField.clearValidationErrors()}};OpenAssessment.RubricCriterion=function(element,notifier){this.element=element;this.notifier=notifier;this.labelSel=$(".openassessment_criterion_label",this.element);this.promptSel=$(".openassessment_criterion_prompt",this.element);this.optionContainer=new OpenAssessment.Container(OpenAssessment.RubricOption,{containerElement:$(".openassessment_criterion_option_list",this.element).get(0),templateElement:$("#openassessment_option_template").get(0),addButtonElement:$(".openassessment_criterion_add_option",this.element).get(0),removeButtonClass:"openassessment_criterion_option_remove_button",containerItemClass:"openassessment_criterion_option",notifier:this.notifier})};OpenAssessment.RubricCriterion.prototype={addEventListeners:function(){this.optionContainer.addEventListeners();$(this.element).focusout($.proxy(this.updateHandler,this))},getFieldValues:function(){var fields={label:this.label(),prompt:this.prompt(),feedback:this.feedback(),options:this.optionContainer.getItemValues()};var nameString=OpenAssessment.Fields.stringField($(".openassessment_criterion_name",this.element));if(nameString!==""){fields.name=nameString}return fields},label:function(label){return OpenAssessment.Fields.stringField(this.labelSel,label)},prompt:function(prompt){return OpenAssessment.Fields.stringField(this.promptSel,prompt)},feedback:function(){return $(".openassessment_criterion_feedback",this.element).val()},addOption:function(){this.optionContainer.add()},addHandler:function(){var criteria=$(".openassessment_criterion",this.element.parent());var name=OpenAssessment.ItemUtilities.createUniqueName(criteria,"data-criterion");$(this.element).attr("data-criterion",name);$(".openassessment_criterion_name",this.element).attr("value",name)},removeHandler:function(){var criterionName=$(this.element).data("criterion");this.notifier.notificationFired("criterionRemove",{criterionName:criterionName})},updateHandler:function(){var fields=this.getFieldValues();var criterionName=fields.name;var criterionLabel=fields.label;this.notifier.notificationFired("criterionUpdated",{criterionName:criterionName,criterionLabel:criterionLabel})},validate:function(){var isValid=this.prompt()!=="";if(!isValid){this.promptSel.addClass("openassessment_highlighted_field")}$.each(this.optionContainer.getAllItems(),function(){isValid=this.validate()&&isValid});return isValid},validationErrors:function(){var errors=[];if(this.promptSel.hasClass("openassessment_highlighted_field")){errors.push("Criterion prompt is invalid.")}$.each(this.optionContainer.getAllItems(),function(){errors=errors.concat(this.validationErrors())});return errors},clearValidationErrors:function(){this.promptSel.removeClass("openassessment_highlighted_field");$.each(this.optionContainer.getAllItems(),function(){this.clearValidationErrors()})}};OpenAssessment.TrainingExample=function(element){this.element=element;this.criteria=$(".openassessment_training_example_criterion_option",this.element);this.answer=$(".openassessment_training_example_essay_part textarea",this.element)};OpenAssessment.TrainingExample.prototype={getFieldValues:function(){var optionsSelected=this.criteria.map(function(){return{criterion:$(this).data("criterion"),option:$(this).prop("value")}}).get();return{answer:this.answer.map(function(){return $(this).prop("value")}).get(),options_selected:optionsSelected}},addHandler:function(){$(".openassessment_training_example_criterion_option",this.element).each(function(){$("option",this).each(function(){OpenAssessment.ItemUtilities.refreshOptionString($(this))})})},addEventListeners:function(){},removeHandler:function(){},updateHandler:function(){},validate:function(){var isValid=true;this.criteria.each(function(){var isOptionValid=$(this).prop("value")!=="";isValid=isOptionValid&&isValid;if(!isOptionValid){$(this).addClass("openassessment_highlighted_field")}});return isValid},validationErrors:function(){var errors=[];this.criteria.each(function(){var hasError=$(this).hasClass("openassessment_highlighted_field");if(hasError){errors.push("Student training example is invalid.")}});return errors},clearValidationErrors:function(){this.criteria.each(function(){$(this).removeClass("openassessment_highlighted_field")})}};OpenAssessment.StudioView=function(runtime,element,server,data){this.element=element;this.runtime=runtime;this.server=server;this.data=data;this.fixModalHeight();this.initializeTabs();this.alert=(new OpenAssessment.ValidationAlert).install();var studentTrainingListener=new OpenAssessment.StudentTrainingListener;this.promptsView=new OpenAssessment.EditPromptsView($("#oa_prompts_editor_wrapper",this.element).get(0),new OpenAssessment.Notifier([studentTrainingListener]));var studentTrainingView=new OpenAssessment.EditStudentTrainingView($("#oa_student_training_editor",this.element).get(0));var peerAssessmentView=new OpenAssessment.EditPeerAssessmentView($("#oa_peer_assessment_editor",this.element).get(0));var selfAssessmentView=new OpenAssessment.EditSelfAssessmentView($("#oa_self_assessment_editor",this.element).get(0));var exampleBasedAssessmentView=new OpenAssessment.EditExampleBasedAssessmentView($("#oa_ai_assessment_editor",this.element).get(0));var assessmentLookupDictionary={};assessmentLookupDictionary[studentTrainingView.getID()]=studentTrainingView;assessmentLookupDictionary[peerAssessmentView.getID()]=peerAssessmentView;assessmentLookupDictionary[selfAssessmentView.getID()]=selfAssessmentView;assessmentLookupDictionary[exampleBasedAssessmentView.getID()]=exampleBasedAssessmentView;this.settingsView=new OpenAssessment.EditSettingsView($("#oa_basic_settings_editor",this.element).get(0),assessmentLookupDictionary,data);this.rubricView=new OpenAssessment.EditRubricView($("#oa_rubric_editor_wrapper",this.element).get(0),new OpenAssessment.Notifier([studentTrainingListener]));$(".openassessment_save_button",this.element).click($.proxy(this.save,this));$(".openassessment_cancel_button",this.element).click($.proxy(this.cancel,this))};OpenAssessment.StudioView.prototype={fixModalHeight:function(){$(this.element).addClass("openassessment_full_height").parentsUntil(".modal-window").addClass("openassessment_full_height");$(this.element).closest(".modal-window").addClass("openassessment_modal_window")},initializeTabs:function(){if(typeof OpenAssessment.lastOpenEditingTab==="undefined"){OpenAssessment.lastOpenEditingTab=2}$(".openassessment_editor_content_and_tabs",this.element).tabs({active:OpenAssessment.lastOpenEditingTab})},saveTabState:function(){var tabElement=$(".openassessment_editor_content_and_tabs",this.element);OpenAssessment.lastOpenEditingTab=tabElement.tabs("option","active")},save:function(){var view=this;this.saveTabState();this.clearValidationErrors();if(!this.validate()){this.alert.setMessage(gettext("Couldn't Save This Assignment"),gettext("Please correct the outlined fields.")).show()}else{this.alert.hide();this.server.checkReleased().done(function(isReleased){if(isReleased){view.confirmPostReleaseUpdate($.proxy(view.updateEditorContext,view))}else{view.updateEditorContext()}}).fail(function(errMsg){view.showError(errMsg)})}},confirmPostReleaseUpdate:function(onConfirm){var msg=gettext("This problem has already been released. Any changes will apply only to future assessments.");if(confirm(msg)){onConfirm()}},updateEditorContext:function(){this.runtime.notify("save",{state:"start"});var view=this;this.server.updateEditorContext({prompts:view.promptsView.promptsDefinition(),feedbackPrompt:view.rubricView.feedbackPrompt(),feedback_default_text:view.rubricView.feedback_default_text(),criteria:view.rubricView.criteriaDefinition(),title:view.settingsView.displayName(),submissionStart:view.settingsView.submissionStart(),submissionDue:view.settingsView.submissionDue(),assessments:view.settingsView.assessmentsDescription(),fileUploadType:view.settingsView.fileUploadType(),fileTypeWhiteList:view.settingsView.fileTypeWhiteList(),latexEnabled:view.settingsView.latexEnabled(),leaderboardNum:view.settingsView.leaderboardNum(),editorAssessmentsOrder:view.settingsView.editorAssessmentsOrder()}).done(function(){view.runtime.notify("save",{state:"end"})}).fail(function(msg){view.showError(msg)})},cancel:function(){this.saveTabState();this.runtime.notify("cancel",{})},showError:function(errorMsg){this.runtime.notify("error",{msg:errorMsg})},validate:function(){var settingsValid=this.settingsView.validate();var rubricValid=this.rubricView.validate();var promptsValid=this.promptsView.validate();return settingsValid&&rubricValid&&promptsValid},validationErrors:function(){return this.settingsView.validationErrors().concat(this.rubricView.validationErrors().concat(this.promptsView.validationErrors()))},clearValidationErrors:function(){this.settingsView.clearValidationErrors();this.rubricView.clearValidationErrors();this.promptsView.clearValidationErrors()}};function OpenAssessmentEditor(runtime,element,data){var server=new OpenAssessment.Server(runtime,element);new OpenAssessment.StudioView(runtime,element,server,data)}OpenAssessment.EditPeerAssessmentView=function(element){this.element=element;this.name="peer-assessment";this.mustGradeField=new OpenAssessment.IntField($("#peer_assessment_must_grade",this.element),{min:0,max:99});this.mustBeGradedByField=new OpenAssessment.IntField($("#peer_assessment_graded_by",this.element),{min:0,max:99});new OpenAssessment.ToggleControl($("#include_peer_assessment",this.element),$("#peer_assessment_settings_editor",this.element),$("#peer_assessment_description_closed",this.element),new OpenAssessment.Notifier([new OpenAssessment.AssessmentToggleListener])).install();this.startDatetimeControl=new OpenAssessment.DatetimeControl(this.element,"#peer_assessment_start_date","#peer_assessment_start_time").install();this.dueDatetimeControl=new OpenAssessment.DatetimeControl(this.element,"#peer_assessment_due_date","#peer_assessment_due_time").install()};OpenAssessment.EditPeerAssessmentView.prototype={description:function(){return{must_grade:this.mustGradeNum(),must_be_graded_by:this.mustBeGradedByNum(),start:this.startDatetime(),due:this.dueDatetime()}},isEnabled:function(isEnabled){var sel=$("#include_peer_assessment",this.element);return OpenAssessment.Fields.booleanField(sel,isEnabled)},toggleEnabled:function(){$("#include_peer_assessment",this.element).click()},mustGradeNum:function(num){if(num!==undefined){this.mustGradeField.set(num)}return this.mustGradeField.get()},mustBeGradedByNum:function(num){if(num!==undefined){this.mustBeGradedByField.set(num)}return this.mustBeGradedByField.get()},startDatetime:function(dateString,timeString){return this.startDatetimeControl.datetime(dateString,timeString)},dueDatetime:function(dateString,timeString){return this.dueDatetimeControl.datetime(dateString,timeString)},getID:function(){return $(this.element).attr("id")},validate:function(){var startValid=this.startDatetimeControl.validate();var dueValid=this.dueDatetimeControl.validate();var mustGradeValid=this.mustGradeField.validate();var mustBeGradedByValid=this.mustBeGradedByField.validate();return startValid&&dueValid&&mustGradeValid&&mustBeGradedByValid},validationErrors:function(){var errors=[];if(this.startDatetimeControl.validationErrors().length>0){errors.push("Peer assessment start is invalid")}if(this.dueDatetimeControl.validationErrors().length>0){errors.push("Peer assessment due is invalid")}if(this.mustGradeField.validationErrors().length>0){errors.push("Peer assessment must grade is invalid")}if(this.mustBeGradedByField.validationErrors().length>0){errors.push("Peer assessment must be graded by is invalid")}return errors},clearValidationErrors:function(){this.startDatetimeControl.clearValidationErrors();this.dueDatetimeControl.clearValidationErrors();this.mustGradeField.clearValidationErrors();this.mustBeGradedByField.clearValidationErrors()}};OpenAssessment.EditSelfAssessmentView=function(element){this.element=element;this.name="self-assessment";new OpenAssessment.ToggleControl($("#include_self_assessment",this.element),$("#self_assessment_settings_editor",this.element),$("#self_assessment_description_closed",this.element),new OpenAssessment.Notifier([new OpenAssessment.AssessmentToggleListener])).install();this.startDatetimeControl=new OpenAssessment.DatetimeControl(this.element,"#self_assessment_start_date","#self_assessment_start_time").install();this.dueDatetimeControl=new OpenAssessment.DatetimeControl(this.element,"#self_assessment_due_date","#self_assessment_due_time").install()};OpenAssessment.EditSelfAssessmentView.prototype={description:function(){return{start:this.startDatetime(),due:this.dueDatetime()}},isEnabled:function(isEnabled){var sel=$("#include_self_assessment",this.element);return OpenAssessment.Fields.booleanField(sel,isEnabled)},toggleEnabled:function(){$("#include_self_assessment",this.element).click()},startDatetime:function(dateString,timeString){return this.startDatetimeControl.datetime(dateString,timeString)},dueDatetime:function(dateString,timeString){return this.dueDatetimeControl.datetime(dateString,timeString)},getID:function(){return $(this.element).attr("id")},validate:function(){var startValid=this.startDatetimeControl.validate();var dueValid=this.dueDatetimeControl.validate();return startValid&&dueValid},validationErrors:function(){var errors=[];if(this.startDatetimeControl.validationErrors().length>0){errors.push("Self assessment start is invalid")}if(this.dueDatetimeControl.validationErrors().length>0){errors.push("Self assessment due is invalid")}return errors},clearValidationErrors:function(){this.startDatetimeControl.clearValidationErrors();this.dueDatetimeControl.clearValidationErrors()}};OpenAssessment.EditStudentTrainingView=function(element){this.element=element;this.name="student-training";new OpenAssessment.ToggleControl($("#include_student_training",this.element),$("#student_training_settings_editor",this.element),$("#student_training_description_closed",this.element),new OpenAssessment.Notifier([new OpenAssessment.AssessmentToggleListener])).install();this.exampleContainer=new OpenAssessment.Container(OpenAssessment.TrainingExample,{containerElement:$("#openassessment_training_example_list",this.element).get(0),templateElement:$("#openassessment_training_example_template",this.element).get(0),addButtonElement:$(".openassessment_add_training_example",this.element).get(0),removeButtonClass:"openassessment_training_example_remove",containerItemClass:"openassessment_training_example"});this.exampleContainer.addEventListeners()};OpenAssessment.EditStudentTrainingView.prototype={description:function(){return{examples:this.exampleContainer.getItemValues()}},isEnabled:function(isEnabled){var sel=$("#include_student_training",this.element);return OpenAssessment.Fields.booleanField(sel,isEnabled)},toggleEnabled:function(){$("#include_student_training",this.element).click()},getID:function(){return $(this.element).attr("id")},validate:function(){var isValid=true;$.each(this.exampleContainer.getAllItems(),function(){isValid=this.validate()&&isValid});return isValid},validationErrors:function(){var errors=[];$.each(this.exampleContainer.getAllItems(),function(){errors=errors.concat(this.validationErrors())});return errors},clearValidationErrors:function(){$.each(this.exampleContainer.getAllItems(),function(){this.clearValidationErrors()})},addTrainingExample:function(){this.exampleContainer.add()}};OpenAssessment.EditExampleBasedAssessmentView=function(element){this.element=element;this.name="example-based-assessment";new OpenAssessment.ToggleControl($("#include_ai_assessment",this.element),$("#ai_assessment_settings_editor",this.element),$("#ai_assessment_description_closed",this.element),new OpenAssessment.Notifier([new OpenAssessment.AssessmentToggleListener])).install()};OpenAssessment.EditExampleBasedAssessmentView.prototype={description:function(){return{examples_xml:this.exampleDefinitions()}},isEnabled:function(isEnabled){var sel=$("#include_ai_assessment",this.element);return OpenAssessment.Fields.booleanField(sel,isEnabled)},toggleEnabled:function(){$("#include_ai_assessment",this.element).click()},exampleDefinitions:function(xml){var sel=$("#ai_training_examples",this.element);return OpenAssessment.Fields.stringField(sel,xml)},getID:function(){return $(this.element).attr("id")},validate:function(){return true},validationErrors:function(){return[]},clearValidationErrors:function(){}};OpenAssessment.Fields={stringField:function(sel,value){if(value!==undefined){sel.val(value)
}return sel.val()},booleanField:function(sel,value){if(value!==undefined){sel.prop("checked",value)}return sel.prop("checked")}};OpenAssessment.IntField=function(inputSel,restrictions){this.max=restrictions.max;this.min=restrictions.min;this.input=$(inputSel)};OpenAssessment.IntField.prototype={get:function(){return parseInt(this.input.val().trim(),10)},set:function(val){this.input.val(val)},validate:function(){var value=this.get();var isValid=!isNaN(value)&&value>=this.min&&value<=this.max;if(this.input.val().indexOf(".")!==-1){isValid=false}if(!isValid){this.input.addClass("openassessment_highlighted_field")}return isValid},clearValidationErrors:function(){this.input.removeClass("openassessment_highlighted_field")},validationErrors:function(){var hasError=this.input.hasClass("openassessment_highlighted_field");return hasError?["Int field is invalid"]:[]}};OpenAssessment.ToggleControl=function(checkboxSel,shownSel,hiddenSel,notifier){this.checkbox=checkboxSel;this.shownSection=shownSel;this.hiddenSection=hiddenSel;this.notifier=notifier};OpenAssessment.ToggleControl.prototype={install:function(){this.checkbox.change(this,function(event){var control=event.data;if(this.checked){control.notifier.notificationFired("toggleOn",{});control.show()}else{control.notifier.notificationFired("toggleOff",{});control.hide()}});return this},show:function(){this.shownSection.removeClass("is--hidden");this.hiddenSection.addClass("is--hidden")},hide:function(){this.shownSection.addClass("is--hidden");this.hiddenSection.removeClass("is--hidden")}};OpenAssessment.DatetimeControl=function(element,datePicker,timePicker){this.element=element;this.datePicker=datePicker;this.timePicker=timePicker};OpenAssessment.DatetimeControl.prototype={install:function(){var dateString=$(this.datePicker,this.element).val();$(this.datePicker,this.element).datepicker({showButtonPanel:true}).datepicker("option","dateFormat","yy-mm-dd").datepicker("setDate",dateString);$(this.timePicker,this.element).timepicker({timeFormat:"H:i",step:60});return this},datetime:function(dateString,timeString){var datePickerSel=$(this.datePicker,this.element);var timePickerSel=$(this.timePicker,this.element);if(typeof dateString!=="undefined"){datePickerSel.val(dateString)}if(typeof timeString!=="undefined"){timePickerSel.val(timeString)}return datePickerSel.val()+"T"+timePickerSel.val()},validate:function(){var dateString=$(this.datePicker,this.element).val();var timeString=$(this.timePicker,this.element).val();var isDateValid=false;try{var parsedDate=$.datepicker.parseDate($.datepicker.ISO_8601,dateString);isDateValid=parsedDate instanceof Date}catch(err){}if(!isDateValid){$(this.datePicker,this.element).addClass("openassessment_highlighted_field")}var matches=timeString.match(/^\d{2}:\d{2}$/g);var isTimeValid=matches!==null;if(!isTimeValid){$(this.timePicker,this.element).addClass("openassessment_highlighted_field")}return isDateValid&&isTimeValid},clearValidationErrors:function(){$(this.datePicker,this.element).removeClass("openassessment_highlighted_field");$(this.timePicker,this.element).removeClass("openassessment_highlighted_field")},validationErrors:function(){var errors=[];var dateHasError=$(this.datePicker,this.element).hasClass("openassessment_highlighted_field");var timeHasError=$(this.timePicker,this.element).hasClass("openassessment_highlighted_field");if(dateHasError){errors.push("Date is invalid")}if(timeHasError){errors.push("Time is invalid")}return errors}};OpenAssessment.SelectControl=function(selectSel,mapping,notifier){this.select=selectSel;this.mapping=mapping;this.notifier=notifier};OpenAssessment.SelectControl.prototype={install:function(){this.select.change(this,function(event){var control=event.data;control.notifier.notificationFired("selectionChanged",{selected:this.value});control.change(this.value)});return this},change:function(selected){$.each(this.mapping,function(option,sel){if(option===selected){sel.removeClass("is--hidden")}else{sel.addClass("is--hidden")}})}};OpenAssessment.InputControl=function(inputSel,validator){this.input=$(inputSel);this.validator=validator;this.errors=[]};OpenAssessment.InputControl.prototype={get:function(){return this.input.val()},set:function(val){this.input.val(val)},validate:function(){this.errors=this.validator(this.get());if(this.errors.length){this.input.addClass("openassessment_highlighted_field");this.input.parent().nextAll(".message-status").text(this.errors.join(";"));this.input.parent().nextAll(".message-status").addClass("is-shown")}return this.errors.length===0},clearValidationErrors:function(){this.input.removeClass("openassessment_highlighted_field");this.input.parent().nextAll(".message-status").removeClass("is-shown")},validationErrors:function(){return this.errors}};OpenAssessment.StudentTrainingListener=function(){this.element=$("#oa_student_training_editor");this.alert=new OpenAssessment.ValidationAlert};OpenAssessment.StudentTrainingListener.prototype={promptAdd:function(){var view=this.element;$("#openassessment_training_example_part_template").children().first().clone().removeAttr("id").toggleClass("is--hidden",false).appendTo(".openassessment_training_example_essay",view)},promptRemove:function(data){var view=this.element;$(".openassessment_training_example_essay li:nth-child("+(data.index+1)+")",view).remove()},optionUpdated:function(data){this._optionSel(data.criterionName).each(function(){var criterion=this;var option=$('option[value="'+data.name+'"]',criterion).attr("data-points",data.points).attr("data-label",data.label);OpenAssessment.ItemUtilities.refreshOptionString(option)})},optionAdd:function(data){var criterionAdded=false;if(this._optionSel(data.criterionName).length===0){this.criterionAdd(data);criterionAdded=true}this._optionSel(data.criterionName).each(function(){var criterion=this;var option=$("<option></option>").attr("value",data.name).attr("data-points",data.points).attr("data-label",data.label);OpenAssessment.ItemUtilities.refreshOptionString(option);$(criterion).append(option)});if(criterionAdded){this.displayAlertMsg(gettext("Criterion Added"),gettext("You have added a criterion. You will need to select an option for the criterion in the Learner Training step. To do this, click the Settings tab."))}},optionRemove:function(data){var handler=this;var invalidated=false;this._optionSel(data.criterionName).each(function(){var criterionOption=this;if($(criterionOption).val()===data.name.toString()){$(criterionOption).val("").addClass("openassessment_highlighted_field").click(function(){$(criterionOption).removeClass("openassessment_highlighted_field")});invalidated=true}$('option[value="'+data.name+'"]',criterionOption).remove();if($("option",criterionOption).length===1){handler.removeAllOptions(data);invalidated=false}});if(invalidated){this.displayAlertMsg(gettext("Option Deleted"),gettext("You have deleted an option. That option has been removed from its criterion in the sample responses in the Learner Training step. You might have to select a new option for the criterion."))}},_optionSel:function(criterionName){return $('.openassessment_training_example_criterion_option[data-criterion="'+criterionName+'"]',this.element)},removeAllOptions:function(data){var changed=false;$(".openassessment_training_example_criterion",this.element).each(function(){var criterion=this;if($(criterion).data("criterion")===data.criterionName){$(criterion).remove();changed=true}});if(changed){this.displayAlertMsg(gettext("Option Deleted"),gettext("You have deleted all the options for this criterion. The criterion has been removed from the sample responses in the Learner Training step."))}},criterionRemove:function(data){var changed=false;var sel='.openassessment_training_example_criterion[data-criterion="'+data.criterionName+'"]';$(sel,this.element).each(function(){$(this).remove();changed=true});if(changed){this.displayAlertMsg(gettext("Criterion Deleted"),gettext("You have deleted a criterion. The criterion has been removed from the example responses in the Learner Training step."))}},displayAlertMsg:function(title,msg){if($("#include_student_training",this.element).is(":checked")&&$(".openassessment_training_example",this.element).length>1){this.alert.setMessage(title,msg).show()}},criterionUpdated:function(data){var sel='.openassessment_training_example_criterion[data-criterion="'+data.criterionName+'"]';$(sel,this.element).each(function(){$(".openassessment_training_example_criterion_name_wrapper",this).text(data.criterionLabel)})},criterionAdd:function(data){var view=this.element;var criterion=$("#openassessment_training_example_criterion_template").children().first().clone().removeAttr("id").attr("data-criterion",data.criterionName).toggleClass("is--hidden",false).appendTo(".openassessment_training_example_criteria_selections",view);criterion.find(".openassessment_training_example_criterion_option").attr("data-criterion",data.criterionName);criterion.find(".openassessment_training_example_criterion_name_wrapper").text(data.label)},examplesCriteriaLabels:function(){var examples=[];$(".openassessment_training_example_criteria_selections",this.element).each(function(){var exampleDescription={};$(".openassessment_training_example_criterion",this).each(function(){var criterionName=$(this).data("criterion");var criterionLabel=$(".openassessment_training_example_criterion_name_wrapper",this).text().trim();exampleDescription[criterionName]=criterionLabel});examples.push(exampleDescription)});return examples},examplesOptionsLabels:function(){var examples=[];$(".openassessment_training_example_criteria_selections",this.element).each(function(){var exampleDescription={};$(".openassessment_training_example_criterion_option",this).each(function(){var criterionName=$(this).data("criterion");exampleDescription[criterionName]={};$("option",this).each(function(){var optionName=$(this).val();var optionLabel=$(this).text().trim();exampleDescription[criterionName][optionName]=optionLabel})});examples.push(exampleDescription)});return examples}};OpenAssessment.AssessmentToggleListener=function(){this.alert=new OpenAssessment.ValidationAlert};OpenAssessment.AssessmentToggleListener.prototype={toggleOff:function(){this.alert.setMessage(gettext("Warning"),gettext("Changes to steps that are not selected as part of the assignment will not be saved.")).show()},toggleOn:function(){this.alert.hide()}};OpenAssessment.Notifier=function(listeners){this.listeners=listeners};OpenAssessment.Notifier.prototype={notificationFired:function(name,data){for(var i=0;i<this.listeners.length;i++){if(typeof this.listeners[i][name]==="function"){this.listeners[i][name](data)}}}};OpenAssessment.EditPromptsView=function(element,notifier){this.element=element;this.editorElement=$(this.element).closest("#openassessment-editor");this.addRemoveEnabled=this.editorElement.attr("data-is-released")!=="true";this.promptsContainer=new OpenAssessment.Container(OpenAssessment.Prompt,{containerElement:$("#openassessment_prompts_list",this.element).get(0),templateElement:$("#openassessment_prompt_template",this.element).get(0),addButtonElement:$("#openassessment_prompts_add_prompt",this.element).get(0),removeButtonClass:"openassessment_prompt_remove_button",containerItemClass:"openassessment_prompt",notifier:notifier,addRemoveEnabled:this.addRemoveEnabled});this.promptsContainer.addEventListeners()};OpenAssessment.EditPromptsView.prototype={promptsDefinition:function(){var prompts=this.promptsContainer.getItemValues();return prompts},addPrompt:function(){if(this.addRemoveEnabled){this.promptsContainer.add()}},removePrompt:function(item){if(this.addRemoveEnabled){this.promptsContainer.remove(item)}},getAllPrompts:function(){return this.promptsContainer.getAllItems()},getPromptItem:function(index){return this.promptsContainer.getItem(index)},validate:function(){return true},validationErrors:function(){var errors=[];return errors},clearValidationErrors:function(){}};OpenAssessment.EditRubricView=function(element,notifier){this.element=element;this.criterionAddButton=$("#openassessment_rubric_add_criterion",this.element);this.criteriaContainer=new OpenAssessment.Container(OpenAssessment.RubricCriterion,{containerElement:$("#openassessment_criterion_list",this.element).get(0),templateElement:$("#openassessment_criterion_template",this.element).get(0),addButtonElement:$("#openassessment_rubric_add_criterion",this.element).get(0),removeButtonClass:"openassessment_criterion_remove_button",containerItemClass:"openassessment_criterion",notifier:notifier});this.criteriaContainer.addEventListeners()};OpenAssessment.EditRubricView.prototype={criteriaDefinition:function(){var criteria=this.criteriaContainer.getItemValues();for(var criterion_idx=0;criterion_idx<criteria.length;criterion_idx++){var criterion=criteria[criterion_idx];criterion.order_num=criterion_idx;for(var option_idx=0;option_idx<criterion.options.length;option_idx++){var option=criterion.options[option_idx];option.order_num=option_idx}}return criteria},feedbackPrompt:function(text){var sel=$("#openassessment_rubric_feedback",this.element);return OpenAssessment.Fields.stringField(sel,text)},feedback_default_text:function(text){var sel=$("#openassessment_rubric_feedback_default_text",this.element);return OpenAssessment.Fields.stringField(sel,text)},addCriterion:function(){this.criteriaContainer.add()},removeCriterion:function(item){this.criteriaContainer.remove(item)},getAllCriteria:function(){return this.criteriaContainer.getAllItems()},getCriterionItem:function(index){return this.criteriaContainer.getItem(index)},addOption:function(criterionIndex){var criterionItem=this.getCriterionItem(criterionIndex);criterionItem.optionContainer.add()},removeOption:function(criterionIndex,item){var criterionItem=this.getCriterionItem(criterionIndex);criterionItem.optionContainer.remove(item)},getAllOptions:function(criterionIndex){var criterionItem=this.getCriterionItem(criterionIndex);return criterionItem.optionContainer.getAllItems()},getOptionItem:function(criterionIndex,optionIndex){var criterionItem=this.getCriterionItem(criterionIndex);return criterionItem.optionContainer.getItem(optionIndex)},validate:function(){var criteria=this.getAllCriteria();var isValid=criteria.length>0;if(!isValid){this.criterionAddButton.addClass("openassessment_highlighted_field").click(function(){$(this).removeClass("openassessment_highlighted_field")})}$.each(criteria,function(){isValid=this.validate()&&isValid});return isValid},validationErrors:function(){var errors=[];if(this.criterionAddButton.hasClass("openassessment_highlighted_field")){errors.push("The rubric must contain at least one criterion")}$.each(this.getAllCriteria(),function(){errors=errors.concat(this.validationErrors())});return errors},clearValidationErrors:function(){this.criterionAddButton.removeClass("openassessment_highlighted_field");$.each(this.getAllCriteria(),function(){this.clearValidationErrors()})}};OpenAssessment.EditSettingsView=function(element,assessmentViews,data){this.settingsElement=element;this.assessmentsElement=$(element).siblings("#openassessment_assessment_module_settings_editors").get(0);this.assessmentViews=assessmentViews;this.startDatetimeControl=new OpenAssessment.DatetimeControl(this.element,"#openassessment_submission_start_date","#openassessment_submission_start_time").install();this.dueDatetimeControl=new OpenAssessment.DatetimeControl(this.element,"#openassessment_submission_due_date","#openassessment_submission_due_time").install();new OpenAssessment.SelectControl($("#openassessment_submission_upload_selector",this.element),{custom:$("#openassessment_submission_white_listed_file_types_wrapper",this.element)},new OpenAssessment.Notifier([new OpenAssessment.AssessmentToggleListener])).install();this.leaderboardIntField=new OpenAssessment.IntField($("#openassessment_leaderboard_editor",this.element),{min:0,max:100});this.fileTypeWhiteListInputField=new OpenAssessment.InputControl($("#openassessment_submission_white_listed_file_types",this.element),function(value){var badExts=[];var errors=[];if(!value){errors.push(gettext("File types can not be empty."));return errors}var whiteList=$.map(value.replace(/\./g,"").toLowerCase().split(","),$.trim);$.each(whiteList,function(index,ext){if(data.FILE_EXT_BLACK_LIST.indexOf(ext)!==-1){badExts.push(ext)}});if(badExts.length){errors.push(gettext("The following file types are not allowed: ")+badExts.join(","))}return errors});this.initializeSortableAssessments()};OpenAssessment.EditSettingsView.prototype={initializeSortableAssessments:function(){var view=this;$("#openassessment_assessment_module_settings_editors",view.element).sortable({start:function(event,ui){$(".openassessment_assessment_module_editor",view.element).hide();var targetHeight="auto";ui.placeholder.height(targetHeight);ui.helper.height(targetHeight);$("#openassessment_assessment_module_settings_editors",view.element).sortable("refresh").sortable("refreshPositions")},stop:function(){$(".openassessment_assessment_module_editor",view.element).show()},snap:true,axis:"y",handle:".drag-handle",cursorAt:{top:20}});$("#openassessment_assessment_module_settings_editors .drag-handle",view.element).disableSelection()},displayName:function(name){var sel=$("#openassessment_title_editor",this.settingsElement);return OpenAssessment.Fields.stringField(sel,name)},submissionStart:function(dateString,timeString){return this.startDatetimeControl.datetime(dateString,timeString)},submissionDue:function(dateString,timeString){return this.dueDatetimeControl.datetime(dateString,timeString)},fileUploadType:function(uploadType){var sel=$("#openassessment_submission_upload_selector",this.settingsElement);if(uploadType!==undefined){sel.val(uploadType)}return sel.val()},fileTypeWhiteList:function(exts){if(exts!==undefined){this.fileTypeWhiteListInputField.set(exts)}return this.fileTypeWhiteListInputField.get()},latexEnabled:function(isEnabled){var sel=$("#openassessment_submission_latex_editor",this.settingsElement);if(isEnabled!==undefined){if(isEnabled){sel.val(1)}else{sel.val(0)}}return sel.val()===1},leaderboardNum:function(num){if(num!==undefined){this.leaderboardIntField.set(num)}return this.leaderboardIntField.get(num)},assessmentsDescription:function(){var assessmentDescList=[];var view=this;$(".openassessment_assessment_module_settings_editor",this.assessmentsElement).each(function(){var asmntView=view.assessmentViews[$(this).attr("id")];if(asmntView.isEnabled()){var description=asmntView.description();description.name=asmntView.name;assessmentDescList.push(description)}});return assessmentDescList},editorAssessmentsOrder:function(){var editorAssessments=[];var view=this;$(".openassessment_assessment_module_settings_editor",this.assessmentsElement).each(function(){var asmntView=view.assessmentViews[$(this).attr("id")];editorAssessments.push(asmntView.name)});return editorAssessments},validate:function(){var isValid=true;isValid=this.startDatetimeControl.validate()&&isValid;isValid=this.dueDatetimeControl.validate()&&isValid;isValid=this.leaderboardIntField.validate()&&isValid;if(this.fileUploadType()==="custom"){isValid=this.fileTypeWhiteListInputField.validate()&&isValid}else{if(this.fileTypeWhiteListInputField.get()&&!this.fileTypeWhiteListInputField.validate()){this.fileTypeWhiteListInputField.set("")}}$.each(this.assessmentViews,function(){if(this.isEnabled()){isValid=this.validate()&&isValid}});return isValid},validationErrors:function(){var errors=[];if(this.startDatetimeControl.validationErrors().length>0){errors.push("Submission start is invalid")}if(this.dueDatetimeControl.validationErrors().length>0){errors.push("Submission due is invalid")}if(this.leaderboardIntField.validationErrors().length>0){errors.push("Leaderboard number is invalid")}if(this.fileTypeWhiteListInputField.validationErrors().length>0){errors=errors.concat(this.fileTypeWhiteListInputField.validationErrors())}$.each(this.assessmentViews,function(){errors=errors.concat(this.validationErrors())});return errors},clearValidationErrors:function(){this.startDatetimeControl.clearValidationErrors();this.dueDatetimeControl.clearValidationErrors();this.leaderboardIntField.clearValidationErrors();this.fileTypeWhiteListInputField.clearValidationErrors();$.each(this.assessmentViews,function(){this.clearValidationErrors()})}};OpenAssessment.ValidationAlert=function(){this.element=$("#openassessment_validation_alert");this.editorElement=$(this.element).parent();this.title=$(".openassessment_alert_title",this.element);this.message=$(".openassessment_alert_message",this.element);this.closeButton=$(".openassessment_alert_close",this.element);this.ALERT_YELLOW="rgb(192, 172, 0)";this.DARK_GREY="#323232"};OpenAssessment.ValidationAlert.prototype={install:function(){var alert=this;this.closeButton.click(function(eventObject){eventObject.preventDefault();alert.hide()});return this},hide:function(){var headerHeight=$("#openassessment_editor_header",this.editorElement).outerHeight();this.element.addClass("covered");var styles={height:"Calc(100% - "+headerHeight+"px)","border-top-right-radius":"3px","border-top-left-radius":"3px"};$(".oa_editor_content_wrapper",this.editorElement).each(function(){$(this).css(styles)});return this},show:function(){var view=this;if(this.isVisible()){$(this.element).animate({"background-color":view.ALERT_YELLOW},300,"swing",function(){$(this).animate({"background-color":view.DARK_GREY},700,"swing")})}else{this.element.removeClass("covered");var alertHeight=this.element.outerHeight();var headerHeight=$("#openassessment_editor_header",this.editorElement).outerHeight();var heightString="Calc(100% - "+(alertHeight+headerHeight)+"px)";var styles={height:heightString,"border-top-right-radius":"0px","border-top-left-radius":"0px"};$(".oa_editor_content_wrapper",this.editorElement).each(function(){$(this).css(styles)})}return this},setMessage:function(newTitle,newMessage){this.title.text(newTitle);this.message.text(newMessage);return this},isVisible:function(){return!this.element.hasClass("covered")},getTitle:function(){return this.title.text()},getMessage:function(){return this.message.text()}}; }return sel.val()},booleanField:function(sel,value){if(value!==undefined){sel.prop("checked",value)}return sel.prop("checked")}};OpenAssessment.IntField=function(inputSel,restrictions){this.max=restrictions.max;this.min=restrictions.min;this.input=$(inputSel)};OpenAssessment.IntField.prototype={get:function(){return parseInt(this.input.val().trim(),10)},set:function(val){this.input.val(val)},validate:function(){var value=this.get();var isValid=!isNaN(value)&&value>=this.min&&value<=this.max;if(this.input.val().indexOf(".")!==-1){isValid=false}if(!isValid){this.input.addClass("openassessment_highlighted_field")}return isValid},clearValidationErrors:function(){this.input.removeClass("openassessment_highlighted_field")},validationErrors:function(){var hasError=this.input.hasClass("openassessment_highlighted_field");return hasError?["Int field is invalid"]:[]}};OpenAssessment.ToggleControl=function(checkboxSel,shownSel,hiddenSel,notifier){this.checkbox=checkboxSel;this.shownSection=shownSel;this.hiddenSection=hiddenSel;this.notifier=notifier};OpenAssessment.ToggleControl.prototype={install:function(){this.checkbox.change(this,function(event){var control=event.data;if(this.checked){control.notifier.notificationFired("toggleOn",{});control.show()}else{control.notifier.notificationFired("toggleOff",{});control.hide()}});return this},show:function(){this.shownSection.removeClass("is--hidden");this.hiddenSection.addClass("is--hidden")},hide:function(){this.shownSection.addClass("is--hidden");this.hiddenSection.removeClass("is--hidden")}};OpenAssessment.DatetimeControl=function(element,datePicker,timePicker){this.element=element;this.datePicker=datePicker;this.timePicker=timePicker};OpenAssessment.DatetimeControl.prototype={install:function(){var dateString=$(this.datePicker,this.element).val();$(this.datePicker,this.element).datepicker({showButtonPanel:true}).datepicker("option","dateFormat","yy-mm-dd").datepicker("setDate",dateString);$(this.timePicker,this.element).timepicker({timeFormat:"H:i",step:60});return this},datetime:function(dateString,timeString){var datePickerSel=$(this.datePicker,this.element);var timePickerSel=$(this.timePicker,this.element);if(typeof dateString!=="undefined"){datePickerSel.val(dateString)}if(typeof timeString!=="undefined"){timePickerSel.val(timeString)}return datePickerSel.val()+"T"+timePickerSel.val()},validate:function(){var dateString=$(this.datePicker,this.element).val();var timeString=$(this.timePicker,this.element).val();var isDateValid=false;try{var parsedDate=$.datepicker.parseDate($.datepicker.ISO_8601,dateString);isDateValid=parsedDate instanceof Date}catch(err){}if(!isDateValid){$(this.datePicker,this.element).addClass("openassessment_highlighted_field")}var matches=timeString.match(/^\d{2}:\d{2}$/g);var isTimeValid=matches!==null;if(!isTimeValid){$(this.timePicker,this.element).addClass("openassessment_highlighted_field")}return isDateValid&&isTimeValid},clearValidationErrors:function(){$(this.datePicker,this.element).removeClass("openassessment_highlighted_field");$(this.timePicker,this.element).removeClass("openassessment_highlighted_field")},validationErrors:function(){var errors=[];var dateHasError=$(this.datePicker,this.element).hasClass("openassessment_highlighted_field");var timeHasError=$(this.timePicker,this.element).hasClass("openassessment_highlighted_field");if(dateHasError){errors.push("Date is invalid")}if(timeHasError){errors.push("Time is invalid")}return errors}};OpenAssessment.SelectControl=function(selectSel,mapping,notifier){this.select=selectSel;this.mapping=mapping;this.notifier=notifier};OpenAssessment.SelectControl.prototype={install:function(){this.select.change(this,function(event){var control=event.data;control.notifier.notificationFired("selectionChanged",{selected:this.value});control.change(this.value)});return this},change:function(selected){$.each(this.mapping,function(option,sel){if(option===selected){sel.removeClass("is--hidden")}else{sel.addClass("is--hidden")}})}};OpenAssessment.InputControl=function(inputSel,validator){this.input=$(inputSel);this.validator=validator;this.errors=[]};OpenAssessment.InputControl.prototype={get:function(){return this.input.val()},set:function(val){this.input.val(val)},validate:function(){this.errors=this.validator(this.get());if(this.errors.length){this.input.addClass("openassessment_highlighted_field");this.input.parent().nextAll(".message-status").text(this.errors.join(";"));this.input.parent().nextAll(".message-status").addClass("is-shown")}return this.errors.length===0},clearValidationErrors:function(){this.input.removeClass("openassessment_highlighted_field");this.input.parent().nextAll(".message-status").removeClass("is-shown")},validationErrors:function(){return this.errors}};OpenAssessment.StudentTrainingListener=function(){this.element=$("#oa_student_training_editor");this.alert=new OpenAssessment.ValidationAlert};OpenAssessment.StudentTrainingListener.prototype={promptAdd:function(){var view=this.element;$("#openassessment_training_example_part_template").children().first().clone().removeAttr("id").toggleClass("is--hidden",false).appendTo(".openassessment_training_example_essay",view)},promptRemove:function(data){var view=this.element;$(".openassessment_training_example_essay li:nth-child("+(data.index+1)+")",view).remove()},optionUpdated:function(data){this._optionSel(data.criterionName).each(function(){var criterion=this;var option=$('option[value="'+data.name+'"]',criterion).attr("data-points",data.points).attr("data-label",data.label);OpenAssessment.ItemUtilities.refreshOptionString(option)})},optionAdd:function(data){var criterionAdded=false;if(this._optionSel(data.criterionName).length===0){this.criterionAdd(data);criterionAdded=true}this._optionSel(data.criterionName).each(function(){var criterion=this;var option=$("<option></option>").attr("value",data.name).attr("data-points",data.points).attr("data-label",data.label);OpenAssessment.ItemUtilities.refreshOptionString(option);$(criterion).append(option)});if(criterionAdded){this.displayAlertMsg(gettext("Criterion Added"),gettext("You have added a criterion. You will need to select an option for the criterion in the Learner Training step. To do this, click the Settings tab."))}},optionRemove:function(data){var handler=this;var invalidated=false;this._optionSel(data.criterionName).each(function(){var criterionOption=this;if($(criterionOption).val()===data.name.toString()){$(criterionOption).val("").addClass("openassessment_highlighted_field").click(function(){$(criterionOption).removeClass("openassessment_highlighted_field")});invalidated=true}$('option[value="'+data.name+'"]',criterionOption).remove();if($("option",criterionOption).length===1){handler.removeAllOptions(data);invalidated=false}});if(invalidated){this.displayAlertMsg(gettext("Option Deleted"),gettext("You have deleted an option. That option has been removed from its criterion in the sample responses in the Learner Training step. You might have to select a new option for the criterion."))}},_optionSel:function(criterionName){return $('.openassessment_training_example_criterion_option[data-criterion="'+criterionName+'"]',this.element)},removeAllOptions:function(data){var changed=false;$(".openassessment_training_example_criterion",this.element).each(function(){var criterion=this;if($(criterion).data("criterion")===data.criterionName){$(criterion).remove();changed=true}});if(changed){this.displayAlertMsg(gettext("Option Deleted"),gettext("You have deleted all the options for this criterion. The criterion has been removed from the sample responses in the Learner Training step."))}},criterionRemove:function(data){var changed=false;var sel='.openassessment_training_example_criterion[data-criterion="'+data.criterionName+'"]';$(sel,this.element).each(function(){$(this).remove();changed=true});if(changed){this.displayAlertMsg(gettext("Criterion Deleted"),gettext("You have deleted a criterion. The criterion has been removed from the example responses in the Learner Training step."))}},displayAlertMsg:function(title,msg){if($("#include_student_training",this.element).is(":checked")&&$(".openassessment_training_example",this.element).length>1){this.alert.setMessage(title,msg).show()}},criterionUpdated:function(data){var sel='.openassessment_training_example_criterion[data-criterion="'+data.criterionName+'"]';$(sel,this.element).each(function(){$(".openassessment_training_example_criterion_name_wrapper",this).text(data.criterionLabel)})},criterionAdd:function(data){var view=this.element;var criterion=$("#openassessment_training_example_criterion_template").children().first().clone().removeAttr("id").attr("data-criterion",data.criterionName).toggleClass("is--hidden",false).appendTo(".openassessment_training_example_criteria_selections",view);criterion.find(".openassessment_training_example_criterion_option").attr("data-criterion",data.criterionName);criterion.find(".openassessment_training_example_criterion_name_wrapper").text(data.label)},examplesCriteriaLabels:function(){var examples=[];$(".openassessment_training_example_criteria_selections",this.element).each(function(){var exampleDescription={};$(".openassessment_training_example_criterion",this).each(function(){var criterionName=$(this).data("criterion");var criterionLabel=$(".openassessment_training_example_criterion_name_wrapper",this).text().trim();exampleDescription[criterionName]=criterionLabel});examples.push(exampleDescription)});return examples},examplesOptionsLabels:function(){var examples=[];$(".openassessment_training_example_criteria_selections",this.element).each(function(){var exampleDescription={};$(".openassessment_training_example_criterion_option",this).each(function(){var criterionName=$(this).data("criterion");exampleDescription[criterionName]={};$("option",this).each(function(){var optionName=$(this).val();var optionLabel=$(this).text().trim();exampleDescription[criterionName][optionName]=optionLabel})});examples.push(exampleDescription)});return examples}};OpenAssessment.AssessmentToggleListener=function(){this.alert=new OpenAssessment.ValidationAlert};OpenAssessment.AssessmentToggleListener.prototype={toggleOff:function(){this.alert.setMessage(gettext("Warning"),gettext("Changes to steps that are not selected as part of the assignment will not be saved.")).show()},toggleOn:function(){this.alert.hide()}};OpenAssessment.Notifier=function(listeners){this.listeners=listeners};OpenAssessment.Notifier.prototype={notificationFired:function(name,data){for(var i=0;i<this.listeners.length;i++){if(typeof this.listeners[i][name]==="function"){this.listeners[i][name](data)}}}};OpenAssessment.EditPromptsView=function(element,notifier){this.element=element;this.editorElement=$(this.element).closest("#openassessment-editor");this.addRemoveEnabled=this.editorElement.attr("data-is-released")!=="true";this.promptsContainer=new OpenAssessment.Container(OpenAssessment.Prompt,{containerElement:$("#openassessment_prompts_list",this.element).get(0),templateElement:$("#openassessment_prompt_template",this.element).get(0),addButtonElement:$("#openassessment_prompts_add_prompt",this.element).get(0),removeButtonClass:"openassessment_prompt_remove_button",containerItemClass:"openassessment_prompt",notifier:notifier,addRemoveEnabled:this.addRemoveEnabled});this.promptsContainer.addEventListeners()};OpenAssessment.EditPromptsView.prototype={promptsDefinition:function(){var prompts=this.promptsContainer.getItemValues();return prompts},addPrompt:function(){if(this.addRemoveEnabled){this.promptsContainer.add()}},removePrompt:function(item){if(this.addRemoveEnabled){this.promptsContainer.remove(item)}},getAllPrompts:function(){return this.promptsContainer.getAllItems()},getPromptItem:function(index){return this.promptsContainer.getItem(index)},validate:function(){return true},validationErrors:function(){var errors=[];return errors},clearValidationErrors:function(){}};OpenAssessment.EditRubricView=function(element,notifier){this.element=element;this.criterionAddButton=$("#openassessment_rubric_add_criterion",this.element);this.criteriaContainer=new OpenAssessment.Container(OpenAssessment.RubricCriterion,{containerElement:$("#openassessment_criterion_list",this.element).get(0),templateElement:$("#openassessment_criterion_template",this.element).get(0),addButtonElement:$("#openassessment_rubric_add_criterion",this.element).get(0),removeButtonClass:"openassessment_criterion_remove_button",containerItemClass:"openassessment_criterion",notifier:notifier});this.criteriaContainer.addEventListeners()};OpenAssessment.EditRubricView.prototype={criteriaDefinition:function(){var criteria=this.criteriaContainer.getItemValues();for(var criterion_idx=0;criterion_idx<criteria.length;criterion_idx++){var criterion=criteria[criterion_idx];criterion.order_num=criterion_idx;for(var option_idx=0;option_idx<criterion.options.length;option_idx++){var option=criterion.options[option_idx];option.order_num=option_idx}}return criteria},feedbackPrompt:function(text){var sel=$("#openassessment_rubric_feedback",this.element);return OpenAssessment.Fields.stringField(sel,text)},feedback_default_text:function(text){var sel=$("#openassessment_rubric_feedback_default_text",this.element);return OpenAssessment.Fields.stringField(sel,text)},addCriterion:function(){this.criteriaContainer.add()},removeCriterion:function(item){this.criteriaContainer.remove(item)},getAllCriteria:function(){return this.criteriaContainer.getAllItems()},getCriterionItem:function(index){return this.criteriaContainer.getItem(index)},addOption:function(criterionIndex){var criterionItem=this.getCriterionItem(criterionIndex);criterionItem.optionContainer.add()},removeOption:function(criterionIndex,item){var criterionItem=this.getCriterionItem(criterionIndex);criterionItem.optionContainer.remove(item)},getAllOptions:function(criterionIndex){var criterionItem=this.getCriterionItem(criterionIndex);return criterionItem.optionContainer.getAllItems()},getOptionItem:function(criterionIndex,optionIndex){var criterionItem=this.getCriterionItem(criterionIndex);return criterionItem.optionContainer.getItem(optionIndex)},validate:function(){var criteria=this.getAllCriteria();var isValid=criteria.length>0;if(!isValid){this.criterionAddButton.addClass("openassessment_highlighted_field").click(function(){$(this).removeClass("openassessment_highlighted_field")})}$.each(criteria,function(){isValid=this.validate()&&isValid});return isValid},validationErrors:function(){var errors=[];if(this.criterionAddButton.hasClass("openassessment_highlighted_field")){errors.push("The rubric must contain at least one criterion")}$.each(this.getAllCriteria(),function(){errors=errors.concat(this.validationErrors())});return errors},clearValidationErrors:function(){this.criterionAddButton.removeClass("openassessment_highlighted_field");$.each(this.getAllCriteria(),function(){this.clearValidationErrors()})}};OpenAssessment.EditSettingsView=function(element,assessmentViews,data){this.settingsElement=element;this.assessmentsElement=$(element).siblings("#openassessment_assessment_module_settings_editors").get(0);this.assessmentViews=assessmentViews;this.startDatetimeControl=new OpenAssessment.DatetimeControl(this.element,"#openassessment_submission_start_date","#openassessment_submission_start_time").install();this.dueDatetimeControl=new OpenAssessment.DatetimeControl(this.element,"#openassessment_submission_due_date","#openassessment_submission_due_time").install();new OpenAssessment.SelectControl($("#openassessment_submission_upload_selector",this.element),{custom:$("#openassessment_submission_white_listed_file_types_wrapper",this.element)},new OpenAssessment.Notifier([new OpenAssessment.AssessmentToggleListener])).install();this.leaderboardIntField=new OpenAssessment.IntField($("#openassessment_leaderboard_editor",this.element),{min:0,max:100});this.fileTypeWhiteListInputField=new OpenAssessment.InputControl($("#openassessment_submission_white_listed_file_types",this.element),function(value){var badExts=[];var errors=[];if(!value){errors.push(gettext("File types can not be empty."));return errors}var whiteList=$.map(value.replace(/\./g,"").toLowerCase().split(","),$.trim);$.each(whiteList,function(index,ext){if(data.FILE_EXT_BLACK_LIST.indexOf(ext)!==-1){badExts.push(ext)}});if(badExts.length){errors.push(gettext("The following file types are not allowed: ")+badExts.join(","))}return errors});this.initializeSortableAssessments()};OpenAssessment.EditSettingsView.prototype={initializeSortableAssessments:function(){var view=this;$("#openassessment_assessment_module_settings_editors",view.element).sortable({start:function(event,ui){$(".openassessment_assessment_module_editor",view.element).hide();var targetHeight="auto";ui.placeholder.height(targetHeight);ui.helper.height(targetHeight);$("#openassessment_assessment_module_settings_editors",view.element).sortable("refresh").sortable("refreshPositions")},stop:function(){$(".openassessment_assessment_module_editor",view.element).show()},snap:true,axis:"y",handle:".drag-handle",cursorAt:{top:20}});$("#openassessment_assessment_module_settings_editors .drag-handle",view.element).disableSelection()},displayName:function(name){var sel=$("#openassessment_title_editor",this.settingsElement);return OpenAssessment.Fields.stringField(sel,name)},submissionStart:function(dateString,timeString){return this.startDatetimeControl.datetime(dateString,timeString)},submissionDue:function(dateString,timeString){return this.dueDatetimeControl.datetime(dateString,timeString)},fileUploadType:function(uploadType){var sel=$("#openassessment_submission_upload_selector",this.settingsElement);if(uploadType!==undefined){sel.val(uploadType)}return sel.val()},fileTypeWhiteList:function(exts){if(exts!==undefined){this.fileTypeWhiteListInputField.set(exts)}return this.fileTypeWhiteListInputField.get()},latexEnabled:function(isEnabled){var sel=$("#openassessment_submission_latex_editor",this.settingsElement);if(isEnabled!==undefined){if(isEnabled){sel.val(1)}else{sel.val(0)}}return sel.val()===1},leaderboardNum:function(num){if(num!==undefined){this.leaderboardIntField.set(num)}return this.leaderboardIntField.get(num)},assessmentsDescription:function(){var assessmentDescList=[];var view=this;$(".openassessment_assessment_module_settings_editor",this.assessmentsElement).each(function(){var asmntView=view.assessmentViews[$(this).attr("id")];if(asmntView.isEnabled()){var description=asmntView.description();description.name=asmntView.name;assessmentDescList.push(description)}});return assessmentDescList},editorAssessmentsOrder:function(){var editorAssessments=[];var view=this;$(".openassessment_assessment_module_settings_editor",this.assessmentsElement).each(function(){var asmntView=view.assessmentViews[$(this).attr("id")];editorAssessments.push(asmntView.name)});return editorAssessments},validate:function(){var isValid=true;isValid=this.startDatetimeControl.validate()&&isValid;isValid=this.dueDatetimeControl.validate()&&isValid;isValid=this.leaderboardIntField.validate()&&isValid;if(this.fileUploadType()==="custom"){isValid=this.fileTypeWhiteListInputField.validate()&&isValid}else{if(this.fileTypeWhiteListInputField.get()&&!this.fileTypeWhiteListInputField.validate()){this.fileTypeWhiteListInputField.set("")}}$.each(this.assessmentViews,function(){if(this.isEnabled()){isValid=this.validate()&&isValid}});return isValid},validationErrors:function(){var errors=[];if(this.startDatetimeControl.validationErrors().length>0){errors.push("Submission start is invalid")}if(this.dueDatetimeControl.validationErrors().length>0){errors.push("Submission due is invalid")}if(this.leaderboardIntField.validationErrors().length>0){errors.push("Leaderboard number is invalid")}if(this.fileTypeWhiteListInputField.validationErrors().length>0){errors=errors.concat(this.fileTypeWhiteListInputField.validationErrors())}$.each(this.assessmentViews,function(){errors=errors.concat(this.validationErrors())});return errors},clearValidationErrors:function(){this.startDatetimeControl.clearValidationErrors();this.dueDatetimeControl.clearValidationErrors();this.leaderboardIntField.clearValidationErrors();this.fileTypeWhiteListInputField.clearValidationErrors();$.each(this.assessmentViews,function(){this.clearValidationErrors()})}};OpenAssessment.ValidationAlert=function(){this.element=$("#openassessment_validation_alert");this.editorElement=$(this.element).parent();this.title=$(".openassessment_alert_title",this.element);this.message=$(".openassessment_alert_message",this.element);this.closeButton=$(".openassessment_alert_close",this.element);this.ALERT_YELLOW="rgb(192, 172, 0)";this.DARK_GREY="#323232"};OpenAssessment.ValidationAlert.prototype={install:function(){var alert=this;this.closeButton.click(function(eventObject){eventObject.preventDefault();alert.hide()});return this},hide:function(){var headerHeight=$("#openassessment_editor_header",this.editorElement).outerHeight();this.element.addClass("covered");var styles={height:"Calc(100% - "+headerHeight+"px)","border-top-right-radius":"3px","border-top-left-radius":"3px"};$(".oa_editor_content_wrapper",this.editorElement).each(function(){$(this).css(styles)});return this},show:function(){var view=this;if(this.isVisible()){$(this.element).animate({"background-color":view.ALERT_YELLOW},300,"swing",function(){$(this).animate({"background-color":view.DARK_GREY},700,"swing")})}else{this.element.removeClass("covered");var alertHeight=this.element.outerHeight();var headerHeight=$("#openassessment_editor_header",this.editorElement).outerHeight();var heightString="Calc(100% - "+(alertHeight+headerHeight)+"px)";var styles={height:heightString,"border-top-right-radius":"0px","border-top-left-radius":"0px"};$(".oa_editor_content_wrapper",this.editorElement).each(function(){$(this).css(styles)})}return this},setMessage:function(newTitle,newMessage){this.title.text(newTitle);this.message.text(newMessage);return this},isVisible:function(){return!this.element.hasClass("covered")},getTitle:function(){return this.title.text()},getMessage:function(){return this.message.text()}};
\ No newline at end of file
...@@ -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,6 +162,26 @@ describe('OpenAssessment.StaffAreaView', function() { ...@@ -165,6 +162,26 @@ describe('OpenAssessment.StaffAreaView', function() {
}); });
}); });
describe('Student Info', function() {
var chooseStudent = function(view, studentName) {
var studentNameField = $('.openassessment__student_username', view.element),
submitButton = $('.action--submit-username', view.element);
studentNameField.val(studentName);
submitButton.click();
};
beforeEach(function() {
loadFixtures('oa_base_course_staff.html');
appendLoadFixtures('oa_student_info.html');
});
it('shows an error when clicking "Submit" with no student name chosen', function() {
var staffArea = createStaffArea();
chooseStudent(staffArea, '');
expect($('.openassessment_student_info_form .form--error', staffArea.element).text().trim())
.toBe('A learner name must be provided.');
});
describe('Submission Management', function() { describe('Submission Management', function() {
it('updates submission cancellation button when comments changes', function() { it('updates submission cancellation button when comments changes', function() {
// Prevent the server's response from resolving, // Prevent the server's response from resolving,
...@@ -173,40 +190,38 @@ describe('OpenAssessment.StaffAreaView', function() { ...@@ -173,40 +190,38 @@ describe('OpenAssessment.StaffAreaView', function() {
return $.Deferred(function() {}).promise(); return $.Deferred(function() {}).promise();
}); });
// Load the fixture var staffArea = createStaffArea();
loadFixtures('oa_student_info.html'); chooseStudent(staffArea, 'testStudent');
var view = createStaffArea();
// comments is blank --> cancel submission button disabled // comments is blank --> cancel submission button disabled
view.comment(''); staffArea.comment('');
view.handleCommentChanged(); staffArea.handleCommentChanged();
expect(view.cancelSubmissionEnabled()).toBe(false); expect(staffArea.cancelSubmissionEnabled()).toBe(false);
// Response is whitespace --> cancel submission button disabled // Response is whitespace --> cancel submission button disabled
view.comment(' \n \n '); staffArea.comment(' \n \n ');
view.handleCommentChanged(); staffArea.handleCommentChanged();
expect(view.cancelSubmissionEnabled()).toBe(false); expect(staffArea.cancelSubmissionEnabled()).toBe(false);
// Response is not blank --> cancel submission button enabled // Response is not blank --> cancel submission button enabled
view.comment('Cancellation reason.'); staffArea.comment('Cancellation reason.');
view.handleCommentChanged(); staffArea.handleCommentChanged();
expect(view.cancelSubmissionEnabled()).toBe(true); expect(staffArea.cancelSubmissionEnabled()).toBe(true);
}); });
it('submits the cancel submission comments to the server', function() { it('submits the cancel submission comments to the server', function() {
spyOn(server, 'cancelSubmission').and.callThrough(); spyOn(server, 'cancelSubmission').and.callThrough();
// Load the fixture var staffArea = createStaffArea();
loadFixtures('oa_student_info.html'); chooseStudent(staffArea, 'testStudent');
var view = createStaffArea();
view.comment('Cancellation reason.'); staffArea.comment('Cancellation reason.');
view.cancelSubmission('Bob'); staffArea.cancelSubmission('Bob');
expect(server.cancelSubmission).toHaveBeenCalledWith('Bob', 'Cancellation reason.'); expect(server.cancelSubmission).toHaveBeenCalledWith('Bob', 'Cancellation reason.');
}); });
}); });
});
describe('Staff Toolbar', function() { describe('Staff Toolbar', function() {
beforeEach(function() { beforeEach(function() {
......
...@@ -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) {
$form.find('.form--error').text(errorMessage);
};
var deferred = $.Deferred();
// Clear any previous student information
$('.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 // Load the HTML and install event handlers
$('#openassessment__student-info', view.element).replaceWith(html); $('.openassessment__student-info', view.element).replaceWith(html);
// Install key handler for new staff grade Save button. // Install key handler for cancel submission button.
var selCancelSub = $('#openassessment__staff-info__cancel__submission', view.element); $staffTools.on('click', '.action--submit-cancel-submission', function(eventObject) {
selCancelSub.on('click', '#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,35 +22,22 @@ if (typeof OpenAssessment.Server === "undefined" || !OpenAssessment.Server) { ...@@ -26,35 +22,22 @@ 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);
...@@ -63,20 +46,21 @@ if (typeof OpenAssessment.Server === "undefined" || !OpenAssessment.Server) { ...@@ -63,20 +46,21 @@ if (typeof OpenAssessment.Server === "undefined" || !OpenAssessment.Server) {
url: url, url: url,
type: "POST", type: "POST",
dataType: "html" dataType: "html"
}).done(function(data) { })
.done(function(data) {
defer.resolveWith(view, [data]); defer.resolveWith(view, [data]);
}).fail(function() { })
.fail(function() {
defer.rejectWith(view, [gettext('This section could not be loaded.')]); 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,15 +116,13 @@ if (typeof OpenAssessment.Server === "undefined" || !OpenAssessment.Server) { ...@@ -135,15 +116,13 @@ 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) {
...@@ -152,7 +131,8 @@ if (typeof OpenAssessment.Server === "undefined" || !OpenAssessment.Server) { ...@@ -152,7 +131,8 @@ if (typeof OpenAssessment.Server === "undefined" || !OpenAssessment.Server) {
url: url, url: url,
data: JSON.stringify({submission: submission}), data: JSON.stringify({submission: submission}),
contentType: jsonContentType contentType: jsonContentType
}).done(function(data) { })
.done(function(data) {
var success = data[0]; var success = data[0];
if (success) { if (success) {
var studentId = data[1]; var studentId = data[1];
...@@ -164,22 +144,20 @@ if (typeof OpenAssessment.Server === "undefined" || !OpenAssessment.Server) { ...@@ -164,22 +144,20 @@ if (typeof OpenAssessment.Server === "undefined" || !OpenAssessment.Server) {
var errorMsg = data[2]; var errorMsg = data[2];
defer.rejectWith(this, [errorNum, errorMsg]); defer.rejectWith(this, [errorNum, errorMsg]);
} }
}).fail(function() { })
.fail(function() {
defer.rejectWith(this, ["AJAX", gettext("This response could not be submitted.")]); 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) {
...@@ -188,33 +166,24 @@ if (typeof OpenAssessment.Server === "undefined" || !OpenAssessment.Server) { ...@@ -188,33 +166,24 @@ if (typeof OpenAssessment.Server === "undefined" || !OpenAssessment.Server) {
url: url, url: url,
data: JSON.stringify({submission: submission}), data: JSON.stringify({submission: submission}),
contentType: jsonContentType contentType: jsonContentType
}).done(function(data) { })
.done(function(data) {
if (data.success) { defer.resolve(); } if (data.success) { defer.resolve(); }
else { defer.rejectWith(this, [data.msg]); } else { defer.rejectWith(this, [data.msg]); }
}).fail(function() { })
.fail(function() {
defer.rejectWith(this, [gettext("This response could not be saved.")]); 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.
* @returns {promise} A JQuery promise, which resolves with no args if successful and
* fails with an error message otherwise. * 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
}).done(function(data) {
if (data.success) { if (data.success) {
defer.resolve(); defer.resolve();
} }
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.')]);
}); });
}).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 { 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.')]);
}); });
}); });
}, },
/** /**
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}
).done(function(data) {
if (data.success) { if (data.success) {
defer.resolveWith(this, [data.msg]); defer.resolveWith(this, [data.msg]);
} }
else { else {
defer.rejectWith(this, [data.msg]); 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) { })
.done(function(data) {
if (data.success) { defer.resolve(); } if (data.success) { defer.resolve(); }
else { defer.rejectWith(this, [data.msg]); } else { defer.rejectWith(this, [data.msg]); }
}).fail(function() { })
.fail(function() {
defer.rejectWith(this, [gettext('This problem could not be saved.')]); 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) { })
.done(function(data) {
if (data.success) { defer.resolveWith(this, [data.is_released]); } if (data.success) { defer.resolveWith(this, [data.is_released]); }
else { defer.rejectWith(this, [data.msg]); } else { defer.rejectWith(this, [data.msg]); }
}).fail(function() { })
.fail(function() {
defer.rejectWith(this, [gettext("The server could not be contacted.")]); 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
}).done(function(data) {
if (data.success) { if (data.success) {
defer.resolveWith(this, [data.msg]); defer.resolveWith(this, [data.msg]);
} }
else { }).fail(function() {
defer.rejectWith(this, [data.msg]);
}
}
).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;
// 'p' elements in LMS have a color set on them. .action--submit {
.student__answer__display__content p { @extend .action--submit;
color: inherit; }
}
.staff-assessment__display {
@extend %ui-subsection;
}
.staff-assessment__display__header {
@include clearfix();
span {
@extend %t-strong; // FIX: needed due to DOM structure
}
.staff-assessment__display__title {
@extend %t-heading;
margin-bottom: ($baseline-v/2);
color: $heading-secondary-color;
}
}
.staff-assessment__display__response {
@extend %ui-subsection-content;
@extend %copy-3;
@extend %ui-content-longanswer;
@extend %ui-well;
color: $copy-color;
}
// assessment form
.staff-assessment__assessment {
// fields
.assessment__fields {
margin-bottom: $baseline-v;
}
// rubric question
.assessment__rubric__question {
@extend %ui-rubric-question;
}
// rubric options
.question__answers {
@extend %ui-rubric-answers;
}
// general feedback question
.assessment__rubric__question--feedback {
.wrapper--input {
margin-top: $baseline-v;
}
.question__title__copy {
@include margin-left(0);
white-space: pre-wrap;
}
textarea {
@extend %ui-content-longanswer;
min-height: ($baseline-v*5);
}
}
} }
} }
...@@ -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:
submission_uuid = self.submission_uuid
if not submission_uuid:
return {} 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,20 +90,13 @@ class OpenAssessmentTest(WebAppTest): ...@@ -90,20 +90,13 @@ 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):
class SelfAssessmentTest(OpenAssessmentTest):
""" """
Test the self-assessment flow. Submits a self assessment, verifies the grade, and returns the username of the student
for which the self assessment was submitted.
""" """
def setUp(self):
super(SelfAssessmentTest, self).setUp('self_only')
@retry()
@attr('acceptance')
def test_self_assessment(self):
# Submit a response
self.auto_auth_page.visit() self.auto_auth_page.visit()
username = self.auto_auth_page.get_username()
self.submission_page.visit().submit_response(self.SUBMISSION) self.submission_page.visit().submit_response(self.SUBMISSION)
self.assertTrue(self.submission_page.has_submitted) self.assertTrue(self.submission_page.has_submitted)
...@@ -116,6 +109,23 @@ class SelfAssessmentTest(OpenAssessmentTest): ...@@ -116,6 +109,23 @@ class SelfAssessmentTest(OpenAssessmentTest):
# Verify the grade # Verify the grade
self.assertEqual(self.grade_page.wait_for_page().score, self.EXPECTED_SCORE) self.assertEqual(self.grade_page.wait_for_page().score, self.EXPECTED_SCORE)
return username
class SelfAssessmentTest(OpenAssessmentTest):
"""
Test the self-assessment flow.
"""
def setUp(self):
super(SelfAssessmentTest, self).setUp('self_only')
@retry()
@attr('acceptance')
def test_self_assessment(self):
# Submit a response
self.do_self_assessment()
# 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