Commit 794f32b1 by Dmitry Viskov

1. New auxiliary view "grade_available_responses_view" which displays the staff grading area

2. New auxiliary view for course ORA blocks listing (based on backgrid.js)
parent 8af51c16
......@@ -145,6 +145,12 @@
"gettext",
"ngettext",
// Backgrid library
"Backgrid",
// XBlock dependencies
"XBlock",
// ORA-specific globals
"OpenAssessment"
]
......
......@@ -32,6 +32,7 @@ before_script:
- "export DISPLAY=:99.0"
- "sh -e /etc/init.d/xvfb start"
script:
- "make verify-generated-files"
- "make test"
- "python manage.py makemessages -l eo"
branches:
......
......@@ -2,9 +2,11 @@ include LICENSE
include AUTHORS
include README.rst
include openassessment/xblock/static/css/*.css
include openassessment/xblock/static/css/lib/backgrid/*.css
include openassessment/xblock/static/js/openassessment*.min.js
include openassessment/xblock/static/js/lib/backgrid/*.js
recursive-include openassessment/xblock/static/js/src *.js
recursive-include openassessment/templates *.html
recursive-include openassessment/templates *.html *.underscore
recursive-include openassessment/locale *.po
recursive-include openassessment/locale *.mo
global-exclude */test*
......
......@@ -28,15 +28,17 @@ install-nltk-data:
STATIC_JS = openassessment/xblock/static/js
STATIC_CSS = openassessment/xblock/static/css
javascript:
node_modules/.bin/uglifyjs $(STATIC_JS)/src/oa_shared.js $(STATIC_JS)/src/*.js $(STATIC_JS)/src/lms/*.js > "$(STATIC_JS)/openassessment-lms.min.js"
node_modules/.bin/uglifyjs $(STATIC_JS)/src/oa_shared.js $(STATIC_JS)/src/*.js $(STATIC_JS)/src/studio/*.js > "$(STATIC_JS)/openassessment-studio.min.js"
javascript: update-npm-requirements
node_modules/.bin/uglifyjs $(STATIC_JS)/src/oa_shared.js $(STATIC_JS)/src/*.js $(STATIC_JS)/src/lms/*.js $(STATIC_JS)/lib/backgrid/backgrid.min.js -c warnings=false > "$(STATIC_JS)/openassessment-lms.min.js"
node_modules/.bin/uglifyjs $(STATIC_JS)/src/oa_shared.js $(STATIC_JS)/src/*.js $(STATIC_JS)/src/studio/*.js $(STATIC_JS)/lib/backgrid/backgrid.min.js -c warnings=false > "$(STATIC_JS)/openassessment-studio.min.js"
sass:
python scripts/compile_sass.py
verify-generated-files: javascript sass
@git diff --quiet || (echo 'Modifications exist locally! Run `make javascript` or `make sass` to update bundled files.'; exit 1)
install-test:
pip install -q -r requirements/test.txt
......@@ -50,8 +52,8 @@ install-dev:
install: install-wheels install-python install-js install-nltk-data install-test install-dev javascript sass
quality:
jshint openassessment/xblock/static/js/src -c .jshintrc --verbose
./node_modules/jscs/bin/jscs openassessment/xblock/static/js/src --verbose
jshint $(STATIC_JS)/src -c .jshintrc --verbose
./node_modules/jscs/bin/jscs $(STATIC_JS)/src --verbose
./scripts/run-pep8.sh
./scripts/run-pylint.sh
......@@ -76,3 +78,8 @@ test-acceptance:
test-a11y:
./scripts/test-acceptance.sh accessibility
update-npm-requirements:
npm update --silent
cp ./node_modules/backgrid/lib/backgrid*.js $(STATIC_JS)/lib/backgrid/
cp ./node_modules/backgrid/lib/backgrid*.css $(STATIC_CSS)/lib/backgrid/
\ No newline at end of file
......@@ -30,6 +30,8 @@ module.exports = function(config) {
'js/lib/jquery.timepicker.min.js',
'js/lib/jquery-ui-1.10.4.min.js',
'js/lib/underscore-min.js',
'../../../node_modules/backbone/backbone.js',
'../../../node_modules/backgrid/lib/backgrid.min.js',
'../../../node_modules/requirejs/require.js',
'../../../require-config.js',
{
......
{% load i18n %}
{% spaceless %}
<div class="wrapper wrapper--xblock wrapper--openassessment theme--basic">
<div class="openassessment problem">
<div class="wrapper--grid">
{% if title %}
<h4 class="openassessment__title problem__header">{% trans title %}</h4>
{% endif %}
{% if staff_assessment_required %}
<div class="openassessment__staff-area">
<div class="openassessment__staff-grading wrapper--staff-grading">
<div class="staff-grading ui-staff">
<h5 class="staff-grading__title">
<span class="staff-info__title__copy">{% trans "Grade Available Responses" %}</span>
</h5>
<div class="staff-info__content ui-staff__content">
{% include "openassessmentblock/staff_area/oa_staff_grade_learners.html" with staff_assessment_ungraded=staff_assessment_ungraded staff_assessment_in_progress=staff_assessment_in_progress %}
</div>
</div>
</div>
</div>
{% else %}
<div class="openassessment__staff-area-unavailable">
{% trans "Grade Available Responses is unavailable. This item is not configured for Staff Assessment." %}
</div>
{% endif %}
</div>
</div>
</div>
{% endspaceless %}
{% load i18n %}
{% spaceless %}
<section class="open-response-assessment-block"
data-item-view-enabled="{{ ora_item_view_enabled|yesno:'1,0' }}"
data-rendered=0>
<div class="open-response-assessment-msg">{% trans "Please wait" %}</div>
<div class="open-response-assessment-content">
<div class="open-response-assessment-summary"></div>
<hr/>
<div class="open-response-assessment-main-table"></div>
</div>
<div class="open-response-assessment-item">
<div class="open-response-assessment-item-breadcrumbs"></div>
<div class="open-response-assessment-item-block"></div>
</div>
</section>
<script type="text/template" id="open-response-assessment-items">{{ ora_items |safe }}</script>
<script type="text/template" id="open-response-assessment-summary-tpl">
{% include "openassessmentblock/instructor_dashboard/open-response-assessment-summary.underscore" %}
</script>
{% endspaceless %}
<table class="backgrid open-response-assessment-summary">
<tbody>
<tr>
<% for (var i = 0; i < oraSummary.length; i++) {
var s = oraSummary[i];
%>
<td class="string-cell renderable <%- s.class %>">
<div class="ora-summary-title"><%- s.title %></div>
<div class="ora-summary-value"><%- s.value %></div>
</td>
<% } %>
</tr>
</tbody>
</table>
"""
The mixin with handlers for the course ora blocks listing view.
"""
import json
from webob import Response
from xblock.core import XBlock
from openassessment.data import OraAggregateData
from openassessment.xblock.staff_area_mixin import require_course_staff
class CourseItemsListingMixin(object):
"""
The mixin with handlers for the course ora blocks listing view.
"""
@XBlock.handler
@require_course_staff('STAFF_AREA')
def get_ora2_responses(self, request, suffix=''): # pylint: disable=unused-argument
"""
Get information about all ora2 blocks in the course with response count for each step.
"""
responses = OraAggregateData.collect_ora2_responses(unicode(self.course_id))
return Response(json.dumps(responses), content_type='application/json')
......@@ -37,6 +37,7 @@ from openassessment.xblock.student_training_mixin import StudentTrainingMixin
from openassessment.xblock.validation import validator
from openassessment.xblock.resolve_dates import resolve_dates, parse_date_value, DISTANT_PAST, DISTANT_FUTURE
from openassessment.xblock.data_conversion import create_prompts_list, create_rubric_dict, update_assessments_format
from openassessment.xblock.course_items_listing_mixin import CourseItemsListingMixin
logger = logging.getLogger(__name__)
......@@ -97,8 +98,7 @@ def load(path):
@XBlock.needs("i18n")
@XBlock.needs("user")
class OpenAssessmentBlock(
MessageMixin,
class OpenAssessmentBlock(MessageMixin,
SubmissionMixin,
PeerAssessmentMixin,
SelfAssessmentMixin,
......@@ -110,8 +110,8 @@ class OpenAssessmentBlock(
WorkflowMixin,
StudentTrainingMixin,
LmsCompatibilityMixin,
XBlock,
):
CourseItemsListingMixin,
XBlock):
"""Displays a prompt and provides an area where students can compose a response."""
public_dir = 'static'
......@@ -361,9 +361,90 @@ class OpenAssessmentBlock(
"show_staff_area": self.is_course_staff and not self.in_studio_preview,
}
template = get_template("openassessmentblock/oa_base.html")
return self._create_fragment(template, context_dict, initialize_js_func='OpenAssessmentBlock')
def ora_blocks_listing_view(self, context=None):
"""This view is used in the Open Response Assessment tab in the LMS Instructor Dashboard
to display all available course ORA blocks.
Args:
context: contains two items:
"ora_items" - all course items with names and parents, example:
[{"parent_name": "Vertical name",
"name": "ORA Display Name",
"url_grade_available_responses": "/grade_available_responses_view",
"staff_assessment": false,
"parent_id": "vertical_block_id",
"url_base": "/student_view",
"id": "openassessment_block_id"
}, ...]
"ora_item_view_enabled" - enabled LMS API endpoint to serve XBlock view or not
Returns:
(Fragment): The HTML Fragment for this XBlock.
"""
ora_items = context.get('ora_items', []) if context else []
ora_item_view_enabled = context.get('ora_item_view_enabled', False) if context else False
context_dict = {
"ora_items": json.dumps(ora_items),
"ora_item_view_enabled": ora_item_view_enabled
}
template = get_template('openassessmentblock/instructor_dashboard/oa_listing.html')
min_postfix = '.min' if settings.DEBUG else ''
return self._create_fragment(
template,
context_dict,
initialize_js_func='CourseOpenResponsesListingBlock',
additional_css=["static/css/lib/backgrid/backgrid%s.css" % min_postfix],
additional_js=["static/js/lib/backgrid/backgrid%s.js" % min_postfix]
)
def grade_available_responses_view(self, context=None):
"""Grade Available Responses view.
Auxiliary view which displays the staff grading area
(used in the Open Response Assessment tab in the Instructor Dashboard of LMS)
Args:
context: Not used for this view.
Returns:
(Fragment): The HTML Fragment for this XBlock.
"""
student_item = self.get_student_item_dict()
staff_assessment_required = "staff-assessment" in self.assessment_steps
context_dict = {
"title": self.title,
'staff_assessment_required': staff_assessment_required
}
if staff_assessment_required:
context_dict.update(
self.get_staff_assessment_statistics_context(student_item["course_id"], student_item["item_id"])
)
template = get_template('openassessmentblock/instructor_dashboard/oa_grade_available_responses.html')
return self._create_fragment(template, context_dict, initialize_js_func='StaffAssessmentBlock')
def _create_fragment(self, template, context_dict, initialize_js_func, additional_css=None, additional_js=None):
"""
Creates a fragment for display.
"""
context = Context(context_dict)
fragment = Fragment(template.render(context))
if additional_css is None:
additional_css = []
if additional_js is None:
additional_js = []
i18n_service = self.runtime.service(self, 'i18n')
if hasattr(i18n_service, 'get_language_bidi') and i18n_service.get_language_bidi():
css_url = "static/css/openassessment-rtl.css"
......@@ -371,13 +452,22 @@ class OpenAssessmentBlock(
css_url = "static/css/openassessment-ltr.css"
if settings.DEBUG:
for css in additional_css:
fragment.add_css_url(self.runtime.local_resource_url(self, css))
fragment.add_css_url(self.runtime.local_resource_url(self, css_url))
for js in additional_js:
self.add_javascript_files(fragment, js)
self.add_javascript_files(fragment, "static/js/src/oa_shared.js")
self.add_javascript_files(fragment, "static/js/src/oa_server.js")
self.add_javascript_files(fragment, "static/js/src/lms")
else:
# TODO: load CSS and JavaScript as URLs once they can be served by the CDN
for css in additional_css:
fragment.add_css(load(css))
fragment.add_css(load(css_url))
# minified additional_js should be already included in 'make javascript'
fragment.add_javascript(load("static/js/openassessment-lms.min.js"))
js_context_dict = {
"ALLOWED_IMAGE_MIME_TYPES": self.ALLOWED_IMAGE_MIME_TYPES,
......@@ -385,7 +475,7 @@ class OpenAssessmentBlock(
"FILE_EXT_BLACK_LIST": self.FILE_EXT_BLACK_LIST,
"FILE_TYPE_WHITE_LIST": self.white_listed_file_types,
}
fragment.initialize_js('OpenAssessmentBlock', js_context_dict)
fragment.initialize_js(initialize_js_func, js_context_dict)
return fragment
@property
......
/*
backgrid
http://github.com/cloudflare/backgrid
Copyright (c) 2013-present Cloudflare, Inc. and contributors
Licensed under the MIT license.
*/
.backgrid-container {
position: relative;
display: block;
width: 100%;
height: 465px;
padding: 0;
overflow: auto;
border: 0;
}
.backgrid {
width: 100%;
max-width: 100%;
background-color: transparent;
border-collapse: collapse;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
}
.backgrid th,
.backgrid td {
display: none;
height: 20px;
max-width: 250px;
padding: 4px 5px;
overflow: hidden;
line-height: 20px;
text-align: left;
text-overflow: ellipsis;
white-space: nowrap;
vertical-align: middle;
border-bottom: 1px solid #DDD;
}
.backgrid th.renderable,
.backgrid td.renderable {
display: table-cell;
}
.backgrid th {
font-weight: bold;
text-align: center;
}
.backgrid th.sortable a {
text-decoration: none;
white-space: nowrap;
cursor: pointer;
}
.backgrid thead th {
vertical-align: bottom;
background-color: #f9f9f9;
}
.backgrid thead th button {
display: block;
padding: 0;
background: none;
border: none;
}
.backgrid.backgrid-striped tbody tr:nth-child(even) {
background-color: #f9f9f9;
}
.backgrid tbody tr.empty {
font-style: italic;
color: gray;
}
.backgrid tbody tr.empty td {
display: table-cell;
text-align: center;
}
.backgrid td.editor {
padding: 0;
}
.backgrid td.editor,
.backgrid tbody tr:nth-child(odd) td.editor {
background-color: rgba(82, 168, 236, 0.1);
outline: 1px solid rgba(82, 168, 236, 0.8);
outline-offset: -1px;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
-webkit-transition-duration: 200ms;
-moz-transition-duration: 200ms;
-o-transition-duration: 200ms;
transition-duration: 200ms;
-webkit-transition-property: width, outline, background-color;
-moz-transition-property: width, outline, background-color;
-o-transition-property: width, outline, background-color;
transition-property: width, outline, background-color;
-webkit-transition-timing-function: ease-in-out;
-moz-transition-timing-function: ease-in-out;
-o-transition-timing-function: ease-in-out;
transition-timing-function: ease-in-out;
}
.backgrid td.editor input[type=text] {
display: block;
width: 100%;
height: 100%;
padding: 0 5px;
margin: 0;
background-color: transparent;
border: 0;
outline: 0;
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
-webkit-appearance: none;
-moz-appearance: none;
}
.backgrid td.editor input[type=text]::-ms-clear {
display: none;
}
.backgrid td.error,
.backgrid tbody tr:nth-child(odd) td.error {
background-color: rgba(255, 210, 77, 0.1);
outline: 1px solid #ffd24d;
}
.backgrid td.editor :focus,
.backgrid th.editor :focus {
outline: 0;
}
.backgrid .sort-caret {
display: inline-block;
width: 0;
height: 0;
margin-left: 0.3em;
border: 0;
content: "";
}
.backgrid .ascending .sort-caret {
vertical-align: baseline;
border-top: none;
border-right: 4px solid transparent;
border-bottom: 4px solid #000000;
border-left: 4px solid transparent;
}
.backgrid .descending .sort-caret {
vertical-align: super;
border-top: 4px solid #000000;
border-right: 4px solid transparent;
border-bottom: none;
border-left: 4px solid transparent;
}
.backgrid .string-cell,
.backgrid .uri-cell,
.backgrid .email-cell,
.backgrid .string-cell.editor input[type=text],
.backgrid .uri-cell.editor input[type=text],
.backgrid .email-cell.editor input[type=text] {
text-align: left;
}
.backgrid .date-cell,
.backgrid .time-cell,
.backgrid .datetime-cell,
.backgrid .number-cell,
.backgrid .integer-cell,
.backgrid .percent-cell,
.backgrid .date-cell.editor input[type=text],
.backgrid .time-cell.editor input[type=text],
.backgrid .datetime-cell.editor input[type=text],
.backgrid .number-cell.editor input[type=text],
.backgrid .integer-cell.editor input[type=text],
.backgrid .percent-cell.editor input[type=text] {
text-align: right;
}
.backgrid .boolean-cell,
.backgrid .boolean-cell.editor input[type=checkbox] {
text-align: center;
}
.backgrid .select-cell {
text-align: center;
}
.backgrid .select-cell.editor {
padding: 0;
}
.backgrid .select-cell.editor select {
display: block;
width: 100%;
height: 28px;
padding: 4px 5px;
margin: 0;
line-height: 28px;
vertical-align: middle;
background-color: white;
border: 0;
outline: 0;
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.backgrid .select-cell.editor select[multiple] {
height: auto;
}
.backgrid .select-cell.editor :focus {
border: 0;
outline: 0;
}
.backgrid .select-cell.editor select::-moz-focus-inner,
.backgrid .select-cell.editor optgroup::-moz-focus-inner,
.backgrid .select-cell.editor option::-moz-focus-inner,
.backgrid .select-cell.editor select::-o-focus-inner,
.backgrid .select-cell.editor optgroup::-o-focus-inner,
.backgrid .select-cell.editor option::-o-focus-inner {
border: 0;
}
\ No newline at end of file
.backgrid-container{position:relative;display:block;width:100%;height:465px;padding:0;overflow:auto;border:0}.backgrid{width:100%;max-width:100%;background-color:transparent;border-collapse:collapse;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.backgrid th,.backgrid td{display:none;height:20px;max-width:250px;padding:4px 5px;overflow:hidden;line-height:20px;text-align:left;text-overflow:ellipsis;white-space:nowrap;vertical-align:middle;border-bottom:1px solid #DDD}.backgrid th.renderable,.backgrid td.renderable{display:table-cell}.backgrid th{font-weight:bold;text-align:center}.backgrid th.sortable a{text-decoration:none;white-space:nowrap;cursor:pointer}.backgrid thead th{vertical-align:bottom;background-color:#f9f9f9}.backgrid thead th button{display:block;padding:0;background:0;border:0}.backgrid.backgrid-striped tbody tr:nth-child(even){background-color:#f9f9f9}.backgrid tbody tr.empty{font-style:italic;color:gray}.backgrid tbody tr.empty td{display:table-cell;text-align:center}.backgrid td.editor{padding:0}.backgrid td.editor,.backgrid tbody tr:nth-child(odd) td.editor{background-color:rgba(82,168,236,0.1);outline:1px solid rgba(82,168,236,0.8);outline-offset:-1px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-transition-duration:200ms;-moz-transition-duration:200ms;-o-transition-duration:200ms;transition-duration:200ms;-webkit-transition-property:width,outline,background-color;-moz-transition-property:width,outline,background-color;-o-transition-property:width,outline,background-color;transition-property:width,outline,background-color;-webkit-transition-timing-function:ease-in-out;-moz-transition-timing-function:ease-in-out;-o-transition-timing-function:ease-in-out;transition-timing-function:ease-in-out}.backgrid td.editor input[type=text]{display:block;width:100%;height:100%;padding:0 5px;margin:0;background-color:transparent;border:0;outline:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-appearance:none;-moz-appearance:none}.backgrid td.editor input[type=text]::-ms-clear{display:none}.backgrid td.error,.backgrid tbody tr:nth-child(odd) td.error{background-color:rgba(255,210,77,0.1);outline:1px solid #ffd24d}.backgrid td.editor :focus,.backgrid th.editor:focus{outline:0}.backgrid .sort-caret{display:inline-block;width:0;height:0;margin-left:.3em;border:0;content:""}.backgrid .ascending .sort-caret{vertical-align:baseline;border-top:0;border-right:4px solid transparent;border-bottom:4px solid #000;border-left:4px solid transparent}.backgrid .descending .sort-caret{vertical-align:super;border-top:4px solid #000;border-right:4px solid transparent;border-bottom:0;border-left:4px solid transparent}.backgrid .string-cell,.backgrid .uri-cell,.backgrid .email-cell,.backgrid .string-cell.editor input[type=text],.backgrid .uri-cell.editor input[type=text],.backgrid .email-cell.editor input[type=text]{text-align:left}.backgrid .date-cell,.backgrid .time-cell,.backgrid .datetime-cell,.backgrid .number-cell,.backgrid .integer-cell,.backgrid .percent-cell,.backgrid .date-cell.editor input[type=text],.backgrid .time-cell.editor input[type=text],.backgrid .datetime-cell.editor input[type=text],.backgrid .number-cell.editor input[type=text],.backgrid .integer-cell.editor input[type=text],.backgrid .percent-cell.editor input[type=text]{text-align:right}.backgrid .boolean-cell,.backgrid .boolean-cell.editor input[type=checkbox]{text-align:center}.backgrid .select-cell{text-align:center}.backgrid .select-cell.editor{padding:0}.backgrid .select-cell.editor select{display:block;width:100%;height:28px;padding:4px 5px;margin:0;line-height:28px;vertical-align:middle;background-color:white;border:0;outline:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.backgrid .select-cell.editor select[multiple]{height:auto}.backgrid .select-cell.editor :focus{border:0;outline:0}.backgrid .select-cell.editor select::-moz-focus-inner,.backgrid .select-cell.editor optgroup::-moz-focus-inner,.backgrid .select-cell.editor option::-moz-focus-inner,.backgrid .select-cell.editor select::-o-focus-inner,.backgrid .select-cell.editor optgroup::-o-focus-inner,.backgrid .select-cell.editor option::-o-focus-inner{border:0}
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -1213,5 +1213,23 @@
"submission_due": "2020-01-01T00:00"
},
"output": "oa_response_date.html"
},
{
"template": "openassessmentblock/instructor_dashboard/oa_grade_available_responses.html",
"context": {
"title": "Test ABC",
"staff_assessment_required": true,
"staff_assessment_ungraded": 9,
"staff_assessment_in_progress": 2
},
"output": "oa_grade_available_responses_separate_view.html"
},
{
"template": "openassessmentblock/instructor_dashboard/oa_listing.html",
"context": {
"ora_item_view_enabled": "1",
"ora_items": ""
},
"output": "oa_listing_view.html"
}
]
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -103,6 +103,13 @@ describe('OpenAssessment.StaffAreaView', function() {
return view;
};
var createGradeAvailableResponsesView = function() {
var assessmentElement = $('.openassessment').get(0);
var view = new OpenAssessment.BaseView(runtime, assessmentElement, server, {});
view.staffAreaView.installHandlers();
return view;
};
/**
* Initialize the staff area view, then check whether it makes
* an AJAX call to populate itself.
......@@ -763,4 +770,51 @@ describe('OpenAssessment.StaffAreaView', function() {
expect(staffArea.baseView.unsavedWarningEnabled()).toBe(false);
});
});
describe('Grade Available Responses as the separate view', function() {
var staffAreaTab = "staff-grading";
var gradingType = "full-grade";
beforeEach(function() {
loadFixtures('oa_grade_available_responses_separate_view.html');
});
it('exists without any additional buttons', function() {
var view = createGradeAvailableResponsesView(),
staffArea = $('.openassessment__staff-area', view.element),
staffGradingButton = $('.button-staff-grading', view.element),
problemHeader = $('.problem__header', view.element),
gradeValue = $('.staff__grade__value', view.element);
expect(staffArea.length).toBe(1);
expect(staffGradingButton.length).toBe(0);
expect(problemHeader.text()).toBe('Test ABC');
expect(gradeValue.text().trim()).toBe("9 Available and 2 Checked Out");
});
it('can submit a staff grade', function() {
var view = createGradeAvailableResponsesView(),
$assessment, $staffGradeButton;
$staffGradeButton = $('.staff__grade__show-form', view.element);
expect($staffGradeButton).toHaveAttr('aria-expanded', 'false');
showInstructorAssessmentForm(view);
expect($staffGradeButton).toHaveAttr('aria-expanded', 'true');
$assessment = getAssessment(view, staffAreaTab);
// Verify that the submission is shown
expect($('.staff-assessment__display__title', view.element).text().trim()).toBe(
'Response for: mock_user'
);
// Fill in and submit the assessment
fillAssessment($assessment, gradingType);
server.staffGradeFormTemplate = 'oa_staff_grade_learners_assessment_2.html';
submitAssessment(view.staffAreaView, staffAreaTab);
verifyAssessType(view.staffAreaView, 'full-grade');
// Verify that the assessment form has been removed
expect($('.staff__grade__form', view.element).html().trim()).toBe('');
expect($staffGradeButton).toHaveAttr('aria-expanded', 'false');
verifyFocused($staffGradeButton[0]);
});
});
});
......@@ -253,7 +253,7 @@ describe("OpenAssessment.Server", function() {
type: "POST",
data: JSON.stringify({
feedback_text: "test feedback",
feedback_options: options,
feedback_options: options
}),
contentType : jsonContentType
});
......
......@@ -384,5 +384,22 @@ function OpenAssessmentBlock(runtime, element, data) {
var server = new OpenAssessment.Server(runtime, element);
var view = new OpenAssessment.BaseView(runtime, element, server, data);
view.load();
}
/* XBlock JavaScript entry point for OpenAssessmentXBlock. */
/* jshint unused:false */
function CourseOpenResponsesListingBlock(runtime, element, data) {
var view = new OpenAssessment.CourseItemsListingView(runtime, element);
view.refreshGrids();
}
/* XBlock JavaScript entry point for OpenAssessmentXBlock. */
/* jshint unused:false */
function StaffAssessmentBlock(runtime, element, data) {
/**
Render auxiliary view which displays the staff grading area
**/
var server = new OpenAssessment.Server(runtime, element);
var view = new OpenAssessment.BaseView(runtime, element, server, data);
view.staffAreaView.installHandlers();
}
......@@ -24,7 +24,7 @@ OpenAssessment.FileUploader = function() {
data: file,
async: false,
processData: false,
contentType: file.type,
contentType: file.type
}).done(
function() {
// Log an analytics event
......
......@@ -81,6 +81,7 @@
@import 'oa/elements/layout'; // applied layouts and deliberate class-based breakpoints
@import 'oa/elements/staff'; // open assessment staff-centric UI
@import 'oa/views/oa-base'; // open assessment base view
@import 'oa/views/oa-course-items-listing'; // open response assessment tab for instructor dashboard
// openassessment: contextual
@import 'oa/contexts/ie'; // open assessment-specific Internet Explorer-specific styling
......
.open-response-assessment-block {
table {
table-layout: fixed;
}
.open-response-assessment-msg {
margin-bottom: 20px;
}
.open-response-assessment-content,
.open-response-assessment-item
{
display: none;
}
.open-response-assessment-summary {
td {
border: none;
}
}
.open-response-assessment-item-breadcrumbs {
padding-bottom: 20px;
a {
cursor: pointer;
}
}
.open-response-assessment-main-table {
th {
background-color: #ffffff;
text-align: left;
button {
background: none;
border: none;
box-shadow: none;
outline: none;
font-size: 14px;
}
a {
color: #000000 !important;
}
}
td {
a {
cursor: pointer;
}
}
tbody tr:nth-child(odd) {
background-color: #f9f9f9;
}
}
.ora-summary-title {
color: #0079bc;
font-weight: bold;
text-transform: uppercase;
font-size: 0.8em;
}
.ora-summary-value {
font-size: 2.2em;
padding-top: 3px;
}
.backgrid {
.peer, .self, .staff, .waiting {
width: 65px;
}
.training {
width: 90px;
}
.total {
width: 150px;
}
.done {
width: 190px;
}
}
}
# -*- coding: utf-8 -*-
"""
Tests for course items listing handlers.
"""
import json
from mock import patch
from .base import scenario, XBlockHandlerTestCase, SubmitAssessmentsMixin
class TestCourseItemsListingHandlers(XBlockHandlerTestCase, SubmitAssessmentsMixin):
"""
Test for course items listing handlers.
"""
@scenario('data/basic_scenario.xml', user_id='Bob')
def test_non_staff_access(self, xblock):
"""
Test non-staff access for endpoint that return items statistics
"""
response = self.request(xblock, 'get_ora2_responses', json.dumps({}),
request_method='GET')
self.assertIn('You do not have permission', response)
@scenario('data/basic_scenario.xml', user_id='Bob')
def test_staff_access(self, xblock):
"""
Test staff access for endpoint that return items statistics
"""
self.set_staff_access(xblock)
xblock.xmodule_runtime.course_id = 'test_course'
return_data = [{'id': 'test_1', 'title': 'title_1'},
{'id': 'test_2', 'title': 'title_2'}]
with patch("openassessment.data.OraAggregateData.collect_ora2_responses", return_value=return_data):
response = self.request(xblock, 'get_ora2_responses', json.dumps({}),
request_method='GET', response_format='json')
self.assertEqual(response, return_data)
......@@ -2,12 +2,15 @@
Tests the Open Assessment XBlock functionality.
"""
import ddt
import json
from collections import namedtuple
import datetime as dt
from freezegun import freeze_time
import pytz
from mock import Mock, patch, MagicMock, PropertyMock
from lxml import etree
from StringIO import StringIO
from openassessment.xblock import openassessmentblock
from openassessment.xblock.resolve_dates import DISTANT_PAST, DISTANT_FUTURE
......@@ -59,6 +62,85 @@ class TestOpenAssessment(XBlockHandlerTestCase):
self.assertIsNotNone(grade_response)
self.assertIn("step--grade", grade_response.body)
def _staff_assessment_view_helper(self, xblock):
"""
Helper for "staff_assessment_view" tests
"""
xblock_fragment = self.runtime.render(xblock, "grade_available_responses_view")
body_html = xblock_fragment.body_html()
self.assertIn("StaffAssessmentBlock", body_html)
self.assertIn("openassessment__title", body_html)
return body_html
@scenario('data/staff_grade_scenario.xml')
def test_staff_assessment_view(self, xblock):
"""OA XBlock returns some HTML for case if Staff Assessment is configured.
View basic test for verifying auxiliary view which displays the staff grading area.
"""
body_html = self._staff_assessment_view_helper(xblock)
self.assertIn("openassessment__staff-area", body_html)
self.assertIn("ui-staff__content", body_html)
self.assertNotIn("openassessment__staff-area-unavailable", body_html)
@scenario('data/basic_scenario.xml')
def test_staff_assessment_view_staff_assessment_not_configured(self, xblock):
"""OA XBlock returns some HTML for case if Staff Assessment is not configured.
View basic test for verifying auxiliary view which displays the staff grading area.
"""
body_html = self._staff_assessment_view_helper(xblock)
self.assertNotIn("ui-staff__content", body_html)
self.assertIn("openassessment__staff-area-unavailable", body_html)
@scenario('data/basic_scenario.xml')
def test_ora_blocks_listing_view(self, xblock):
"""
Test view for listing all courses OA blocks.
"""
xblock_fragment = self.runtime.render(xblock, "ora_blocks_listing_view")
body_html = xblock_fragment.body_html()
self.assertIn("CourseOpenResponsesListingBlock", body_html)
parser = etree.HTMLParser()
tree = etree.parse(StringIO(body_html), parser)
xpath_query_to_get_main_section = "//section[contains(@class, 'open-response-assessment-block')]"
xpath_query_to_get_course_items = "//script[contains(@id, 'open-response-assessment-items')]"
sections = tree.xpath(xpath_query_to_get_main_section)
self.assertEqual(len(sections), 1)
self.assertEquals(sections[0].get('data-item-view-enabled'), '0')
scripts = tree.xpath(xpath_query_to_get_course_items)
self.assertEqual(len(scripts), 1)
self.assertEqual(scripts[0].text, '[]')
defined_ora_items = [{'id': 'test-id1', 'val': 'test-val1'},
{'id': 'test-id2', 'val': 'test-val2'}]
xblock_fragment = self.runtime.render(xblock, "ora_blocks_listing_view", context={
'ora_items': defined_ora_items,
'ora_item_view_enabled': True
})
body_html = xblock_fragment.body_html()
parser = etree.HTMLParser()
tree = etree.parse(StringIO(body_html), parser)
sections = tree.xpath(xpath_query_to_get_main_section)
self.assertEqual(len(sections), 1)
self.assertEquals(sections[0].get('data-item-view-enabled'), '1')
scripts = tree.xpath(xpath_query_to_get_course_items)
self.assertEqual(len(scripts), 1)
items = json.loads(scripts[0].text)
self.assertEqual(items, defined_ora_items)
@scenario('data/empty_prompt.xml')
def test_prompt_intentionally_empty(self, xblock):
# Verify that prompts intentionally left empty don't create DOM elements
......
......@@ -3,6 +3,8 @@
"version": "0.2.0",
"repository": "https://github.com/edx/edx-ora2.git",
"dependencies": {
"backbone": "~1.2.3",
"backgrid ": "~0.3.8",
"edx-ui-toolkit": "1.5.1",
"moment": "^2.15.1",
"moment-timezone": "~0.5.5",
......
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