Commit fb9b38b7 by David Adams

Merge pull request #3584 from edx/dcadams/metrics_tab_download_csv

Metrics tab - fix click handlers and add download buttons
parents aacbc5ed 9bc7a518
...@@ -95,12 +95,15 @@ class TestGetProblemGradeDistribution(ModuleStoreTestCase): ...@@ -95,12 +95,15 @@ class TestGetProblemGradeDistribution(ModuleStoreTestCase):
def test_get_problem_grade_distribution(self): 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: for problem in prob_grade_distrib:
max_grade = prob_grade_distrib[problem]['max_grade'] max_grade = prob_grade_distrib[problem]['max_grade']
self.assertEquals(1, 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): def test_get_sequential_open_distibution(self):
sequential_open_distrib = get_sequential_open_distrib(self.course.id) sequential_open_distrib = get_sequential_open_distrib(self.course.id)
...@@ -243,6 +246,61 @@ class TestGetProblemGradeDistribution(ModuleStoreTestCase): ...@@ -243,6 +246,61 @@ class TestGetProblemGradeDistribution(ModuleStoreTestCase):
# Check response contains 1 line for each user +1 for the header # Check response contains 1 line for each user +1 for the header
self.assertEquals(USER_COUNT + 1, len(response.content.splitlines())) 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): def test_get_section_display_name(self):
section_display_name = get_section_display_name(self.course.id) 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): ...@@ -250,10 +250,12 @@ def _section_metrics(course_id, access):
'section_key': 'metrics', 'section_key': 'metrics',
'section_display_name': ('Metrics'), 'section_display_name': ('Metrics'),
'access': access, 'access': access,
'course_id': course_id,
'sub_section_display_name': get_section_display_name(course_id), 'sub_section_display_name': get_section_display_name(course_id),
'section_has_problem': get_array_section_has_problem(course_id), 'section_has_problem': get_array_section_has_problem(course_id),
'get_students_opened_subsection_url': reverse('get_students_opened_subsection'), 'get_students_opened_subsection_url': reverse('get_students_opened_subsection'),
'get_students_problem_grades_url': reverse('get_students_problem_grades'), 'get_students_problem_grades_url': reverse('get_students_problem_grades'),
'post_metrics_data_csv_url': reverse('post_metrics_data_csv'),
} }
return section_data return section_data
......
...@@ -591,17 +591,16 @@ section.instructor-dashboard-content-2 { ...@@ -591,17 +591,16 @@ section.instructor-dashboard-content-2 {
.instructor-dashboard-wrapper-2 section.idash-section#metrics { .instructor-dashboard-wrapper-2 section.idash-section#metrics {
.metrics-container { .metrics-container, .metrics-header-container {
position: relative; position: relative;
width: 100%; width: 100%;
float: left; float: left;
clear: both; clear: both;
margin-top: 25px; margin-top: 25px;
.metrics-left { .metrics-left, .metrics-left-header {
position: relative; position: relative;
width: 30%; width: 30%;
height: 640px;
float: left; float: left;
margin-right: 2.5%; margin-right: 2.5%;
...@@ -609,10 +608,13 @@ section.instructor-dashboard-content-2 { ...@@ -609,10 +608,13 @@ section.instructor-dashboard-content-2 {
width: 100%; width: 100%;
} }
} }
.metrics-right { .metrics-section.metrics-left {
height: 640px;
}
.metrics-right, .metrics-right-header {
position: relative; position: relative;
width: 65%; width: 65%;
height: 295px;
float: left; float: left;
margin-left: 2.5%; margin-left: 2.5%;
margin-bottom: 25px; margin-bottom: 25px;
...@@ -622,6 +624,10 @@ section.instructor-dashboard-content-2 { ...@@ -622,6 +624,10 @@ section.instructor-dashboard-content-2 {
} }
} }
.metrics-section.metrics-right {
height: 295px;
}
svg { svg {
.stacked-bar { .stacked-bar {
cursor: pointer; cursor: pointer;
...@@ -718,10 +724,6 @@ section.instructor-dashboard-content-2 { ...@@ -718,10 +724,6 @@ section.instructor-dashboard-content-2 {
border-radius: 5px; border-radius: 5px;
margin-top: 25px; 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 import json
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
...@@ -30,6 +30,13 @@ $(function () { ...@@ -30,6 +30,13 @@ $(function () {
margin: {left:0}, 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"), barGraphOpened = edx_d3CreateStackedBarGraph(paramOpened, d3.select(curr_id).append("svg"),
d3.select("#${id_tooltip_prefix}"+i)); d3.select("#${id_tooltip_prefix}"+i));
barGraphOpened.scale.stackColor.range(["#555555","#555555"]); barGraphOpened.scale.stackColor.range(["#555555","#555555"]);
...@@ -68,6 +75,17 @@ $(function () { ...@@ -68,6 +75,17 @@ $(function () {
bVerticalXAxisLabel : true, 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"), barGraphGrade = edx_d3CreateStackedBarGraph(paramGrade, d3.select(curr_id).append("svg"),
d3.select("#${id_tooltip_prefix}"+i)); d3.select("#${id_tooltip_prefix}"+i));
barGraphGrade.scale.stackColor.domain([0,50,100]).range(["#e13f29","#cccccc","#17a74d"]); barGraphGrade.scale.stackColor.domain([0,50,100]).range(["#e13f29","#cccccc","#17a74d"]);
...@@ -83,6 +101,7 @@ $(function () { ...@@ -83,6 +101,7 @@ $(function () {
i+=1; i+=1;
} }
}); });
}); });
\ No newline at end of file
...@@ -349,8 +349,20 @@ edx_d3CreateStackedBarGraph = function(parameters, svg, divTooltip) { ...@@ -349,8 +349,20 @@ edx_d3CreateStackedBarGraph = function(parameters, svg, divTooltip) {
var top = pos[1]-10; var top = pos[1]-10;
var width = $('#'+graph.divTooltip.attr("id")).width(); 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") graph.divTooltip.style("visibility", "visible")
.text(d.tooltip); .text(tooltip_str);
if ((left+width+30) > $("#"+graph.divTooltip.node().parentNode.id).width()) if ((left+width+30) > $("#"+graph.divTooltip.node().parentNode.id).width())
left -= (width+30); left -= (width+30);
......
...@@ -725,7 +725,9 @@ function goto( mode) ...@@ -725,7 +725,9 @@ function goto( mode)
</div> </div>
%endfor %endfor
<script> <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> </script>
%endif %endif
......
...@@ -378,23 +378,7 @@ if settings.COURSEWARE_ENABLED and settings.FEATURES.get('ENABLE_INSTRUCTOR_LEGA ...@@ -378,23 +378,7 @@ if settings.COURSEWARE_ENABLED and settings.FEATURES.get('ENABLE_INSTRUCTOR_LEGA
if settings.FEATURES.get('CLASS_DASHBOARD'): if settings.FEATURES.get('CLASS_DASHBOARD'):
urlpatterns += ( urlpatterns += (
# Json request data for metrics for entire course url(r'^class_dashboard/', include('class_dashboard.urls')),
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"),
) )
if settings.DEBUG or settings.FEATURES.get('ENABLE_DJANGO_ADMIN_SITE'): 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