edx-enterprise-data 9.0.1__py3-none-any.whl → 9.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. {edx_enterprise_data-9.0.1.dist-info → edx_enterprise_data-9.1.0.dist-info}/METADATA +1 -1
  2. {edx_enterprise_data-9.0.1.dist-info → edx_enterprise_data-9.1.0.dist-info}/RECORD +22 -23
  3. enterprise_data/__init__.py +1 -1
  4. enterprise_data/admin_analytics/constants.py +0 -16
  5. enterprise_data/admin_analytics/database/queries/fact_engagement_admin_dash.py +90 -0
  6. enterprise_data/admin_analytics/database/queries/fact_enrollment_admin_dash.py +83 -4
  7. enterprise_data/admin_analytics/database/tables/fact_engagement_admin_dash.py +116 -0
  8. enterprise_data/admin_analytics/database/tables/fact_enrollment_admin_dash.py +97 -3
  9. enterprise_data/api/v1/serializers.py +1 -55
  10. enterprise_data/api/v1/urls.py +14 -17
  11. enterprise_data/api/v1/views/analytics_completions.py +150 -0
  12. enterprise_data/api/v1/views/analytics_engagements.py +106 -351
  13. enterprise_data/api/v1/views/analytics_enrollments.py +3 -6
  14. enterprise_data/renderers.py +22 -7
  15. enterprise_data/tests/admin_analytics/mock_analytics_data.py +12 -90
  16. enterprise_data/tests/admin_analytics/mock_enrollments.py +0 -66
  17. enterprise_data/tests/admin_analytics/test_analytics_engagements.py +120 -240
  18. enterprise_data/tests/admin_analytics/test_analytics_enrollments.py +12 -81
  19. enterprise_data/tests/admin_analytics/test_enterprise_completions.py +105 -120
  20. enterprise_data/admin_analytics/completions_utils.py +0 -261
  21. enterprise_data/api/v1/views/enterprise_completions.py +0 -200
  22. {edx_enterprise_data-9.0.1.dist-info → edx_enterprise_data-9.1.0.dist-info}/LICENSE +0 -0
  23. {edx_enterprise_data-9.0.1.dist-info → edx_enterprise_data-9.1.0.dist-info}/WHEEL +0 -0
  24. {edx_enterprise_data-9.0.1.dist-info → edx_enterprise_data-9.1.0.dist-info}/top_level.txt +0 -0
