Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
28bf6234
Commit
28bf6234
authored
Apr 14, 2014
by
David Adams
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #3240 from edx/dcadams/enhanced_metric_reporting
This makes the metrics tab "bars" clickable.
parents
ada5cb4a
0f37ee69
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
439 additions
and
22 deletions
+439
-22
lms/djangoapps/class_dashboard/dashboard_data.py
+128
-0
lms/djangoapps/class_dashboard/test/test_dashboard_data.py
+100
-8
lms/djangoapps/instructor/views/instructor_dashboard.py
+3
-1
lms/static/sass/course/instructor/_instructor_2.scss
+79
-5
lms/templates/class_dashboard/d3_stacked_bar_graph.js
+1
-0
lms/templates/instructor/instructor_dashboard_2/metrics.html
+120
-8
lms/urls.py
+8
-0
No files found.
lms/djangoapps/class_dashboard/dashboard_data.py
View file @
28bf6234
"""
Computes the data to display on the Instructor Dashboard
"""
from
util.json_request
import
JsonResponse
from
courseware
import
models
from
django.db.models
import
Count
...
...
@@ -9,7 +10,10 @@ from django.utils.translation import ugettext as _
from
xmodule.course_module
import
CourseDescriptor
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.inheritance
import
own_metadata
from
analytics.csvs
import
create_csv_response
# Used to limit the length of list displayed to the screen.
MAX_SCREEN_LIST_LENGTH
=
250
def
get_problem_grade_distribution
(
course_id
):
"""
...
...
@@ -193,6 +197,7 @@ def get_d3_problem_grade_distrib(course_id):
'color'
:
percent
,
'value'
:
count_grade
,
'tooltip'
:
tooltip
,
'module_url'
:
child
.
location
.
url
(),
})
problem
=
{
...
...
@@ -251,6 +256,7 @@ def get_d3_sequential_open_distrib(course_id):
'color'
:
0
,
'value'
:
num_students
,
'tooltip'
:
tooltip
,
'module_url'
:
subsection
.
location
.
url
(),
})
subsection
=
{
'xValue'
:
"SS {0}"
.
format
(
c_subsection
),
...
...
@@ -399,3 +405,125 @@ def get_array_section_has_problem(course_id):
i
+=
1
return
b_section_has_problem
def
get_students_opened_subsection
(
request
,
csv
=
False
):
"""
Get a list of students that opened a particular subsection.
If 'csv' is False, returns a dict of student's name: username.
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'
)
# Query for "opened a subsection" students
students
=
models
.
StudentModule
.
objects
.
select_related
(
'student'
)
.
filter
(
module_state_key__exact
=
module_id
,
module_type__exact
=
'sequential'
,
)
.
values
(
'student__username'
,
'student__profile__name'
)
.
order_by
(
'student__profile__name'
)
results
=
[]
if
not
csv
:
# Restrict screen list length
# Adding 1 so can tell if list is larger than MAX_SCREEN_LIST_LENGTH
# without doing another select.
for
student
in
students
[
0
:
MAX_SCREEN_LIST_LENGTH
+
1
]:
results
.
append
({
'name'
:
student
[
'student__profile__name'
],
'username'
:
student
[
'student__username'
],
})
max_exceeded
=
False
if
len
(
results
)
>
MAX_SCREEN_LIST_LENGTH
:
# Remove the last item so list length is exactly MAX_SCREEN_LIST_LENGTH
del
results
[
-
1
]
max_exceeded
=
True
response_payload
=
{
'results'
:
results
,
'max_exceeded'
:
max_exceeded
,
}
return
JsonResponse
(
response_payload
)
else
:
tooltip
=
request
.
GET
.
get
(
'tooltip'
)
filename
=
sanitize_filename
(
tooltip
[
tooltip
.
index
(
'S'
):])
header
=
[
'Name'
,
'Username'
]
for
student
in
students
:
results
.
append
([
student
[
'student__profile__name'
],
student
[
'student__username'
]])
response
=
create_csv_response
(
filename
,
header
,
results
)
return
response
def
get_students_problem_grades
(
request
,
csv
=
False
):
"""
Get a list of students and grades for a particular problem.
If 'csv' is False, returns a dict of student's name: username: grade: percent.
If 'csv' is True, returns a header array, and an array of arrays in the format:
student names, usernames, grades, percents for CSV download.
"""
module_id
=
request
.
GET
.
get
(
'module_id'
)
csv
=
request
.
GET
.
get
(
'csv'
)
# Query for "problem grades" students
students
=
models
.
StudentModule
.
objects
.
select_related
(
'student'
)
.
filter
(
module_state_key__exact
=
module_id
,
module_type__exact
=
'problem'
,
grade__isnull
=
False
,
)
.
values
(
'student__username'
,
'student__profile__name'
,
'grade'
,
'max_grade'
)
.
order_by
(
'student__profile__name'
)
results
=
[]
if
not
csv
:
# Restrict screen list length
# Adding 1 so can tell if list is larger than MAX_SCREEN_LIST_LENGTH
# without doing another select.
for
student
in
students
[
0
:
MAX_SCREEN_LIST_LENGTH
+
1
]:
student_dict
=
{
'name'
:
student
[
'student__profile__name'
],
'username'
:
student
[
'student__username'
],
'grade'
:
student
[
'grade'
],
}
student_dict
[
'percent'
]
=
0
if
student
[
'max_grade'
]
>
0
:
student_dict
[
'percent'
]
=
round
(
student
[
'grade'
]
*
100
/
student
[
'max_grade'
])
results
.
append
(
student_dict
)
max_exceeded
=
False
if
len
(
results
)
>
MAX_SCREEN_LIST_LENGTH
:
# Remove the last item so list length is exactly MAX_SCREEN_LIST_LENGTH
del
results
[
-
1
]
max_exceeded
=
True
response_payload
=
{
'results'
:
results
,
'max_exceeded'
:
max_exceeded
,
}
return
JsonResponse
(
response_payload
)
else
:
tooltip
=
request
.
GET
.
get
(
'tooltip'
)
filename
=
sanitize_filename
(
tooltip
[:
tooltip
.
rfind
(
' - '
)])
header
=
[
'Name'
,
'Username'
,
'Grade'
,
'Percent'
]
for
student
in
students
:
percent
=
0
if
student
[
'max_grade'
]
>
0
:
percent
=
round
(
student
[
'grade'
]
*
100
/
student
[
'max_grade'
])
results
.
append
([
student
[
'student__profile__name'
],
student
[
'student__username'
],
student
[
'grade'
],
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
[
0
:
25
]
+
'.csv'
return
filename
lms/djangoapps/class_dashboard/test/test_dashboard_data.py
View file @
28bf6234
...
...
@@ -3,10 +3,11 @@ Tests for class dashboard (Metrics tab in instructor dashboard)
"""
import
json
from
mock
import
patch
from
django.test.utils
import
override_settings
from
django.core.urlresolvers
import
reverse
from
django.test.client
import
RequestFactory
from
xmodule.modulestore.tests.factories
import
CourseFactory
,
ItemFactory
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
courseware.tests.tests
import
TEST_DATA_MONGO_MODULESTORE
...
...
@@ -18,7 +19,8 @@ from xmodule.modulestore import Location
from
class_dashboard.dashboard_data
import
(
get_problem_grade_distribution
,
get_sequential_open_distrib
,
get_problem_set_grade_distrib
,
get_d3_problem_grade_distrib
,
get_d3_sequential_open_distrib
,
get_d3_section_grade_distrib
,
get_section_display_name
,
get_array_section_has_problem
get_section_display_name
,
get_array_section_has_problem
,
get_students_opened_subsection
,
get_students_problem_grades
,
)
from
class_dashboard.views
import
has_instructor_access_for_class
...
...
@@ -33,6 +35,7 @@ class TestGetProblemGradeDistribution(ModuleStoreTestCase):
def
setUp
(
self
):
self
.
request_factory
=
RequestFactory
()
self
.
instructor
=
AdminFactory
.
create
()
self
.
client
.
login
(
username
=
self
.
instructor
.
username
,
password
=
'test'
)
self
.
attempts
=
3
...
...
@@ -45,27 +48,27 @@ class TestGetProblemGradeDistribution(ModuleStoreTestCase):
category
=
"chapter"
,
display_name
=
u"test factory section omega
\u03a9
"
,
)
sub_section
=
ItemFactory
.
create
(
s
elf
.
s
ub_section
=
ItemFactory
.
create
(
parent_location
=
section
.
location
,
category
=
"sequential"
,
display_name
=
u"test subsection omega
\u03a9
"
,
)
unit
=
ItemFactory
.
create
(
parent_location
=
sub_section
.
location
,
parent_location
=
s
elf
.
s
ub_section
.
location
,
category
=
"vertical"
,
metadata
=
{
'graded'
:
True
,
'format'
:
'Homework'
},
display_name
=
u"test unit omega
\u03a9
"
,
)
self
.
users
=
[
UserFactory
.
create
(
)
for
_
in
xrange
(
USER_COUNT
)]
self
.
users
=
[
UserFactory
.
create
(
username
=
"metric"
+
str
(
__
))
for
_
_
in
xrange
(
USER_COUNT
)]
for
user
in
self
.
users
:
CourseEnrollmentFactory
.
create
(
user
=
user
,
course_id
=
self
.
course
.
id
)
for
i
in
xrange
(
USER_COUNT
-
1
):
category
=
"problem"
item
=
ItemFactory
.
create
(
self
.
item
=
ItemFactory
.
create
(
parent_location
=
unit
.
location
,
category
=
category
,
data
=
StringResponseXMLFactory
()
.
build_xml
(
answer
=
'foo'
),
...
...
@@ -79,7 +82,7 @@ class TestGetProblemGradeDistribution(ModuleStoreTestCase):
max_grade
=
1
if
i
<
j
else
0.5
,
student
=
user
,
course_id
=
self
.
course
.
id
,
module_state_key
=
Location
(
item
.
location
)
.
url
(),
module_state_key
=
Location
(
self
.
item
.
location
)
.
url
(),
state
=
json
.
dumps
({
'attempts'
:
self
.
attempts
}),
)
...
...
@@ -87,7 +90,7 @@ class TestGetProblemGradeDistribution(ModuleStoreTestCase):
StudentModuleFactory
.
create
(
course_id
=
self
.
course
.
id
,
module_type
=
'sequential'
,
module_state_key
=
Location
(
item
.
location
)
.
url
(),
module_state_key
=
Location
(
self
.
item
.
location
)
.
url
(),
)
def
test_get_problem_grade_distribution
(
self
):
...
...
@@ -151,6 +154,95 @@ class TestGetProblemGradeDistribution(ModuleStoreTestCase):
sum_values
+=
problem
[
'value'
]
self
.
assertEquals
(
USER_COUNT
,
sum_values
)
def
test_get_students_problem_grades
(
self
):
attributes
=
'?module_id='
+
self
.
item
.
location
.
url
()
request
=
self
.
request_factory
.
get
(
reverse
(
'get_students_problem_grades'
)
+
attributes
)
response
=
get_students_problem_grades
(
request
)
response_content
=
json
.
loads
(
response
.
content
)[
'results'
]
response_max_exceeded
=
json
.
loads
(
response
.
content
)[
'max_exceeded'
]
self
.
assertEquals
(
USER_COUNT
,
len
(
response_content
))
self
.
assertEquals
(
False
,
response_max_exceeded
)
for
item
in
response_content
:
if
item
[
'grade'
]
==
0
:
self
.
assertEquals
(
0
,
item
[
'percent'
])
else
:
self
.
assertEquals
(
100
,
item
[
'percent'
])
def
test_get_students_problem_grades_max
(
self
):
with
patch
(
'class_dashboard.dashboard_data.MAX_SCREEN_LIST_LENGTH'
,
2
):
attributes
=
'?module_id='
+
self
.
item
.
location
.
url
()
request
=
self
.
request_factory
.
get
(
reverse
(
'get_students_problem_grades'
)
+
attributes
)
response
=
get_students_problem_grades
(
request
)
response_results
=
json
.
loads
(
response
.
content
)[
'results'
]
response_max_exceeded
=
json
.
loads
(
response
.
content
)[
'max_exceeded'
]
# Only 2 students in the list and response_max_exceeded is True
self
.
assertEquals
(
2
,
len
(
response_results
))
self
.
assertEquals
(
True
,
response_max_exceeded
)
def
test_get_students_problem_grades_csv
(
self
):
tooltip
=
'P1.2.1 Q1 - 3382 Students (100
%
: 1/1 questions)'
attributes
=
'?module_id='
+
self
.
item
.
location
.
url
()
+
'&tooltip='
+
tooltip
+
'&csv=true'
request
=
self
.
request_factory
.
get
(
reverse
(
'get_students_problem_grades'
)
+
attributes
)
response
=
get_students_problem_grades
(
request
)
# Check header and a row for each student in csv response
self
.
assertContains
(
response
,
'"Name","Username","Grade","Percent"'
)
self
.
assertContains
(
response
,
'"metric0","0.0","0.0"'
)
self
.
assertContains
(
response
,
'"metric1","0.0","0.0"'
)
self
.
assertContains
(
response
,
'"metric2","0.0","0.0"'
)
self
.
assertContains
(
response
,
'"metric3","0.0","0.0"'
)
self
.
assertContains
(
response
,
'"metric4","0.0","0.0"'
)
self
.
assertContains
(
response
,
'"metric5","0.0","0.0"'
)
self
.
assertContains
(
response
,
'"metric6","0.0","0.0"'
)
self
.
assertContains
(
response
,
'"metric7","0.0","0.0"'
)
self
.
assertContains
(
response
,
'"metric8","0.0","0.0"'
)
self
.
assertContains
(
response
,
'"metric9","0.0","0.0"'
)
self
.
assertContains
(
response
,
'"metric10","1.0","100.0"'
)
def
test_get_students_opened_subsection
(
self
):
attributes
=
'?module_id='
+
self
.
item
.
location
.
url
()
request
=
self
.
request_factory
.
get
(
reverse
(
'get_students_opened_subsection'
)
+
attributes
)
response
=
get_students_opened_subsection
(
request
)
response_results
=
json
.
loads
(
response
.
content
)[
'results'
]
response_max_exceeded
=
json
.
loads
(
response
.
content
)[
'max_exceeded'
]
self
.
assertEquals
(
USER_COUNT
,
len
(
response_results
))
self
.
assertEquals
(
False
,
response_max_exceeded
)
def
test_get_students_opened_subsection_max
(
self
):
with
patch
(
'class_dashboard.dashboard_data.MAX_SCREEN_LIST_LENGTH'
,
2
):
attributes
=
'?module_id='
+
self
.
item
.
location
.
url
()
request
=
self
.
request_factory
.
get
(
reverse
(
'get_students_opened_subsection'
)
+
attributes
)
response
=
get_students_opened_subsection
(
request
)
response_results
=
json
.
loads
(
response
.
content
)[
'results'
]
response_max_exceeded
=
json
.
loads
(
response
.
content
)[
'max_exceeded'
]
# Only 2 students in the list and response_max_exceeded is True
self
.
assertEquals
(
2
,
len
(
response_results
))
self
.
assertEquals
(
True
,
response_max_exceeded
)
def
test_get_students_opened_subsection_csv
(
self
):
tooltip
=
'4162 student(s) opened Subsection 5: Relational Algebra Exercises'
attributes
=
'?module_id='
+
self
.
item
.
location
.
url
()
+
'&tooltip='
+
tooltip
+
'&csv=true'
request
=
self
.
request_factory
.
get
(
reverse
(
'get_students_opened_subsection'
)
+
attributes
)
response
=
get_students_opened_subsection
(
request
)
self
.
assertContains
(
response
,
'"Name","Username"'
)
# Check response contains 1 line for each user +1 for the header
self
.
assertEquals
(
USER_COUNT
+
1
,
len
(
response
.
content
.
splitlines
()))
def
test_get_section_display_name
(
self
):
section_display_name
=
get_section_display_name
(
self
.
course
.
id
)
...
...
lms/djangoapps/instructor/views/instructor_dashboard.py
View file @
28bf6234
...
...
@@ -241,6 +241,8 @@ def _section_metrics(course_id, access):
'section_display_name'
:
(
'Metrics'
),
'access'
:
access
,
'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_problem_grades_url'
:
reverse
(
'get_students_problem_grades'
),
}
return
section_data
lms/static/sass/course/instructor/_instructor_2.scss
View file @
28bf6234
...
...
@@ -555,17 +555,18 @@ section.instructor-dashboard-content-2 {
float
:
left
;
clear
:
both
;
margin-top
:
25px
;
}
.metrics-left
{
position
:
relative
;
width
:
30%
;
height
:
640px
;
float
:
left
;
margin-right
:
2
.5%
;
}
.metrics-left
svg
{
svg
{
width
:
100%
;
}
}
.metrics-right
{
position
:
relative
;
width
:
65%
;
...
...
@@ -573,10 +574,17 @@ section.instructor-dashboard-content-2 {
float
:
left
;
margin-left
:
2
.5%
;
margin-bottom
:
25px
;
}
.metrics-right
svg
{
svg
{
width
:
100%
;
}
}
svg
{
.stacked-bar
{
cursor
:
pointer
;
}
}
.metrics-tooltip
{
width
:
250px
;
...
...
@@ -584,6 +592,71 @@ section.instructor-dashboard-content-2 {
padding
:
3px
;
}
.metrics-overlay
{
position
:
absolute
;
top
:
0
;
right
:
0
;
bottom
:
0
;
left
:
0
;
background-color
:
rgba
(
255
,
255
,
255
,
.75
);
display
:
none
;
.metrics-overlay-content-wrapper
{
position
:
relative
;
display
:
block
;
height
:
475px
;
width
:
85%
;
margin
:
5%
;
background-color
:
#fff
;
border
:
1px
solid
#000
;
border-radius
:
25px
;
padding
:
2
.5%
;
.metrics-overlay-title
{
display
:
block
;
height
:
50px
;
margin-bottom
:
10px
;
font-weight
:
bold
;
}
.metrics-overlay-content
{
width
:
100%
;
height
:
370px
;
overflow
:
auto
;
border
:
1px
solid
#000
;
table
{
width
:
100%
;
.header
{
background-color
:
#ddd
;
}
th
,
td
{
padding
:
10px
;
}
}
}
.overflow-message
{
padding-top
:
20px
;
}
.download-csv
{
position
:
absolute
;
display
:
none
;
right
:
2%
;
bottom
:
2%
;
}
.close-button
{
position
:
absolute
;
right
:
1
.5%
;
top
:
2%
;
font-size
:
2em
;
}
}
}
.stacked-bar-graph-legend
{
fill
:
white
;
}
...
...
@@ -607,6 +680,7 @@ section.instructor-dashboard-content-2 {
input
#graph_reload
{
display
:
none
;
}
}
}
...
...
lms/templates/class_dashboard/d3_stacked_bar_graph.js
View file @
28bf6234
...
...
@@ -329,6 +329,7 @@ edx_d3CreateStackedBarGraph = function(parameters, svg, divTooltip) {
.
attr
(
"height"
,
function
(
d
)
{
return
graph
.
scale
.
y
(
d
.
y0
)
-
graph
.
scale
.
y
(
d
.
y1
);
})
.
attr
(
"id"
,
function
(
d
)
{
return
d
.
module_url
})
.
style
(
"fill"
,
function
(
d
)
{
return
graph
.
scale
.
stackColor
(
d
.
color
);
})
.
style
(
"stroke"
,
"white"
)
.
style
(
"stroke-width"
,
"0.5px"
);
...
...
lms/templates/instructor/instructor_dashboard_2/metrics.html
View file @
28bf6234
<
%!
from
django
.
utils
.
translation
import
ugettext
as
_
%
>
<
%
page
args=
"section_data"
/>
<script>
$
{
d3_stacked_bar_graph
.
body
()}
</script>
%if not any (section_data.values()):
%if not any (section_data.values()):
<p>
${_("There is no data available to display at this time.")}
</p>
%else:
%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
")}"
/>
%for i in range(0,len(section_data['sub_section_display_name'])):
<!-- 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>
<div
class=
"metrics-tooltip"
id=
"metric_tooltip_${i}"
></div>
<div
class=
"
metrics-left"
id=
"metric_opened_${i}"
>
<div
class=
"metrics-section
metrics-left"
id=
"metric_opened_${i}"
>
<h3>
${_("Count of Students Opened a Subsection")}
</h3>
</div>
<div
class=
"
metrics-right"
id=
"metric_grade_${i}"
data-section-has-problem=
${section_data['section_has_problem'][i]}
>
<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>
</div>
<div
class=
"metrics-overlay"
>
<div
class=
"metrics-overlay-content-wrapper"
>
<div
class=
"metrics-overlay-content"
>
<table>
<thead></thead>
<tbody></tbody>
</table>
</div>
<input
class=
"download-csv metrics-student-opened"
type=
"button"
name=
"dump_student_opened"
value=
"${_("
Download
Student
Opened
as
a
CSV
")}"
data-endpoint=
"${section_data['get_students_opened_subsection_url']}"
data-csv=
"true"
>
<input
class=
"download-csv metrics-student-grades"
type=
"button"
name=
"dump_student_grades"
value=
"${_("
Download
Student
Grades
as
a
CSV
")}"
data-endpoint=
"${section_data['get_students_problem_grades_url']}"
data-csv=
"true"
>
<a
class=
"close-button"
href=
"#"
><i
class=
"icon-remove"
></i><span
class=
"sr"
>
${_("Close")}
</span></a>
</div>
</div>
</div>
%endfor
<script>
...
...
@@ -40,6 +57,7 @@
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
);
});
...
...
@@ -53,10 +71,86 @@
});
$
(
'.metrics-left svg, .metrics-right svg'
).
remove
();
$
{
all_section_metrics
.
body
(
"metric_opened_"
,
"metric_grade_"
,
"metric_attempts_"
,
"metric_tooltip_"
,
course
.
id
)}
$
{
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
()
{
var
module_id
=
$
(
'rect'
,
this
).
attr
(
'id'
);
var
metrics_overlay
=
$
(
this
).
closest
(
'.metrics-left'
).
siblings
(
'.metrics-overlay'
);
// Set module_id attribute on metrics_overlay
metrics_overlay
.
data
(
"module-id"
,
module_id
);
var
header
=
$
(
this
).
closest
(
'.metrics-left'
).
siblings
(
'.metrics-tooltip'
).
text
();
var
overlay_content
=
'<h3 class="metrics-overlay-title">'
+
header
+
'</h3>'
;
$
(
'.metrics-overlay-content'
,
metrics_overlay
).
before
(
overlay_content
);
$
.
ajax
({
url
:
"${section_data['get_students_opened_subsection_url']}"
,
type
:
"GET"
,
data
:
{
module_id
:
module_id
},
dataType
:
"json"
,
success
:
function
(
response
)
{
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
){
overlay_content
=
'<tr><td>'
+
value
[
'name'
]
+
"</td><td>"
+
value
[
'username'
]
+
'</td></tr>'
;
$
(
'.metrics-overlay-content tbody'
,
metrics_overlay
).
append
(
overlay_content
);
});
// 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>'
;
$
(
'.metrics-overlay-content'
,
metrics_overlay
).
after
(
overlay_content
);
}
}
})
metrics_overlay
.
find
(
'.metrics-student-opened'
).
show
();
metrics_overlay
.
show
();
});
$
(
'.metrics-right .stacked-bar'
).
on
(
"click"
,
function
()
{
var
module_id
=
$
(
'rect'
,
this
).
attr
(
'id'
);
var
metrics_overlay
=
$
(
this
).
closest
(
'.metrics-right'
).
siblings
(
'.metrics-overlay'
);
//Set module_id attribute on metrics_overlay
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
overlay_content
=
'<h3 class="metrics-overlay-title">'
+
title
+
'</h3>'
;
$
(
'.metrics-overlay-content'
,
metrics_overlay
).
before
(
overlay_content
);
$
.
ajax
({
url
:
"${section_data['get_students_problem_grades_url']}"
,
type
:
"GET"
,
data
:
{
module_id
:
module_id
},
dataType
:
"json"
,
success
:
function
(
response
)
{
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
){
overlay_content
=
'<tr><td>'
+
value
[
'name'
]
+
"</td><td>"
+
value
[
'username'
]
+
"</td><td>"
+
value
[
'grade'
]
+
"</td><td>"
+
value
[
'percent'
]
+
'</td></tr>'
;
$
(
'.metrics-overlay-content tbody'
,
metrics_overlay
).
append
(
overlay_content
);
});
// 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>'
;
$
(
'.metrics-overlay-content'
,
metrics_overlay
).
after
(
overlay_content
);
}
},
})
metrics_overlay
.
find
(
'.metrics-student-grades'
).
show
();
metrics_overlay
.
show
();
});
},
5000
);
}
...
...
@@ -75,6 +169,24 @@
$
(
'.instructor-nav a[data-section="metrics"]'
).
click
();
}
});
</script>
$
(
'.metrics-overlay .close-button'
).
click
(
function
(
event
)
{
event
.
preventDefault
();
$
(
'.metrics-overlay-content table thead, .metrics-overlay-content table tbody'
).
empty
();
$
(
'.metrics-overlay-content-wrapper h3'
).
remove
();
$
(
'.metrics-overlay-content-wrapper p'
).
remove
();
$
(
this
).
closest
(
".metrics-overlay"
).
hide
();
$
(
'.metrics-overlay .download-csv'
).
hide
();
});
$
(
'.metrics-overlay .download-csv'
).
click
(
function
(
event
)
{
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
url
=
$
(
this
).
data
(
"endpoint"
);
url
+=
attributes
;
%endif
return
location
.
href
=
url
;
});
</script>
%endif
lms/urls.py
View file @
28bf6234
...
...
@@ -381,6 +381,14 @@ if settings.FEATURES.get('CLASS_DASHBOARD'):
# 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'
):
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment