Commit 01e30ce9 by Tim Krones

Address review comments.

parent 9fe9f345
...@@ -51,7 +51,7 @@ ...@@ -51,7 +51,7 @@
border-left: 1px solid #999; border-left: 1px solid #999;
padding: 5px; padding: 5px;
} }
.data-export-results .even { .data-export-results tr:nth-child(odd) {
background-color: #eee; background-color: #eee;
} }
.data-export-info p { .data-export-info p {
......
...@@ -18,9 +18,9 @@ function StudentAnswersDashboardBlock(runtime, element) { ...@@ -18,9 +18,9 @@ function StudentAnswersDashboardBlock(runtime, element) {
model: Result, model: Result,
getCurrentPage: function(object) { getCurrentPage: function(returnObject) {
var currentPage = this.state.currentPage; var currentPage = this.state.currentPage;
if (object) { if (returnObject) {
return this.getPage(currentPage); return this.getPage(currentPage);
} }
return currentPage; return currentPage;
...@@ -46,9 +46,6 @@ function StudentAnswersDashboardBlock(runtime, element) { ...@@ -46,9 +46,6 @@ function StudentAnswersDashboardBlock(runtime, element) {
tbody.empty(); tbody.empty();
records.each(function(result, index) { records.each(function(result, index) {
var row = $('<tr>'); var row = $('<tr>');
if (index % 2 === 0) {
row.addClass('even');
}
_.each(Result.properties, function(name) { _.each(Result.properties, function(name) {
row.append($('<td>').text(result.get(name))); row.append($('<td>').text(result.get(name)));
}); });
...@@ -94,38 +91,16 @@ function StudentAnswersDashboardBlock(runtime, element) { ...@@ -94,38 +91,16 @@ function StudentAnswersDashboardBlock(runtime, element) {
_updateControls: function() { _updateControls: function() {
var currentPage = this.collection.getCurrentPage(), var currentPage = this.collection.getCurrentPage(),
totalPages = this.collection.getTotalPages(), totalPages = this.collection.getTotalPages() || 0,
firstPage = '#first-page', backward = ["#first-page", "#prev-page"],
prevPage = '#prev-page', forward = ["#next-page", "#last-page"];
nextPage = '#next-page', this._enable(backward, currentPage > 1);
lastPage = '#last-page', this._enable(forward, currentPage < totalPages);
all = [firstPage, prevPage, nextPage, lastPage],
backward = [firstPage, prevPage],
forward = [nextPage, lastPage];
if (!totalPages || totalPages === 1) {
this._disable(all);
} else {
if (currentPage === 1) {
this._disable(backward);
this._enable(forward);
} else if (currentPage === totalPages) {
this._enable(backward);
this._disable(forward);
} else {
this._enable(all);
}
}
},
_enable: function(controls) {
_.each(controls, function(control) {
this.$(control).prop('disabled', false);
}, this);
}, },
_disable: function(controls) { _enable: function(controls, condition) {
_.each(controls, function(control) { _.each(controls, function(control) {
this.$(control).prop('disabled', true); this.$(control).prop('disabled', !condition);
}, this); }, this);
} }
...@@ -187,12 +162,8 @@ function StudentAnswersDashboardBlock(runtime, element) { ...@@ -187,12 +162,8 @@ function StudentAnswersDashboardBlock(runtime, element) {
} }
function showResults() { function showResults() {
$resultTable.show();
}
function maybeShowResults() {
if (status.last_export_result) { if (status.last_export_result) {
showResults(); $resultTable.show();
} }
} }
...@@ -286,7 +257,7 @@ function StudentAnswersDashboardBlock(runtime, element) { ...@@ -286,7 +257,7 @@ function StudentAnswersDashboardBlock(runtime, element) {
addHandler($deleteButton, 'delete_export'); addHandler($deleteButton, 'delete_export');
$startButton.on('click', hideResults); $startButton.on('click', hideResults);
$cancelButton.on('click', maybeShowResults); $cancelButton.on('click', showResults);
$deleteButton.on('click', hideResults); $deleteButton.on('click', hideResults);
$downloadButton.on('click', function() { $downloadButton.on('click', function() {
......
...@@ -7,9 +7,8 @@ from celery.task import task ...@@ -7,9 +7,8 @@ from celery.task import task
from celery.utils.log import get_task_logger from celery.utils.log import get_task_logger
from instructor_task.models import ReportStore from instructor_task.models import ReportStore
from opaque_keys import InvalidKeyError from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import UsageKey, CourseKey from opaque_keys.edx.keys import CourseKey
from student.models import user_by_anonymous_id from student.models import user_by_anonymous_id
from submissions.models import StudentItem
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from xmodule.modulestore.exceptions import ItemNotFoundError from xmodule.modulestore.exceptions import ItemNotFoundError
...@@ -67,36 +66,14 @@ def export_data(course_id, source_block_id_str, block_types, user_id, match_stri ...@@ -67,36 +66,14 @@ def export_data(course_id, source_block_id_str, block_types, user_id, match_stri
scan_for_blocks(root) scan_for_blocks(root)
# Define the header rows of our CSV: # Define the header row of our CSV:
rows = [] rows = []
rows.append(["Section", "Subsection", "Unit", "Type", "Question", "Answer", "Username"]) rows.append(["Section", "Subsection", "Unit", "Type", "Question", "Answer", "Username"])
# Load the actual student submissions for each block in blocks_to_include. # Collect results for each block in blocks_to_include
# Note this requires one giant query per block (all student submissions for each block, one block at a time)
for block in blocks_to_include: for block in blocks_to_include:
# Get all of the most recent student submissions for this block: results = _extract_data(course_key_str, block, user_id, match_string)
block_id = unicode(block.scope_ids.usage_id.replace(branch=None, version_guid=None)) rows += results
block_type = block.scope_ids.block_type
if not user_id:
submissions = sub_api.get_all_submissions(course_key_str, block_id, block_type)
else:
student_dict = {
'student_id': user_id,
'item_id': block_id,
'course_id': course_key_str,
'item_type': block_type,
}
submissions = sub_api.get_submissions(student_dict, limit=1)
for submission in submissions:
# If the student ID key doesn't exist, we're dealing with a single student and know the ID already.
student_id = submission.get('student_id', user_id)
# Extract data for display
# "row" will be None if answer does not match "match_string"
row = _extract_data_for_display(submission, student_id, block_type, match_string)
if row:
rows.append(row)
# Generate the CSV: # Generate the CSV:
filename = u"pb-data-export-{}.csv".format(time.strftime("%Y-%m-%d-%H%M%S", time.gmtime(start_timestamp))) filename = u"pb-data-export-{}.csv".format(time.strftime("%Y-%m-%d-%H%M%S", time.gmtime(start_timestamp)))
...@@ -111,29 +88,100 @@ def export_data(course_id, source_block_id_str, block_types, user_id, match_stri ...@@ -111,29 +88,100 @@ def export_data(course_id, source_block_id_str, block_types, user_id, match_stri
"report_filename": filename, "report_filename": filename,
"start_timestamp": start_timestamp, "start_timestamp": start_timestamp,
"generation_time_s": generation_time_s, "generation_time_s": generation_time_s,
"display_data": [] if len(rows) == 1 else rows[1:] "display_data": [] if len(rows) == 1 else rows[1:1001] # Limit to preview of 1000 items
} }
def _extract_data_for_display(submission, student_id, block_type, match_string): def _extract_data(course_key_str, block, user_id, match_string):
""" """
Extract data that will be displayed on Student Answers Dashboard Extract results for `block`.
from `submission`.
""" """
rows = []
# Username # Extract info for "Section", "Subsection", and "Unit" columns
user = user_by_anonymous_id(student_id) section_name, subsection_name, unit_name = _get_context(block)
username = user.username
# Question # Extract info for "Type" column
student_item = StudentItem.objects.get(pk=submission['student_item']) block_type = _get_type(block)
block_key = UsageKey.from_string(student_item.item_id) # Extract info for "Question" column
block = modulestore().get_item(block_key) block_question = block.question
# Extract info for "Answer" and "Username" columns
# - Get all of the most recent student submissions for this block:
submissions = _get_submissions(course_key_str, block, user_id)
# - For each submission, look up student's username and answer:
for submission in submissions:
username = _get_username(submission, user_id)
answer = _get_answer(block, submission)
# Short-circuit if answer does not match search criteria
if not match_string.lower() in answer.lower():
continue
rows.append([section_name, subsection_name, unit_name, block_type, block_question, answer, username])
return rows
def _get_context(block):
"""
Return section, subsection, and unit names for `block`.
"""
block_names_by_type = {}
block_iter = block
while block_iter:
block_iter_type = block_iter.scope_ids.block_type
block_names_by_type[block_iter_type] = block_iter.display_name_with_default
block_iter = block_iter.get_parent() if block_iter.parent else None
section_name = block_names_by_type.get('chapter', '')
subsection_name = block_names_by_type.get('sequential', '')
unit_name = block_names_by_type.get('vertical', '')
return section_name, subsection_name, unit_name
def _get_type(block):
"""
Return type of `block`.
"""
return block.scope_ids.block_type
# Answer
answer = submission['answer']
def _get_submissions(course_key_str, block, user_id):
"""
Return submissions for `block`.
"""
# Load the actual student submissions for `block`.
# Note this requires one giant query that retrieves all student submissions for `block` at once.
block_id = unicode(block.scope_ids.usage_id.replace(branch=None, version_guid=None))
block_type = _get_type(block)
if not user_id:
return sub_api.get_all_submissions(course_key_str, block_id, block_type)
else:
student_dict = {
'student_id': user_id,
'item_id': block_id,
'course_id': course_key_str,
'item_type': block_type,
}
return sub_api.get_submissions(student_dict, limit=1)
def _get_username(submission, user_id):
"""
Return username of student who provided `submission`.
"""
# If the student ID key doesn't exist, we're dealing with a single student and know the ID already.
student_id = submission.get('student_id', user_id)
return user_by_anonymous_id(student_id).username
def _get_answer(block, submission):
"""
Return answer associated with this `submission` to `block`.
"""
answer = submission['answer']
try: try:
choices = block.children choices = block.children
except AttributeError: except AttributeError:
...@@ -144,27 +192,4 @@ def _extract_data_for_display(submission, student_id, block_type, match_string): ...@@ -144,27 +192,4 @@ def _extract_data_for_display(submission, student_id, block_type, match_string):
if choice_block.value == answer: if choice_block.value == answer:
answer = choice_block.content answer = choice_block.content
break break
return answer
# Short-circuit if answer does not match search criteria
if not match_string.lower() in answer.lower():
return
# Unit
mentoring_block = modulestore().get_item(block.parent)
unit = modulestore().get_item(mentoring_block.parent)
# Subsection
subsection = modulestore().get_item(unit.parent)
# Section
section = modulestore().get_item(subsection.parent)
return [
section.display_name,
subsection.display_name,
unit.display_name,
block_type,
block.question,
answer,
username
]
...@@ -54,6 +54,7 @@ BLOCKS = [ ...@@ -54,6 +54,7 @@ BLOCKS = [
'pb-choice = problem_builder:ChoiceBlock', 'pb-choice = problem_builder:ChoiceBlock',
'pb-dashboard = problem_builder:DashboardBlock', 'pb-dashboard = problem_builder:DashboardBlock',
'pb-data-export = problem_builder:DataExportBlock', # Deprecated; use pb-student-answers-dashboard instead
'pb-student-answers-dashboard = problem_builder:StudentAnswersDashboardBlock', 'pb-student-answers-dashboard = problem_builder:StudentAnswersDashboardBlock',
] ]
......
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