@@ -1,200 +0,0 @@
1
- """Views for enterprise admin completions analytics."""
2
- import datetime
3
- from datetime import datetime, timedelta
4
- from logging import getLogger
5
-
6
- from edx_rbac.decorators import permission_required
7
- from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication
8
- from rest_framework.response import Response
9
- from rest_framework.status import HTTP_200_OK
10
- from rest_framework.views import APIView
11
-
12
- from django.http import HttpResponse
13
-
14
- from enterprise_data.admin_analytics.completions_utils import (
15
- get_completions_over_time,
16
- get_csv_data_for_completions_over_time,
17
- get_csv_data_for_top_courses_by_completions,
18
- get_csv_data_for_top_subjects_by_completions,
19
- get_top_courses_by_completions,
20
- get_top_subjects_by_completions,
21
- )
22
- from enterprise_data.admin_analytics.constants import Calculation, Granularity
23
- from enterprise_data.admin_analytics.data_loaders import fetch_max_enrollment_datetime
24
- from enterprise_data.admin_analytics.utils import ChartType, fetch_and_cache_enrollments_data
25
- from enterprise_data.api.v1 import serializers
26
- from enterprise_data.api.v1.paginators import AdvanceAnalyticsPagination
27
- from enterprise_data.utils import date_filter, timer
28
-
29
- LOGGER = getLogger(__name__)
30
-
31
-
32
- class EnterrpiseAdminCompletionsStatsView(APIView):
33
- """
34
- API for getting the enterprise admin completions.
35
- """
36
- authentication_classes = (JwtAuthentication,)
37
- http_method_names = ['get']
38
-
39
- @permission_required(
40
- "can_access_enterprise", fn=lambda request, enterprise_id: enterprise_id
41
- )
42
- def get(self, request, enterprise_id):
43
- """
44
- HTTP GET endpoint to retrieve the enterprise admin completions
45
- """
46
- serializer = serializers.AdminAnalyticsAggregatesQueryParamsSerializer(
47
- data=request.GET
48
- )
49
- serializer.is_valid(raise_exception=True)
50
-
51
- last_updated_at = fetch_max_enrollment_datetime()
52
- cache_expiry = (
53
- last_updated_at + timedelta(days=1) if last_updated_at else datetime.now()
54
- )
55
-
56
- enrollments = fetch_and_cache_enrollments_data(
57
- enterprise_id, cache_expiry
58
- ).copy()
59
- # Use start and end date if provided by the client, if client has not provided then use
60
- # 1. minimum enrollment date from the data as the start_date
61
- # 2. today's date as the end_date
62
- start_date = serializer.data.get(
63
- "start_date", enrollments.enterprise_enrollment_date.min()
64
- )
65
- end_date = serializer.data.get("end_date", datetime.now())
66
-
67
- if serializer.data.get('response_type') == 'csv':
68
- chart_type = serializer.data.get('chart_type')
69
- response = HttpResponse(content_type='text/csv')
70
- csv_data = {}
71
-
72
- if chart_type == ChartType.COMPLETIONS_OVER_TIME.value:
73
- with timer('completions_over_time_csv_data'):
74
- csv_data = get_csv_data_for_completions_over_time(
75
- start_date=start_date,
76
- end_date=end_date,
77
- enrollments=enrollments.copy(),
78
- date_agg=serializer.data.get('granularity', Granularity.DAILY.value),
79
- calc=serializer.data.get('calculation', Calculation.TOTAL.value),
80
- )
81
- elif chart_type == ChartType.TOP_COURSES_BY_COMPLETIONS.value:
82
- with timer('top_courses_by_completions_csv_data'):
83
- csv_data = get_csv_data_for_top_courses_by_completions(
84
- start_date=start_date, end_date=end_date, enrollments=enrollments.copy()
85
- )
86
- elif chart_type == ChartType.TOP_SUBJECTS_BY_COMPLETIONS.value:
87
- with timer('top_subjects_by_completions_csv_data'):
88
- csv_data = get_csv_data_for_top_subjects_by_completions(
89
- start_date=start_date, end_date=end_date, enrollments=enrollments.copy()
90
- )
91
- filename = csv_data['filename']
92
- response['Content-Disposition'] = f'attachment; filename="{filename}"'
93
- response['Access-Control-Expose-Headers'] = 'Content-Disposition'
94
- csv_data['data'].to_csv(path_or_buf=response)
95
- return response
96
-
97
- with timer('completions_all_charts_data'):
98
- completions_over_time = get_completions_over_time(
99
- start_date=start_date,
100
- end_date=end_date,
101
- dff=enrollments.copy(),
102
- date_agg=serializer.data.get('granularity', Granularity.DAILY.value),
103
- calc=serializer.data.get('calculation', Calculation.TOTAL.value),
104
- )
105
- top_courses_by_completions = get_top_courses_by_completions(
106
- start_date=start_date, end_date=end_date, dff=enrollments.copy()
107
- )
108
- top_subjects_by_completions = get_top_subjects_by_completions(
109
- start_date=start_date, end_date=end_date, dff=enrollments.copy()
110
- )
111
-
112
- return Response(
113
- data={
114
- 'completions_over_time': completions_over_time.to_dict(
115
- orient="records"
116
- ),
117
- 'top_courses_by_completions': top_courses_by_completions.to_dict(
118
- orient="records"
119
- ),
120
- 'top_subjects_by_completions': top_subjects_by_completions.to_dict(
121
- orient="records"
122
- ),
123
- },
124
- status=HTTP_200_OK,
125
- )
126
-
127
-
128
- class EnterrpiseAdminCompletionsView(APIView):
129
- """
130
- API for getting the enterprise admin completions.
131
- """
132
- authentication_classes = (JwtAuthentication,)
133
- http_method_names = ['get']
134
- pagination_class = AdvanceAnalyticsPagination
135
-
136
- @permission_required(
137
- "can_access_enterprise", fn=lambda request, enterprise_id: enterprise_id
138
- )
139
- def get(self, request, enterprise_id):
140
- """
141
- HTTP GET endpoint to retrieve the enterprise admin completions
142
- """
143
- serializer = serializers.AdminAnalyticsAggregatesQueryParamsSerializer(
144
- data=request.GET
145
- )
146
- serializer.is_valid(raise_exception=True)
147
-
148
- last_updated_at = fetch_max_enrollment_datetime()
149
- cache_expiry = (
150
- last_updated_at + timedelta(days=1) if last_updated_at else datetime.now()
151
- )
152
-
153
- enrollments = fetch_and_cache_enrollments_data(
154
- enterprise_id, cache_expiry
155
- ).copy()
156
- # Use start and end date if provided by the client, if client has not provided then use
157
- # 1. minimum enrollment date from the data as the start_date
158
- # 2. today's date as the end_date
159
- start_date = serializer.data.get(
160
- 'start_date', enrollments.enterprise_enrollment_date.min()
161
- )
162
- end_date = serializer.data.get('end_date', datetime.now())
163
-
164
- LOGGER.info(
165
- "Completions data requested for enterprise [%s] from [%s] to [%s]",
166
- enterprise_id,
167
- start_date,
168
- end_date,
169
- )
170
-
171
- dff = enrollments[enrollments['has_passed'] == 1]
172
-
173
- # Date filtering
174
- dff = date_filter(start=start_date, end=end_date, data_frame=dff, date_column='passed_date')
175
-
176
- dff = dff[['email', 'course_title', 'course_subject', 'passed_date']]
177
- dff['passed_date'] = dff['passed_date'].dt.date
178
- dff = dff.sort_values(by="passed_date", ascending=False).reset_index(drop=True)
179
-
180
- LOGGER.info(
181
- "Completions data prepared for enterprise [%s] from [%s] to [%s]",
182
- enterprise_id,
183
- start_date,
184
- end_date,
185
- )
186
-
187
- if serializer.data.get('response_type') == 'csv':
188
- response = HttpResponse(content_type='text/csv')
189
- filename = f"Individual Completions, {start_date} - {end_date}.csv"
190
- response['Content-Disposition'] = f'attachment; filename="{filename}"'
191
- response['Access-Control-Expose-Headers'] = 'Content-Disposition'
192
- dff.to_csv(path_or_buf=response, index=False)
193
- return response
194
-
195
- paginator = self.pagination_class()
196
- page = paginator.paginate_queryset(dff, request)
197
- serialized_data = page.data.to_dict(orient='records')
198
- response = paginator.get_paginated_response(serialized_data)
199
-
200
- return response