Commit 5a530dab by Andy Armstrong

First phase of staff re-grading UI

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