Commit 9bc7a518 by David Adams

Fixes issue with metrics tab click handlers

  Click handlers were not getting attached to DOM elements in some cases on slow running machines.
  Added logic to attach handlers when elements are ready.

Added 2 buttons on metrics tab:
  Download Subsection Data for downloading to csv.
  Download Problem Data for downloading to csv.
parent aacbc5ed
......@@ -2,6 +2,7 @@
Computes the data to display on the Instructor Dashboard
"""
from util.json_request import JsonResponse
import json
from courseware import models
from django.db.models import Count
......@@ -21,9 +22,12 @@ def get_problem_grade_distribution(course_id):
`course_id` the course ID for the course interested in
Output is a dict, where the key is the problem 'module_id' and the value is a dict with:
Output is 2 dicts:
'prob-grade_distrib' where the key is the problem 'module_id' and the value is a dict with:
'max_grade' - max grade for this problem
'grade_distrib' - array of tuples (`grade`,`count`).
'total_student_count' where the key is problem 'module_id' and the value is number of students
attempting the problem
"""
# Aggregate query on studentmodule table for grade data for all problems in course
......@@ -34,6 +38,7 @@ def get_problem_grade_distribution(course_id):
).values('module_state_key', 'grade', 'max_grade').annotate(count_grade=Count('grade'))
prob_grade_distrib = {}
total_student_count = {}
# Loop through resultset building data for each problem
for row in db_query:
......@@ -53,7 +58,10 @@ def get_problem_grade_distribution(course_id):
'grade_distrib': [(row['grade'], row['count_grade'])]
}
return prob_grade_distrib
# Build set of total students attempting each problem
total_student_count[curr_problem] = total_student_count.get(curr_problem, 0) + row['count_grade']
return prob_grade_distrib, total_student_count
def get_sequential_open_distrib(course_id):
......@@ -136,7 +144,7 @@ def get_d3_problem_grade_distrib(course_id):
'data' - data for the d3_stacked_bar_graph function of the grade distribution for that problem
"""
prob_grade_distrib = get_problem_grade_distribution(course_id)
prob_grade_distrib, total_student_count = get_problem_grade_distribution(course_id)
d3_data = []
# Retrieve course object down to problems
......@@ -178,19 +186,24 @@ def get_d3_problem_grade_distrib(course_id):
for (grade, count_grade) in problem_info['grade_distrib']:
percent = 0.0
if max_grade > 0:
percent = (grade * 100.0) / max_grade
# Construct tooltip for problem in grade distibution view
tooltip = _("{label} {problem_name} - {count_grade} {students} ({percent:.0f}%: {grade:.0f}/{max_grade:.0f} {questions})").format(
label=label,
problem_name=problem_name,
count_grade=count_grade,
students=_("students"),
percent=percent,
grade=grade,
max_grade=max_grade,
questions=_("questions"),
)
percent = round((grade * 100.0) / max_grade, 1)
# Compute percent of students with this grade
student_count_percent = 0
if total_student_count.get(child.location.url(), 0) > 0:
student_count_percent = count_grade * 100 / total_student_count[child.location.url()]
# Tooltip parameters for problem in grade distribution view
tooltip = {
'type': 'problem',
'label': label,
'problem_name': problem_name,
'count_grade': count_grade,
'percent': percent,
'grade': grade,
'max_grade': max_grade,
'student_count_percent': student_count_percent,
}
# Construct data to be sent to d3
stack_data.append({
......@@ -246,11 +259,14 @@ def get_d3_sequential_open_distrib(course_id):
num_students = sequential_open_distrib[subsection.location.url()]
stack_data = []
tooltip = _("{num_students} student(s) opened Subsection {subsection_num}: {subsection_name}").format(
num_students=num_students,
subsection_num=c_subsection,
subsection_name=subsection_name,
)
# Tooltip parameters for subsection in open_distribution view
tooltip = {
'type': 'subsection',
'num_students': num_students,
'subsection_num': c_subsection,
'subsection_name': subsection_name
}
stack_data.append({
'color': 0,
......@@ -329,19 +345,18 @@ def get_d3_section_grade_distrib(course_id, section):
for (grade, count_grade) in grade_distrib[problem]['grade_distrib']:
percent = 0.0
if max_grade > 0:
percent = (grade * 100.0) / max_grade
percent = round((grade * 100.0) / max_grade, 1)
# Construct tooltip for problem in grade distibution view
tooltip = _("{problem_info_x} {problem_info_n} - {count_grade} {students} ({percent:.0f}%: {grade:.0f}/{max_grade:.0f} {questions})").format(
problem_info_x=problem_info[problem]['x_value'],
count_grade=count_grade,
students=_("students"),
percent=percent,
problem_info_n=problem_info[problem]['display_name'],
grade=grade,
max_grade=max_grade,
questions=_("questions"),
)
tooltip = {
'type': 'problem',
'problem_info_x': problem_info[problem]['x_value'],
'count_grade': count_grade,
'percent': percent,
'problem_info_n': problem_info[problem]['display_name'],
'grade': grade,
'max_grade': max_grade,
}
stack_data.append({
'color': percent,
......@@ -415,6 +430,7 @@ def get_students_opened_subsection(request, csv=False):
If 'csv' is True, returns a header array, and an array of arrays in the format:
student names, usernames for CSV download.
"""
module_id = request.GET.get('module_id')
csv = request.GET.get('csv')
......@@ -447,9 +463,11 @@ def get_students_opened_subsection(request, csv=False):
return JsonResponse(response_payload)
else:
tooltip = request.GET.get('tooltip')
filename = sanitize_filename(tooltip[tooltip.index('S'):])
header = ['Name', 'Username']
# Subsection name is everything after 3rd space in tooltip
filename = sanitize_filename(' '.join(tooltip.split(' ')[3:]))
header = [_("Name").encode('utf-8'), _("Username").encode('utf-8')]
for student in students:
results.append([student['student__profile__name'], student['student__username']])
......@@ -507,7 +525,7 @@ def get_students_problem_grades(request, csv=False):
tooltip = request.GET.get('tooltip')
filename = sanitize_filename(tooltip[:tooltip.rfind(' - ')])
header = ['Name', 'Username', 'Grade', 'Percent']
header = [_("Name").encode('utf-8'), _("Username").encode('utf-8'), _("Grade").encode('utf-8'), _("Percent").encode('utf-8')]
for student in students:
percent = 0
......@@ -519,11 +537,60 @@ def get_students_problem_grades(request, csv=False):
return response
def post_metrics_data_csv(request):
"""
Generate a list of opened subsections or problems for the entire course for CSV download.
Returns a header array, and an array of arrays in the format:
section, subsection, count of students for subsections
or section, problem, name, count of students, percent of students, score for problems.
"""
data = json.loads(request.POST['data'])
sections = json.loads(data['sections'])
tooltips = json.loads(data['tooltips'])
course_id = data['course_id']
data_type = data['data_type']
results = []
if data_type == 'subsection':
header = [_("Section").encode('utf-8'), _("Subsection").encode('utf-8'), _("Opened by this number of students").encode('utf-8')]
filename = sanitize_filename(_('subsections') + '_' + course_id)
elif data_type == 'problem':
header = [_("Section").encode('utf-8'), _("Problem").encode('utf-8'), _("Name").encode('utf-8'), _("Count of Students").encode('utf-8'), _("% of Students").encode('utf-8'), _("Score").encode('utf-8')]
filename = sanitize_filename(_('problems') + '_' + course_id)
for index, section in enumerate(sections):
results.append([section])
# tooltips array is array of dicts for subsections and
# array of array of dicts for problems.
if data_type == 'subsection':
for tooltip_dict in tooltips[index]:
num_students = tooltip_dict['num_students']
subsection = tooltip_dict['subsection_name']
# Append to results offsetting 1 column to the right.
results.append(['', subsection, num_students])
elif data_type == 'problem':
for tooltip in tooltips[index]:
for tooltip_dict in tooltip:
label = tooltip_dict['label']
problem_name = tooltip_dict['problem_name']
count_grade = tooltip_dict['count_grade']
student_count_percent = tooltip_dict['student_count_percent']
percent = tooltip_dict['percent']
# Append to results offsetting 1 column to the right.
results.append(['', label, problem_name, count_grade, student_count_percent, percent])
response = create_csv_response(filename, header, results)
return response
def sanitize_filename(filename):
"""
Utility function
"""
filename = filename.replace(" ", "_")
filename = filename.encode('ascii')
filename = filename.encode('utf-8')
filename = filename[0:25] + '.csv'
return filename
......@@ -95,12 +95,15 @@ class TestGetProblemGradeDistribution(ModuleStoreTestCase):
def test_get_problem_grade_distribution(self):
prob_grade_distrib = get_problem_grade_distribution(self.course.id)
prob_grade_distrib, total_student_count = get_problem_grade_distribution(self.course.id)
for problem in prob_grade_distrib:
max_grade = prob_grade_distrib[problem]['max_grade']
self.assertEquals(1, max_grade)
for val in total_student_count.values():
self.assertEquals(USER_COUNT, val)
def test_get_sequential_open_distibution(self):
sequential_open_distrib = get_sequential_open_distrib(self.course.id)
......@@ -243,6 +246,61 @@ class TestGetProblemGradeDistribution(ModuleStoreTestCase):
# Check response contains 1 line for each user +1 for the header
self.assertEquals(USER_COUNT + 1, len(response.content.splitlines()))
def test_post_metrics_data_subsections_csv(self):
url = reverse('post_metrics_data_csv')
sections = json.dumps(["Introduction"])
tooltips = json.dumps([[{"subsection_name": "Pre-Course Survey", "subsection_num": 1, "type": "subsection", "num_students": 18963}]])
course_id = self.course.id
data_type = 'subsection'
data = json.dumps({'sections': sections,
'tooltips': tooltips,
'course_id': course_id,
'data_type': data_type,
})
response = self.client.post(url, {'data': data})
# Check response contains 1 line for header, 1 line for Section and 1 line for Subsection
self.assertEquals(3, len(response.content.splitlines()))
def test_post_metrics_data_problems_csv(self):
url = reverse('post_metrics_data_csv')
sections = json.dumps(["Introduction"])
tooltips = json.dumps([[[
{'student_count_percent': 0,
'problem_name': 'Q1',
'grade': 0,
'percent': 0,
'label': 'P1.2.1',
'max_grade': 1,
'count_grade': 26,
'type': u'problem'},
{'student_count_percent': 99,
'problem_name': 'Q1',
'grade': 1,
'percent': 100,
'label': 'P1.2.1',
'max_grade': 1,
'count_grade': 4763,
'type': 'problem'},
]]])
course_id = self.course.id
data_type = 'problem'
data = json.dumps({'sections': sections,
'tooltips': tooltips,
'course_id': course_id,
'data_type': data_type,
})
response = self.client.post(url, {'data': data})
# Check response contains 1 line for header, 1 line for Sections and 2 lines for problems
self.assertEquals(4, len(response.content.splitlines()))
def test_get_section_display_name(self):
section_display_name = get_section_display_name(self.course.id)
......
"""
Class Dashboard API endpoint urls.
"""
from django.conf.urls import patterns, url
urlpatterns = patterns('', # nopep8
# Json request data for metrics for entire course
url(r'^(?P<course_id>[^/]+/[^/]+/[^/]+)/all_sequential_open_distrib$',
'class_dashboard.views.all_sequential_open_distrib', name="all_sequential_open_distrib"),
url(r'^(?P<course_id>[^/]+/[^/]+/[^/]+)/all_problem_grade_distribution$',
'class_dashboard.views.all_problem_grade_distribution', name="all_problem_grade_distribution"),
# Json request data for metrics for particular section
url(r'^(?P<course_id>[^/]+/[^/]+/[^/]+)/problem_grade_distribution/(?P<section>\d+)$',
'class_dashboard.views.section_problem_grade_distrib', name="section_problem_grade_distrib"),
# For listing students that opened a sub-section
url(r'^get_students_opened_subsection$',
'class_dashboard.dashboard_data.get_students_opened_subsection', name="get_students_opened_subsection"),
# For listing of students' grade per problem
url(r'^get_students_problem_grades$',
'class_dashboard.dashboard_data.get_students_problem_grades', name="get_students_problem_grades"),
# For generating metrics data as a csv
url(r'^post_metrics_data_csv_url',
'class_dashboard.dashboard_data.post_metrics_data_csv', name="post_metrics_data_csv"),
)
......@@ -250,10 +250,12 @@ def _section_metrics(course_id, access):
'section_key': 'metrics',
'section_display_name': ('Metrics'),
'access': access,
'course_id': course_id,
'sub_section_display_name': get_section_display_name(course_id),
'section_has_problem': get_array_section_has_problem(course_id),
'get_students_opened_subsection_url': reverse('get_students_opened_subsection'),
'get_students_problem_grades_url': reverse('get_students_problem_grades'),
'post_metrics_data_csv_url': reverse('post_metrics_data_csv'),
}
return section_data
......
......@@ -591,17 +591,16 @@ section.instructor-dashboard-content-2 {
.instructor-dashboard-wrapper-2 section.idash-section#metrics {
.metrics-container {
.metrics-container, .metrics-header-container {
position: relative;
width: 100%;
float: left;
clear: both;
margin-top: 25px;
.metrics-left {
.metrics-left, .metrics-left-header {
position: relative;
width: 30%;
height: 640px;
float: left;
margin-right: 2.5%;
......@@ -609,10 +608,13 @@ section.instructor-dashboard-content-2 {
width: 100%;
}
}
.metrics-right {
.metrics-section.metrics-left {
height: 640px;
}
.metrics-right, .metrics-right-header {
position: relative;
width: 65%;
height: 295px;
float: left;
margin-left: 2.5%;
margin-bottom: 25px;
......@@ -622,6 +624,10 @@ section.instructor-dashboard-content-2 {
}
}
.metrics-section.metrics-right {
height: 295px;
}
svg {
.stacked-bar {
cursor: pointer;
......@@ -718,10 +724,6 @@ section.instructor-dashboard-content-2 {
border-radius: 5px;
margin-top: 25px;
}
input#graph_reload {
display: none;
}
}
}
......
<%page args="id_opened_prefix, id_grade_prefix, id_attempt_prefix, id_tooltip_prefix, course_id, **kwargs"/>
<%page args="id_opened_prefix, id_grade_prefix, id_attempt_prefix, id_tooltip_prefix, course_id, allSubsectionTooltipArr, allProblemTooltipArr, **kwargs"/>
<%!
import json
from django.core.urlresolvers import reverse
......@@ -30,6 +30,13 @@ $(function () {
margin: {left:0},
};
// Construct array of tooltips for all sections for the "Download Subsection Data" button.
var sectionTooltipArr = new Array();
paramOpened.data.forEach( function(element, index, array) {
sectionTooltipArr[index] = element.stackData[0].tooltip;
});
allSubsectionTooltipArr[i] = sectionTooltipArr;
barGraphOpened = edx_d3CreateStackedBarGraph(paramOpened, d3.select(curr_id).append("svg"),
d3.select("#${id_tooltip_prefix}"+i));
barGraphOpened.scale.stackColor.range(["#555555","#555555"]);
......@@ -68,6 +75,17 @@ $(function () {
bVerticalXAxisLabel : true,
};
// Construct array of tooltips for all sections for the "Download Problem Data" button.
var sectionTooltipArr = new Array();
paramGrade.data.forEach( function(element, index, array) {
var stackDataArr = new Array();
for (var j = 0; j < element.stackData.length; j++) {
stackDataArr[j] = element.stackData[j].tooltip
}
sectionTooltipArr[index] = stackDataArr;
});
allProblemTooltipArr[i] = sectionTooltipArr;
barGraphGrade = edx_d3CreateStackedBarGraph(paramGrade, d3.select(curr_id).append("svg"),
d3.select("#${id_tooltip_prefix}"+i));
barGraphGrade.scale.stackColor.domain([0,50,100]).range(["#e13f29","#cccccc","#17a74d"]);
......@@ -83,6 +101,7 @@ $(function () {
i+=1;
}
});
});
\ No newline at end of file
......@@ -349,8 +349,20 @@ edx_d3CreateStackedBarGraph = function(parameters, svg, divTooltip) {
var top = pos[1]-10;
var width = $('#'+graph.divTooltip.attr("id")).width();
// Construct the tooltip
if (d.tooltip['type'] == 'subsection') {
tooltip_str = d.tooltip['num_students'] + ' ' + gettext('student(s) opened Subsection') + ' ' \
+ d.tooltip['subsection_num'] + ': ' + d.tooltip['subsection_name']
}else if (d.tooltip['type'] == 'problem') {
tooltip_str = d.tooltip['label'] + ' ' + d.tooltip['problem_name'] + ' - ' \
+ d.tooltip['count_grade'] + ' ' + gettext('students') + ' (' \
+ d.tooltip['student_count_percent'] + '%) (' + \
+ d.tooltip['percent'] + '%: ' + \
+ d.tooltip['grade'] +'/' + d.tooltip['max_grade'] + ' '
+ gettext('questions') + ')'
}
graph.divTooltip.style("visibility", "visible")
.text(d.tooltip);
.text(tooltip_str);
if ((left+width+30) > $("#"+graph.divTooltip.node().parentNode.id).width())
left -= (width+30);
......
......@@ -725,7 +725,9 @@ function goto( mode)
</div>
%endfor
<script>
${all_section_metrics.body("metric_opened_","metric_grade_","metric_attempts_","metric_tooltip_",course.id)}
var allSubsectionTooltipArr = new Array();
var allProblemTooltipArr = new Array();
${all_section_metrics.body("metric_opened_","metric_grade_","metric_attempts_","metric_tooltip_",course.id, allSubsectionTooltipArr, allProblemTooltipArr)}
</script>
%endif
......
<%! from django.utils.translation import ugettext as _ %>
<%! from django.utils.translation import ugettext as _ %>
<%page args="section_data"/>
......@@ -11,19 +11,35 @@
%else:
<%namespace name="d3_stacked_bar_graph" file="/class_dashboard/d3_stacked_bar_graph.js"/>
<%namespace name="all_section_metrics" file="/class_dashboard/all_section_metrics.js"/>
<h3 class="attention" id="graph_load">${_("Loading the latest graphs for you; depending on your class size, this may take a few minutes.")}</h3>
<input type="button" id="graph_reload" value="${_("Reload Graphs")}" />
<div id="graph_reload">
<p>${_("Use Reload Graphs to refresh the graphs.")}</p>
<p><input type="button" value="${_("Reload Graphs")}"/></p>
</div>
<div class="metrics-header-container">
<div class="metrics-left-header">
<h2>${_("Subsection Data")}</h2>
<p>${_("Each bar shows the number of students that opened the subsection.")}</p>
<p>${_("You can click on any of the bars to list the students that opened the subsection.")}</p>
<p>${_("You can also download this data as a CSV file.")}</p>
<p><input type="button" id="download_subsection_data" value="${_("Download Subsection Data for all Subsections as a CSV")}" /></p>
</div>
<div class="metrics-right-header">
<h2>${_("Grade Distribution Data")}</h2>
<p>${_("Each bar shows the grade distribution for that problem.")}</p>
<p>${_("You can click on any of the bars to list the students that attempted the problem, along with the grades they received.")}</p>
<p>${_("You can also download this data as a CSV file.")}</p>
<p><input type="button" id="download_problem_data" value="${_("Download Problem Data for all Problems as a CSV")}" /></p>
</div>
</div>
<!-- For each section with data, create the divs for displaying the graphs
and the popup window for listing the students
-->
%for i in range(0, len(section_data['sub_section_display_name'])):
<div class="metrics-container" id="metrics_section_${i}">
<h2>${_("Section:")} ${section_data['sub_section_display_name'][i]}</h2>
<h2>${_("Section")}: ${section_data['sub_section_display_name'][i]}</h2>
<div class="metrics-tooltip" id="metric_tooltip_${i}"></div>
<div class="metrics-section metrics-left" id="metric_opened_${i}">
<h3>${_("Count of Students Opened a Subsection")}</h3>
</div>
<div class="metrics-section metrics-right" id="metric_grade_${i}" data-section-has-problem=${section_data['section_has_problem'][i]}>
<h3>${_("Grade Distribution per Problem")}</h3>
......@@ -46,36 +62,11 @@
<script>
$(function () {
var firstLoad = true;
var allSubsectionTooltipArr = new Array();
var allProblemTooltipArr = new Array();
loadGraphs = function() {
$('#graph_load').show();
$('#graph_reload').hide();
$('.loading').remove();
var nothingText = "${_('There are no problems in this section.')}";
var loadingText = "${_('Loading...')}";
var nothingP = '<p class="nothing">' + nothingText + '</p>';
var loading = '<p class="loading"><i class="icon-spinner icon-spin icon-large"></i>' + loadingText + '</p>';
// Display spinners or "There are no problems in this section" message
$('.metrics-left').each(function() {
$(this).append(loading);
});
$('.metrics-right p.nothing').remove();
$('.metrics-right').each(function() {
if ($(this).data('section-has-problem') === "False") {
$(this).append(nothingP);
} else {
$(this).append(loading);
}
});
$('.metrics-left svg, .metrics-right svg').remove();
${all_section_metrics.body("metric_opened_", "metric_grade_", "metric_attempts_", "metric_tooltip_", course.id)}
setTimeout(function() {
$('#graph_load, #graph_reload').toggle();
$('.metrics-left .stacked-bar').on("click", function () {
// Click handler for left bars
$('.metrics-container').on("click", '.metrics-left .stacked-bar', function () {
var module_id = $('rect', this).attr('id');
var metrics_overlay = $(this).closest('.metrics-left').siblings('.metrics-overlay');
......@@ -93,7 +84,7 @@
dataType: "json",
success: function(response) {
overlay_content = '<tr class="header"><th>${_("Name")}</th><th>${_("Username")}</th></tr>';
overlay_content = "<tr class='header'><th>${_('Name')}</th><th>${_('Username')}</th></tr>";
$('.metrics-overlay-content thead', metrics_overlay).append(overlay_content);
$.each(response.results, function(index, value ){
......@@ -102,7 +93,7 @@
});
// If student list too long, append message to screen.
if (response.max_exceeded) {
overlay_content = '<p class="overflow-message">${_("This is a partial list, to view all students download as a csv.")}</p>';
overlay_content = "<p class='overflow-message'>${_('This is a partial list, to view all students download as a csv.')}</p>";
$('.metrics-overlay-content', metrics_overlay).after(overlay_content);
}
}
......@@ -111,7 +102,8 @@
metrics_overlay.show();
});
$('.metrics-right .stacked-bar').on("click",function () {
// Click handler for right bars
$('.metrics-container').on("click", '.metrics-right .stacked-bar', function () {
var module_id = $('rect', this).attr('id');
var metrics_overlay = $(this).closest('.metrics-right').siblings('.metrics-overlay');
......@@ -119,9 +111,8 @@
metrics_overlay.data("module-id", module_id);
var header = $(this).closest('.metrics-right').siblings('.metrics-tooltip').text();
var far_index = header.indexOf(' students (');
var near_index = header.substr(0, far_index).lastIndexOf(' ') + 1;
var title = header.substring(0, near_index -3);
var far_index = header.indexOf(' - ');
var title = header.substring(0, far_index);
var overlay_content = '<h3 class="metrics-overlay-title">' + title + '</h3>';
$('.metrics-overlay-content', metrics_overlay).before(overlay_content);
......@@ -133,7 +124,7 @@
dataType: "json",
success: function(response) {
overlay_content = '<tr class="header"><th>${_("Name")}</th><th>${_("Username")}</th><th>${_("Grade")}</th><th>${_("Percent")}</th></tr>';
overlay_content = "<tr class='header'><th>${_('Name')}</th><th>${_('Username')}</th><th>${_('Grade')}</th><th>${_('Percent')}</th></tr>";
$('.metrics-overlay-content thead', metrics_overlay).append(overlay_content);
$.each(response.results, function(index, value ){
......@@ -142,7 +133,7 @@
});
// If student list too long, append message to screen.
if (response.max_exceeded) {
overlay_content = '<p class="overflow-message">${_("This is a partial list, to view all students download as a csv.")}</p>';
overlay_content = "<p class='overflow-message'>${_('This is a partial list, to view all students download as a csv.')}</p>";
$('.metrics-overlay-content', metrics_overlay).after(overlay_content);
}
},
......@@ -151,23 +142,112 @@
metrics_overlay.show();
});
}, 5000);
loadGraphs = function() {
$('#graph_reload').hide();
$('.metrics-header-container').hide();
$('.loading').remove();
var nothingText = "${_('There are no problems in this section.')}";
var loadingText = "${_('Loading...')}";
var nothingP = '<p class="nothing">' + nothingText + '</p>';
var loading = '<p class="loading"><i class="icon-spinner icon-spin icon-large"></i>' + loadingText + '</p>';
// Display spinners or "There are no problems in this section" message
$('.metrics-left').each(function() {
$(this).append(loading);
});
$('.metrics-right p.nothing').remove();
$('.metrics-right').each(function() {
if ($(this).data('section-has-problem') === "False") {
$(this).append(nothingP);
} else {
$(this).append(loading);
}
});
$('.metrics-left svg, .metrics-right svg').remove();
${all_section_metrics.body("metric_opened_", "metric_grade_", "metric_attempts_", "metric_tooltip_", course.id, allSubsectionTooltipArr, allProblemTooltipArr)}
}
// For downloading subsection and problem data as csv
download_csv_data = function(event) {
var allSectionArr = []
var allTooltipArr = []
if (event.type == 'subsection') {
allTooltipArr = allSubsectionTooltipArr;
} else if (event.type == 'problem') {
allTooltipArr = allProblemTooltipArr;
}
allTooltipArr.forEach( function(element, index, array) {
var metrics_section = 'metrics_section' + '_' + index
// Get Section heading which is everything after first ': '
var heading = $('#' + metrics_section).children('h2').text();
allSectionArr[index] = heading.substr(heading.indexOf(': ') +2)
});
var data = {}
data['sections'] = JSON.stringify(allSectionArr);
data['tooltips'] = JSON.stringify(allTooltipArr);
data['course_id'] = "${section_data['course_id']}";
data['data_type'] = event.type;
var input_data = document.createElement("input");
input_data.name = 'data';
input_data.value = JSON.stringify(data);
var csrf_token_input = document.createElement("input");
csrf_token_input.name = 'csrfmiddlewaretoken';
csrf_token_input.value = "${ csrf_token }"
// Send data as a POST so it doesn't create a huge url
var form = document.createElement("form");
form.action = "${section_data['post_metrics_data_csv_url']}";
form.method = 'post'
form.appendChild(input_data);
form.appendChild(csrf_token_input)
document.body.appendChild(form);
form.submit();
}
$('.instructor-nav a').click(function () {
if ($(this).data('section') === "metrics" && firstLoad) {
loadGraphs();
firstLoad = false;
$('#graph_reload').show();
$('.metrics-header-container').show();
}
});
$('#graph_reload').click(function () {
loadGraphs();
$('#graph_reload').show();
$('.metrics-header-container').show();
});
$('#download_subsection_data').click(function() {
download_csv_data({'type': 'subsection'});
});
$('#download_problem_data').click(function() {
download_csv_data({'type': 'problem'});
});
if (window.location.hash === "#view-metrics") {
$('.instructor-nav a[data-section="metrics"]').click();
$('#graph_reload').hide();
$('.metrics-header-container').hide();
}
$(document).ajaxStop(function() {
$('#graph_reload').show();
$('.metrics-header-container').show();
});
});
$('.metrics-overlay .close-button').click(function(event) {
event.preventDefault();
......@@ -181,11 +261,12 @@
var module_id = $(this).closest('.metrics-overlay').data("module-id");
var tooltip = $(this).closest('.metrics-container').children('.metrics-tooltip').text();
var attributes = '?module_id=' + module_id + '&tooltip=' + tooltip + '&csv=true';
var attributes = '?module_id=' + module_id + '&csv=true' + '&tooltip=' + tooltip;
var url = $(this).data("endpoint");
url += attributes;
return location.href = url;
});
</script>
......
......@@ -378,23 +378,7 @@ if settings.COURSEWARE_ENABLED and settings.FEATURES.get('ENABLE_INSTRUCTOR_LEGA
if settings.FEATURES.get('CLASS_DASHBOARD'):
urlpatterns += (
# Json request data for metrics for entire course
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/all_sequential_open_distrib$',
'class_dashboard.views.all_sequential_open_distrib', name="all_sequential_open_distrib"),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/all_problem_grade_distribution$',
'class_dashboard.views.all_problem_grade_distribution', name="all_problem_grade_distribution"),
# Json request data for metrics for particular section
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/problem_grade_distribution/(?P<section>\d+)$',
'class_dashboard.views.section_problem_grade_distrib', name="section_problem_grade_distrib"),
# For listing students that opened a sub-section
url(r'^get_students_opened_subsection$',
'class_dashboard.dashboard_data.get_students_opened_subsection', name="get_students_opened_subsection"),
# For listing of students' grade per problem
url(r'^get_students_problem_grades$',
'class_dashboard.dashboard_data.get_students_problem_grades', name="get_students_problem_grades"),
url(r'^class_dashboard/', include('class_dashboard.urls')),
)
if settings.DEBUG or settings.FEATURES.get('ENABLE_DJANGO_ADMIN_SITE'):
......
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