edx-enterprise-data 9.0.0__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.
- {edx_enterprise_data-9.0.0.dist-info → edx_enterprise_data-9.1.0.dist-info}/METADATA +4 -3
- {edx_enterprise_data-9.0.0.dist-info → edx_enterprise_data-9.1.0.dist-info}/RECORD +22 -23
- {edx_enterprise_data-9.0.0.dist-info → edx_enterprise_data-9.1.0.dist-info}/WHEEL +1 -1
- enterprise_data/__init__.py +1 -1
- enterprise_data/admin_analytics/constants.py +0 -16
- enterprise_data/admin_analytics/database/queries/fact_engagement_admin_dash.py +90 -0
- enterprise_data/admin_analytics/database/queries/fact_enrollment_admin_dash.py +83 -4
- enterprise_data/admin_analytics/database/tables/fact_engagement_admin_dash.py +116 -0
- enterprise_data/admin_analytics/database/tables/fact_enrollment_admin_dash.py +97 -3
- enterprise_data/api/v1/serializers.py +1 -55
- enterprise_data/api/v1/urls.py +14 -17
- enterprise_data/api/v1/views/analytics_completions.py +150 -0
- enterprise_data/api/v1/views/analytics_engagements.py +106 -351
- enterprise_data/api/v1/views/analytics_enrollments.py +3 -6
- enterprise_data/renderers.py +22 -7
- enterprise_data/tests/admin_analytics/mock_analytics_data.py +12 -90
- enterprise_data/tests/admin_analytics/mock_enrollments.py +0 -66
- enterprise_data/tests/admin_analytics/test_analytics_engagements.py +120 -240
- enterprise_data/tests/admin_analytics/test_analytics_enrollments.py +12 -81
- enterprise_data/tests/admin_analytics/test_enterprise_completions.py +105 -120
- enterprise_data/admin_analytics/completions_utils.py +0 -261
- enterprise_data/api/v1/views/enterprise_completions.py +0 -200
- {edx_enterprise_data-9.0.0.dist-info → edx_enterprise_data-9.1.0.dist-info}/LICENSE +0 -0
- {edx_enterprise_data-9.0.0.dist-info → edx_enterprise_data-9.1.0.dist-info}/top_level.txt +0 -0
@@ -8,28 +8,13 @@ from rest_framework import status
|
|
8
8
|
from rest_framework.reverse import reverse
|
9
9
|
from rest_framework.test import APITransactionTestCase
|
10
10
|
|
11
|
-
from enterprise_data.admin_analytics.constants import
|
12
|
-
from enterprise_data.
|
13
|
-
from enterprise_data.tests.admin_analytics.mock_analytics_data import (
|
14
|
-
ENGAGEMENT_STATS_CSVS,
|
15
|
-
ENGAGEMENTS,
|
16
|
-
engagements_csv_content,
|
17
|
-
engagements_dataframe,
|
18
|
-
enrollments_dataframe,
|
19
|
-
)
|
11
|
+
from enterprise_data.admin_analytics.constants import ResponseType
|
12
|
+
from enterprise_data.tests.admin_analytics.mock_analytics_data import ENGAGEMENTS
|
20
13
|
from enterprise_data.tests.mixins import JWTTestMixin
|
21
14
|
from enterprise_data.tests.test_utils import UserFactory
|
22
15
|
from enterprise_data_roles.constants import ENTERPRISE_DATA_ADMIN_ROLE
|
23
16
|
from enterprise_data_roles.models import EnterpriseDataFeatureRole, EnterpriseDataRoleAssignment
|
24
17
|
|
25
|
-
INVALID_CALCULATION_ERROR = (
|
26
|
-
f"Calculation must be one of {EngagementSerializer.CALCULATION_CHOICES}"
|
27
|
-
)
|
28
|
-
INVALID_GRANULARITY_ERROR = (
|
29
|
-
f"Granularity must be one of {EngagementSerializer.GRANULARITY_CHOICES}"
|
30
|
-
)
|
31
|
-
INVALID_CSV_ERROR1 = f"chart_type must be one of {EngagementSerializer.CHART_TYPES}"
|
32
|
-
|
33
18
|
|
34
19
|
@ddt.ddt
|
35
20
|
class TestIndividualEngagementsAPI(JWTTestMixin, APITransactionTestCase):
|
@@ -49,161 +34,136 @@ class TestIndividualEngagementsAPI(JWTTestMixin, APITransactionTestCase):
|
|
49
34
|
)
|
50
35
|
self.client.force_authenticate(user=self.user)
|
51
36
|
|
52
|
-
self.enterprise_uuid =
|
37
|
+
self.enterprise_uuid = 'ee5e6b3a-069a-4947-bb8d-d2dbc323396c'
|
53
38
|
self.set_jwt_cookie()
|
54
39
|
|
55
40
|
self.url = reverse(
|
56
|
-
|
41
|
+
'v1:enterprise-admin-analytics-engagements',
|
57
42
|
kwargs={"enterprise_uuid": self.enterprise_uuid},
|
58
43
|
)
|
59
44
|
|
60
|
-
|
61
|
-
'enterprise_data.
|
62
|
-
return_value=datetime.now()
|
45
|
+
get_enrollment_date_range_patcher = patch(
|
46
|
+
'enterprise_data.api.v1.views.analytics_enrollments.FactEnrollmentAdminDashTable.get_enrollment_date_range',
|
47
|
+
return_value=(datetime.now(), datetime.now())
|
63
48
|
)
|
64
49
|
|
65
|
-
|
66
|
-
self.addCleanup(
|
67
|
-
|
68
|
-
def verify_engagement_data(self, results, results_count):
|
69
|
-
"""Verify the received engagement data."""
|
70
|
-
attrs = [
|
71
|
-
"email",
|
72
|
-
"course_title",
|
73
|
-
"activity_date",
|
74
|
-
"course_subject",
|
75
|
-
]
|
76
|
-
|
77
|
-
assert len(results) == results_count
|
78
|
-
|
79
|
-
filtered_data = []
|
80
|
-
for engagement in ENGAGEMENTS:
|
81
|
-
for result in results:
|
82
|
-
if engagement["email"] == result["email"]:
|
83
|
-
data = {attr: engagement[attr] for attr in attrs}
|
84
|
-
data["learning_time_hours"] = round(engagement["learning_time_seconds"] / 3600, 1)
|
85
|
-
filtered_data.append(data)
|
86
|
-
break
|
87
|
-
|
88
|
-
received_data = sorted(results, key=lambda x: x["email"])
|
89
|
-
expected_data = sorted(filtered_data, key=lambda x: x["email"])
|
90
|
-
assert received_data == expected_data
|
50
|
+
get_enrollment_date_range_patcher.start()
|
51
|
+
self.addCleanup(get_enrollment_date_range_patcher.stop)
|
91
52
|
|
92
|
-
@patch(
|
93
|
-
|
94
|
-
)
|
95
|
-
@patch(
|
96
|
-
"enterprise_data.api.v1.views.analytics_engagements.fetch_and_cache_engagements_data"
|
97
|
-
)
|
98
|
-
def test_get(self, mock_fetch_and_cache_engagements_data, mock_fetch_and_cache_enrollments_data):
|
53
|
+
@patch('enterprise_data.api.v1.views.analytics_engagements.FactEngagementAdminDashTable.get_engagement_count')
|
54
|
+
@patch('enterprise_data.api.v1.views.analytics_engagements.FactEngagementAdminDashTable.get_all_engagements')
|
55
|
+
def test_get(self, mock_get_all_engagements, mock_get_engagement_count):
|
99
56
|
"""
|
100
57
|
Test the GET method for the AdvanceAnalyticsIndividualEngagementsView works.
|
101
58
|
"""
|
102
|
-
|
103
|
-
|
59
|
+
mock_get_all_engagements.return_value = ENGAGEMENTS
|
60
|
+
mock_get_engagement_count.return_value = len(ENGAGEMENTS)
|
104
61
|
|
105
|
-
response = self.client.get(self.url
|
62
|
+
response = self.client.get(self.url + '?page_size=2')
|
106
63
|
assert response.status_code == status.HTTP_200_OK
|
107
64
|
data = response.json()
|
108
|
-
assert data[
|
109
|
-
assert data[
|
110
|
-
assert data[
|
111
|
-
assert data[
|
112
|
-
assert data[
|
113
|
-
|
114
|
-
|
115
|
-
response = self.client.get(self.url, {"page_size": 2, "page": 2})
|
65
|
+
assert data['next'] == f"http://testserver{self.url}?page=2&page_size=2"
|
66
|
+
assert data['previous'] is None
|
67
|
+
assert data['current_page'] == 1
|
68
|
+
assert data['num_pages'] == 6
|
69
|
+
assert data['count'] == 12
|
70
|
+
|
71
|
+
response = self.client.get(self.url + '?page_size=2&page=2')
|
116
72
|
assert response.status_code == status.HTTP_200_OK
|
117
73
|
data = response.json()
|
118
|
-
assert data[
|
119
|
-
assert data[
|
120
|
-
assert data[
|
121
|
-
assert data[
|
122
|
-
assert data[
|
123
|
-
|
124
|
-
|
125
|
-
response = self.client.get(self.url, {"page_size": 2, "page": 5})
|
74
|
+
assert data['next'] == f"http://testserver{self.url}?page=3&page_size=2"
|
75
|
+
assert data['previous'] == f"http://testserver{self.url}?page_size=2"
|
76
|
+
assert data['current_page'] == 2
|
77
|
+
assert data['num_pages'] == 6
|
78
|
+
assert data['count'] == 12
|
79
|
+
|
80
|
+
response = self.client.get(self.url + '?page_size=2&page=6')
|
126
81
|
assert response.status_code == status.HTTP_200_OK
|
127
82
|
data = response.json()
|
128
|
-
assert data[
|
129
|
-
assert data[
|
130
|
-
assert data[
|
131
|
-
assert data[
|
132
|
-
assert data[
|
133
|
-
|
134
|
-
|
135
|
-
response = self.client.get(self.url, {"page_size": 9})
|
83
|
+
assert data['next'] is None
|
84
|
+
assert data['previous'] == f"http://testserver{self.url}?page=5&page_size=2"
|
85
|
+
assert data['current_page'] == 6
|
86
|
+
assert data['num_pages'] == 6
|
87
|
+
assert data['count'] == 12
|
88
|
+
|
89
|
+
response = self.client.get(self.url + '?page_size=12')
|
136
90
|
assert response.status_code == status.HTTP_200_OK
|
137
91
|
data = response.json()
|
138
|
-
assert data[
|
139
|
-
assert data[
|
140
|
-
assert data[
|
141
|
-
assert data[
|
142
|
-
assert data[
|
143
|
-
|
144
|
-
|
145
|
-
@patch(
|
146
|
-
|
147
|
-
)
|
148
|
-
@patch(
|
149
|
-
"enterprise_data.api.v1.views.analytics_engagements.fetch_and_cache_engagements_data"
|
150
|
-
)
|
151
|
-
def test_get_csv(self, mock_fetch_and_cache_engagements_data, mock_fetch_and_cache_enrollments_data):
|
92
|
+
assert data['next'] is None
|
93
|
+
assert data['previous'] is None
|
94
|
+
assert data['current_page'] == 1
|
95
|
+
assert data['num_pages'] == 1
|
96
|
+
assert data['count'] == 12
|
97
|
+
|
98
|
+
@patch('enterprise_data.api.v1.views.analytics_engagements.FactEngagementAdminDashTable.get_engagement_count')
|
99
|
+
@patch('enterprise_data.api.v1.views.analytics_engagements.FactEngagementAdminDashTable.get_all_engagements')
|
100
|
+
def test_get_csv(self, mock_get_all_engagements, mock_get_engagement_count):
|
152
101
|
"""
|
153
102
|
Test the GET method for the AdvanceAnalyticsIndividualEngagementsView return correct CSV data.
|
154
103
|
"""
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
end_date = datetime.now().strftime('%Y/%m/%d')
|
104
|
+
mock_get_all_engagements.return_value = ENGAGEMENTS[0:4]
|
105
|
+
mock_get_engagement_count.return_value = 4
|
106
|
+
|
159
107
|
response = self.client.get(self.url, {"response_type": ResponseType.CSV.value})
|
160
108
|
assert response.status_code == status.HTTP_200_OK
|
161
109
|
|
162
110
|
# verify the response headers
|
163
111
|
assert response["Content-Type"] == "text/csv"
|
164
|
-
filename = f"""Individual Engagements, {start_date} - {end_date}.csv"""
|
165
|
-
assert (
|
166
|
-
response["Content-Disposition"] == f'attachment; filename="{filename}"'
|
167
|
-
)
|
168
112
|
|
169
113
|
# verify the response content
|
170
|
-
content = b"".join(response.streaming_content)
|
171
|
-
assert content ==
|
114
|
+
content = b"".join(response.streaming_content).decode().splitlines()
|
115
|
+
assert len(content) == 5
|
116
|
+
|
117
|
+
# Verify CSV header.
|
118
|
+
assert 'email,course_title,course_subject,enroll_type,activity_date,learning_time_hours' == content[0]
|
119
|
+
|
120
|
+
# verify the content
|
121
|
+
assert (
|
122
|
+
'padillamichelle@example.org,Synergized reciprocal encoding,business-management,certificate,2021-08-05,'
|
123
|
+
'1.0344444444444445'
|
124
|
+
in content
|
125
|
+
)
|
126
|
+
assert (
|
127
|
+
'yallison@example.org,Synergized reciprocal encoding,business-management,certificate,2021-07-27,'
|
128
|
+
'1.2041666666666666'
|
129
|
+
in content
|
130
|
+
)
|
131
|
+
assert (
|
132
|
+
'weaverpatricia@example.net,Synergized reciprocal encoding,business-management,certificate,2021-08-05,'
|
133
|
+
'2.6225'
|
134
|
+
in content
|
135
|
+
)
|
136
|
+
assert (
|
137
|
+
'seth57@example.org,Synergized reciprocal encoding,business-management,certificate,2021-08-21,'
|
138
|
+
'2.7494444444444444'
|
139
|
+
in content
|
140
|
+
)
|
172
141
|
|
173
142
|
@ddt.data(
|
174
143
|
{
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
144
|
+
'params': {'start_date': 1},
|
145
|
+
'error': {
|
146
|
+
'start_date': [
|
147
|
+
'Date has wrong format. Use one of these formats instead: YYYY-MM-DD.'
|
179
148
|
]
|
180
149
|
},
|
181
150
|
},
|
182
151
|
{
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
152
|
+
'params': {'end_date': 2},
|
153
|
+
'error': {
|
154
|
+
'end_date': [
|
155
|
+
'Date has wrong format. Use one of these formats instead: YYYY-MM-DD.'
|
187
156
|
]
|
188
157
|
},
|
189
158
|
},
|
190
159
|
{
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
160
|
+
'params': {"start_date": "2024-01-01", "end_date": "2023-01-01"},
|
161
|
+
'error': {
|
162
|
+
'non_field_errors': [
|
163
|
+
'start_date should be less than or equal to end_date.'
|
195
164
|
]
|
196
165
|
},
|
197
166
|
},
|
198
|
-
{
|
199
|
-
"params": {"calculation": "invalid"},
|
200
|
-
"error": {"calculation": [INVALID_CALCULATION_ERROR]},
|
201
|
-
},
|
202
|
-
{
|
203
|
-
"params": {"granularity": "invalid"},
|
204
|
-
"error": {"granularity": [INVALID_GRANULARITY_ERROR]},
|
205
|
-
},
|
206
|
-
{"params": {"chart_type": "invalid"}, "error": {"chart_type": [INVALID_CSV_ERROR1]}},
|
207
167
|
)
|
208
168
|
@ddt.unpack
|
209
169
|
def test_get_invalid_query_params(self, params, error):
|
@@ -233,152 +193,72 @@ class TestEngagementStatsAPI(JWTTestMixin, APITransactionTestCase):
|
|
233
193
|
)
|
234
194
|
self.client.force_authenticate(user=self.user)
|
235
195
|
|
236
|
-
self.enterprise_uuid =
|
196
|
+
self.enterprise_uuid = 'ee5e6b3a-069a-4947-bb8d-d2dbc323396c'
|
237
197
|
self.set_jwt_cookie()
|
238
198
|
|
239
199
|
self.url = reverse(
|
240
|
-
|
241
|
-
kwargs={
|
200
|
+
'v1:enterprise-admin-analytics-engagements-stats',
|
201
|
+
kwargs={'enterprise_uuid': self.enterprise_uuid},
|
242
202
|
)
|
243
203
|
|
244
|
-
|
245
|
-
'enterprise_data.
|
246
|
-
return_value=datetime.now()
|
204
|
+
get_enrollment_date_range_patcher = patch(
|
205
|
+
'enterprise_data.api.v1.views.analytics_enrollments.FactEnrollmentAdminDashTable.get_enrollment_date_range',
|
206
|
+
return_value=(datetime.now(), datetime.now())
|
247
207
|
)
|
248
208
|
|
249
|
-
|
250
|
-
self.addCleanup(
|
251
|
-
|
252
|
-
def verify_engagement_data(self, results):
|
253
|
-
"""Verify the received engagement data."""
|
254
|
-
attrs = [
|
255
|
-
"email",
|
256
|
-
"course_title",
|
257
|
-
"activity_date",
|
258
|
-
"course_subject",
|
259
|
-
]
|
260
|
-
|
261
|
-
filtered_data = []
|
262
|
-
for engagement in ENGAGEMENTS:
|
263
|
-
for result in results:
|
264
|
-
if engagement["email"] == result["email"]:
|
265
|
-
data = {attr: engagement[attr] for attr in attrs}
|
266
|
-
data["learning_time_hours"] = round(engagement["learning_time_seconds"] / 3600, 1)
|
267
|
-
filtered_data.append(data)
|
268
|
-
break
|
269
|
-
|
270
|
-
received_data = sorted(results, key=lambda x: x["email"])
|
271
|
-
expected_data = sorted(filtered_data, key=lambda x: x["email"])
|
272
|
-
assert received_data == expected_data
|
209
|
+
get_enrollment_date_range_patcher.start()
|
210
|
+
self.addCleanup(get_enrollment_date_range_patcher.stop)
|
273
211
|
|
274
212
|
@patch(
|
275
|
-
|
213
|
+
'enterprise_data.api.v1.views.analytics_engagements.FactEngagementAdminDashTable.'
|
214
|
+
'get_engagement_time_series_data'
|
215
|
+
)
|
216
|
+
@patch(
|
217
|
+
'enterprise_data.api.v1.views.analytics_engagements.FactEngagementAdminDashTable.get_top_courses_by_engagement'
|
276
218
|
)
|
277
219
|
@patch(
|
278
|
-
|
220
|
+
'enterprise_data.api.v1.views.analytics_engagements.FactEngagementAdminDashTable.get_top_subjects_by_engagement'
|
279
221
|
)
|
280
|
-
def test_get(self,
|
222
|
+
def test_get(self, mock_get_top_subjects_by_engagement, mock_get_top_courses_by_engagement, mock_get_time_series):
|
281
223
|
"""
|
282
224
|
Test the GET method for the AdvanceAnalyticsEnrollmentStatsView works.
|
283
225
|
"""
|
284
|
-
|
285
|
-
|
226
|
+
mock_get_top_subjects_by_engagement.return_value = []
|
227
|
+
mock_get_top_courses_by_engagement.return_value = []
|
228
|
+
mock_get_time_series.return_value = []
|
286
229
|
|
287
230
|
response = self.client.get(self.url)
|
288
231
|
assert response.status_code == status.HTTP_200_OK
|
289
232
|
data = response.json()
|
290
|
-
assert
|
291
|
-
|
292
|
-
|
293
|
-
{'activity_date': '2021-07-26T00:00:00', 'enroll_type': 'certificate', 'sum': 4.4},
|
294
|
-
{'activity_date': '2021-07-27T00:00:00', 'enroll_type': 'certificate', 'sum': 1.2},
|
295
|
-
{'activity_date': '2021-08-05T00:00:00', 'enroll_type': 'certificate', 'sum': 3.6},
|
296
|
-
{'activity_date': '2021-08-21T00:00:00', 'enroll_type': 'certificate', 'sum': 2.7},
|
297
|
-
{'activity_date': '2021-09-02T00:00:00', 'enroll_type': 'certificate', 'sum': 1.3},
|
298
|
-
{'activity_date': '2021-09-21T00:00:00', 'enroll_type': 'certificate', 'sum': 1.5},
|
299
|
-
{'activity_date': '2022-05-17T00:00:00', 'enroll_type': 'certificate', 'sum': 0.0}
|
300
|
-
],
|
301
|
-
'top_courses_by_engagement': [
|
302
|
-
{
|
303
|
-
'course_key': 'Kcpr+XoR30',
|
304
|
-
'course_title': 'Assimilated even-keeled focus group',
|
305
|
-
'enroll_type': 'certificate',
|
306
|
-
'count': 0.0
|
307
|
-
},
|
308
|
-
{
|
309
|
-
'course_key': 'luGg+KNt30',
|
310
|
-
'course_title': 'Synergized reciprocal encoding',
|
311
|
-
'enroll_type': 'certificate',
|
312
|
-
'count': 14.786944444444444
|
313
|
-
}
|
314
|
-
],
|
315
|
-
'top_subjects_by_engagement': [
|
316
|
-
{
|
317
|
-
'course_subject': 'business-management',
|
318
|
-
'enroll_type': 'certificate',
|
319
|
-
'count': 14.786944444444444
|
320
|
-
},
|
321
|
-
{
|
322
|
-
'course_subject': 'engineering',
|
323
|
-
'enroll_type': 'certificate',
|
324
|
-
'count': 0.0
|
325
|
-
}
|
326
|
-
]
|
327
|
-
}
|
328
|
-
|
329
|
-
@patch("enterprise_data.api.v1.views.analytics_engagements.fetch_and_cache_enrollments_data")
|
330
|
-
@patch("enterprise_data.api.v1.views.analytics_engagements.fetch_and_cache_engagements_data")
|
331
|
-
@ddt.data(
|
332
|
-
EngagementChart.ENGAGEMENTS_OVER_TIME.value,
|
333
|
-
EngagementChart.TOP_COURSES_BY_ENGAGEMENTS.value,
|
334
|
-
EngagementChart.TOP_SUBJECTS_BY_ENGAGEMENTS.value,
|
335
|
-
)
|
336
|
-
def test_get_csv(self, chart_type, mock_fetch_and_cache_engagements_data, mock_fetch_and_cache_enrollments_data):
|
337
|
-
"""
|
338
|
-
Test that AdvanceAnalyticsEngagementStatsView return correct CSV data.
|
339
|
-
"""
|
340
|
-
mock_fetch_and_cache_enrollments_data.return_value = enrollments_dataframe()
|
341
|
-
mock_fetch_and_cache_engagements_data.return_value = engagements_dataframe()
|
342
|
-
response = self.client.get(self.url, {"response_type": ResponseType.CSV.value, "chart_type": chart_type})
|
343
|
-
assert response.status_code == status.HTTP_200_OK
|
344
|
-
assert response["Content-Type"] == "text/csv"
|
345
|
-
# verify the response content
|
346
|
-
assert response.content == ENGAGEMENT_STATS_CSVS[chart_type]
|
233
|
+
assert 'engagement_over_time' in data
|
234
|
+
assert 'top_courses_by_engagement' in data
|
235
|
+
assert 'top_subjects_by_engagement' in data
|
347
236
|
|
348
237
|
@ddt.data(
|
349
238
|
{
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
239
|
+
'params': {'start_date': 1},
|
240
|
+
'error': {
|
241
|
+
'start_date': [
|
242
|
+
'Date has wrong format. Use one of these formats instead: YYYY-MM-DD.'
|
354
243
|
]
|
355
244
|
},
|
356
245
|
},
|
357
246
|
{
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
247
|
+
'params': {'end_date': 2},
|
248
|
+
'error': {
|
249
|
+
'end_date': [
|
250
|
+
'Date has wrong format. Use one of these formats instead: YYYY-MM-DD.'
|
362
251
|
]
|
363
252
|
},
|
364
253
|
},
|
365
254
|
{
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
255
|
+
'params': {'start_date': '2024-01-01', 'end_date': '2023-01-01'},
|
256
|
+
'error': {
|
257
|
+
'non_field_errors': [
|
258
|
+
'start_date should be less than or equal to end_date.'
|
370
259
|
]
|
371
260
|
},
|
372
261
|
},
|
373
|
-
{
|
374
|
-
"params": {"calculation": "invalid"},
|
375
|
-
"error": {"calculation": [INVALID_CALCULATION_ERROR]},
|
376
|
-
},
|
377
|
-
{
|
378
|
-
"params": {"granularity": "invalid"},
|
379
|
-
"error": {"granularity": [INVALID_GRANULARITY_ERROR]},
|
380
|
-
},
|
381
|
-
{"params": {"chart_type": "invalid"}, "error": {"chart_type": [INVALID_CSV_ERROR1]}},
|
382
262
|
)
|
383
263
|
@ddt.unpack
|
384
264
|
def test_get_invalid_query_params(self, params, error):
|
@@ -8,27 +8,18 @@ from rest_framework.reverse import reverse
|
|
8
8
|
from rest_framework.test import APITransactionTestCase
|
9
9
|
|
10
10
|
from enterprise_data.admin_analytics.constants import ResponseType
|
11
|
-
from enterprise_data.api.v1.serializers import AdvanceAnalyticsEnrollmentStatsSerializer as EnrollmentStatsSerializer
|
12
|
-
from enterprise_data.api.v1.serializers import AdvanceAnalyticsQueryParamSerializer
|
13
11
|
from enterprise_data.tests.admin_analytics.mock_analytics_data import ENROLLMENTS
|
14
12
|
from enterprise_data.tests.mixins import JWTTestMixin
|
15
13
|
from enterprise_data.tests.test_utils import UserFactory
|
16
14
|
from enterprise_data_roles.constants import ENTERPRISE_DATA_ADMIN_ROLE
|
17
15
|
from enterprise_data_roles.models import EnterpriseDataFeatureRole, EnterpriseDataRoleAssignment
|
18
16
|
|
19
|
-
INVALID_CALCULATION_ERROR = (
|
20
|
-
f"Calculation must be one of {AdvanceAnalyticsQueryParamSerializer.CALCULATION_CHOICES}"
|
21
|
-
)
|
22
|
-
INVALID_GRANULARITY_ERROR = (
|
23
|
-
f"Granularity must be one of {AdvanceAnalyticsQueryParamSerializer.GRANULARITY_CHOICES}"
|
24
|
-
)
|
25
|
-
INVALID_RESPONSE_TYPE_ERROR = f"response_type must be one of {AdvanceAnalyticsQueryParamSerializer.RESPONSE_TYPES}"
|
26
|
-
INVALID_CHART_TYPE_ERROR = f"chart_type must be one of {EnrollmentStatsSerializer.CHART_TYPES}"
|
27
|
-
|
28
17
|
|
29
18
|
@ddt.ddt
|
30
19
|
class TestIndividualEnrollmentsAPI(JWTTestMixin, APITransactionTestCase):
|
31
|
-
"""
|
20
|
+
"""
|
21
|
+
Tests for AdvanceAnalyticsIndividualEnrollmentsView.
|
22
|
+
"""
|
32
23
|
|
33
24
|
def setUp(self):
|
34
25
|
"""
|
@@ -52,36 +43,13 @@ class TestIndividualEnrollmentsAPI(JWTTestMixin, APITransactionTestCase):
|
|
52
43
|
kwargs={"enterprise_uuid": self.enterprise_uuid},
|
53
44
|
)
|
54
45
|
|
55
|
-
|
46
|
+
get_enrollment_date_range_patcher = patch(
|
56
47
|
'enterprise_data.api.v1.views.analytics_enrollments.FactEnrollmentAdminDashTable.get_enrollment_date_range',
|
57
48
|
return_value=(datetime.now(), datetime.now())
|
58
49
|
)
|
59
50
|
|
60
|
-
|
61
|
-
self.addCleanup(
|
62
|
-
|
63
|
-
def verify_enrollment_data(self, results, results_count):
|
64
|
-
"""Verify the received enrollment data."""
|
65
|
-
attrs = [
|
66
|
-
'email',
|
67
|
-
'course_title',
|
68
|
-
'course_subject',
|
69
|
-
'enroll_type',
|
70
|
-
'enterprise_enrollment_date',
|
71
|
-
]
|
72
|
-
|
73
|
-
assert len(results) == results_count
|
74
|
-
|
75
|
-
filtered_data = []
|
76
|
-
for enrollment in ENROLLMENTS:
|
77
|
-
for result in results:
|
78
|
-
if enrollment["email"] == result["email"]:
|
79
|
-
filtered_data.append({attr: enrollment[attr] for attr in attrs})
|
80
|
-
break
|
81
|
-
|
82
|
-
received_data = sorted(results, key=lambda x: x["email"])
|
83
|
-
expected_data = sorted(filtered_data, key=lambda x: x["email"])
|
84
|
-
assert received_data == expected_data
|
51
|
+
get_enrollment_date_range_patcher.start()
|
52
|
+
self.addCleanup(get_enrollment_date_range_patcher.stop)
|
85
53
|
|
86
54
|
@patch('enterprise_data.api.v1.views.analytics_enrollments.FactEnrollmentAdminDashTable.get_enrollment_count')
|
87
55
|
@patch('enterprise_data.api.v1.views.analytics_enrollments.FactEnrollmentAdminDashTable.get_all_enrollments')
|
@@ -200,15 +168,6 @@ class TestIndividualEnrollmentsAPI(JWTTestMixin, APITransactionTestCase):
|
|
200
168
|
]
|
201
169
|
},
|
202
170
|
},
|
203
|
-
{
|
204
|
-
"params": {"calculation": "invalid"},
|
205
|
-
"error": {"calculation": [INVALID_CALCULATION_ERROR]},
|
206
|
-
},
|
207
|
-
{
|
208
|
-
"params": {"granularity": "invalid"},
|
209
|
-
"error": {"granularity": [INVALID_GRANULARITY_ERROR]},
|
210
|
-
},
|
211
|
-
{"params": {"response_type": "invalid"}, "error": {"response_type": [INVALID_RESPONSE_TYPE_ERROR]}},
|
212
171
|
)
|
213
172
|
@ddt.unpack
|
214
173
|
def test_get_invalid_query_params(self, params, error):
|
@@ -222,7 +181,9 @@ class TestIndividualEnrollmentsAPI(JWTTestMixin, APITransactionTestCase):
|
|
222
181
|
|
223
182
|
@ddt.ddt
|
224
183
|
class TestEnrollmentStatsAPI(JWTTestMixin, APITransactionTestCase):
|
225
|
-
"""
|
184
|
+
"""
|
185
|
+
Tests for AdvanceAnalyticsEnrollmentStatsView.
|
186
|
+
"""
|
226
187
|
|
227
188
|
def setUp(self):
|
228
189
|
"""
|
@@ -246,34 +207,13 @@ class TestEnrollmentStatsAPI(JWTTestMixin, APITransactionTestCase):
|
|
246
207
|
kwargs={"enterprise_uuid": self.enterprise_uuid},
|
247
208
|
)
|
248
209
|
|
249
|
-
|
210
|
+
get_enrollment_date_range_patcher = patch(
|
250
211
|
'enterprise_data.api.v1.views.analytics_enrollments.FactEnrollmentAdminDashTable.get_enrollment_date_range',
|
251
212
|
return_value=(datetime.now(), datetime.now())
|
252
213
|
)
|
253
214
|
|
254
|
-
|
255
|
-
self.addCleanup(
|
256
|
-
|
257
|
-
def verify_enrollment_data(self, results):
|
258
|
-
"""Verify the received enrollment data."""
|
259
|
-
attrs = [
|
260
|
-
"email",
|
261
|
-
"course_title",
|
262
|
-
"course_subject",
|
263
|
-
"enroll_type",
|
264
|
-
"enterprise_enrollment_date",
|
265
|
-
]
|
266
|
-
|
267
|
-
filtered_data = []
|
268
|
-
for enrollment in ENROLLMENTS:
|
269
|
-
for result in results:
|
270
|
-
if enrollment["email"] == result["email"]:
|
271
|
-
filtered_data.append({attr: enrollment[attr] for attr in attrs})
|
272
|
-
break
|
273
|
-
|
274
|
-
received_data = sorted(results, key=lambda x: x["email"])
|
275
|
-
expected_data = sorted(filtered_data, key=lambda x: x["email"])
|
276
|
-
assert received_data == expected_data
|
215
|
+
get_enrollment_date_range_patcher.start()
|
216
|
+
self.addCleanup(get_enrollment_date_range_patcher.stop)
|
277
217
|
|
278
218
|
@patch(
|
279
219
|
'enterprise_data.api.v1.views.analytics_enrollments.FactEnrollmentAdminDashTable.'
|
@@ -331,15 +271,6 @@ class TestEnrollmentStatsAPI(JWTTestMixin, APITransactionTestCase):
|
|
331
271
|
]
|
332
272
|
},
|
333
273
|
},
|
334
|
-
{
|
335
|
-
"params": {"calculation": "invalid"},
|
336
|
-
"error": {"calculation": [INVALID_CALCULATION_ERROR]},
|
337
|
-
},
|
338
|
-
{
|
339
|
-
"params": {"granularity": "invalid"},
|
340
|
-
"error": {"granularity": [INVALID_GRANULARITY_ERROR]},
|
341
|
-
},
|
342
|
-
{"params": {"chart_type": "invalid"}, "error": {"chart_type": [INVALID_CHART_TYPE_ERROR]}},
|
343
274
|
)
|
344
275
|
@ddt.unpack
|
345
276
|
def test_get_invalid_query_params(self, params, error):
|