Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-analytics-data-api
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-data-api
Commits
c05732b5
Commit
c05732b5
authored
Jun 20, 2016
by
Anjali Pal
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add problem_attempts_per_completed to payload
parent
489e0f6d
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
69 additions
and
86 deletions
+69
-86
analytics_data_api/constants/engagement_entity_types.py
+0
-8
analytics_data_api/constants/engagement_events.py
+21
-10
analytics_data_api/constants/engagement_types.py
+6
-2
analytics_data_api/management/commands/generate_fake_course_data.py
+21
-24
analytics_data_api/v0/serializers.py
+11
-19
analytics_data_api/v0/tests/views/test_engagement_timelines.py
+2
-2
analytics_data_api/v0/tests/views/test_learners.py
+8
-21
No files found.
analytics_data_api/constants/engagement_entity_types.py
deleted
100644 → 0
View file @
489e0f6d
DISCUSSION
=
'discussion'
PROBLEM
=
'problem'
VIDEO
=
'video'
INDIVIDUAL_TYPES
=
[
DISCUSSION
,
PROBLEM
,
VIDEO
]
PROBLEMS
=
'problems'
VIDEOS
=
'videos'
AGGREGATE_TYPES
=
[
DISCUSSION
,
PROBLEMS
,
VIDEOS
]
analytics_data_api/constants/engagement_events.py
View file @
c05732b5
from
analytics_data_api.constants
import
engagement_entity_types
ATTEMPTED
=
'attempted'
ATTEMPTED
=
'attempted'
ATTEMPTS_PER_COMPLETED
=
'attempts_per_completed'
ATTEMPTS_PER_COMPLETED
=
'attempts_per_completed'
COMPLETED
=
'completed'
COMPLETED
=
'completed'
CONTRIBUTED
=
'contributed'
CONTRIBUTED
=
'contributed'
VIEWED
=
'viewed'
VIEWED
=
'viewed'
# map entity types to events
DISCUSSION
=
'discussion'
EVENTS
=
{
PROBLEM
=
'problem'
engagement_entity_types
.
DISCUSSION
:
[
CONTRIBUTED
],
VIDEO
=
'video'
engagement_entity_types
.
PROBLEM
:
[
ATTEMPTED
,
ATTEMPTS_PER_COMPLETED
,
COMPLETED
],
PROBLEMS
=
'problems'
engagement_entity_types
.
PROBLEMS
:
[
ATTEMPTED
,
COMPLETED
],
VIDEOS
=
'videos'
engagement_entity_types
.
VIDEO
:
[
VIEWED
],
engagement_entity_types
.
VIDEOS
:
[
VIEWED
],
INDIVIDUAL_EVENTS
=
[
}
'problem_attempts_per_completed'
,
'problem_attempted'
,
'problem_completed'
,
'discussion_contributed'
,
'video_viewed'
]
EVENTS
=
[
'problem_attempts_per_completed'
,
'problems_attempted'
,
'problems_completed'
,
'discussion_contributions'
,
'videos_viewed'
]
analytics_data_api/constants/engagement_types.py
View file @
c05732b5
from
analytics_data_api.constants.engagement_e
ntity_types
import
DISCUSSION
,
PROBLEM
,
VIDEO
from
analytics_data_api.constants.engagement_e
vents
import
(
ATTEMPTED
,
ATTEMPTS_PER_COMPLETED
,
COMPLETED
,
from
analytics_data_api.constants.engagement_events
import
ATTEMPTED
,
COMPLETED
,
CONTRIBUTED
,
VIEWED
CONTRIBUTED
,
DISCUSSION
,
PROBLEM
,
VIDEO
,
VIEWED
)
class
EngagementType
(
object
):
class
EngagementType
(
object
):
...
@@ -12,6 +12,7 @@ class EngagementType(object):
...
@@ -12,6 +12,7 @@ class EngagementType(object):
# Defines the current canonical set of engagement types used in the Learner
# Defines the current canonical set of engagement types used in the Learner
# Analytics API.
# Analytics API.
ALL_TYPES
=
(
ALL_TYPES
=
(
'problem_attempts_per_completed'
,
'problems_attempted'
,
'problems_attempted'
,
'problems_completed'
,
'problems_completed'
,
'videos_viewed'
,
'videos_viewed'
,
...
@@ -30,6 +31,9 @@ class EngagementType(object):
...
@@ -30,6 +31,9 @@ class EngagementType(object):
if
event_type
==
ATTEMPTED
:
if
event_type
==
ATTEMPTED
:
self
.
name
=
'problems_attempted'
self
.
name
=
'problems_attempted'
self
.
is_counted_by_entity
=
True
self
.
is_counted_by_entity
=
True
if
event_type
==
ATTEMPTS_PER_COMPLETED
:
self
.
name
=
'problem_attempts_per_completed'
self
.
is_counted_by_entity
=
True
if
event_type
==
COMPLETED
:
if
event_type
==
COMPLETED
:
self
.
name
=
'problems_completed'
self
.
name
=
'problems_completed'
self
.
is_counted_by_entity
=
True
self
.
is_counted_by_entity
=
True
...
...
analytics_data_api/management/commands/generate_fake_course_data.py
View file @
c05732b5
...
@@ -8,7 +8,7 @@ import random
...
@@ -8,7 +8,7 @@ import random
from
django.core.management.base
import
BaseCommand
from
django.core.management.base
import
BaseCommand
from
django.utils
import
timezone
from
django.utils
import
timezone
from
analytics_data_api.v0
import
models
from
analytics_data_api.v0
import
models
from
analytics_data_api.constants
import
engagement_e
ntity_types
,
engagement_e
vents
from
analytics_data_api.constants
import
engagement_events
logging
.
basicConfig
(
level
=
logging
.
INFO
)
logging
.
basicConfig
(
level
=
logging
.
INFO
)
logger
=
logging
.
getLogger
(
__name__
)
logger
=
logging
.
getLogger
(
__name__
)
...
@@ -194,16 +194,17 @@ class Command(BaseCommand):
...
@@ -194,16 +194,17 @@ class Command(BaseCommand):
current
=
start_date
current
=
start_date
while
current
<
end_date
:
while
current
<
end_date
:
current
=
current
+
datetime
.
timedelta
(
days
=
1
)
current
=
current
+
datetime
.
timedelta
(
days
=
1
)
for
entity_type
in
engagement_entity_types
.
INDIVIDUAL_TYPES
:
for
metric
in
engagement_events
.
INDIVIDUAL_EVENTS
:
for
event
in
engagement_events
.
EVENTS
[
entity_type
]:
num_events
=
random
.
randint
(
0
,
max_value
)
num_events
=
random
.
randint
(
0
,
max_value
)
if
num_events
:
if
num_events
:
for
_
in
xrange
(
num_events
):
for
_
in
xrange
(
num_events
):
count
=
random
.
randint
(
0
,
max_value
/
20
)
count
=
random
.
randint
(
0
,
max_value
/
20
)
entity_type
=
metric
.
split
(
'_'
,
1
)[
0
]
entity_id
=
'an-id-{}-{}'
.
format
(
entity_type
,
event
)
event
=
metric
.
split
(
'_'
,
1
)[
1
]
models
.
ModuleEngagement
.
objects
.
create
(
entity_id
=
'an-id-{}-{}'
.
format
(
entity_type
,
event
)
course_id
=
course_id
,
username
=
username
,
date
=
current
,
models
.
ModuleEngagement
.
objects
.
create
(
entity_type
=
entity_type
,
entity_id
=
entity_id
,
event
=
event
,
count
=
count
)
course_id
=
course_id
,
username
=
username
,
date
=
current
,
entity_type
=
entity_type
,
entity_id
=
entity_id
,
event
=
event
,
count
=
count
)
logger
.
info
(
"Done!"
)
logger
.
info
(
"Done!"
)
def
generate_learner_engagement_range_data
(
self
,
course_id
,
start_date
,
end_date
,
max_value
=
100
):
def
generate_learner_engagement_range_data
(
self
,
course_id
,
start_date
,
end_date
,
max_value
=
100
):
...
@@ -211,19 +212,15 @@ class Command(BaseCommand):
...
@@ -211,19 +212,15 @@ class Command(BaseCommand):
models
.
ModuleEngagementMetricRanges
.
objects
.
all
()
.
delete
()
models
.
ModuleEngagementMetricRanges
.
objects
.
all
()
.
delete
()
logger
.
info
(
"Generating engagement range data..."
)
logger
.
info
(
"Generating engagement range data..."
)
for
entity_type
in
engagement_entity_types
.
AGGREGATE_TYPES
:
for
event
in
engagement_events
.
EVENTS
:
for
event
in
engagement_events
.
EVENTS
[
entity_type
]:
low_ceil
=
random
.
random
()
*
max_value
*
0.5
metric
=
'{0}_{1}'
.
format
(
entity_type
,
event
)
models
.
ModuleEngagementMetricRanges
.
objects
.
create
(
course_id
=
course_id
,
start_date
=
start_date
,
end_date
=
end_date
,
metric
=
event
,
low_ceil
=
random
.
random
()
*
max_value
*
0.5
range_type
=
'low'
,
low_value
=
0
,
high_value
=
low_ceil
)
models
.
ModuleEngagementMetricRanges
.
objects
.
create
(
high_floor
=
random
.
random
()
*
max_value
*
0.5
+
low_ceil
course_id
=
course_id
,
start_date
=
start_date
,
end_date
=
end_date
,
metric
=
metric
,
models
.
ModuleEngagementMetricRanges
.
objects
.
create
(
range_type
=
'low'
,
low_value
=
0
,
high_value
=
low_ceil
)
course_id
=
course_id
,
start_date
=
start_date
,
end_date
=
end_date
,
metric
=
event
,
range_type
=
'high'
,
low_value
=
high_floor
,
high_value
=
max_value
)
high_floor
=
random
.
random
()
*
max_value
*
0.5
+
low_ceil
models
.
ModuleEngagementMetricRanges
.
objects
.
create
(
course_id
=
course_id
,
start_date
=
start_date
,
end_date
=
end_date
,
metric
=
metric
,
range_type
=
'high'
,
low_value
=
high_floor
,
high_value
=
max_value
)
def
handle
(
self
,
*
args
,
**
options
):
def
handle
(
self
,
*
args
,
**
options
):
course_id
=
options
[
'course_id'
]
course_id
=
options
[
'course_id'
]
...
...
analytics_data_api/v0/serializers.py
View file @
c05732b5
...
@@ -3,7 +3,6 @@ from django.conf import settings
...
@@ -3,7 +3,6 @@ from django.conf import settings
from
rest_framework
import
pagination
,
serializers
from
rest_framework
import
pagination
,
serializers
from
analytics_data_api.constants
import
(
from
analytics_data_api.constants
import
(
engagement_entity_types
,
engagement_events
,
engagement_events
,
enrollment_modes
,
enrollment_modes
,
genders
,
genders
,
...
@@ -446,23 +445,16 @@ class CourseLearnerMetadataSerializer(serializers.Serializer):
...
@@ -446,23 +445,16 @@ class CourseLearnerMetadataSerializer(serializers.Serializer):
'date_range'
:
DateRangeSerializer
(
query_set
[
0
]
if
len
(
query_set
)
else
None
)
.
data
'date_range'
:
DateRangeSerializer
(
query_set
[
0
]
if
len
(
query_set
)
else
None
)
.
data
}
}
# go through each entity and event type combination and fill in the ranges
for
metric
in
engagement_events
.
EVENTS
:
for
entity_type
in
engagement_entity_types
.
AGGREGATE_TYPES
:
low_range_queryset
=
query_set
.
filter
(
metric
=
metric
,
range_type
=
'low'
)
for
event
in
engagement_events
.
EVENTS
[
entity_type
]:
normal_range_queryset
=
query_set
.
filter
(
metric
=
metric
,
range_type
=
'normal'
)
metric
=
'{0}_{1}'
.
format
(
entity_type
,
event
)
high_range_queryset
=
query_set
.
filter
(
metric
=
metric
,
range_type
=
'high'
)
# It's assumed that there may be any combination of low, normal,
engagement_ranges
.
update
({
# and high ranges in the database for the given course. Some
metric
:
EnagementRangeMetricSerializer
({
# edge cases result from a lack of available data; in such
'low_range'
:
low_range_queryset
[
0
]
if
len
(
low_range_queryset
)
else
None
,
# cases, only some ranges may be returned.
'normal_range'
:
normal_range_queryset
[
0
]
if
len
(
normal_range_queryset
)
else
None
,
low_range_queryset
=
query_set
.
filter
(
metric
=
metric
,
range_type
=
'low'
)
'high_range'
:
high_range_queryset
[
0
]
if
len
(
high_range_queryset
)
else
None
,
normal_range_queryset
=
query_set
.
filter
(
metric
=
metric
,
range_type
=
'normal'
)
})
.
data
high_range_queryset
=
query_set
.
filter
(
metric
=
metric
,
range_type
=
'high'
)
})
engagement_ranges
.
update
({
metric
:
EnagementRangeMetricSerializer
({
'low_range'
:
low_range_queryset
[
0
]
if
len
(
low_range_queryset
)
else
None
,
'normal_range'
:
normal_range_queryset
[
0
]
if
len
(
normal_range_queryset
)
else
None
,
'high_range'
:
high_range_queryset
[
0
]
if
len
(
high_range_queryset
)
else
None
,
})
.
data
})
return
engagement_ranges
return
engagement_ranges
analytics_data_api/v0/tests/views/test_engagement_timelines.py
View file @
c05732b5
...
@@ -9,8 +9,8 @@ import pytz
...
@@ -9,8 +9,8 @@ import pytz
from
rest_framework
import
status
from
rest_framework
import
status
from
analyticsdataserver.tests
import
TestCaseWithAuthentication
from
analyticsdataserver.tests
import
TestCaseWithAuthentication
from
analytics_data_api.constants.engagement_e
ntity_types
import
DISCUSSION
,
PROBLEM
,
VIDEO
from
analytics_data_api.constants.engagement_e
vents
import
(
ATTEMPTED
,
COMPLETED
,
CONTRIBUTED
,
DISCUSSION
,
from
analytics_data_api.constants.engagement_events
import
ATTEMPTED
,
COMPLETED
,
CONTRIBUTED
,
VIEWED
PROBLEM
,
VIDEO
,
VIEWED
)
from
analytics_data_api.v0
import
models
from
analytics_data_api.v0
import
models
from
analytics_data_api.v0.tests.views
import
DemoCourseMixin
,
VerifyCourseIdMixin
from
analytics_data_api.v0.tests.views
import
DemoCourseMixin
,
VerifyCourseIdMixin
...
...
analytics_data_api/v0/tests/views/test_learners.py
View file @
c05732b5
...
@@ -17,7 +17,7 @@ from django.conf import settings
...
@@ -17,7 +17,7 @@ from django.conf import settings
from
django.core
import
management
from
django.core
import
management
from
analyticsdataserver.tests
import
TestCaseWithAuthentication
from
analyticsdataserver.tests
import
TestCaseWithAuthentication
from
analytics_data_api.constants
import
engagement_e
ntity_types
,
engagement_e
vents
from
analytics_data_api.constants
import
engagement_events
from
analytics_data_api.v0.models
import
ModuleEngagementMetricRanges
from
analytics_data_api.v0.models
import
ModuleEngagementMetricRanges
from
analytics_data_api.v0.tests.views
import
DemoCourseMixin
,
VerifyCourseIdMixin
from
analytics_data_api.v0.tests.views
import
DemoCourseMixin
,
VerifyCourseIdMixin
...
@@ -571,19 +571,10 @@ class CourseLearnerMetadataTests(DemoCourseMixin, VerifyCourseIdMixin,
...
@@ -571,19 +571,10 @@ class CourseLearnerMetadataTests(DemoCourseMixin, VerifyCourseIdMixin,
empty_range
=
{
empty_range
=
{
range_type
:
None
for
range_type
in
[
'below_average'
,
'average'
,
'above_average'
]
range_type
:
None
for
range_type
in
[
'below_average'
,
'average'
,
'above_average'
]
}
}
for
metric
in
self
.
engagement_metrics
:
for
metric
in
engagement_events
.
EVENTS
:
empty_engagement_ranges
[
'engagement_ranges'
][
metric
]
=
copy
.
deepcopy
(
empty_range
)
empty_engagement_ranges
[
'engagement_ranges'
][
metric
]
=
copy
.
deepcopy
(
empty_range
)
return
empty_engagement_ranges
return
empty_engagement_ranges
@property
def
engagement_metrics
(
self
):
""" Convenience method for getting the metric types. """
metrics
=
[]
for
entity_type
in
engagement_entity_types
.
AGGREGATE_TYPES
:
for
event
in
engagement_events
.
EVENTS
[
entity_type
]:
metrics
.
append
(
'{0}_{1}'
.
format
(
entity_type
,
event
))
return
metrics
def
test_no_engagement_ranges
(
self
):
def
test_no_engagement_ranges
(
self
):
response
=
self
.
_get
(
self
.
course_id
)
response
=
self
.
_get
(
self
.
course_id
)
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
response
.
status_code
,
200
)
...
@@ -627,7 +618,7 @@ class CourseLearnerMetadataTests(DemoCourseMixin, VerifyCourseIdMixin,
...
@@ -627,7 +618,7 @@ class CourseLearnerMetadataTests(DemoCourseMixin, VerifyCourseIdMixin,
}
}
max_value
=
1000.0
max_value
=
1000.0
for
metric_type
in
self
.
engagement_metrics
:
for
metric_type
in
engagement_events
.
EVENTS
:
low_ceil
=
100.5
low_ceil
=
100.5
G
(
ModuleEngagementMetricRanges
,
course_id
=
self
.
course_id
,
start_date
=
start_date
,
end_date
=
end_date
,
G
(
ModuleEngagementMetricRanges
,
course_id
=
self
.
course_id
,
start_date
=
start_date
,
end_date
=
end_date
,
metric
=
metric_type
,
range_type
=
'low'
,
low_value
=
0
,
high_value
=
low_ceil
)
metric
=
metric_type
,
range_type
=
'low'
,
low_value
=
0
,
high_value
=
low_ceil
)
...
@@ -649,12 +640,8 @@ class CourseLearnerMetadataTests(DemoCourseMixin, VerifyCourseIdMixin,
...
@@ -649,12 +640,8 @@ class CourseLearnerMetadataTests(DemoCourseMixin, VerifyCourseIdMixin,
self
.
assertDictContainsSubset
(
expected
,
json
.
loads
(
response
.
content
))
self
.
assertDictContainsSubset
(
expected
,
json
.
loads
(
response
.
content
))
def
test_engagement_ranges_fields
(
self
):
def
test_engagement_ranges_fields
(
self
):
actual_entity_types
=
engagement_entity_types
.
INDIVIDUAL_TYPES
expected_events
=
engagement_events
.
EVENTS
expected_entity_types
=
[
'discussion'
,
'problem'
,
'video'
]
response
=
json
.
loads
(
self
.
_get
(
self
.
course_id
)
.
content
)
self
.
assertEqual
(
actual_entity_types
,
expected_entity_types
)
self
.
assertTrue
(
'engagement_ranges'
in
response
)
actual_events
=
[]
for
event
in
expected_events
:
for
entity_type
in
actual_entity_types
:
self
.
assertTrue
(
event
in
response
[
'engagement_ranges'
])
for
event
in
engagement_events
.
EVENTS
[
entity_type
]:
actual_events
.
append
(
event
)
expected_events
=
[
'contributed'
,
'attempted'
,
'attempts_per_completed'
,
'completed'
,
'viewed'
]
self
.
assertEqual
(
actual_events
,
expected_events
)
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