Commit ac4251dd by Will Daly

Merge branch 'master' into ai-grading

Conflicts:
	apps/openassessment/xblock/static/js/fixtures/templates.json
	apps/openassessment/xblock/static/js/openassessment.min.js
	apps/openassessment/xblock/static/js/spec/oa_staff_info.js
parents 1eee3a83 10df7db7
{% load i18n %}
{% spaceless %}
<div id="openassessment__message" class="openassessment__message message">
<h3 class="message__title">
{% if not_yet_open %}
{% trans "This Task Is Not Yet Available" %}
{% else %}
{% trans "This Assignment Has Closed" %}
{% endif %}
</h3>
<div class="message__content">
<p>
{% if not_yet_open %}
{% trans "Check back to complete the assignment once this section has opened." %}
{% else %}
{% trans "One or more deadlines for this assignment have passed. You will receive an incomplete grade for this assignment." %}
{% endif %}
</p>
</div>
</div>
{% endspaceless %}
\ No newline at end of file
{% load i18n %}
{% spaceless %}
<div id="openassessment__message" class="openassessment__message message">
<h3 class="message__title">{% trans "You Have Completed This Assignment" %} </h3>
<div class="message__content">
<p>
{% if waiting %}
{% trans "Your grade will be available when your peers have completed their assessments of your response." %}
{% else %}
{% blocktrans %}
Review <a data-behavior="ui-scroll" href="#openassessment__grade"> your grade and your assessment details</a>.
{% endblocktrans %}
{% endif %}
</p>
</div>
</div>
{% endspaceless %}
\ No newline at end of file
{% load i18n %}
{% spaceless %}
<div id="openassessment__message" class="openassessment__message message">
<div class="message__content">
<p>
{% if approaching %}
{% blocktrans %}
Assignment submissions will close soon. To receive a grade, first provide a response to the question, then complete the steps below the <strong>Your Response</strong> field.
{% endblocktrans %}
{% else %}
{% blocktrans %}
This assignment has several steps. In the first step, you'll provide a response to the question. The other steps appear below the <strong>Your Response</strong> field.
{% endblocktrans %}
{% endif %}
</p>
</div>
</div>
{% endspaceless %}
\ No newline at end of file
{% load i18n %}
{% spaceless %}
<div id="openassessment__message" class="openassessment__message message">
<h3 class="message__title">
{% if waiting %}
{% trans "Waiting for Peer Submissions" %}
{% elif peer_not_released %}
{% trans "The Period For Peer Evaluation Has Not Started" %}
{% else %}
{% trans "Your Response Has Been Submitted For Peer Assessment" %}
{% endif %}
</h3>
<div class="message__content">
<p>
{% if peer_not_released %}
{% trans "Check back later when the assessment period has opened." %}
{% else %}
{% if peer_approaching %}
<strong> {% trans "Peer evaluation of this assignment will close soon. " %} </strong>
{% endif %}
{% if waiting %}
{% trans "All submitted peer responses have been assessed. Check back later to see if more students have submitted responses. " %}
{% endif %}
{% if has_self %}
{% blocktrans %}
You'll receive your grade after you complete the <a data-behavior="ui-scroll" href=#openassessment__peer-assessment">peer assessment</a> and <a data-behavior="ui-scroll" href="#openassessment__self-assessment">self assessment</a> steps, and after your peers have assessed your response.
{% endblocktrans %}
{% else %}
{% blocktrans %}
You'll receive your grade after you complete the <a data-behavior="ui-scroll" href="#openassessment__peer-assessment">peer assessment</a> step.
{% endblocktrans %}
{% endif %}
{% endif %}
</p>
</div>
</div>
{% endspaceless %}
\ No newline at end of file
{% load i18n %}
{% spaceless %}
<div id="openassessment__message" class="openassessment__message message">
<h3 class="message__title">
{% if self_not_released %}
{% trans "The Period For Self Evaluation Has Not Started" %}
{% elif has_peer %}
{% trans "Your Response Has Been Submitted For Peer Assessment" %}
{% else %}
{% trans "Your Response Is Ready For Self Assessment" %}
{% endif %}
</h3>
<div class="message__content">
<p>
{% if self_not_released %}
{% trans "Check back later when the assessment period has opened." %}
{% else %}
{% if self_approaching %}
<strong> {% trans "Self evaluation of this assignment will close soon. " %} </strong>
{% endif %}
{% if has_peer %}
{% blocktrans %}
You'll receive your grade after the required number of your peers have assessed your response and you complete the <a data-behavior="ui-scroll" href="#openassessment__self-assessment">self assessment</a> step.
{% endblocktrans %}
{% else %}
{% blocktrans %}
You'll receive your grade after you complete the <a data-behavior="ui-scroll" href="#openassessment__self-assessment">self assessment</a> step.
{% endblocktrans %}
{% endif %}
{% endif %}
</p>
</div>
</div>
{% endspaceless %}
\ No newline at end of file
{% load i18n %}
{% spaceless %}
<div id="openassessment__message" class="openassessment__message message">
<div class="message__content">
<p>
{% if approaching %}
{% trans "Student training for peer assessment will close soon. " %}
{% endif %}
{% trans "Complete the student training section to move on to peer assessment." %}
</p>
</div>
</div>
{% endspaceless %}
\ No newline at end of file
{% load i18n %}
{% spaceless %}
<div id="openassessment__message" class="openassessment__message message">
<h3 class="message__title">{% trans "Instructions Unavailable" %} </h3>
<div class="message__content">
<p> {% trans "The instructions for this step could not be loaded." %}</p>
</div>
</div>
{% endspaceless %}
\ No newline at end of file
...@@ -20,14 +20,16 @@ ...@@ -20,14 +20,16 @@
</ol> </ol>
</nav> </nav>
{% block message %} <div class="wrapper-openassessment__message">
<!-- if the problem is unstarted or response hasn't been submitted --> {% block message %}
<div id="openassessment__message" class="openassessment__message message"> <div id="openassessment__message" class="openassessment__message message">
<div class="message__content"> <h3 class="message__title">Instructions</h3>
<p>{% trans "This assignment has several steps. In the first step, you'll provide a response to the question. The other steps appear below the <strong>Your Response</strong> field." %}</p> <div class="message__content">
<p>{% trans "This assignment has several steps. In the first step, you'll provide a response to the question. The other steps appear below the Your Response field." %}</p>
</div>
</div> </div>
{% endblock %}
</div> </div>
{% endblock %}
<div class="wrapper--openassessment__prompt"> <div class="wrapper--openassessment__prompt">
{% if question %} {% if question %}
...@@ -65,7 +67,7 @@ ...@@ -65,7 +67,7 @@
{% endfor %} {% endfor %}
</ol> </ol>
{% if is_course_staff %} {% if show_staff_debug_info %}
<div id="openassessment__staff-info"></div> <div id="openassessment__staff-info"></div>
{% endif %} {% endif %}
</div> </div>
......
"""
Message step in the OpenAssessment XBlock.
"""
import datetime as dt
import pytz
from xblock.core import XBlock
class MessageMixin(object):
"""
Message Mixin introduces all handlers for displaying the banner message
MessageMixin is a Mixin for the OpenAssessmentBlock. Functions in the
MessageMixin call into the OpenAssessmentBlock functions and will not work
outside of OpenAssessmentBlock.
"""
@XBlock.handler
def render_message(self, data, suffix=''):
"""
Render the message step.
Args:
data: Not used.
Kwargs:
suffix: Not used.
Returns:
unicode: HTML content of the message banner.
"""
# Retrieve the status of the workflow and information about deadlines.
workflow = self.get_workflow_info()
deadline_info = self._get_deadline_info()
# Finds the cannonical status of the workflow and the is_closed status of the problem
status = workflow.get('status')
is_closed = deadline_info.get('general').get('is_closed')
# Finds the status_information which describes the closed status of the current step (defaults to submission)
status_info = deadline_info.get(status, deadline_info.get("submission"))
status_is_closed = status_info.get('is_closed')
# Default context is empty
context = {}
# Default path leads to an "instruction-unavailable" block
path = 'openassessmentblock/message/oa_message_unavailable.html'
# Render the instruction message based on the status of the workflow
# and the closed status.
if status == "done" or status == "waiting":
path, context = self.render_message_complete(status)
elif is_closed or status_is_closed:
path, context = self.render_message_closed(status_info)
elif status == "self":
path, context = self.render_message_self(deadline_info)
elif status == "peer":
path, context = self.render_message_peer(deadline_info)
elif status == "training":
path, context = self.render_message_training(deadline_info)
elif status is None:
path, context = self.render_message_open(deadline_info)
return self.render_assessment(path, context)
def render_message_complete(self, status):
"""
Renders the "Complete" message state (Either Waiting or Done)
Args:
status (String): indicates the canonical status of the workflow
Returns:
The path (String) and context (dict) to render the "Complete" message template
"""
context = {
"waiting": (status == "waiting")
}
return 'openassessmentblock/message/oa_message_complete.html', context
def render_message_training(self, deadline_info):
"""
Renders the "Student-Training" message state (Either Waiting or Done)
Args:
status (String): indicates the canonical status of the workflow
Returns:
The path (String) and context (dict) to render the "Complete" message template
"""
approaching = deadline_info.get('training').get('approaching')
context = {
'approaching': approaching
}
return 'openassessmentblock/message/oa_message_training.html', context
def render_message_closed(self, status_info):
"""
Renders the "Closed" message state
Args:
status_info (dict): The dictionary describing the closed status of the current step
Returns:
The path (String) and context (dict) to render the "Closed" template
"""
reason = status_info.get("reason")
context = {
"not_yet_open": (reason == "start")
}
return 'openassessmentblock/message/oa_message_closed.html', context
def render_message_self(self, deadline_info):
"""
Renders the "Self" message state
Args:
deadline_info (dict): The dictionary of boolean assessment near/closed states
Returns:
The path (String) and context (dict) to render the "Self" template
"""
has_peer = 'peer-assessment' in self.assessment_steps
self_info = deadline_info.get("self")
context = {
"has_peer": has_peer,
"self_approaching": self_info.get("approaching"),
"self_closed": self_info.get("is_closed"),
"self_not_released": (self_info.get("reason") == "start")
}
return 'openassessmentblock/message/oa_message_self.html', context
def render_message_peer(self, deadline_info):
"""
Renders the "Peer" message state
Args:
deadline_info (dict): The dictionary of boolean assessment near/closed states
Returns:
The path (String) and context (dict) to render the "Peer" template
"""
#Uses a static field in the XBlock to determine if the PeerAssessment Block was able to pick up an assessment.
waiting = self.no_peers
has_self = 'self-assessment' in self.assessment_steps
peer_info = deadline_info.get("peer")
context = {
"has_self": has_self,
"waiting": waiting,
"peer_approaching": peer_info.get("approaching"),
"peer_closed": peer_info.get("is_closed"),
"peer_not_released": (peer_info.get("reason") == "start")
}
return 'openassessmentblock/message/oa_message_peer.html', context
def render_message_open(self, deadline_info):
"""
Renders the "Open" message state
Args:
deadline_info (dict): The dictionary of boolean assessment near/closed states
Returns:
The path (String) and context (dict) to render the "Open" template
"""
submission_approaching = deadline_info.get("submission").get("approaching")
context = {
"approaching": submission_approaching
}
return 'openassessmentblock/message/oa_message_open.html', context
def _get_deadline_info(self):
"""
Get detailed information about the standing of all deadlines.
Args:
None.
Returns:
dict with the following elements
"submission" : dictionary on submission closure of this^ form
"general" : dictionary on problem (all elements) closure of this^ form
"peer": dictionary on peer closure of this^ form *If Assessment has a Peer Section*
"self": dictionary on self closure of this^ form *If Assessment has a Self Section*
this^ form:
"is_closed": (bool) Indicating whether or not that section has closed
"reason": (str) The reason that the section is closed (None if !is_closed)
"approaching": (bool) Indicates whether or not the section deadline is within a day.
"""
# Methods which use datetime.deltatime to figure out if a deadline is approaching.
now = dt.datetime.utcnow().replace(tzinfo=pytz.utc)
def _is_approaching(date):
# Determines if the deadline is within one day of now. (Approaching = True if so)
delta = date - now
return delta.days == 0
# problem_info has form (is_closed, reason, start_date, due_date)
problem_info = self.is_closed()
# submission_info has form (is_closed, reason, start_date, due_date)
submission_info = self.is_closed("submission")
# The information we will always pass on to the user. Adds additional dicts on peer and self if applicable.
deadline_info = {
"submission": {
"is_closed": submission_info[0],
"reason": submission_info[1],
"approaching": _is_approaching(submission_info[3])
},
"general": {
"is_closed": problem_info[0],
"reason": problem_info[1],
"approaching": _is_approaching(problem_info[3])
}
}
has_training = 'student-training' in self.assessment_steps
if has_training:
training_info = self.is_closed("student-training")
training_dict = {
"training": {
"is_closed": training_info[0],
"reason": training_info[1],
"approaching": _is_approaching(training_info[3])
}
}
deadline_info.update(training_dict)
has_peer = 'peer-assessment' in self.assessment_steps
# peer_info has form (is_closed, reason, start_date, due_date)
if has_peer:
peer_info = self.is_closed("peer-assessment")
peer_dict = {
"peer": {
"is_closed": peer_info[0],
"reason": peer_info[1],
"approaching": _is_approaching(peer_info[3])
}
}
deadline_info.update(peer_dict)
has_self = 'self-assessment' in self.assessment_steps
# self_info has form (is_closed, reason, start_date, due_date)
if has_self:
self_info = self.is_closed("self-assessment")
self_dict = {
"self": {
"is_closed": self_info[0],
"reason": self_info[1],
"approaching": _is_approaching(self_info[3])
}
}
deadline_info.update(self_dict)
return deadline_info
...@@ -16,6 +16,7 @@ from xblock.fragment import Fragment ...@@ -16,6 +16,7 @@ from xblock.fragment import Fragment
from openassessment.xblock.grade_mixin import GradeMixin from openassessment.xblock.grade_mixin import GradeMixin
from openassessment.xblock.defaults import * # pylint: disable=wildcard-import, unused-wildcard-import from openassessment.xblock.defaults import * # pylint: disable=wildcard-import, unused-wildcard-import
from openassessment.xblock.message_mixin import MessageMixin
from openassessment.xblock.peer_assessment_mixin import PeerAssessmentMixin from openassessment.xblock.peer_assessment_mixin import PeerAssessmentMixin
from openassessment.xblock.lms_mixin import LmsCompatibilityMixin from openassessment.xblock.lms_mixin import LmsCompatibilityMixin
from openassessment.xblock.self_assessment_mixin import SelfAssessmentMixin from openassessment.xblock.self_assessment_mixin import SelfAssessmentMixin
...@@ -82,6 +83,7 @@ def load(path): ...@@ -82,6 +83,7 @@ def load(path):
class OpenAssessmentBlock( class OpenAssessmentBlock(
XBlock, XBlock,
MessageMixin,
SubmissionMixin, SubmissionMixin,
PeerAssessmentMixin, PeerAssessmentMixin,
SelfAssessmentMixin, SelfAssessmentMixin,
...@@ -149,7 +151,7 @@ class OpenAssessmentBlock( ...@@ -149,7 +151,7 @@ class OpenAssessmentBlock(
has_saved = Boolean( has_saved = Boolean(
default=False, default=False,
scope=Scope.user_state, scope=Scope.user_state,
help="Indicates whether the user has saved a response" help="Indicates whether the user has saved a response."
) )
saved_response = String( saved_response = String(
...@@ -158,6 +160,12 @@ class OpenAssessmentBlock( ...@@ -158,6 +160,12 @@ class OpenAssessmentBlock(
help="Saved response submission for the current user." help="Saved response submission for the current user."
) )
no_peers = Boolean(
default=False,
scope=Scope.user_state,
help="Indicates whether or not there are peers to grade."
)
def get_student_item_dict(self): def get_student_item_dict(self):
"""Create a student_item_dict from our surrounding context. """Create a student_item_dict from our surrounding context.
...@@ -220,7 +228,7 @@ class OpenAssessmentBlock( ...@@ -220,7 +228,7 @@ class OpenAssessmentBlock(
"question": self.prompt, "question": self.prompt,
"rubric_criteria": self.rubric_criteria, "rubric_criteria": self.rubric_criteria,
"rubric_assessments": ui_models, "rubric_assessments": ui_models,
"is_course_staff": self.is_course_staff, "show_staff_debug_info": self.is_course_staff and not self.in_studio_preview,
} }
template = get_template("openassessmentblock/oa_base.html") template = get_template("openassessmentblock/oa_base.html")
...@@ -448,7 +456,7 @@ class OpenAssessmentBlock( ...@@ -448,7 +456,7 @@ class OpenAssessmentBlock(
self.start, self.due, [submission_range] + assessment_ranges self.start, self.due, [submission_range] + assessment_ranges
) )
open_range = (start, due) open_range = (start, due)
assessment_steps = self.assessment_steps assessment_steps = self.assessment_steps
if step == 'submission': if step == 'submission':
open_range = date_ranges[0] open_range = date_ranges[0]
...@@ -534,3 +542,4 @@ class OpenAssessmentBlock( ...@@ -534,3 +542,4 @@ class OpenAssessmentBlock(
return key.to_deprecated_string() return key.to_deprecated_string()
else: else:
return unicode(key) return unicode(key)
...@@ -203,8 +203,12 @@ class PeerAssessmentMixin(object): ...@@ -203,8 +203,12 @@ class PeerAssessmentMixin(object):
if peer_sub: if peer_sub:
path = 'openassessmentblock/peer/oa_peer_assessment.html' path = 'openassessmentblock/peer/oa_peer_assessment.html'
context_dict["peer_submission"] = peer_sub context_dict["peer_submission"] = peer_sub
# Sets the XBlock boolean to signal to Message that it WAS NOT able to grab a submission
self.no_peers = False
else: else:
path = 'openassessmentblock/peer/oa_peer_waiting.html' path = 'openassessmentblock/peer/oa_peer_waiting.html'
# Sets the XBlock boolean to signal to Message that it WAS able to grab a submission
self.no_peers = True
return path, context_dict return path, context_dict
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
{ {
"template": "openassessmentblock/oa_base.html", "template": "openassessmentblock/oa_base.html",
"context": { "context": {
"show_staff_debug_info": false,
"title": "Test title", "title": "Test title",
"question": "Test prompt", "question": "Test prompt",
"rubric_criteria": [], "rubric_criteria": [],
...@@ -35,6 +36,42 @@ ...@@ -35,6 +36,42 @@
"output": "oa_base.html" "output": "oa_base.html"
}, },
{ {
"template": "openassessmentblock/oa_base.html",
"context": {
"show_staff_debug_info": true,
"title": "Test title",
"question": "Test prompt",
"rubric_criteria": [],
"rubric_assessments": [
{
"name": "submission",
"class_id": "openassessment__response",
"navigation_text": "Your response to this problem",
"title": "Your Response"
},
{
"name": "peer-assessment",
"class_id": "openassessment__peer-assessment",
"navigation_text": "Your assessment(s) of peer responses",
"title": "Assess Peers' Responses"
},
{
"name": "self-assessment",
"class_id": "openassessment__self-assessment",
"navigation_text": "Your assessment of your response",
"title": "Assess Your Response"
},
{
"name": "grade",
"class_id": "openassessment__grade",
"navigation_text": "Your grade for this problem",
"title": "Your Grade:"
}
]
},
"output": "oa_base_course_staff.html"
},
{
"template": "openassessmentblock/response/oa_response.html", "template": "openassessmentblock/response/oa_response.html",
"context": { "context": {
"saved_response": "", "saved_response": "",
...@@ -348,17 +385,33 @@ ...@@ -348,17 +385,33 @@
}, },
{ {
"template": "openassessmentblock/staff_debug/staff_debug.html", "template": "openassessmentblock/staff_debug/staff_debug.html",
"output": "staff_debug.html",
"context": { "context": {
"status_counts": [ "status_counts": {
"self": 1,
"peer": 2,
"waiting": 3,
"done": 4
},
"num_submissions": 10,
"item_id": "test_item",
"step_dates": [
{
"step": "submission",
"start": "2014-01-01",
"due": "N/A"
},
{ {
"status": "peer", "step": "peer",
"count": 0 "start": "2014-02-02",
"due": "N/A"
},
{
"step": "self",
"start": "2014-03-03",
"due": "2015-04-05"
} }
], ]
"item_id": ".openassessment.d91.u0", },
"display_schedule_training": true, "output": "oa_staff_info.html"
"num_submissions": 0
}
} }
] ]
...@@ -28,6 +28,7 @@ describe("OpenAssessment.PeerView", function() { ...@@ -28,6 +28,7 @@ describe("OpenAssessment.PeerView", function() {
this.setUpCollapseExpand = function(sel) {}; this.setUpCollapseExpand = function(sel) {};
this.scrollToTop = function() {}; this.scrollToTop = function() {};
this.loadAssessmentModules = function() {}; this.loadAssessmentModules = function() {};
this.loadMessageView = function() {};
}; };
// Stubs // Stubs
......
...@@ -4,14 +4,20 @@ ...@@ -4,14 +4,20 @@
describe("OpenAssessment.StaffInfoView", function() { describe("OpenAssessment.StaffInfoView", function() {
// Stub server // Stub server that returns dummy data for the staff info view
var StubServer = function() { var StubServer = function() {
var successPromise = $.Deferred(
function(defer) { defer.resolve(); }
).promise();
this.render = function(step) { // Remember which fragments have been loaded
return successPromise; this.fragmentsLoaded = [];
// Render the template for the staff info view
this.render = function(component) {
var server = this;
this.fragmentsLoaded.push(component);
return $.Deferred(function(defer) {
fragment = readFixtures("oa_staff_info.html");
defer.resolveWith(this, [fragment]);
});
}; };
this.scheduleTraining = function() { this.scheduleTraining = function() {
...@@ -21,8 +27,6 @@ describe("OpenAssessment.StaffInfoView", function() { ...@@ -21,8 +27,6 @@ describe("OpenAssessment.StaffInfoView", function() {
}).promise(); }).promise();
}; };
this.data = {};
}; };
// Stub base view // Stub base view
...@@ -32,30 +36,38 @@ describe("OpenAssessment.StaffInfoView", function() { ...@@ -32,30 +36,38 @@ describe("OpenAssessment.StaffInfoView", function() {
this.setUpCollapseExpand = function(sel) {}; this.setUpCollapseExpand = function(sel) {};
this.scrollToTop = function() {}; this.scrollToTop = function() {};
this.loadAssessmentModules = function() {}; this.loadAssessmentModules = function() {};
this.loadMessageView = function() {};
}; };
// Stubs // Stubs
var baseView = null; var baseView = null;
var server = null; var server = null;
// View under test /**
var view = null; Initialize the staff info view, then check whether it makes
an AJAX call to load the staff info section.
**/
var assertStaffInfoAjaxCall = function(shouldCall) {
// Load the staff info view
var el = $("#openassessment-base").get(0);
var view = new OpenAssessment.StaffInfoView(el, server, baseView);
view.load();
// Check whether it tried to load staff info from the server
var expectedFragments = [];
if (shouldCall) { expectedFragments = ['staff_info']; }
expect(server.fragmentsLoaded).toEqual(expectedFragments);
};
beforeEach(function() { beforeEach(function() {
// Load the DOM fixture // Configure the Jasmine fixtures path
jasmine.getFixtures().fixturesPath = 'base/fixtures'; jasmine.getFixtures().fixturesPath = 'base/fixtures';
loadFixtures('staff_debug.html');
// Create a new stub server // Create a new stub server
server = new StubServer(); server = new StubServer();
// Create the stub base view // Create the stub base view
baseView = new StubBaseView(); baseView = new StubBaseView();
// Create the object under test
var el = $("#openassessment-base").get(0);
view = new OpenAssessment.StaffInfoView(el, server, baseView);
view.installHandlers();
}); });
it("schedules training of AI classifiers", function() { it("schedules training of AI classifiers", function() {
...@@ -66,10 +78,31 @@ describe("OpenAssessment.StaffInfoView", function() { ...@@ -66,10 +78,31 @@ describe("OpenAssessment.StaffInfoView", function() {
}; };
spyOn(server, 'scheduleTraining').andCallThrough(); spyOn(server, 'scheduleTraining').andCallThrough();
// Load the fixture
loadFixtures('oa_base.html');
// Load the view
var el = $("#openassessment-base").get(0);
var view = new OpenAssessment.StaffInfoView(el, server, baseView);
view.load();
// Submit the assessment // Submit the assessment
view.scheduleTraining(); view.scheduleTraining();
// Expect that the assessment was sent to the server // Expect that the assessment was sent to the server
expect(server.scheduleTraining).toHaveBeenCalled(); expect(server.scheduleTraining).toHaveBeenCalled();
}); });
it("Loads staff info if the page contains a course staff section", function() {
// Load the fixture for the container page that DOES include a course staff section
loadFixtures('oa_base_course_staff.html');
assertStaffInfoAjaxCall(true);
});
it("Does NOT load staff info if the page does NOT contain a course staff section", function() {
// Load the fixture for the container page that does NOT include a course staff section
loadFixtures('oa_base.html');
assertStaffInfoAjaxCall(false);
});
}); });
...@@ -19,7 +19,7 @@ OpenAssessment.BaseView = function(runtime, element, server) { ...@@ -19,7 +19,7 @@ OpenAssessment.BaseView = function(runtime, element, server) {
this.selfView = new OpenAssessment.SelfView(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.peerView = new OpenAssessment.PeerView(this.element, this.server, this);
this.gradeView = new OpenAssessment.GradeView(this.element, this.server, this); this.gradeView = new OpenAssessment.GradeView(this.element, this.server, this);
this.messageView = new OpenAssessment.MessageView(this.element, this.server, this);
// Staff only information about student progress. // Staff only information about student progress.
this.staffInfoView = new OpenAssessment.StaffInfoView(this.element, this.server, this); this.staffInfoView = new OpenAssessment.StaffInfoView(this.element, this.server, this);
}; };
...@@ -80,6 +80,26 @@ OpenAssessment.BaseView.prototype = { ...@@ -80,6 +80,26 @@ OpenAssessment.BaseView.prototype = {
this.peerView.load(); this.peerView.load();
this.selfView.load(); this.selfView.load();
this.gradeView.load(); this.gradeView.load();
/**
this.messageView.load() is intentionally omitted.
Because of the asynchronous loading, there is no way to tell (from the perspective of the
messageView) whether or not the peer view was able to grab an assessment to assess. Any
asynchronous strategy would run into a race condition based around this problem at some
point. Instead, we created a field in the XBlock called no_peers, which is set by the
Peer XBlock Handler, and which is examined by the Message XBlock Handler.
To Avoid rendering the message more than one time per update/load (and avoiding all comp-
lications that that would likely induce), we chose to load the method view only after
the peer view has been loaded. This is achieved by having the peer view call to render
the message view after rendering itself but before exiting its load method.
*/
},
/**
Refresh the message only (called by PeerView to update and avoid race condition)
**/
loadMessageView: function() {
this.messageView.load();
}, },
/** /**
......
/**
Interface for Message banner.
Args:
element (DOM element): The DOM element representing the XBlock.
server (OpenAssessment.Server): The interface to the XBlock server.
baseView (OpenAssessment.BaseView): Container view.
Returns:
OpenAssessment.ResponseView
**/
OpenAssessment.MessageView = function(element, server, baseView) {
this.element = element;
this.server = server;
this.baseView = baseView;
};
OpenAssessment.MessageView.prototype = {
/**
Loads the message view.
**/
load: function() {
var view = this;
var baseView = this.baseView;
this.server.render('message').done(
function(html) {
//Load the HTML
$('#openassessment__message', view.element).replaceWith(html);
}
).fail(function(errMsg) {
baseView.showLoadError('message', errMsg);
});
}
}
\ No newline at end of file
...@@ -33,6 +33,9 @@ OpenAssessment.PeerView.prototype = { ...@@ -33,6 +33,9 @@ OpenAssessment.PeerView.prototype = {
).fail(function(errMsg) { ).fail(function(errMsg) {
view.baseView.showLoadError('peer-assessment'); view.baseView.showLoadError('peer-assessment');
}); });
// Called to update Messagview with info on whether or not it was able to grab a submission
// See detailed explanation/Methodology in oa_base.loadAssessmentModules
view.baseView.loadMessageView();
}, },
/** /**
......
...@@ -23,15 +23,21 @@ OpenAssessment.StaffInfoView.prototype = { ...@@ -23,15 +23,21 @@ OpenAssessment.StaffInfoView.prototype = {
**/ **/
load: function() { load: function() {
var view = this; var view = this;
this.server.render('staff_info').done(
function(html) { // If we're course staff, the base template should contain a section
// Load the HTML and install event handlers // for us to render the staff info to. If that doesn't exist,
$('#openassessment__staff-info', view.element).replaceWith(html); // then we're not staff, so we don't need to send the AJAX request.
view.installHandlers(); if ($('#openassessment__staff-info', view.element).length > 0) {
} this.server.render('staff_info').done(
).fail(function(errMsg) { function(html) {
view.baseView.showLoadError('staff_info'); // Load the HTML and install event handlers
}); $('#openassessment__staff-info', view.element).replaceWith(html);
view.installHandlers();
}
).fail(function(errMsg) {
view.baseView.showLoadError('staff_info');
});
}
}, },
/** /**
......
<openassessment submission_due="2099-01-23">
<title>Open Assessment Test</title>
<prompt>
Given the state of the world today, what do you think should be done to
combat poverty? Please answer in a short essay of 200-300 words.
</prompt>
<rubric>
<prompt>Read for conciseness, clarity of thought, and form.</prompt>
<criterion>
<name>𝓒𝓸𝓷𝓬𝓲𝓼𝓮</name>
<prompt>How concise is it?</prompt>
<option points="3">
<name>ﻉซƈﻉɭɭﻉกՇ</name>
<explanation>Extremely concise</explanation>
</option>
<option points="2">
<name>Ġööḋ</name>
<explanation>Concise</explanation>
</option>
<option points="1">
<name>ק๏๏г</name>
<explanation>Wordy</explanation>
</option>
</criterion>
<criterion>
<name>Form</name>
<prompt>How well-formed is it?</prompt>
<option points="3">
<name>Good</name>
<explanation>Good</explanation>
</option>
<option points="2">
<name>Fair</name>
<explanation>Fair</explanation>
</option>
<option points="1">
<name>Poor</name>
<explanation>Poor</explanation>
</option>
</criterion>
</rubric>
<assessments>
<assessment name="peer-assessment" must_grade="1" must_be_graded_by="1" due="2099-05-23"/>
<assessment name="self-assessment" due="2099-05-23"/>
</assessments>
</openassessment>
<openassessment>
<title>Only Self Assessment</title>
<prompt>
Given the state of the world today, what do you think should be done to
combat poverty? Please answer in a short essay of 200-300 words.
</prompt>
<rubric>
<prompt>Read for conciseness, clarity of thought, and form.</prompt>
<criterion>
<name>Concise</name>
<prompt>How concise is it?</prompt>
<option points="0">
<name>Neal Stephenson (late)</name>
<explanation>Neal Stephenson explanation</explanation>
</option>
<option points="1">
<name>HP Lovecraft</name>
<explanation>HP Lovecraft explanation</explanation>
</option>
<option points="3">
<name>Robert Heinlein</name>
<explanation>Robert Heinlein explanation</explanation>
</option>
<option points="4">
<name>Neal Stephenson (early)</name>
<explanation>Neal Stephenson (early) explanation</explanation>
</option>
<option points="5">
<name>Earnest Hemingway</name>
<explanation>Earnest Hemingway</explanation>
</option>
</criterion>
<criterion>
<name>Clear-headed</name>
<prompt>How clear is the thinking?</prompt>
<option points="0">
<name>Yogi Berra</name>
<explanation>Yogi Berra explanation</explanation>
</option>
<option points="1">
<name>Hunter S. Thompson</name>
<explanation>Hunter S. Thompson explanation</explanation>
</option>
<option points="2">
<name>Robert Heinlein</name>
<explanation>Robert Heinlein explanation</explanation>
</option>
<option points="3">
<name>Isaac Asimov</name>
<explanation>Isaac Asimov explanation</explanation>
</option>
<option points="10">
<name>Spock</name>
<explanation>Spock explanation</explanation>
</option>
</criterion>
<criterion>
<name>Form</name>
<prompt>Lastly, how is its form? Punctuation, grammar, and spelling all count.</prompt>
<option points="0">
<name>lolcats</name>
<explanation>lolcats explanation</explanation>
</option>
<option points="1">
<name>Facebook</name>
<explanation>Facebook explanation</explanation>
</option>
<option points="2">
<name>Reddit</name>
<explanation>Reddit explanation</explanation>
</option>
<option points="3">
<name>metafilter</name>
<explanation>metafilter explanation</explanation>
</option>
<option points="4">
<name>Usenet, 1996</name>
<explanation>Usenet, 1996 explanation</explanation>
</option>
<option points="5">
<name>The Elements of Style</name>
<explanation>The Elements of Style explanation</explanation>
</option>
</criterion>
</rubric>
<assessments>
<assessment name="self-assessment" />
</assessments>
</openassessment>
<openassessment>
<title>Open Assessment Test</title>
<prompt>
Given the state of the world today, what do you think should be done to
combat poverty? Please answer in a short essay of 200-300 words.
</prompt>
<rubric>
<prompt>Read for conciseness, clarity of thought, and form.</prompt>
<criterion>
<name>Concise</name>
<prompt>How concise is it?</prompt>
<option points="0">
<name>Neal Stephenson (late)</name>
<explanation>Neal Stephenson explanation</explanation>
</option>
<option points="1">
<name>HP Lovecraft</name>
<explanation>HP Lovecraft explanation</explanation>
</option>
<option points="3">
<name>Robert Heinlein</name>
<explanation>Robert Heinlein explanation</explanation>
</option>
<option points="4">
<name>Neal Stephenson (early)</name>
<explanation>Neal Stephenson (early) explanation</explanation>
</option>
<option points="5">
<name>Earnest Hemingway</name>
<explanation>Earnest Hemingway</explanation>
</option>
</criterion>
<criterion>
<name>Clear-headed</name>
<prompt>How clear is the thinking?</prompt>
<option points="0">
<name>Yogi Berra</name>
<explanation>Yogi Berra explanation</explanation>
</option>
<option points="1">
<name>Hunter S. Thompson</name>
<explanation>Hunter S. Thompson explanation</explanation>
</option>
<option points="2">
<name>Robert Heinlein</name>
<explanation>Robert Heinlein explanation</explanation>
</option>
<option points="3">
<name>Isaac Asimov</name>
<explanation>Isaac Asimov explanation</explanation>
</option>
<option points="10">
<name>Spock</name>
<explanation>Spock explanation</explanation>
</option>
</criterion>
<criterion>
<name>Form</name>
<prompt>Lastly, how is its form? Punctuation, grammar, and spelling all count.</prompt>
<option points="0">
<name>lolcats</name>
<explanation>lolcats explanation</explanation>
</option>
<option points="1">
<name>Facebook</name>
<explanation>Facebook explanation</explanation>
</option>
<option points="2">
<name>Reddit</name>
<explanation>Reddit explanation</explanation>
</option>
<option points="3">
<name>metafilter</name>
<explanation>metafilter explanation</explanation>
</option>
<option points="4">
<name>Usenet, 1996</name>
<explanation>Usenet, 1996 explanation</explanation>
</option>
<option points="5">
<name>The Elements of Style</name>
<explanation>The Elements of Style explanation</explanation>
</option>
</criterion>
</rubric>
<assessments>
<assessment name="peer-assessment" must_grade="5" must_be_graded_by="3" />
</assessments>
</openassessment>
<openassessment>
<title>Student training test</title>
<prompt>Test prompt</prompt>
<rubric>
<prompt>Test rubric prompt</prompt>
<criterion>
<name>Vocabulary</name>
<prompt>How varied is the vocabulary?</prompt>
<option points="0">
<name>Poor</name>
<explanation>Poor job</explanation>
</option>
<option points="1">
<name>Good</name>
<explanation>Good job</explanation>
</option>
<option points="3">
<name>Excellent</name>
<explanation>Excellent job</explanation>
</option>
</criterion>
<criterion>
<name>Grammar</name>
<prompt>How correct is the grammar?</prompt>
<option points="0">
<name>Poor</name>
<explanation>Poor job</explanation>
</option>
<option points="1">
<name>Good</name>
<explanation>Good job</explanation>
</option>
<option points="3">
<name>Excellent</name>
<explanation>Excellent job</explanation>
</option>
</criterion>
</rubric>
<assessments>
<assessment name="student-training">
<example>
<answer>This is my answer.</answer>
<select criterion="Vocabulary" option="Good" />
<select criterion="Grammar" option="Excellent" />
</example>
<example>
<answer>тєѕт αηѕωєя</answer>
<select criterion="Vocabulary" option="Excellent" />
<select criterion="Grammar" option="Poor" />
</example>
</assessment>
<assessment name="peer-assessment" must_grade="5" must_be_graded_by="3" />
<assessment name="self-assessment" />
</assessments>
</openassessment>
...@@ -108,7 +108,6 @@ class TestCourseStaff(XBlockHandlerTestCase): ...@@ -108,7 +108,6 @@ class TestCourseStaff(XBlockHandlerTestCase):
resp = self.request(xblock, 'render_student_info', json.dumps({})) resp = self.request(xblock, 'render_student_info', json.dumps({}))
self.assertIn("couldn\'t find a response for this student.", resp.decode('utf-8').lower()) self.assertIn("couldn\'t find a response for this student.", resp.decode('utf-8').lower())
@scenario('data/basic_scenario.xml') @scenario('data/basic_scenario.xml')
def test_hide_course_staff_debug_info_in_studio_preview(self, xblock): def test_hide_course_staff_debug_info_in_studio_preview(self, xblock):
# If we are in Studio preview mode, don't show the staff debug info # If we are in Studio preview mode, don't show the staff debug info
...@@ -117,8 +116,15 @@ class TestCourseStaff(XBlockHandlerTestCase): ...@@ -117,8 +116,15 @@ class TestCourseStaff(XBlockHandlerTestCase):
xblock.xmodule_runtime = self._create_mock_runtime( xblock.xmodule_runtime = self._create_mock_runtime(
xblock.scope_ids.usage_id, True, False, "Bob" xblock.scope_ids.usage_id, True, False, "Bob"
) )
# If the client requests the staff info directly, they should get an error
resp = self.request(xblock, 'render_staff_info', json.dumps({})) resp = self.request(xblock, 'render_staff_info', json.dumps({}))
self.assertNotIn("course staff information", resp.decode('utf-8').lower()) self.assertNotIn("course staff information", resp.decode('utf-8').lower())
self.assertIn("do not have permission", resp.decode('utf-8').lower())
# The container page should not contain a staff info section at all
xblock_fragment = self.runtime.render(xblock, 'student_view')
self.assertNotIn(u'staff-info', xblock_fragment.body_html())
@scenario('data/staff_dates_scenario.xml', user_id='Bob') @scenario('data/staff_dates_scenario.xml', user_id='Bob')
def test_staff_debug_dates_table(self, xblock): def test_staff_debug_dates_table(self, xblock):
......
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