edx-enterprise-data 8.7.0__py3-none-any.whl → 8.8.1__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-8.7.0.dist-info → edx_enterprise_data-8.8.1.dist-info}/METADATA +1 -1
- {edx_enterprise_data-8.7.0.dist-info → edx_enterprise_data-8.8.1.dist-info}/RECORD +23 -20
- enterprise_data/__init__.py +1 -1
- enterprise_data/admin_analytics/constants.py +9 -3
- enterprise_data/admin_analytics/data_loaders.py +7 -3
- enterprise_data/admin_analytics/utils.py +52 -10
- enterprise_data/api/v1/paginators.py +1 -1
- enterprise_data/api/v1/serializers.py +39 -30
- enterprise_data/api/v1/urls.py +12 -6
- enterprise_data/api/v1/views/analytics_enrollments.py +85 -73
- enterprise_data/api/v1/views/analytics_leaderboard.py +141 -0
- enterprise_data/api/v1/views/enterprise_admin.py +20 -19
- enterprise_data/api/v1/views/enterprise_completions.py +49 -28
- enterprise_data/renderers.py +14 -0
- enterprise_data/tests/admin_analytics/mock_analytics_data.py +511 -0
- enterprise_data/tests/admin_analytics/mock_enrollments.py +4 -4
- enterprise_data/tests/admin_analytics/test_analytics_enrollments.py +23 -22
- enterprise_data/tests/admin_analytics/test_analytics_leaderboard.py +163 -0
- enterprise_data/tests/admin_analytics/test_enterprise_completions.py +1 -1
- enterprise_data/utils.py +16 -0
- {edx_enterprise_data-8.7.0.dist-info → edx_enterprise_data-8.8.1.dist-info}/LICENSE +0 -0
- {edx_enterprise_data-8.7.0.dist-info → edx_enterprise_data-8.8.1.dist-info}/WHEEL +0 -0
- {edx_enterprise_data-8.7.0.dist-info → edx_enterprise_data-8.8.1.dist-info}/top_level.txt +0 -0
@@ -1,20 +1,20 @@
|
|
1
|
-
enterprise_data/__init__.py,sha256=
|
1
|
+
enterprise_data/__init__.py,sha256=SFfjo0qp4b4IjWzit7qM4Eib5E1or2vxpohALjmvTNM,123
|
2
2
|
enterprise_data/apps.py,sha256=aF6hZwDfI2oWj95tUTm_2ikHueQj-jLj-u0GrgzpsQI,414
|
3
3
|
enterprise_data/clients.py,sha256=GvQupy5TVYfO_IKC3yzXSAgNP54r-PtIjidM5ws9Iks,3947
|
4
4
|
enterprise_data/constants.py,sha256=uCKjfpdlMYFZJsAj3n9RMw4Cmg5_6s3NuwocO-fch3s,238
|
5
5
|
enterprise_data/filters.py,sha256=D2EiK12MMpBoz6eOUmTpoJEhj_sH7bA93NRRAdvkDVo,6163
|
6
6
|
enterprise_data/models.py,sha256=khGcOh7NWP8KGu84t78Y2zAu3knREeXA_prApmU2NX8,24428
|
7
7
|
enterprise_data/paginators.py,sha256=YPrC5TeXFt-ymenT2H8H2nCbDCnAzJQlH9kFPElRxWE,269
|
8
|
-
enterprise_data/renderers.py,sha256=
|
8
|
+
enterprise_data/renderers.py,sha256=9gIzavWspZTk4vDfVKXJtdn0tSZ2xNgkF-Akf7AWIDM,2389
|
9
9
|
enterprise_data/signals.py,sha256=8eqNPnlvmfsKf19lGWv5xTIuBgQIqR8EZSp9UYzC8Rc,1024
|
10
10
|
enterprise_data/urls.py,sha256=bqtKF5OEWEwrNmHG3os-pZNuNsmjlhxEqp7yM4TbPf4,243
|
11
|
-
enterprise_data/utils.py,sha256=
|
11
|
+
enterprise_data/utils.py,sha256=KykylyK9KTauj239-_V5w5iQHQBhFF3iuY3Df7p6Q5E,3017
|
12
12
|
enterprise_data/admin_analytics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
13
13
|
enterprise_data/admin_analytics/completions_utils.py,sha256=kGmLy7x6aD0coNYgzLa5XzJypLkGTT5clDHLSH_QFDE,9442
|
14
|
-
enterprise_data/admin_analytics/constants.py,sha256=
|
15
|
-
enterprise_data/admin_analytics/data_loaders.py,sha256=
|
14
|
+
enterprise_data/admin_analytics/constants.py,sha256=aHDgTHdsjbKNpgtNLDsl4giqhhrRkCGi72ysGIEk0Ao,817
|
15
|
+
enterprise_data/admin_analytics/data_loaders.py,sha256=YpSyygATVFtItcWlkIHvjsX5Lh1qMw6onDK-ZHP_AUw,5586
|
16
16
|
enterprise_data/admin_analytics/database.py,sha256=mNS_9xE5h6O7oMMzr6kr6LDTTSNvKzo8vaM-YG8tOd8,1312
|
17
|
-
enterprise_data/admin_analytics/utils.py,sha256=
|
17
|
+
enterprise_data/admin_analytics/utils.py,sha256=CQuTlg36AALJiopp4us-JN8oTXsw-jDXSJenbphLDME,12270
|
18
18
|
enterprise_data/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
19
19
|
enterprise_data/api/urls.py,sha256=POqc_KATHdnpMf9zHtpO46pKD5KAlAExtx7G6iylLcU,273
|
20
20
|
enterprise_data/api/v0/__init__.py,sha256=1aAzAYU5hk-RW6cKUxa1645cbZMxn7GIZ7OMjWc9MKI,46
|
@@ -22,14 +22,15 @@ enterprise_data/api/v0/serializers.py,sha256=dngZTk6DhRxApchQKCMp1B_c8aVnQtH0NCq
|
|
22
22
|
enterprise_data/api/v0/urls.py,sha256=vzJjqIo_S3AXWs9Us8XTaJc3FnxLbYzAkmLyuDQqum0,699
|
23
23
|
enterprise_data/api/v0/views.py,sha256=4RslZ4NZOU-844bnebEQ71ji2utRY7jEijqC45oQQD0,14380
|
24
24
|
enterprise_data/api/v1/__init__.py,sha256=1aAzAYU5hk-RW6cKUxa1645cbZMxn7GIZ7OMjWc9MKI,46
|
25
|
-
enterprise_data/api/v1/paginators.py,sha256=
|
26
|
-
enterprise_data/api/v1/serializers.py,sha256=
|
27
|
-
enterprise_data/api/v1/urls.py,sha256=
|
25
|
+
enterprise_data/api/v1/paginators.py,sha256=f0xsilLaU94jSBltJk46tR1rLEIt7YrqSzMAAVtPXjA,3592
|
26
|
+
enterprise_data/api/v1/serializers.py,sha256=9F2LGa8IKvglgeYNHw3Q0eEZUWknwHZMNZOdpDviEo4,12327
|
27
|
+
enterprise_data/api/v1/urls.py,sha256=xFsBf3TTsdblFAiHq1Bj3h82Ye1PS3cgqLC0pIso2js,3504
|
28
28
|
enterprise_data/api/v1/views/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
29
|
-
enterprise_data/api/v1/views/analytics_enrollments.py,sha256=
|
29
|
+
enterprise_data/api/v1/views/analytics_enrollments.py,sha256=om8i5HTDxDMSny1fy49Wh38LdYLLz-k-oFQJqlQLOxg,16812
|
30
|
+
enterprise_data/api/v1/views/analytics_leaderboard.py,sha256=EE34fJWYp8OGbIj8oRLlgMhbSm37w98oPXn1aPIZWWE,5860
|
30
31
|
enterprise_data/api/v1/views/base.py,sha256=FTAxlz5EzvAY657wzVgzhJPFSCHHzct7IDcvm71Smt8,866
|
31
|
-
enterprise_data/api/v1/views/enterprise_admin.py,sha256=
|
32
|
-
enterprise_data/api/v1/views/enterprise_completions.py,sha256=
|
32
|
+
enterprise_data/api/v1/views/enterprise_admin.py,sha256=F34FbOz1gLwHPUAQOg3mOjZoKirbCRrjZS2xFJSUBpw,9274
|
33
|
+
enterprise_data/api/v1/views/enterprise_completions.py,sha256=OkH6eYNql6UGVU-Xjx7PzwzaHeUHfa6e4WJmkUoHGDM,8221
|
33
34
|
enterprise_data/api/v1/views/enterprise_learner.py,sha256=yABjJje3CT8I8YOhWr1_tTkdKtnGJom8eu3EFz_-0BU,18517
|
34
35
|
enterprise_data/api/v1/views/enterprise_offers.py,sha256=VifxgqTLFLVw4extYPlHcN1N_yjXcsYsAlYEnAbpb10,1266
|
35
36
|
enterprise_data/fixtures/enterprise_enrollment.json,sha256=6onPXXR29pMdTdbl_mn81sDi3Re5jkLUZz2TPMB_1IY,5786
|
@@ -103,10 +104,12 @@ enterprise_data/tests/test_models.py,sha256=MWBY-LY5TPBjZ4GlvpM-h4W-BvRKr2Rml8Bz
|
|
103
104
|
enterprise_data/tests/test_utils.py,sha256=vbmYM7DMN-lHS2p4yaa0Yd6uSGXd2qoZRDE9X3J4Sec,18385
|
104
105
|
enterprise_data/tests/test_views.py,sha256=UvDRNTxruy5zBK_KgUy2cBMbwlaTW_vkM0-TCXbQZiY,69667
|
105
106
|
enterprise_data/tests/admin_analytics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
106
|
-
enterprise_data/tests/admin_analytics/
|
107
|
-
enterprise_data/tests/admin_analytics/
|
107
|
+
enterprise_data/tests/admin_analytics/mock_analytics_data.py,sha256=mLGTIP89VAQgqKtYlYnKUAKyZATzKnBtI_5aJwMmr7Q,18344
|
108
|
+
enterprise_data/tests/admin_analytics/mock_enrollments.py,sha256=LfuMo9Kn-OQD4z42G3BRuM5MXUUXXlaAMhTqfJf46XE,7266
|
109
|
+
enterprise_data/tests/admin_analytics/test_analytics_enrollments.py,sha256=UdKRkP6BNbsSo-gm0YCoddT-ReUMI1x9E6HNLSHT7pY,15177
|
110
|
+
enterprise_data/tests/admin_analytics/test_analytics_leaderboard.py,sha256=VSEyDAHfWBJvqmx9yzd4NnPAqK3TqaKrMBWswMAdzfU,6206
|
108
111
|
enterprise_data/tests/admin_analytics/test_data_loaders.py,sha256=o3denJ4aUS1pI5Crksl4C6m-NtCBm8ynoHBnLkf-v2U,4641
|
109
|
-
enterprise_data/tests/admin_analytics/test_enterprise_completions.py,sha256=
|
112
|
+
enterprise_data/tests/admin_analytics/test_enterprise_completions.py,sha256=afkHQFy4bvqZ0pq5Drl1t2nv8zxbgca2jzOQbihlPG0,7359
|
110
113
|
enterprise_data/tests/admin_analytics/test_utils.py,sha256=y33HXy6BDOoftdcz3qYlOYhgx7JSXDki-OLzBdTpiwA,11449
|
111
114
|
enterprise_data/tests/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
112
115
|
enterprise_data/tests/api/v0/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -156,8 +159,8 @@ enterprise_reporting/tests/test_send_enterprise_reports.py,sha256=WtL-RqGgu2x5PP
|
|
156
159
|
enterprise_reporting/tests/test_utils.py,sha256=Zt_TA0LVb-B6fQGkUkAKKVlUKKnQh8jnw1US1jKe7g8,9493
|
157
160
|
enterprise_reporting/tests/test_vertica_client.py,sha256=-R2yNCGUjRtoXwLMBloVFQkFYrJoo613VCr61gwI3kQ,140
|
158
161
|
enterprise_reporting/tests/utils.py,sha256=xms2LM7DV3wczXEfctOK1ddel1EE0J_YSr17UzbCDy4,1401
|
159
|
-
edx_enterprise_data-8.
|
160
|
-
edx_enterprise_data-8.
|
161
|
-
edx_enterprise_data-8.
|
162
|
-
edx_enterprise_data-8.
|
163
|
-
edx_enterprise_data-8.
|
162
|
+
edx_enterprise_data-8.8.1.dist-info/LICENSE,sha256=dql8h4yceoMhuzlcK0TT_i-NgTFNIZsgE47Q4t3dUYI,34520
|
163
|
+
edx_enterprise_data-8.8.1.dist-info/METADATA,sha256=hQ_trgT7XN1RJtLpZUIF33MB0ycCnKRAf1aOKxatsWw,1569
|
164
|
+
edx_enterprise_data-8.8.1.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
|
165
|
+
edx_enterprise_data-8.8.1.dist-info/top_level.txt,sha256=f5F2kU-dob6MqiHJpgZkFzoCD5VMhsdpkTV5n9Tvq3I,59
|
166
|
+
edx_enterprise_data-8.8.1.dist-info/RECORD,,
|
enterprise_data/__init__.py
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
from enum import Enum
|
4
4
|
|
5
5
|
|
6
|
-
class
|
6
|
+
class Granularity(Enum):
|
7
7
|
"""Granularity choices"""
|
8
8
|
DAILY = 'Daily'
|
9
9
|
WEEKLY = 'Weekly'
|
@@ -11,7 +11,7 @@ class GRANULARITY(Enum):
|
|
11
11
|
QUARTERLY = 'Quarterly'
|
12
12
|
|
13
13
|
|
14
|
-
class
|
14
|
+
class Calculation(Enum):
|
15
15
|
"""Calculation choices"""
|
16
16
|
TOTAL = 'Total'
|
17
17
|
RUNNING_TOTAL = 'Running Total'
|
@@ -19,9 +19,15 @@ class CALCULATION(Enum):
|
|
19
19
|
MOVING_AVERAGE_7_PERIOD = 'Moving Average (7 Period)'
|
20
20
|
|
21
21
|
|
22
|
-
class
|
22
|
+
class EnrollmentChart(Enum):
|
23
23
|
"""CSV choices"""
|
24
24
|
ENROLLMENTS_OVER_TIME = 'enrollments_over_time'
|
25
25
|
TOP_COURSES_BY_ENROLLMENTS = 'top_courses_by_enrollments'
|
26
26
|
TOP_SUBJECTS_BY_ENROLLMENTS = 'top_subjects_by_enrollments'
|
27
27
|
INDIVIDUAL_ENROLLMENTS = 'individual_enrollments'
|
28
|
+
|
29
|
+
|
30
|
+
class ResponseType(Enum):
|
31
|
+
"""Response type choices"""
|
32
|
+
JSON = 'json'
|
33
|
+
CSV = 'csv'
|
@@ -7,6 +7,7 @@ import pandas
|
|
7
7
|
from django.http import Http404
|
8
8
|
|
9
9
|
from enterprise_data.admin_analytics.database import run_query
|
10
|
+
from enterprise_data.utils import timer
|
10
11
|
|
11
12
|
|
12
13
|
def get_select_query(table: str, columns: list, enterprise_uuid: str) -> str:
|
@@ -65,7 +66,8 @@ def fetch_enrollment_data(enterprise_uuid: str):
|
|
65
66
|
enterprise_uuid=enterprise_uuid,
|
66
67
|
)
|
67
68
|
|
68
|
-
|
69
|
+
with timer('fetch_enrollment_data'):
|
70
|
+
results = run_query(query=query)
|
69
71
|
if not results:
|
70
72
|
raise Http404(f'No enrollment data found for enterprise {enterprise_uuid}')
|
71
73
|
|
@@ -113,7 +115,8 @@ def fetch_engagement_data(enterprise_uuid: str):
|
|
113
115
|
table='fact_enrollment_engagement_day_admin_dash', columns=columns, enterprise_uuid=enterprise_uuid
|
114
116
|
)
|
115
117
|
|
116
|
-
|
118
|
+
with timer('fetch_engagement_data'):
|
119
|
+
results = run_query(query=query)
|
117
120
|
if not results:
|
118
121
|
raise Http404(f'No engagement data found for enterprise {enterprise_uuid}')
|
119
122
|
|
@@ -171,7 +174,8 @@ def fetch_skills_data(enterprise_uuid: str):
|
|
171
174
|
table='skills_daily_rollup_admin_dash', columns=cols, enterprise_uuid=enterprise_uuid
|
172
175
|
)
|
173
176
|
|
174
|
-
|
177
|
+
with timer('fetch_skills_data'):
|
178
|
+
skills = run_query(query=query)
|
175
179
|
|
176
180
|
if not skills:
|
177
181
|
raise Http404(f'No skills data found for enterprise {enterprise_uuid}')
|
@@ -1,15 +1,23 @@
|
|
1
1
|
"""
|
2
2
|
Utility functions for fetching data from the database.
|
3
3
|
"""
|
4
|
-
from datetime import datetime
|
4
|
+
from datetime import datetime, timedelta
|
5
5
|
from enum import Enum
|
6
|
+
from logging import getLogger
|
6
7
|
|
7
8
|
from edx_django_utils.cache import TieredCache, get_cache_key
|
8
9
|
|
9
|
-
from enterprise_data.admin_analytics.constants import
|
10
|
-
from enterprise_data.admin_analytics.data_loaders import
|
10
|
+
from enterprise_data.admin_analytics.constants import Calculation, Granularity
|
11
|
+
from enterprise_data.admin_analytics.data_loaders import (
|
12
|
+
fetch_engagement_data,
|
13
|
+
fetch_enrollment_data,
|
14
|
+
fetch_max_enrollment_datetime,
|
15
|
+
fetch_skills_data,
|
16
|
+
)
|
11
17
|
from enterprise_data.utils import date_filter, primary_subject_truncate
|
12
18
|
|
19
|
+
LOGGER = getLogger(__name__)
|
20
|
+
|
13
21
|
|
14
22
|
class ChartType(Enum):
|
15
23
|
"""
|
@@ -23,14 +31,45 @@ class ChartType(Enum):
|
|
23
31
|
TOP_SUBJECTS_BY_COMPLETIONS = 'top_subjects_by_completions'
|
24
32
|
|
25
33
|
|
34
|
+
def fetch_enrollments_cache_expiry_timestamp():
|
35
|
+
"""Calculate cache expiry timestamp"""
|
36
|
+
# TODO: Implement correct cache expiry logic for `enrollments` data.
|
37
|
+
# Current cache expiry logic is based on `enterprise_learner_enrollment` table,
|
38
|
+
# Which has nothing to do with the `enrollments` data. Instead cache expiry should
|
39
|
+
# be based on `fact_enrollment_admin_dash` table. Currently we have no timestamp in
|
40
|
+
# `fact_enrollment_admin_dash` table that can be used for cache expiry. Add a new
|
41
|
+
# column in the table for this purpose and then use that column for cache expiry.
|
42
|
+
last_updated_at = fetch_max_enrollment_datetime()
|
43
|
+
cache_expiry = (
|
44
|
+
last_updated_at + timedelta(days=1) if last_updated_at else datetime.now()
|
45
|
+
)
|
46
|
+
return cache_expiry
|
47
|
+
|
48
|
+
|
49
|
+
def fetch_engagements_cache_expiry_timestamp():
|
50
|
+
"""Calculate cache expiry timestamp"""
|
51
|
+
# TODO: Implement correct cache expiry logic for `engagements` data.
|
52
|
+
# Current cache expiry logic is based on `enterprise_learner_enrollment` table,
|
53
|
+
# Which has nothing to do with the `engagements` data. Instead cache expiry should
|
54
|
+
# be based on `fact_enrollment_engagement_day_admin_dash` table. Currently we have
|
55
|
+
# no timestamp in `fact_enrollment_engagement_day_admin_dash` table that can be used
|
56
|
+
# for cache expiry. Add a new column in the table for this purpose and then use that
|
57
|
+
# column for cache expiry.
|
58
|
+
last_updated_at = fetch_max_enrollment_datetime()
|
59
|
+
cache_expiry = (
|
60
|
+
last_updated_at + timedelta(days=1) if last_updated_at else datetime.now()
|
61
|
+
)
|
62
|
+
return cache_expiry
|
63
|
+
|
64
|
+
|
26
65
|
def granularity_aggregation(level, group, date, data_frame, aggregation_type="count"):
|
27
66
|
"""Aggregate data based on granularity"""
|
28
67
|
df = data_frame
|
29
68
|
|
30
69
|
period_mapping = {
|
31
|
-
|
32
|
-
|
33
|
-
|
70
|
+
Granularity.WEEKLY.value: "W",
|
71
|
+
Granularity.MONTHLY.value: "M",
|
72
|
+
Granularity.QUARTERLY.value: "Q"
|
34
73
|
}
|
35
74
|
|
36
75
|
if level in period_mapping:
|
@@ -52,15 +91,15 @@ def calculation_aggregation(calc, data_frame, aggregation_type="count"):
|
|
52
91
|
df = data_frame
|
53
92
|
|
54
93
|
window_mapping = {
|
55
|
-
|
56
|
-
|
94
|
+
Calculation.MOVING_AVERAGE_3_PERIOD.value: 3,
|
95
|
+
Calculation.MOVING_AVERAGE_7_PERIOD.value: 7,
|
57
96
|
}
|
58
97
|
|
59
98
|
aggregation_column = "count" if aggregation_type == "count" else "sum"
|
60
99
|
|
61
|
-
if calc ==
|
100
|
+
if calc == Calculation.RUNNING_TOTAL.value:
|
62
101
|
df[aggregation_column] = df.groupby("enroll_type")[aggregation_column].cumsum()
|
63
|
-
elif calc in [
|
102
|
+
elif calc in [Calculation.MOVING_AVERAGE_3_PERIOD.value, Calculation.MOVING_AVERAGE_7_PERIOD.value]:
|
64
103
|
df[aggregation_column] = (
|
65
104
|
df.groupby("enroll_type")[aggregation_column]
|
66
105
|
.rolling(window_mapping[calc])
|
@@ -108,6 +147,7 @@ def fetch_and_cache_enrollments_data(enterprise_id, cache_expiry):
|
|
108
147
|
cached_response = TieredCache.get_cached_response(cache_key)
|
109
148
|
|
110
149
|
if cached_response.is_found:
|
150
|
+
LOGGER.info(f"Enrollments data found in cache for Enterprise [{enterprise_id}]")
|
111
151
|
return cached_response.value
|
112
152
|
else:
|
113
153
|
enrollments = fetch_enrollment_data(enterprise_id)
|
@@ -135,6 +175,7 @@ def fetch_and_cache_engagements_data(enterprise_id, cache_expiry):
|
|
135
175
|
cached_response = TieredCache.get_cached_response(cache_key)
|
136
176
|
|
137
177
|
if cached_response.is_found:
|
178
|
+
LOGGER.info(f"Engagements data found in cache for Enterprise [{enterprise_id}]")
|
138
179
|
return cached_response.value
|
139
180
|
else:
|
140
181
|
engagements = fetch_engagement_data(enterprise_id)
|
@@ -162,6 +203,7 @@ def fetch_and_cache_skills_data(enterprise_id, cache_expiry):
|
|
162
203
|
cached_response = TieredCache.get_cached_response(cache_key)
|
163
204
|
|
164
205
|
if cached_response.is_found:
|
206
|
+
LOGGER.info(f"Skills data found in cache for Enterprise [{enterprise_id}]")
|
165
207
|
return cached_response.value
|
166
208
|
else:
|
167
209
|
skills = fetch_skills_data(enterprise_id)
|
@@ -72,7 +72,7 @@ class AdvanceAnalyticsPagination(PageNumberPagination):
|
|
72
72
|
max_page_size (int): The maximum allowed page size.
|
73
73
|
"""
|
74
74
|
page_size_query_param = "page_size"
|
75
|
-
page_size =
|
75
|
+
page_size = 50
|
76
76
|
max_page_size = 100
|
77
77
|
|
78
78
|
def paginate_queryset(self, queryset, request, view=None):
|
@@ -5,7 +5,7 @@ from uuid import UUID
|
|
5
5
|
|
6
6
|
from rest_framework import serializers
|
7
7
|
|
8
|
-
from enterprise_data.admin_analytics.constants import
|
8
|
+
from enterprise_data.admin_analytics.constants import Calculation, EnrollmentChart, Granularity, ResponseType
|
9
9
|
from enterprise_data.models import (
|
10
10
|
EnterpriseAdminLearnerProgress,
|
11
11
|
EnterpriseAdminSummarizeInsights,
|
@@ -237,23 +237,28 @@ class EnterpriseExecEdLCModulePerformanceSerializer(serializers.ModelSerializer)
|
|
237
237
|
|
238
238
|
class AdvanceAnalyticsQueryParamSerializer(serializers.Serializer): # pylint: disable=abstract-method
|
239
239
|
"""Serializer for validating query params"""
|
240
|
+
RESPONSE_TYPES = [
|
241
|
+
ResponseType.JSON.value,
|
242
|
+
ResponseType.CSV.value
|
243
|
+
]
|
240
244
|
GRANULARITY_CHOICES = [
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
+
Granularity.DAILY.value,
|
246
|
+
Granularity.WEEKLY.value,
|
247
|
+
Granularity.MONTHLY.value,
|
248
|
+
Granularity.QUARTERLY.value
|
245
249
|
]
|
246
250
|
CALCULATION_CHOICES = [
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
+
Calculation.TOTAL.value,
|
252
|
+
Calculation.RUNNING_TOTAL.value,
|
253
|
+
Calculation.MOVING_AVERAGE_3_PERIOD.value,
|
254
|
+
Calculation.MOVING_AVERAGE_7_PERIOD.value
|
251
255
|
]
|
252
256
|
|
253
257
|
start_date = serializers.DateField(required=False)
|
254
258
|
end_date = serializers.DateField(required=False)
|
255
259
|
granularity = serializers.CharField(required=False)
|
256
260
|
calculation = serializers.CharField(required=False)
|
261
|
+
response_type = serializers.CharField(required=False)
|
257
262
|
|
258
263
|
def validate(self, attrs):
|
259
264
|
"""
|
@@ -270,6 +275,17 @@ class AdvanceAnalyticsQueryParamSerializer(serializers.Serializer): # pylint: d
|
|
270
275
|
|
271
276
|
return attrs
|
272
277
|
|
278
|
+
def validate_response_type(self, value):
|
279
|
+
"""
|
280
|
+
Validate the response_type value.
|
281
|
+
|
282
|
+
Raises:
|
283
|
+
serializers.ValidationError: If response_type is not one of the valid choices in `RESPONSE_TYPES`.
|
284
|
+
"""
|
285
|
+
if value not in self.RESPONSE_TYPES:
|
286
|
+
raise serializers.ValidationError(f"response_type must be one of {self.RESPONSE_TYPES}")
|
287
|
+
return value
|
288
|
+
|
273
289
|
def validate_granularity(self, value):
|
274
290
|
"""
|
275
291
|
Validate the granularity value.
|
@@ -293,32 +309,25 @@ class AdvanceAnalyticsQueryParamSerializer(serializers.Serializer): # pylint: d
|
|
293
309
|
return value
|
294
310
|
|
295
311
|
|
296
|
-
class
|
297
|
-
|
298
|
-
|
299
|
-
|
312
|
+
class AdvanceAnalyticsEnrollmentStatsSerializer(
|
313
|
+
AdvanceAnalyticsQueryParamSerializer
|
314
|
+
): # pylint: disable=abstract-method
|
315
|
+
"""Serializer for validating Advance Analytics Enrollments Stats API"""
|
316
|
+
CHART_TYPES = [
|
317
|
+
EnrollmentChart.ENROLLMENTS_OVER_TIME.value,
|
318
|
+
EnrollmentChart.TOP_COURSES_BY_ENROLLMENTS.value,
|
319
|
+
EnrollmentChart.TOP_SUBJECTS_BY_ENROLLMENTS.value
|
300
320
|
]
|
301
321
|
|
302
|
-
|
322
|
+
chart_type = serializers.CharField(required=False)
|
303
323
|
|
304
|
-
def
|
324
|
+
def validate_chart_type(self, value):
|
305
325
|
"""
|
306
|
-
Validate the
|
326
|
+
Validate the chart_type value.
|
307
327
|
|
308
328
|
Raises:
|
309
|
-
serializers.ValidationError: If
|
329
|
+
serializers.ValidationError: If chart_type is not one of the valid choices
|
310
330
|
"""
|
311
|
-
if value not in self.
|
312
|
-
raise serializers.ValidationError(f"
|
331
|
+
if value not in self.CHART_TYPES:
|
332
|
+
raise serializers.ValidationError(f"chart_type must be one of {self.CHART_TYPES}")
|
313
333
|
return value
|
314
|
-
|
315
|
-
|
316
|
-
class AdvanceAnalyticsEnrollmentStatsSerializer(
|
317
|
-
AdvanceAnalyticsEnrollmentSerializer
|
318
|
-
): # pylint: disable=abstract-method
|
319
|
-
"""Serializer for validating Advance Analytics Enrollments Stats API"""
|
320
|
-
CSV_TYPES = [
|
321
|
-
ENROLLMENT_CSV.ENROLLMENTS_OVER_TIME.value,
|
322
|
-
ENROLLMENT_CSV.TOP_COURSES_BY_ENROLLMENTS.value,
|
323
|
-
ENROLLMENT_CSV.TOP_SUBJECTS_BY_ENROLLMENTS.value
|
324
|
-
]
|
enterprise_data/api/v1/urls.py
CHANGED
@@ -15,6 +15,7 @@ from enterprise_data.api.v1.views.analytics_enrollments import (
|
|
15
15
|
AdvanceAnalyticsEnrollmentStatsView,
|
16
16
|
AdvanceAnalyticsIndividualEnrollmentsView,
|
17
17
|
)
|
18
|
+
from enterprise_data.api.v1.views.analytics_leaderboard import AdvanceAnalyticsLeaderboardView
|
18
19
|
from enterprise_data.constants import UUID4_REGEX
|
19
20
|
|
20
21
|
app_name = 'enterprise_data_api_v1'
|
@@ -53,32 +54,37 @@ urlpatterns = [
|
|
53
54
|
name='enterprise-admin-insights'
|
54
55
|
),
|
55
56
|
re_path(
|
56
|
-
fr'^admin/
|
57
|
+
fr'^admin/analytics/(?P<enterprise_id>{UUID4_REGEX})$',
|
57
58
|
enterprise_admin_views.EnterpriseAdminAnalyticsAggregatesView.as_view(),
|
58
59
|
name='enterprise-admin-analytics-aggregates'
|
59
60
|
),
|
60
61
|
re_path(
|
61
|
-
fr'^admin/
|
62
|
+
fr'^admin/analytics/(?P<enterprise_uuid>{UUID4_REGEX})/leaderboard$',
|
63
|
+
AdvanceAnalyticsLeaderboardView.as_view(),
|
64
|
+
name='enterprise-admin-analytics-leaderboard'
|
65
|
+
),
|
66
|
+
re_path(
|
67
|
+
fr'^admin/analytics/(?P<enterprise_uuid>{UUID4_REGEX})/enrollments/stats$',
|
62
68
|
AdvanceAnalyticsEnrollmentStatsView.as_view(),
|
63
69
|
name='enterprise-admin-analytics-enrollments-stats'
|
64
70
|
),
|
65
71
|
re_path(
|
66
|
-
fr'^admin/
|
72
|
+
fr'^admin/analytics/(?P<enterprise_uuid>{UUID4_REGEX})/enrollments$',
|
67
73
|
AdvanceAnalyticsIndividualEnrollmentsView.as_view(),
|
68
74
|
name='enterprise-admin-analytics-enrollments'
|
69
75
|
),
|
70
76
|
re_path(
|
71
|
-
fr'^admin/
|
77
|
+
fr'^admin/analytics/(?P<enterprise_id>{UUID4_REGEX})/skills/stats',
|
72
78
|
enterprise_admin_views.EnterpriseAdminAnalyticsSkillsView.as_view(),
|
73
79
|
name='enterprise-admin-analytics-skills'
|
74
80
|
),
|
75
81
|
re_path(
|
76
|
-
fr'^admin/
|
82
|
+
fr'^admin/analytics/(?P<enterprise_id>{UUID4_REGEX})/completions/stats$',
|
77
83
|
enterprise_completions_views.EnterrpiseAdminCompletionsStatsView.as_view(),
|
78
84
|
name='enterprise-admin-analytics-completions-stats'
|
79
85
|
),
|
80
86
|
re_path(
|
81
|
-
fr'^admin/
|
87
|
+
fr'^admin/analytics/(?P<enterprise_id>{UUID4_REGEX})/completions$',
|
82
88
|
enterprise_completions_views.EnterrpiseAdminCompletionsView.as_view(),
|
83
89
|
name='enterprise-admin-analytics-completions'
|
84
90
|
),
|