Commit c4804485 by Zia Fazal Committed by Jonathan Piacenti

ziafazal/api-added-participant-activity-to-metrics: added

active_users and users_enrolled metrics
parent 00008c06
...@@ -2095,6 +2095,11 @@ class CoursesApiTests(ModuleStoreTestCase): ...@@ -2095,6 +2095,11 @@ class CoursesApiTests(ModuleStoreTestCase):
self.assertEqual(len(response.data['modules_completed']), 5) self.assertEqual(len(response.data['modules_completed']), 5)
total_modules_completed = sum([completed[1] for completed in response.data['modules_completed']]) total_modules_completed = sum([completed[1] for completed in response.data['modules_completed']])
self.assertEqual(total_modules_completed, 4) self.assertEqual(total_modules_completed, 4)
self.assertEqual(len(response.data['active_users']), 5)
total_active = sum([active[1] for active in response.data['active_users']])
self.assertEqual(total_active, 5)
self.assertEqual(response.data['users_enrolled'][0][1], 25)
# get modules completed for first 5 days # get modules completed for first 5 days
start_date = datetime.now().date() + relativedelta(days=-USER_COUNT) start_date = datetime.now().date() + relativedelta(days=-USER_COUNT)
...@@ -2112,7 +2117,7 @@ class CoursesApiTests(ModuleStoreTestCase): ...@@ -2112,7 +2117,7 @@ class CoursesApiTests(ModuleStoreTestCase):
# metrics with weeks as interval # metrics with weeks as interval
end_date = datetime.now().date() end_date = datetime.now().date()
start_date = end_date + relativedelta(days=-10) start_date = end_date + relativedelta(weeks=-2)
course_metrics_uri = '{}/{}/time-series-metrics/?start_date={}&end_date={}&' \ course_metrics_uri = '{}/{}/time-series-metrics/?start_date={}&end_date={}&' \
'interval=weeks'.format(self.base_courses_uri, 'interval=weeks'.format(self.base_courses_uri,
test_course_id, test_course_id,
...@@ -2121,9 +2126,9 @@ class CoursesApiTests(ModuleStoreTestCase): ...@@ -2121,9 +2126,9 @@ class CoursesApiTests(ModuleStoreTestCase):
response = self.do_get(course_metrics_uri) response = self.do_get(course_metrics_uri)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.data['users_not_started']), 2) self.assertGreaterEqual(len(response.data['users_not_started']), 2)
self.assertEqual(len(response.data['users_started']), 2) self.assertGreaterEqual(len(response.data['users_started']), 2)
self.assertEqual(len(response.data['users_completed']), 2) self.assertGreaterEqual(len(response.data['users_completed']), 2)
# metrics with months as interval # metrics with months as interval
start_date = end_date + relativedelta(months=-3) start_date = end_date + relativedelta(months=-3)
...@@ -2157,6 +2162,7 @@ class CoursesApiTests(ModuleStoreTestCase): ...@@ -2157,6 +2162,7 @@ class CoursesApiTests(ModuleStoreTestCase):
response = self.do_get(course_metrics_uri) response = self.do_get(course_metrics_uri)
self.assertEqual(response.status_code, 400) self.assertEqual(response.status_code, 400)
def test_course_workgroups_list(self): def test_course_workgroups_list(self):
projects_uri = self.base_projects_uri projects_uri = self.base_projects_uri
data = { data = {
......
...@@ -5,6 +5,7 @@ import logging ...@@ -5,6 +5,7 @@ import logging
import itertools import itertools
from lxml import etree from lxml import etree
from StringIO import StringIO from StringIO import StringIO
from dateutil.relativedelta import relativedelta
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import Group, User from django.contrib.auth.models import Group, User
...@@ -1541,80 +1542,6 @@ class CoursesMetrics(SecureAPIView): ...@@ -1541,80 +1542,6 @@ class CoursesMetrics(SecureAPIView):
data.update(thread_stats) data.update(thread_stats)
return Response(data, status=status.HTTP_200_OK) return Response(data, status=status.HTTP_200_OK)
class CoursesTimeSeriesMetrics(SecureAPIView):
"""
### The CoursesTimeSeriesMetrics view allows clients to retrieve a list of Metrics for the specified Course
in time series format.
- URI: ```/api/courses/{course_id}/time-series-metrics/?start_date={date}&end_date={date}&interval={interval}&organization={organization_id}```
- interval can be `days`, `weeks` or `months`
- GET: Returns a JSON representation with three metrics
{
"users_not_started": [[datetime-1, count-1], [datetime-2, count-2], ........ [datetime-n, count-n]],
"users_started": [[datetime-1, count-1], [datetime-2, count-2], ........ [datetime-n, count-n]],
"users_completed": [[datetime-1, count-1], [datetime-2, count-2], ........ [datetime-n, count-n]]
}
- metrics can be filtered by organization by adding organization parameter to GET request
### Use Cases/Notes:
* Example: Display number of users completed, started or not started in a given course for a given time period
"""
def get(self, request, course_id): # pylint: disable=W0613
"""
GET /api/courses/{course_id}/time-series-metrics/
"""
if not course_exists(request, request.user, course_id):
return Response({}, status=status.HTTP_404_NOT_FOUND)
start = request.QUERY_PARAMS.get('start_date', None)
end = request.QUERY_PARAMS.get('end_date', None)
interval = request.QUERY_PARAMS.get('interval', 'days')
if not start or not end:
return Response({"message": _("Both start_date and end_date parameters are required")}
, status=status.HTTP_400_BAD_REQUEST)
if interval not in ['days', 'weeks', 'months']:
return Response({"message": _("Interval parameter is not valid. It should be one of these "
"'days', 'weeks', 'months'")}, status=status.HTTP_400_BAD_REQUEST)
start_dt = parse_datetime(start)
end_dt = parse_datetime(end)
course_key = get_course_key(course_id)
exclude_users = _get_aggregate_exclusion_user_ids(course_key)
grade_complete_match_range = getattr(settings, 'GRADEBOOK_GRADE_COMPLETE_PROFORMA_MATCH_RANGE', 0.01)
grades_qs = StudentGradebook.objects.filter(course_id__exact=course_key, user__is_active=True).\
exclude(user_id__in=exclude_users)
grades_complete_qs = grades_qs.filter(proforma_grade__lte=F('grade') + grade_complete_match_range,
proforma_grade__gt=0)
enrolled_qs = CourseEnrollment.objects.filter(course_id__exact=course_key, user__is_active=True)\
.exclude(id__in=exclude_users)
users_started_qs = StudentProgress.objects.filter(course_id__exact=course_key, user__is_active=True)\
.exclude(user_id__in=exclude_users)
organization = request.QUERY_PARAMS.get('organization', None)
if organization:
enrolled_qs = enrolled_qs.filter(user__organizations=organization)
grades_complete_qs = grades_complete_qs.filter(user__organizations=organization)
users_started_qs = users_started_qs.filter(user__organizations=organization)
total_enrolled = enrolled_qs.filter(created__lt=start_dt).count()
total_started = users_started_qs.filter(created__lt=start_dt).count()
enrolled_series = get_time_series_data(enrolled_qs, start_dt, end_dt, interval=interval,
date_field='created', aggregate=Count('id'))
started_series = get_time_series_data(users_started_qs, start_dt, end_dt, interval=interval,
date_field='created', aggregate=Count('id'))
completed_series = get_time_series_data(grades_complete_qs, start_dt, end_dt, interval=interval,
date_field='modified', aggregate=Count('id'))
not_started_series = []
for enrolled, started in zip(enrolled_series, started_series):
not_started_series.append((started[0], (total_enrolled + enrolled[1]) - (total_started + started[1])))
total_started += started[1]
total_enrolled += enrolled[1]
data = {
'users_not_started': not_started_series,
'users_started': started_series,
'users_completed': completed_series
}
return Response(data, status=status.HTTP_200_OK)
class CoursesTimeSeriesMetrics(SecureAPIView): class CoursesTimeSeriesMetrics(SecureAPIView):
""" """
...@@ -1626,7 +1553,10 @@ class CoursesTimeSeriesMetrics(SecureAPIView): ...@@ -1626,7 +1553,10 @@ class CoursesTimeSeriesMetrics(SecureAPIView):
{ {
"users_not_started": [[datetime-1, count-1], [datetime-2, count-2], ........ [datetime-n, count-n]], "users_not_started": [[datetime-1, count-1], [datetime-2, count-2], ........ [datetime-n, count-n]],
"users_started": [[datetime-1, count-1], [datetime-2, count-2], ........ [datetime-n, count-n]], "users_started": [[datetime-1, count-1], [datetime-2, count-2], ........ [datetime-n, count-n]],
"users_completed": [[datetime-1, count-1], [datetime-2, count-2], ........ [datetime-n, count-n]] "users_completed": [[datetime-1, count-1], [datetime-2, count-2], ........ [datetime-n, count-n]],
"modules_completed": [[datetime-1, count-1], [datetime-2, count-2], ........ [datetime-n, count-n]]
"users_enrolled": [[datetime-1, count-1], [datetime-2, count-2], ........ [datetime-n, count-n]]
"active_users": [[datetime-1, count-1], [datetime-2, count-2], ........ [datetime-n, count-n]]
} }
- metrics can be filtered by organization by adding organization parameter to GET request - metrics can be filtered by organization by adding organization parameter to GET request
### Use Cases/Notes: ### Use Cases/Notes:
...@@ -1659,18 +1589,22 @@ class CoursesTimeSeriesMetrics(SecureAPIView): ...@@ -1659,18 +1589,22 @@ class CoursesTimeSeriesMetrics(SecureAPIView):
grades_complete_qs = grades_qs.filter(proforma_grade__lte=F('grade') + grade_complete_match_range, grades_complete_qs = grades_qs.filter(proforma_grade__lte=F('grade') + grade_complete_match_range,
proforma_grade__gt=0) proforma_grade__gt=0)
enrolled_qs = CourseEnrollment.objects.filter(course_id__exact=course_key, user__is_active=True)\ enrolled_qs = CourseEnrollment.objects.filter(course_id__exact=course_key, user__is_active=True)\
.exclude(id__in=exclude_users) .exclude(user_id__in=exclude_users)
users_started_qs = StudentProgressHistory.objects.filter(course_id__exact=course_key, user__is_active=True)\ users_started_qs = StudentProgressHistory.objects.filter(course_id__exact=course_key, user__is_active=True)\
.exclude(user_id__in=exclude_users) .exclude(user_id__in=exclude_users)
modules_completed_qs = CourseModuleCompletion.get_actual_completions().filter(course_id__exact=course_key, modules_completed_qs = CourseModuleCompletion.get_actual_completions().filter(course_id__exact=course_key,
user__is_active=True)\ user__is_active=True)\
.exclude(id__in=exclude_users) .exclude(user_id__in=exclude_users)
active_users_qs = StudentModule.objects.filter(course_id__exact=course_key, student__is_active=True)\
.exclude(student_id__in=exclude_users)
organization = request.QUERY_PARAMS.get('organization', None) organization = request.QUERY_PARAMS.get('organization', None)
if organization: if organization:
enrolled_qs = enrolled_qs.filter(user__organizations=organization) enrolled_qs = enrolled_qs.filter(user__organizations=organization)
grades_complete_qs = grades_complete_qs.filter(user__organizations=organization) grades_complete_qs = grades_complete_qs.filter(user__organizations=organization)
users_started_qs = users_started_qs.filter(user__organizations=organization) users_started_qs = users_started_qs.filter(user__organizations=organization)
modules_completed_qs = modules_completed_qs.filter(user__organizations=organization) modules_completed_qs = modules_completed_qs.filter(user__organizations=organization)
active_users_qs = active_users_qs.filter(student__organizations=organization)
total_enrolled = enrolled_qs.filter(created__lt=start_dt).count() total_enrolled = enrolled_qs.filter(created__lt=start_dt).count()
total_started_count = users_started_qs.filter(created__lt=start_dt).aggregate(Count('user', distinct=True)) total_started_count = users_started_qs.filter(created__lt=start_dt).aggregate(Count('user', distinct=True))
...@@ -1684,18 +1618,25 @@ class CoursesTimeSeriesMetrics(SecureAPIView): ...@@ -1684,18 +1618,25 @@ class CoursesTimeSeriesMetrics(SecureAPIView):
date_field='modified', aggregate=Count('id')) date_field='modified', aggregate=Count('id'))
modules_completed_series = get_time_series_data(modules_completed_qs, start_dt, end_dt, interval=interval, modules_completed_series = get_time_series_data(modules_completed_qs, start_dt, end_dt, interval=interval,
date_field='created', aggregate=Count('id')) date_field='created', aggregate=Count('id'))
# active users are those who accessed course in last 24 hours
not_started_series = [] start_dt = start_dt + relativedelta(hours=-24)
end_dt = end_dt + relativedelta(hours=-24)
active_users_series = get_time_series_data(active_users_qs, start_dt, end_dt, interval=interval,
date_field='modified', aggregate=Count('student', distinct=True))
not_started_series, total_enrolled_series = [], []
for enrolled, started in zip(enrolled_series, started_series): for enrolled, started in zip(enrolled_series, started_series):
not_started_series.append((started[0], (total_enrolled + enrolled[1]) - (total_started + started[1]))) not_started_series.append((started[0], (total_enrolled + enrolled[1]) - (total_started + started[1])))
total_started += started[1] total_started += started[1]
total_enrolled += enrolled[1] total_enrolled += enrolled[1]
total_enrolled_series.append((started[0], total_enrolled))
data = { data = {
'users_not_started': not_started_series, 'users_not_started': not_started_series,
'users_started': started_series, 'users_started': started_series,
'users_completed': completed_series, 'users_completed': completed_series,
'modules_completed': modules_completed_series 'modules_completed': modules_completed_series,
'users_enrolled': total_enrolled_series,
'active_users': active_users_series
} }
return Response(data, status=status.HTTP_200_OK) return Response(data, status=status.HTTP_200_OK)
......
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