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):
self.assertEqual(len(response.data['modules_completed']), 5)
total_modules_completed = sum([completed[1] for completed in response.data['modules_completed']])
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
start_date = datetime.now().date() + relativedelta(days=-USER_COUNT)
......@@ -2112,7 +2117,7 @@ class CoursesApiTests(ModuleStoreTestCase):
# metrics with weeks as interval
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={}&' \
'interval=weeks'.format(self.base_courses_uri,
test_course_id,
......@@ -2121,9 +2126,9 @@ class CoursesApiTests(ModuleStoreTestCase):
response = self.do_get(course_metrics_uri)
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.data['users_not_started']), 2)
self.assertEqual(len(response.data['users_started']), 2)
self.assertEqual(len(response.data['users_completed']), 2)
self.assertGreaterEqual(len(response.data['users_not_started']), 2)
self.assertGreaterEqual(len(response.data['users_started']), 2)
self.assertGreaterEqual(len(response.data['users_completed']), 2)
# metrics with months as interval
start_date = end_date + relativedelta(months=-3)
......@@ -2157,6 +2162,7 @@ class CoursesApiTests(ModuleStoreTestCase):
response = self.do_get(course_metrics_uri)
self.assertEqual(response.status_code, 400)
def test_course_workgroups_list(self):
projects_uri = self.base_projects_uri
data = {
......
......@@ -5,6 +5,7 @@ import logging
import itertools
from lxml import etree
from StringIO import StringIO
from dateutil.relativedelta import relativedelta
from django.conf import settings
from django.contrib.auth.models import Group, User
......@@ -1541,80 +1542,6 @@ class CoursesMetrics(SecureAPIView):
data.update(thread_stats)
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):
"""
......@@ -1626,7 +1553,10 @@ class CoursesTimeSeriesMetrics(SecureAPIView):
{
"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]]
"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
### Use Cases/Notes:
......@@ -1659,18 +1589,22 @@ class CoursesTimeSeriesMetrics(SecureAPIView):
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)
.exclude(user_id__in=exclude_users)
users_started_qs = StudentProgressHistory.objects.filter(course_id__exact=course_key, user__is_active=True)\
.exclude(user_id__in=exclude_users)
modules_completed_qs = CourseModuleCompletion.get_actual_completions().filter(course_id__exact=course_key,
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)
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)
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_started_count = users_started_qs.filter(created__lt=start_dt).aggregate(Count('user', distinct=True))
......@@ -1684,18 +1618,25 @@ class CoursesTimeSeriesMetrics(SecureAPIView):
date_field='modified', aggregate=Count('id'))
modules_completed_series = get_time_series_data(modules_completed_qs, start_dt, end_dt, interval=interval,
date_field='created', aggregate=Count('id'))
not_started_series = []
# active users are those who accessed course in last 24 hours
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):
not_started_series.append((started[0], (total_enrolled + enrolled[1]) - (total_started + started[1])))
total_started += started[1]
total_enrolled += enrolled[1]
total_enrolled_series.append((started[0], total_enrolled))
data = {
'users_not_started': not_started_series,
'users_started': started_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)
......
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