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
4afde4dd
Commit
4afde4dd
authored
Aug 01, 2013
by
Miles Steele
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
add proxied analytics graphs, refactor analytics
parent
fb8c84a5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
303 additions
and
98 deletions
+303
-98
lms/djangoapps/instructor/views/instructor_dashboard.py
+1
-0
lms/static/coffee/src/instructor_dashboard/analytics.coffee
+158
-61
lms/static/coffee/src/instructor_dashboard/instructor_dashboard.coffee
+59
-14
lms/static/sass/course/instructor/_instructor_2.scss
+33
-13
lms/templates/instructor/instructor_dashboard_2/analytics.html
+52
-10
No files found.
lms/djangoapps/instructor/views/instructor_dashboard.py
View file @
4afde4dd
...
...
@@ -142,5 +142,6 @@ def _section_analytics(course_id):
'section_key'
:
'analytics'
,
'section_display_name'
:
'Analytics'
,
'get_distribution_url'
:
reverse
(
'get_distribution'
,
kwargs
=
{
'course_id'
:
course_id
}),
'proxy_legacy_analytics_url'
:
reverse
(
'proxy_legacy_analytics'
,
kwargs
=
{
'course_id'
:
course_id
}),
}
return
section_data
lms/static/coffee/src/instructor_dashboard/analytics.coffee
View file @
4afde4dd
...
...
@@ -6,60 +6,32 @@
plantTimeout
=
->
window
.
InstructorDashboard
.
util
.
plantTimeout
.
apply
this
,
arguments
std_ajax_err
=
->
window
.
InstructorDashboard
.
util
.
std_ajax_err
.
apply
this
,
arguments
# Analytics Section
class
Analytics
constructor
:
(
@
$section
)
->
@
$section
.
data
'wrapper'
,
@
# gather elements
@
$display
=
@
$section
.
find
'.distribution-display'
@
$display_text
=
@
$display
.
find
'.distribution-display-text'
@
$display_graph
=
@
$display
.
find
'.distribution-display-graph'
@
$display_table
=
@
$display
.
find
'.distribution-display-table'
@
$distribution_select
=
@
$section
.
find
'select#distributions'
@
$request_response_error
=
@
$display
.
find
'.request-response-error'
@
populate_selector
=>
@
$distribution_select
.
change
=>
@
on_selector_change
()
class
ProfileDistributionWidget
constructor
:
({
@
$container
,
@
feature
,
title
,
@
endpoint
})
->
# render template
template_params
=
title
:
title
feature
:
@
feature
endpoint
:
@
endpoint
template_html
=
$
(
"#profile-distribution-widget-template"
).
html
()
@
$container
.
html
Mustache
.
render
template_html
,
template_params
reset_display
:
->
@
$display_text
.
empty
()
@
$display_graph
.
empty
()
@
$display_table
.
empty
()
@
$request_response_error
.
empty
()
# fetch and list available distributions
# `cb` is a callback to be run after
populate_selector
:
(
cb
)
->
# ask for no particular distribution to get list of available distribuitions.
@
get_profile_distributions
undefined
,
# on error, print to console and dom.
error
:
std_ajax_err
=>
@
$request_response_error
.
text
"Error getting available distributions."
success
:
(
data
)
=>
# replace loading text in drop-down with "-- Select Distribution --"
@
$distribution_select
.
find
(
'option'
).
eq
(
0
).
text
"-- Select Distribution --"
@
$container
.
find
(
'.display-errors'
).
empty
()
@
$container
.
find
(
'.display-text'
).
empty
()
@
$container
.
find
(
'.display-graph'
).
empty
()
@
$container
.
find
(
'.display-table'
).
empty
()
# add all fetched available features to drop-down
for
feature
in
data
.
available_features
opt
=
$
'<option/>'
,
text
:
data
.
feature_display_names
[
feature
]
data
:
feature
:
feature
@
$distribution_select
.
append
opt
# call callback if one was supplied
cb
?
()
show_error
:
(
msg
)
->
@
$container
.
find
(
'.display-errors'
).
text
msg
# display data
on_selector_change
:
->
opt
=
@
$distribution_select
.
children
(
'option:selected'
)
feature
=
opt
.
data
'feature'
load
:
->
@
reset_display
()
# only proceed if there is a feature attached to the selected option.
return
unless
feature
@
get_profile_distributions
feature
,
error
:
std_ajax_err
=>
@
$request_response_error
.
text
"Error getting distribution for '
#{
feature
}
'."
@
get_profile_distributions
@
feature
,
error
:
std_ajax_err
=>
@
show_error
"Error fetching distribution."
success
:
(
data
)
=>
feature_res
=
data
.
feature_results
if
feature_res
.
type
is
'EASY_CHOICE'
...
...
@@ -70,9 +42,9 @@ class Analytics
forceFitColumns
:
true
columns
=
[
id
:
feature
field
:
feature
name
:
feature
id
:
@
feature
field
:
@
feature
name
:
data
.
feature_display_names
[
@
feature
]
,
id
:
'count'
field
:
'count'
...
...
@@ -81,16 +53,16 @@ class Analytics
grid_data
=
_
.
map
feature_res
.
data
,
(
value
,
key
)
->
datapoint
=
{}
datapoint
[
feature
]
=
feature_res
.
choices_display_names
[
key
]
datapoint
[
@
feature
]
=
feature_res
.
choices_display_names
[
key
]
datapoint
[
'count'
]
=
value
datapoint
table_placeholder
=
$
'<div/>'
,
class
:
'slickgrid'
@
$
display_table
.
append
table_placeholder
@
$
container
.
find
(
'.display-table'
)
.
append
table_placeholder
grid
=
new
Slick
.
Grid
(
table_placeholder
,
grid_data
,
columns
,
options
)
else
if
feature_res
.
feature
is
'year_of_birth'
graph_placeholder
=
$
'<div/>'
,
class
:
'
year-of-birth
'
@
$
display_graph
.
append
graph_placeholder
graph_placeholder
=
$
'<div/>'
,
class
:
'
graph-placeholder
'
@
$
container
.
find
(
'.display-graph'
)
.
append
graph_placeholder
graph_data
=
_
.
map
feature_res
.
data
,
(
value
,
key
)
->
[
parseInt
(
key
),
value
]
...
...
@@ -99,7 +71,7 @@ class Analytics
]
else
console
.
warn
(
"unable to show distribution
#{
feature_res
.
type
}
"
)
@
$display_text
.
text
'Unavailable Metric Display
\n
'
+
JSON
.
stringify
(
feature_res
)
@
show_error
'Unavailable metric display.'
# fetch distribution data from server.
# `handler` can be either a callback for success
...
...
@@ -107,7 +79,7 @@ class Analytics
get_profile_distributions
:
(
feature
,
handler
)
->
settings
=
dataType
:
'json'
url
:
@
$distribution_select
.
data
'endpoint'
url
:
@
endpoint
data
:
feature
:
feature
if
typeof
handler
is
'function'
...
...
@@ -117,13 +89,138 @@ class Analytics
$
.
ajax
settings
# slickgrid's layout collapses when rendered
# in an invisible div. use this method to reload
# the AuthList widget
class
GradeDistributionDisplay
constructor
:
({
@
$container
,
@
endpoint
})
->
template_params
=
{}
template_html
=
$
(
'#grade-distributions-widget-template'
).
html
()
@
$container
.
html
Mustache
.
render
template_html
,
template_params
@
$problem_selector
=
@
$container
.
find
'.problem-selector'
reset_display
:
->
@
$container
.
find
(
'.display-errors'
).
empty
()
@
$container
.
find
(
'.display-text'
).
empty
()
@
$container
.
find
(
'.display-graph'
).
empty
()
show_error
:
(
msg
)
->
@
$container
.
find
(
'.display-errors'
).
text
msg
load
:
->
@
get_grade_distributions
error
:
std_ajax_err
=>
@
show_error
"Error fetching grade distributions."
success
:
(
data
)
=>
@
$container
.
find
(
'.last-updated'
).
text
"Last Updated:
#{
data
.
time
}
"
# populate selector
@
$problem_selector
.
empty
()
for
{
module_id
,
grade_info
}
in
data
.
data
I4X_PROBLEM
=
/i4x:\/\/.*\/.*\/problem\/(.*)/
label
=
(
I4X_PROBLEM
.
exec
module_id
)
?
[
1
]
label
?=
module_id
@
$problem_selector
.
append
$
'<option/>'
,
text
:
label
data
:
module_id
:
module_id
grade_info
:
grade_info
@
$problem_selector
.
change
=>
$opt
=
@
$problem_selector
.
children
(
'option:selected'
)
return
unless
$opt
.
length
>
0
@
reset_display
()
@
render_distribution
module_id
:
$opt
.
data
'module_id'
grade_info
:
$opt
.
data
'grade_info'
# one-time first selection of first list item.
@
$problem_selector
.
change
()
render_distribution
:
({
module_id
,
grade_info
})
->
$display_graph
=
@
$container
.
find
(
'.display-graph'
)
graph_data
=
grade_info
.
map
({
grade
,
max_grade
,
num_students
})
->
[
grade
,
num_students
]
total_students
=
_
.
reduce
([
0
].
concat
grade_info
),
(
accum
,
{
grade
,
max_grade
,
num_students
})
->
accum
+
num_students
# show total students
@
$container
.
find
(
'.display-text'
).
text
"
#{
total_students
}
students scored."
# render to graph
graph_placeholder
=
$
'<div/>'
,
class
:
'graph-placeholder'
$display_graph
.
append
graph_placeholder
graph_data
=
graph_data
$
.
plot
graph_placeholder
,
[
data
:
graph_data
bars
:
show
:
true
color
:
'#1d9dd9'
]
# `handler` can be either a callback for success
# or a mapping e.g. {success: ->, error: ->, complete: ->}
#
# the data passed to the success handler takes this form:
# {
# "aname": "ProblemGradeDistribution",
# "time": "2013-07-31T20:25:56+00:00",
# "course_id": "MITx/6.002x/2013_Spring",
# "options": {
# "course_id": "MITx/6.002x/2013_Spring",
# "_id": "6fudge2b49somedbid1e1",
# "data": [
# {
# "module_id": "i4x://MITx/6.002x/problem/Capacitors_and_Energy_Storage",
# "grade_info": [
# {
# "grade": 0.0,
# "max_grade": 100.0,
# "num_students": 3
# }, ... for each grade number between 0 and max_grade
# ],
# }
get_grade_distributions
:
(
handler
)
->
settings
=
dataType
:
'json'
url
:
@
endpoint
data
:
aname
:
'ProblemGradeDistribution'
if
typeof
handler
is
'function'
_
.
extend
settings
,
success
:
handler
else
_
.
extend
settings
,
handler
$
.
ajax
settings
# Analytics Section
class
Analytics
constructor
:
(
@
$section
)
->
@
$section
.
data
'wrapper'
,
@
@
$pd_containers
=
@
$section
.
find
'.profile-distribution-widget-container'
@
$gd_containers
=
@
$section
.
find
'.grade-distributions-widget-container'
@
pdws
=
_
.
map
(
@
$pd_containers
),
(
container
)
=>
new
ProfileDistributionWidget
$container
:
$
(
container
)
feature
:
$
(
container
).
data
'feature'
title
:
$
(
container
).
data
'title'
endpoint
:
$
(
container
).
data
'endpoint'
@
gdws
=
_
.
map
(
@
$gd_containers
),
(
container
)
=>
new
GradeDistributionDisplay
$container
:
$
(
container
)
endpoint
:
$
(
container
).
data
'endpoint'
refresh
:
->
@
on_selector_change
()
for
pdw
in
@
pdws
pdw
.
load
()
for
gdw
in
@
gdws
gdw
.
load
()
# handler for when the section title is clicked.
onClickTitle
:
->
@
refresh
()
...
...
lms/static/coffee/src/instructor_dashboard/instructor_dashboard.coffee
View file @
4afde4dd
...
...
@@ -33,6 +33,46 @@ CSS_INSTRUCTOR_NAV = 'instructor-nav'
# prefix for deep-linking
HASH_LINK_PREFIX
=
'#view-'
# helper class for queueing and fault isolation.
# Will execute functions marked by waiter.after only after all functions marked by
# waiter.waitFor have been called.
class
SafeWaiter
constructor
:
->
@
after_handlers
=
[]
@
waitFor_handlers
=
[]
@
fired
=
false
after
:
(
f
)
->
if
@
fired
f
()
else
@
after_handlers
.
push
f
waitFor
:
(
f
)
->
return
if
@
fired
@
waitFor_handlers
.
push
f
# wrap the function so that it notifies the waiter
# and can fire the after handlers.
=>
@
waitFor_handlers
=
@
waitFor_handlers
.
filter
(
g
)
->
g
isnt
f
if
@
waitFor_handlers
.
length
is
0
plantTimeout
0
,
=>
@
fired
=
true
for
cb
in
@
after_handlers
cb
()
f
.
apply
this
,
arguments
# waiter for dashboard sections.
# Will only execute after all sections have at least attempted to load.
# This is here to facilitate section constructors isolated by setTimeout
# while still being able to interact with them under the guarantee
# that the sections will be initialized at call time.
sections_have_loaded
=
new
SafeWaiter
# once we're ready, check if this page is the instructor dashboard
$
=>
instructor_dashboard_content
=
$
".
#{
CSS_INSTRUCTOR_CONTENT
}
"
...
...
@@ -45,9 +85,9 @@ $ =>
# handles hiding and showing sections
setup_instructor_dashboard
=
(
idash_content
)
=>
# clickable section titles
links
=
idash_content
.
find
(
".
#{
CSS_INSTRUCTOR_NAV
}
"
).
find
(
'a'
)
$
links
=
idash_content
.
find
(
".
#{
CSS_INSTRUCTOR_NAV
}
"
).
find
(
'a'
)
for
link
in
(
$
link
for
link
in
links
)
for
link
in
(
$
link
for
link
in
$
links
)
link
.
click
(
e
)
->
e
.
preventDefault
()
...
...
@@ -70,24 +110,24 @@ setup_instructor_dashboard = (idash_content) =>
# write to url
location
.
hash
=
"
#{
HASH_LINK_PREFIX
}#{
section_name
}
"
plantTimeout
0
,
->
section
.
data
(
'wrapper'
)
?
.
onClickTitle
?
()
# plantTimeout 0, -> section.data('wrapper')?.onExit?()
sections_have_loaded
.
after
->
section
.
data
(
'wrapper'
)
?
.
onClickTitle
?
()
# TODO enable onExit handler
# activate an initial section by 'clicking' on it.
# check for a deep-link, or click the first link.
click_first_link
=
->
link
=
links
.
eq
(
0
)
link
=
$
links
.
eq
(
0
)
link
.
click
()
link
.
data
(
'wrapper'
)
?
.
onClickTitle
?
()
if
(
new
RegExp
"^
#{
HASH_LINK_PREFIX
}
"
).
test
location
.
hash
rmatch
=
(
new
RegExp
"^
#{
HASH_LINK_PREFIX
}
(.*)"
).
exec
location
.
hash
section_name
=
rmatch
[
1
]
link
=
links
.
filter
"[data-section='
#{
section_name
}
']"
link
=
$
links
.
filter
"[data-section='
#{
section_name
}
']"
if
link
.
length
==
1
link
.
click
()
link
.
data
(
'wrapper'
)
?
.
onClickTitle
?
()
else
click_first_link
()
else
...
...
@@ -98,9 +138,14 @@ setup_instructor_dashboard = (idash_content) =>
# enable sections
setup_instructor_dashboard_sections
=
(
idash_content
)
->
# see fault isolation NOTE at top of file.
# an error thrown in one section will not block other sections from exectuing.
plantTimeout
0
,
->
new
window
.
InstructorDashboard
.
sections
.
CourseInfo
idash_content
.
find
".
#{
CSS_IDASH_SECTION
}
#course_info"
plantTimeout
0
,
->
new
window
.
InstructorDashboard
.
sections
.
DataDownload
idash_content
.
find
".
#{
CSS_IDASH_SECTION
}
#data_download"
plantTimeout
0
,
->
new
window
.
InstructorDashboard
.
sections
.
Membership
idash_content
.
find
".
#{
CSS_IDASH_SECTION
}
#membership"
plantTimeout
0
,
->
new
window
.
InstructorDashboard
.
sections
.
StudentAdmin
idash_content
.
find
".
#{
CSS_IDASH_SECTION
}
#student_admin"
plantTimeout
0
,
->
new
window
.
InstructorDashboard
.
sections
.
Analytics
idash_content
.
find
".
#{
CSS_IDASH_SECTION
}
#analytics"
# If an error thrown in one section, it will not stop other sections from exectuing.
plantTimeout
0
,
sections_have_loaded
.
waitFor
->
new
window
.
InstructorDashboard
.
sections
.
CourseInfo
idash_content
.
find
".
#{
CSS_IDASH_SECTION
}
#course_info"
plantTimeout
0
,
sections_have_loaded
.
waitFor
->
new
window
.
InstructorDashboard
.
sections
.
DataDownload
idash_content
.
find
".
#{
CSS_IDASH_SECTION
}
#data_download"
plantTimeout
0
,
sections_have_loaded
.
waitFor
->
new
window
.
InstructorDashboard
.
sections
.
Membership
idash_content
.
find
".
#{
CSS_IDASH_SECTION
}
#membership"
plantTimeout
0
,
sections_have_loaded
.
waitFor
->
new
window
.
InstructorDashboard
.
sections
.
StudentAdmin
idash_content
.
find
".
#{
CSS_IDASH_SECTION
}
#student_admin"
plantTimeout
0
,
sections_have_loaded
.
waitFor
->
new
window
.
InstructorDashboard
.
sections
.
Analytics
idash_content
.
find
".
#{
CSS_IDASH_SECTION
}
#analytics"
lms/static/sass/course/instructor/_instructor_2.scss
View file @
4afde4dd
...
...
@@ -36,6 +36,11 @@ section.instructor-dashboard-content-2 {
color
:
$error-red
;
}
.display-errors
{
line-height
:
3em
;
color
:
$error-red
;
}
.slickgrid
{
margin-left
:
1px
;
color
:
#333333
;
...
...
@@ -320,25 +325,40 @@ section.instructor-dashboard-content-2 {
}
.instructor-dashboard-wrapper-2
section
.idash-section
#analytics
{
.distribution-display
{
margin-top
:
1
.2em
;
.profile-distribution-widget
{
margin-bottom
:
$baseline
*
2
;
.distribution-display-graph
{
.year-of-birth
{
width
:
750px
;
height
:
250px
;
}
}
.display-text
{}
.distribution-display-table
{
.slickgrid
{
height
:
400px
;
}
.display-graph
.graph-placeholder
{
width
:
750px
;
height
:
250px
;
}
.display-table
{
.slickgrid
{
height
:
250px
;
}
}
}
.grade-distributions-widget
{
margin-bottom
:
$baseline
*
2
;
.last-updated
{
line-height
:
2
.2em
;
font-size
:
10pt
;
}
.display-graph
.graph-placeholder
{
width
:
750px
;
height
:
200px
;
}
.display-text
{
line-height
:
2em
;
}
}
.member-list-widget
{
$width
:
20
*
$baseline
;
...
...
lms/templates/instructor/instructor_dashboard_2/analytics.html
View file @
4afde4dd
<
%
page
args=
"section_data"
/>
<h2>
Distributions
</h2>
<select
id=
"distributions"
data-endpoint=
"${ section_data['get_distribution_url'] }"
>
<option>
Getting available distributions...
</option>
</select>
<div
class=
"distribution-display"
>
<div
class=
"distribution-display-text"
></div>
<div
class=
"distribution-display-graph"
></div>
<div
class=
"distribution-display-table"
></div>
<div
class=
"request-response-error"
></div>
</div>
<script
type=
"text/template"
id=
"profile-distribution-widget-template"
>
<
div
class
=
"profile-distribution-widget"
>
<
div
class
=
"header"
>
<
h2
class
=
"title"
>
{{
title
}}
<
/h2
>
<
/div
>
<
div
class
=
"view"
>
<
div
class
=
"display-errors"
><
/div
>
<
div
class
=
"display-text"
><
/div
>
<
div
class
=
"display-graph"
><
/div
>
<
div
class
=
"display-table"
><
/div
>
<
/div
>
<
/div
>
</script>
<script
type=
"text/template"
id=
"grade-distributions-widget-template"
>
<
div
class
=
"grade-distributions-widget"
>
<
div
class
=
"header"
>
<
h2
class
=
"title"
>
Grade
Distribution
<
/h2
>
Problem
:
<
select
class
=
"problem-selector"
>
<
option
>
Loading
problem
list
...
<
/option
>
<
/select
>
<
div
class
=
"last-updated"
><
/div
>
<
/div
>
<
div
class
=
"view"
>
<
div
class
=
"display-errors"
><
/div
>
<
div
class
=
"display-text"
><
/div
>
<
div
class
=
"display-graph"
><
/div
>
<
/div
>
<
/div
>
</script>
<div
class=
"grade-distributions-widget-container"
data-endpoint=
"${ section_data['proxy_legacy_analytics_url'] }"
></div>
<div
class=
"profile-distribution-widget-container"
data-title=
"Year of Birth"
data-feature=
"year_of_birth"
data-endpoint=
"${ section_data['get_distribution_url'] }"
></div>
<div
class=
"profile-distribution-widget-container"
data-title=
"Gender Distribution"
data-feature=
"gender"
data-endpoint=
"${ section_data['get_distribution_url'] }"
></div>
<div
class=
"profile-distribution-widget-container"
data-title=
"Level of Education"
data-feature=
"level_of_education"
data-endpoint=
"${ section_data['get_distribution_url'] }"
></div>
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