Commit f4638973 by Tim Krones

Rework UI of Student Answers Dashboard.

parent e0f62ff8
.data-export-options, .data-export-results { .data-export-options, .data-export-results, .data-export-status {
margin-top: 2em; margin-top: 2em;
} }
.data-export-actions button { .data-export-options, .data-export-results table {
display: none; border: 2px solid #999;
} }
.data-export-status { .data-export-options, .data-export-results thead {
margin-top: 2em; background-color: #ddd;
height: 8em; }
.data-export-options {
display: table;
padding: 1em;
}
.data-export-header, .data-export-row {
display: table-row;
}
.data-export-header h3, .data-export-results thead {
font-weight: bold;
}
.data-export-header h3 {
margin-top: 0px;
margin-bottom: 10px;
}
.data-export-field-container, .data-export-options .data-export-actions {
display: table-cell;
padding-left: 1em;
} }
.data-export-field { .data-export-field {
margin-top: .5em; margin-top: .5em;
margin-bottom: .5em; margin-bottom: .5em;
} }
.data-export-field label span { .data-export-field label span {
font-weight: bold; padding-right: .5em;
} vertical-align: middle;
.data-export-helptext {
font-size: 75%;
} }
.data-export-field-container { .data-export-field input, .data-export-field select {
margin-bottom: 1em; max-width: 60%;
float: right;
} }
.data-export-results { .data-export-results, .data-export-download, .data-export-cancel, .data-export-delete {
display: none; display: none;
} }
.data-export-results table { .data-export-results table {
margin-top: 1em; margin-top: 1em;
border: 2px solid gray;
} }
.data-export-results thead { .data-export-results thead {
border-bottom: 2px solid gray; border-bottom: 2px solid #999;
font-weight: bold;
} }
.data-export-results td { .data-export-results td {
border-left: 1px solid #999;
padding: 5px; padding: 5px;
} }
.data-export-results .even {
background-color: #eee;
}
.data-export-info p {
font-size: 75%;
}
.data-export-status {
margin-bottom: 1em;
}
.data-export-status i {
font-size: 3em;
}
.data-export-actions {
text-align: right;
}
...@@ -44,7 +44,7 @@ function StudentAnswersDashboardBlock(runtime, element) { ...@@ -44,7 +44,7 @@ function StudentAnswersDashboardBlock(runtime, element) {
$deleteButton.prop('disabled', true); $deleteButton.prop('disabled', true);
$('.data-export-status', $element).empty().append( $('.data-export-status', $element).empty().append(
$('<i>').addClass('icon fa fa-spinner fa-spin') $('<i>').addClass('icon fa fa-spinner fa-spin')
); ).css("text-align", "center");
} }
function hideResults() { function hideResults() {
...@@ -68,8 +68,10 @@ function StudentAnswersDashboardBlock(runtime, element) { ...@@ -68,8 +68,10 @@ function StudentAnswersDashboardBlock(runtime, element) {
} }
function updateView() { function updateView() {
var $statusArea = $('.data-export-status', $element), startTime; var $exportInfo = $('.data-export-info', $element),
$statusArea = $('.data-export-status', $element), startTime;
$statusArea.empty(); $statusArea.empty();
$exportInfo.empty();
$startButton.toggle(!status.export_pending).prop('disabled', false); $startButton.toggle(!status.export_pending).prop('disabled', false);
$cancelButton.toggle(status.export_pending).prop('disabled', false); $cancelButton.toggle(status.export_pending).prop('disabled', false);
$downloadButton.toggle(Boolean(status.download_url)).prop('disabled', false); $downloadButton.toggle(Boolean(status.download_url)).prop('disabled', false);
...@@ -82,16 +84,14 @@ function StudentAnswersDashboardBlock(runtime, element) { ...@@ -82,16 +84,14 @@ function StudentAnswersDashboardBlock(runtime, element) {
{'error': status.last_export_result.error} {'error': status.last_export_result.error}
) )
)); ));
hideResults();
} else { } else {
startTime = new Date(status.last_export_result.start_timestamp * 1000); startTime = new Date(status.last_export_result.start_timestamp * 1000);
$statusArea.append($('<p>').text( $exportInfo.append($('<p>').text(
gettext('A report is available for download.')
));
$statusArea.append($('<p>').text(
_.template( _.template(
ngettext( ngettext(
'It was created at <%= creation_time %> and took <%= seconds %> second to finish.', 'Results retrieved on <%= creation_time %> (<%= seconds %> second).',
'It was created at <%= creation_time %> and took <%= seconds %> seconds to finish.', 'Results retrieved on <%= creation_time %> (<%= seconds %> seconds).',
status.last_export_result.generation_time_s.toFixed(1) status.last_export_result.generation_time_s.toFixed(1)
), ),
{ {
...@@ -100,35 +100,27 @@ function StudentAnswersDashboardBlock(runtime, element) { ...@@ -100,35 +100,27 @@ function StudentAnswersDashboardBlock(runtime, element) {
} }
) )
)); ));
}
// Display results // Display results
var $resultTableBody = $resultTable.find('tbody'); var $resultTableBody = $resultTable.find('tbody');
$resultTableBody.empty(); $resultTableBody.empty();
_.each(status.last_export_result.display_data, function(row, index) {
_.each(status.last_export_result.display_data, function(row) {
var tr = $('<tr>'); var tr = $('<tr>');
if (index % 2 === 0) {
tr.addClass('even');
}
_.each(row, function(cell) { _.each(row, function(cell) {
tr.append($('<td>').text(cell)); tr.append($('<td>').text(cell));
}); });
$resultTableBody.append(tr); $resultTableBody.append(tr);
}); });
showResults(); showResults();
}
} else { } else {
if (status.export_pending) { if (status.export_pending) {
$statusArea.append($('<p>').text( $statusArea.append($('<p>').text(
gettext('The report is currently being generated…') gettext('The report is currently being generated…')
)); ));
} else {
$statusArea.append($('<p>').text(
gettext('No report data available.')
));
} }
} }
} }
......
...@@ -164,20 +164,16 @@ class StudentAnswersDashboardBlock(XBlock): ...@@ -164,20 +164,16 @@ class StudentAnswersDashboardBlock(XBlock):
username = data.get('username', None) username = data.get('username', None)
root_block_id = data.get('root_block_id', None) root_block_id = data.get('root_block_id', None)
match_string = data.get('match_string', None) match_string = data.get('match_string', None)
if not root_block_id:
root_block_id = self.scope_ids.usage_id # Process user-submitted data
# Block ID not in workbench runtime. if block_types == 'all':
root_block_id = unicode(getattr(root_block_id, 'block_id', root_block_id)) block_types = []
get_root = True
else: else:
get_root = False block_types = [block_types]
user_service = self.runtime.service(self, 'user') user_service = self.runtime.service(self, 'user')
if not self.user_is_staff(): if not self.user_is_staff():
return {'error': 'permission denied'} return {'error': 'permission denied'}
from .tasks import export_data as export_data_task # Import here since this is edX LMS specific
self._delete_export()
# Make sure we nail down our state before sending off an asynchronous task.
self.save()
if not username: if not username:
user_id = None user_id = None
else: else:
...@@ -185,6 +181,19 @@ class StudentAnswersDashboardBlock(XBlock): ...@@ -185,6 +181,19 @@ class StudentAnswersDashboardBlock(XBlock):
if user_id is None: if user_id is None:
self.raise_error(404, _("Could not find the specified username.")) self.raise_error(404, _("Could not find the specified username."))
if not root_block_id:
root_block_id = self.scope_ids.usage_id
# Block ID not in workbench runtime.
root_block_id = unicode(getattr(root_block_id, 'block_id', root_block_id))
get_root = True
else:
get_root = False
# Launch task
from .tasks import export_data as export_data_task # Import here since this is edX LMS specific
self._delete_export()
# Make sure we nail down our state before sending off an asynchronous task.
self.save()
async_result = export_data_task.delay( async_result = export_data_task.delay(
# course_id not available in workbench. # course_id not available in workbench.
unicode(getattr(self.runtime, 'course_id', 'course_id')), unicode(getattr(self.runtime, 'course_id', 'course_id')),
......
{% load i18n %} {% load i18n %}
<h3>{% trans "Student Answers Dashboard" %}</h3> <h2>{% trans "Student Answers Dashboard" %}</h3>
<p>{% trans "You can export all student answers to multiple-choice questions and long-form answers to a CSV file here." %}</p>
<div class="data-export-options"> <div class="data-export-options">
<div class="data-export-header">
<h3>{% trans "Filters" %}</h3>
</div>
<div class="data-export-row">
<div class="data-export-field-container"> <div class="data-export-field-container">
<div class="data-export-field"> <div class="data-export-field">
<label> <label>
<span>{% trans "Problem types:" %}</span> <span>{% trans "Username:" %}</span>
<select multiple name="block_types"> <input type="text" name="username" />
{% for label, value in block_choices.items %}
<option value="{{value}}">{{label}}</option>
{% endfor %}
</select>
</label> </label>
</div> </div>
<div class="data-export-helptext">
{% trans "Select which types of problem to include (selecting none will grab all types)" %}
</div>
</div> </div>
<div class="data-export-field-container"> <div class="data-export-field-container">
<div class="data-export-field"> <div class="data-export-field">
<label> <label>
<span>{% trans "Root block ID:" %}</span> <span>{% trans "Text:" %}</span>
<input type="text" name="root_block_id" /> <input type="text" name="match_string" />
</label> </label>
</div> </div>
<div class="data-export-helptext">
{% trans "Input the ID of a chapter, section, or unit if you wish to only get results under it. Otherwise, it will grab all results for the course." %}
</div> </div>
</div> </div>
<div class="data-export-row">
<div class="data-export-field-container"> <div class="data-export-field-container">
<div class="data-export-field"> <div class="data-export-field">
<label> <label>
<span>{% trans "Username:" %}</span> <span>{% trans "Root block ID:" %}</span>
<input type="text" name="username" /> <input type="text" name="root_block_id" />
</label> </label>
</div> </div>
<div class="data-export-helptext">
{% trans "Input the username of a student if you wish to query for a specific one. Otherwise, it will grab all results for all students." %}
</div>
</div> </div>
<div class="data-export-field-container"> <div class="data-export-field-container">
<div class="data-export-field"> <div class="data-export-field">
<label> <label>
<span>{% trans "Match answers containing:" %}</span> <span>{% trans "Problem types:" %}</span>
<input type="text" name="match_string" /> <select name="block_types">
<option value="all">All</option>
{% for label, value in block_choices.items %}
<option value="{{value}}">{{label}}</option>
{% endfor %}
</select>
</label> </label>
</div> </div>
<div class="data-export-helptext"> </div>
{% trans "Input text that all answers must match (case will be ignored). Otherwise, answers will not be filtered by content." %} <div class="data-export-actions">
<button class="data-export-start">Search</button>
</div> </div>
</div> </div>
</div>
<div class="data-export-actions">
<button class="data-export-start">{% trans "Start a new export" %}</button>
</div> </div>
<div class="data-export-results"> <div class="data-export-results">
<h3>{% trans "Results" %}</h3>
<table> <table>
<thead> <thead>
<tr> <tr>
...@@ -73,12 +66,13 @@ ...@@ -73,12 +66,13 @@
</thead> </thead>
<tbody></tbody> <tbody></tbody>
</table> </table>
<div class="data-export-info"></div>
</div> </div>
<div class="data-export-status"></div> <div class="data-export-status"></div>
<div class="data-export-actions"> <div class="data-export-actions">
<button class="data-export-download">{% trans "Download result" %}</button> <button class="data-export-download">{% trans "Download as CSV" %}</button>
<button class="data-export-cancel">{% trans "Cancel current export" %}</button> <button class="data-export-cancel">{% trans "Cancel search" %}</button>
<button class="data-export-delete">{% trans "Delete result" %}</button> <button class="data-export-delete">{% trans "Delete results" %}</button>
</div> </div>
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