Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-analytics-dashboard
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-analytics-dashboard
Commits
18921872
Commit
18921872
authored
Oct 06, 2014
by
Dennis Jen
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Display empty previous data point on trend line when only one data point exists.
parent
140e5294
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
99 additions
and
19 deletions
+99
-19
analytics_dashboard/courses/presenters.py
+27
-5
analytics_dashboard/courses/tests/test_presenters.py
+35
-5
analytics_dashboard/courses/tests/utils.py
+18
-0
analytics_dashboard/static/js/views/trends-view.js
+19
-9
No files found.
analytics_dashboard/courses/presenters.py
View file @
18921872
...
@@ -52,24 +52,36 @@ class CourseEngagementPresenter(BasePresenter):
...
@@ -52,24 +52,36 @@ class CourseEngagementPresenter(BasePresenter):
return
activities
return
activities
def
_build_trend_week
(
self
,
trend_types
,
week_ending
,
api_trend
):
trend_week
=
{
'weekEnding'
:
week_ending
.
isoformat
()}
for
trend_type
in
trend_types
:
if
trend_type
in
api_trend
:
trend_week
[
trend_type
]
=
api_trend
[
trend_type
]
or
0
else
:
trend_week
[
trend_type
]
=
0
return
trend_week
def
_build_trend
(
self
,
api_trends
):
def
_build_trend
(
self
,
api_trends
):
"""
"""
Format activity trends for specified date range and return results with
Format activity trends for specified date range and return results with
zeros filled in for all activities.
zeros filled in for all activities.
"""
"""
trend_types
=
self
.
get_activity_types
()
trend_types
=
self
.
get_activity_types
()
trends
=
[]
# add zeros for the week prior if we only have a single point (prevents just a single point in the chart)
if
len
(
api_trends
)
==
1
:
interval_end
=
self
.
parse_api_datetime
(
api_trends
[
0
][
'interval_end'
])
week_ending
=
interval_end
.
date
()
-
datetime
.
timedelta
(
days
=
8
)
trends
.
append
(
self
.
_build_trend_week
(
trend_types
,
week_ending
,
{}))
# fill in gaps in activity with zero for display (api doesn't return
# fill in gaps in activity with zero for display (api doesn't return
# the field if no data exists for it, so we fill in the zeros here)
# the field if no data exists for it, so we fill in the zeros here)
trends
=
[]
for
datum
in
api_trends
:
for
datum
in
api_trends
:
# convert end of interval to ending day of week
# convert end of interval to ending day of week
interval_end
=
self
.
parse_api_datetime
(
datum
[
'interval_end'
])
interval_end
=
self
.
parse_api_datetime
(
datum
[
'interval_end'
])
week_ending
=
interval_end
.
date
()
-
datetime
.
timedelta
(
days
=
1
)
week_ending
=
interval_end
.
date
()
-
datetime
.
timedelta
(
days
=
1
)
trend_week
=
{
'weekEnding'
:
week_ending
.
isoformat
()}
trends
.
append
(
self
.
_build_trend_week
(
trend_types
,
week_ending
,
datum
))
for
trend_type
in
trend_types
:
trend_week
[
trend_type
]
=
datum
[
trend_type
]
or
0
trends
.
append
(
trend_week
)
return
trends
return
trends
...
@@ -116,8 +128,18 @@ class CourseEnrollmentPresenter(BasePresenter):
...
@@ -116,8 +128,18 @@ class CourseEnrollmentPresenter(BasePresenter):
now
=
datetime
.
datetime
.
utcnow
()
.
strftime
(
Client
.
DATE_FORMAT
)
now
=
datetime
.
datetime
.
utcnow
()
.
strftime
(
Client
.
DATE_FORMAT
)
trends
=
self
.
course
.
enrollment
(
start_date
=
None
,
end_date
=
now
)
trends
=
self
.
course
.
enrollment
(
start_date
=
None
,
end_date
=
now
)
summary
=
self
.
_build_summary
(
trends
)
summary
=
self
.
_build_summary
(
trends
)
# add zero for the day prior (prevents just a single point in the chart)
if
len
(
trends
)
==
1
:
trends
.
insert
(
0
,
self
.
_build_empty_trend
(
self
.
parse_api_date
(
trends
[
0
][
'date'
])))
return
summary
,
trends
return
summary
,
trends
def
_build_empty_trend
(
self
,
day
):
day
=
day
-
datetime
.
timedelta
(
days
=
1
)
trend
=
{
'date'
:
day
.
isoformat
(),
'count'
:
0
}
return
trend
def
get_geography_data
(
self
):
def
get_geography_data
(
self
):
"""
"""
Returns a list of course geography data and the updated date (ex. 2014-1-31).
Returns a list of course geography data and the updated date (ex. 2014-1-31).
...
...
analytics_dashboard/courses/tests/test_presenters.py
View file @
18921872
...
@@ -6,7 +6,8 @@ from django.test import TestCase
...
@@ -6,7 +6,8 @@ from django.test import TestCase
import
analyticsclient.constants.activity_type
as
AT
import
analyticsclient.constants.activity_type
as
AT
from
courses.presenters
import
CourseEngagementPresenter
,
CourseEnrollmentPresenter
,
BasePresenter
from
courses.presenters
import
CourseEngagementPresenter
,
CourseEnrollmentPresenter
,
BasePresenter
from
courses.tests.utils
import
get_mock_enrollment_data
,
get_mock_enrollment_summary
,
\
from
courses.tests.utils
import
get_mock_enrollment_data
,
get_mock_presenter_enrollment_data_small
,
\
get_mock_enrollment_summary
,
get_mock_presenter_enrollment_summary_small
,
\
get_mock_api_enrollment_geography_data
,
get_mock_presenter_enrollment_geography_data
,
\
get_mock_api_enrollment_geography_data
,
get_mock_presenter_enrollment_geography_data
,
\
get_mock_api_enrollment_geography_data_limited
,
get_mock_presenter_enrollment_geography_data_limited
,
\
get_mock_api_enrollment_geography_data_limited
,
get_mock_presenter_enrollment_geography_data_limited
,
\
mock_course_activity
,
CREATED_DATETIME
mock_course_activity
,
CREATED_DATETIME
...
@@ -39,12 +40,24 @@ class CourseEngagementPresenterTests(TestCase):
...
@@ -39,12 +40,24 @@ class CourseEngagementPresenterTests(TestCase):
return
trends
return
trends
def
assertSummaryAndTrendsValid
(
self
,
include_forum_activity
):
def
get_expected_trends_small
(
self
,
include_forum_data
):
trends
=
self
.
get_expected_trends
(
include_forum_data
)
trends
[
0
]
.
update
({
AT
.
ANY
:
0
,
AT
.
ATTEMPTED_PROBLEM
:
0
,
AT
.
PLAYED_VIDEO
:
0
})
if
include_forum_data
:
trends
[
0
][
AT
.
POSTED_FORUM
]
=
0
return
trends
def
assertSummaryAndTrendsValid
(
self
,
include_forum_activity
,
expected_trends
):
switch
,
_created
=
Switch
.
objects
.
get_or_create
(
name
=
'show_engagement_forum_activity'
)
switch
,
_created
=
Switch
.
objects
.
get_or_create
(
name
=
'show_engagement_forum_activity'
)
switch
.
active
=
include_forum_activity
switch
.
active
=
include_forum_activity
switch
.
save
()
switch
.
save
()
expected_trends
=
self
.
get_expected_trends
(
include_forum_activity
)
summary
,
trends
=
self
.
presenter
.
get_summary_and_trend_data
()
summary
,
trends
=
self
.
presenter
.
get_summary_and_trend_data
()
# Validate the trends
# Validate the trends
...
@@ -66,8 +79,16 @@ class CourseEngagementPresenterTests(TestCase):
...
@@ -66,8 +79,16 @@ class CourseEngagementPresenterTests(TestCase):
@mock.patch
(
'analyticsclient.course.Course.activity'
,
mock
.
Mock
(
side_effect
=
mock_course_activity
))
@mock.patch
(
'analyticsclient.course.Course.activity'
,
mock
.
Mock
(
side_effect
=
mock_course_activity
))
def
test_get_summary_and_trend_data
(
self
):
def
test_get_summary_and_trend_data
(
self
):
self
.
assertSummaryAndTrendsValid
(
False
)
self
.
assertSummaryAndTrendsValid
(
False
,
self
.
get_expected_trends
(
False
))
self
.
assertSummaryAndTrendsValid
(
True
)
self
.
assertSummaryAndTrendsValid
(
True
,
self
.
get_expected_trends
(
True
))
@mock.patch
(
'analyticsclient.course.Course.activity'
)
def
test_get_summary_and_trend_data_small
(
self
,
mock_activity
):
api_trend
=
[
mock_course_activity
()[
-
1
]]
mock_activity
.
return_value
=
api_trend
self
.
assertSummaryAndTrendsValid
(
False
,
self
.
get_expected_trends_small
(
False
))
self
.
assertSummaryAndTrendsValid
(
True
,
self
.
get_expected_trends_small
(
True
))
class
BasePresenterTests
(
TestCase
):
class
BasePresenterTests
(
TestCase
):
...
@@ -119,6 +140,15 @@ class CourseEnrollmentPresenterTests(TestCase):
...
@@ -119,6 +140,15 @@ class CourseEnrollmentPresenterTests(TestCase):
self
.
assertListEqual
(
actual_trend
,
expected_trend
)
self
.
assertListEqual
(
actual_trend
,
expected_trend
)
@mock.patch
(
'analyticsclient.course.Course.enrollment'
)
@mock.patch
(
'analyticsclient.course.Course.enrollment'
)
def
test_get_summary_and_trend_data_small
(
self
,
mock_enrollment
):
api_trend
=
[
get_mock_enrollment_data
(
self
.
course_id
)[
-
1
]]
mock_enrollment
.
return_value
=
api_trend
actual_summary
,
actual_trend
=
self
.
presenter
.
get_summary_and_trend_data
()
self
.
assertDictEqual
(
actual_summary
,
get_mock_presenter_enrollment_summary_small
())
self
.
assertListEqual
(
actual_trend
,
get_mock_presenter_enrollment_data_small
(
self
.
course_id
))
@mock.patch
(
'analyticsclient.course.Course.enrollment'
)
def
test_get_geography_data
(
self
,
mock_enrollment
):
def
test_get_geography_data
(
self
,
mock_enrollment
):
# test with a full set of countries
# test with a full set of countries
mock_data
=
get_mock_api_enrollment_geography_data
(
self
.
course_id
)
mock_data
=
get_mock_api_enrollment_geography_data
(
self
.
course_id
)
...
...
analytics_dashboard/courses/tests/utils.py
View file @
18921872
...
@@ -42,6 +42,24 @@ def get_mock_enrollment_summary_and_trend(course_id):
...
@@ -42,6 +42,24 @@ def get_mock_enrollment_summary_and_trend(course_id):
return
get_mock_enrollment_summary
(),
get_mock_enrollment_data
(
course_id
)
return
get_mock_enrollment_summary
(),
get_mock_enrollment_data
(
course_id
)
def
get_mock_presenter_enrollment_data_small
(
course_id
):
single_enrollment
=
get_mock_enrollment_data
(
course_id
)[
-
1
]
empty_enrollment
=
{
'count'
:
0
,
'date'
:
'2014-01-30'
}
return
[
empty_enrollment
,
single_enrollment
]
def
get_mock_presenter_enrollment_summary_small
():
return
{
'last_updated'
:
CREATED_DATETIME
,
'current_enrollment'
:
30
,
'enrollment_change_last_7_days'
:
None
,
}
def
get_mock_api_enrollment_geography_data
(
course_id
):
def
get_mock_api_enrollment_geography_data
(
course_id
):
data
=
[]
data
=
[]
items
=
((
u'USA'
,
u'United States'
,
500
),
(
None
,
UNKNOWN_COUNTRY_CODE
,
300
),
items
=
((
u'USA'
,
u'United States'
,
500
),
(
None
,
UNKNOWN_COUNTRY_CODE
,
300
),
...
...
analytics_dashboard/static/js/views/trends-view.js
View file @
18921872
...
@@ -18,9 +18,9 @@ define(['bootstrap', 'd3', 'jquery', 'moment', 'nvd3', 'underscore', 'views/attr
...
@@ -18,9 +18,9 @@ define(['bootstrap', 'd3', 'jquery', 'moment', 'nvd3', 'underscore', 'views/attr
*/
*/
assembleTrendData
:
function
()
{
assembleTrendData
:
function
()
{
var
self
=
this
,
var
self
=
this
,
combinedTrends
,
data
=
self
.
model
.
get
(
self
.
options
.
modelAttribute
),
data
=
self
.
model
.
get
(
self
.
options
.
modelAttribute
),
trendOptions
=
self
.
options
.
trends
;
trendOptions
=
self
.
options
.
trends
,
combinedTrends
;
// parse and format the data for nvd3
// parse and format the data for nvd3
combinedTrends
=
_
(
trendOptions
).
map
(
function
(
trendOption
)
{
combinedTrends
=
_
(
trendOptions
).
map
(
function
(
trendOption
)
{
...
@@ -68,9 +68,6 @@ define(['bootstrap', 'd3', 'jquery', 'moment', 'nvd3', 'underscore', 'views/attr
...
@@ -68,9 +68,6 @@ define(['bootstrap', 'd3', 'jquery', 'moment', 'nvd3', 'underscore', 'views/attr
// Remove the grid lines
// Remove the grid lines
canvas
.
selectAll
(
'.nvd3 .nv-axis line'
).
remove
();
canvas
.
selectAll
(
'.nvd3 .nv-axis line'
).
remove
();
// Remove max value from the X-axis
canvas
.
select
(
'.nvd3 .nv-axis.nv-x .nv-axisMaxMin:nth-child(3)'
).
remove
();
// Get the existing X-axis translation and shift it down a few more pixels.
// Get the existing X-axis translation and shift it down a few more pixels.
axisEl
=
canvas
.
select
(
'.nvd3 .nv-axis.nv-x'
);
axisEl
=
canvas
.
select
(
'.nvd3 .nv-axis.nv-x'
);
matches
=
translateRegex
.
exec
(
axisEl
.
attr
(
'transform'
));
matches
=
translateRegex
.
exec
(
axisEl
.
attr
(
'transform'
));
...
@@ -92,11 +89,14 @@ define(['bootstrap', 'd3', 'jquery', 'moment', 'nvd3', 'underscore', 'views/attr
...
@@ -92,11 +89,14 @@ define(['bootstrap', 'd3', 'jquery', 'moment', 'nvd3', 'underscore', 'views/attr
AttributeListenerView
.
prototype
.
render
.
call
(
this
);
AttributeListenerView
.
prototype
.
render
.
call
(
this
);
var
self
=
this
,
var
self
=
this
,
canvas
=
d3
.
select
(
self
.
el
),
canvas
=
d3
.
select
(
self
.
el
),
assembledData
=
self
.
assembleTrendData
(),
displayExplicitTicksThreshold
=
11
,
chart
,
chart
,
$tooltip
;
$tooltip
,
xTicks
;
chart
=
nvd3
.
models
.
lineChart
()
chart
=
nvd3
.
models
.
lineChart
()
.
margin
({
top
:
1
})
.
margin
({
top
:
6
})
// minimize the spacing, but leave enough for point at the top to be shown w/o being clipped
.
height
(
300
)
// This should be the same as the height set on the chart container in CSS.
.
height
(
300
)
// This should be the same as the height set on the chart container in CSS.
.
showLegend
(
false
)
.
showLegend
(
false
)
.
useInteractiveGuideline
(
true
)
.
useInteractiveGuideline
(
true
)
...
@@ -110,6 +110,16 @@ define(['bootstrap', 'd3', 'jquery', 'moment', 'nvd3', 'underscore', 'views/attr
...
@@ -110,6 +110,16 @@ define(['bootstrap', 'd3', 'jquery', 'moment', 'nvd3', 'underscore', 'views/attr
return
d
[
self
.
options
.
y
.
key
];
return
d
[
self
.
options
.
y
.
key
];
});
});
// explicitly display tick marks for small numbers of points, otherwise
// ticks will be interpolated and dates look to be repeated on the x-axis
if
(
self
.
model
.
get
(
self
.
options
.
modelAttribute
).
length
<
displayExplicitTicksThreshold
)
{
// get dates for the explicit ticks -- assuming data isn't sparse
xTicks
=
_
(
self
.
assembleTrendData
()[
0
].
values
).
map
(
function
(
data
)
{
return
Date
.
parse
(
data
[
self
.
options
.
x
.
key
]);
});
chart
.
xAxis
.
tickValues
(
xTicks
);
}
chart
.
xAxis
chart
.
xAxis
.
tickFormat
(
function
(
d
)
{
.
tickFormat
(
function
(
d
)
{
// date is converted to unix timestamp from our original mm-dd-yyyy format
// date is converted to unix timestamp from our original mm-dd-yyyy format
...
@@ -139,13 +149,13 @@ define(['bootstrap', 'd3', 'jquery', 'moment', 'nvd3', 'underscore', 'views/attr
...
@@ -139,13 +149,13 @@ define(['bootstrap', 'd3', 'jquery', 'moment', 'nvd3', 'underscore', 'views/attr
.
append
(
'div'
)
.
append
(
'div'
)
.
attr
(
'class'
,
'line-chart'
)
.
attr
(
'class'
,
'line-chart'
)
.
append
(
'svg'
)
.
append
(
'svg'
)
.
datum
(
self
.
assembleTrendData
()
)
.
datum
(
assembledData
)
.
call
(
chart
);
.
call
(
chart
);
self
.
styleChart
();
self
.
styleChart
();
nvd3
.
utils
.
windowResize
(
chart
.
update
);
nvd3
.
utils
.
windowResize
(
chart
.
update
);
nv
.
utils
.
windowResize
(
function
()
{
nv
d3
.
utils
.
windowResize
(
function
()
{
self
.
styleChart
();
self
.
styleChart
();
});
});
...
...
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