edx-enterprise-data 9.1.0__py3-none-any.whl → 9.2.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.1.0.dist-info → edx_enterprise_data-9.2.0.dist-info}/METADATA +1 -1
- {edx_enterprise_data-9.1.0.dist-info → edx_enterprise_data-9.2.0.dist-info}/RECORD +22 -25
- enterprise_data/__init__.py +1 -1
- enterprise_data/admin_analytics/constants.py +3 -17
- enterprise_data/admin_analytics/data_loaders.py +0 -125
- enterprise_data/admin_analytics/database/queries/fact_engagement_admin_dash.py +85 -0
- enterprise_data/admin_analytics/database/tables/fact_engagement_admin_dash.py +50 -0
- enterprise_data/api/v1/serializers.py +1 -38
- enterprise_data/api/v1/urls.py +2 -2
- enterprise_data/api/v1/views/analytics_completions.py +0 -2
- enterprise_data/api/v1/views/analytics_engagements.py +0 -2
- enterprise_data/api/v1/views/analytics_enrollments.py +0 -2
- enterprise_data/api/v1/views/analytics_leaderboard.py +65 -102
- enterprise_data/api/v1/views/enterprise_learner.py +17 -14
- enterprise_data/renderers.py +2 -2
- enterprise_data/tests/admin_analytics/mock_analytics_data.py +15 -60
- enterprise_data/tests/admin_analytics/test_analytics_leaderboard.py +48 -81
- enterprise_data/tests/admin_analytics/test_data_loaders.py +1 -58
- enterprise_data/utils.py +0 -16
- enterprise_data/admin_analytics/utils.py +0 -180
- enterprise_data/api/v1/paginators.py +0 -121
- enterprise_data/tests/admin_analytics/test_utils.py +0 -102
- {edx_enterprise_data-9.1.0.dist-info → edx_enterprise_data-9.2.0.dist-info}/LICENSE +0 -0
- {edx_enterprise_data-9.1.0.dist-info → edx_enterprise_data-9.2.0.dist-info}/WHEEL +0 -0
- {edx_enterprise_data-9.1.0.dist-info → edx_enterprise_data-9.2.0.dist-info}/top_level.txt +0 -0
@@ -1,27 +1,26 @@
|
|
1
|
-
enterprise_data/__init__.py,sha256=
|
1
|
+
enterprise_data/__init__.py,sha256=lw4RdMlnQpEaiuEI442W84A9nCk5Y2AFB3wxyg2DhY0,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=d_bJZjeUTyHRBBtpCcslrTyldv6IMYQ_QW-GWijwGHU,3026
|
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=Hrmz6uB8LsPSrau-lK0vvBWzRKTMl_Tygxv8qWG2NNw,2471
|
12
12
|
enterprise_data/admin_analytics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
13
|
-
enterprise_data/admin_analytics/constants.py,sha256
|
14
|
-
enterprise_data/admin_analytics/data_loaders.py,sha256=
|
15
|
-
enterprise_data/admin_analytics/utils.py,sha256=DftyBaSKnf0f6E3adH8yVAvGNeQGROywufqnJxZ0W84,6295
|
13
|
+
enterprise_data/admin_analytics/constants.py,sha256=-6uLAq5DUeA_rv5eUb9SeqlG3iVWV30qUS8asbK4430,160
|
14
|
+
enterprise_data/admin_analytics/data_loaders.py,sha256=NixI-4M3D4MnI279x5hqqTw84uKpQy0TRib_g-0Bt5Q,726
|
16
15
|
enterprise_data/admin_analytics/database/__init__.py,sha256=vNSWKf2VV5xMegN7htJJtxtQEb0ASLC6frE2w0ZpYpE,104
|
17
16
|
enterprise_data/admin_analytics/database/utils.py,sha256=5u-d6ZQW95mF_r4bH8Xdi7DgpYAuDFOG_q0P-bjKXHU,1712
|
18
17
|
enterprise_data/admin_analytics/database/queries/__init__.py,sha256=IC5TLOr_GnydbrVbl2mWhwO3aUbYeHuDmfPTLmwGhZA,218
|
19
|
-
enterprise_data/admin_analytics/database/queries/fact_engagement_admin_dash.py,sha256=
|
18
|
+
enterprise_data/admin_analytics/database/queries/fact_engagement_admin_dash.py,sha256=28R1uISsLmMrbvTZXF2bGsVCCOyfQzXY-tMzThDndHo,7809
|
20
19
|
enterprise_data/admin_analytics/database/queries/fact_enrollment_admin_dash.py,sha256=brRgmgtzPLsOdt1vYrk_EpoQLm2izmOdBapxAxNkXzA,8420
|
21
20
|
enterprise_data/admin_analytics/database/queries/skills_daily_rollup_admin_dash.py,sha256=PgWwvtVCK5lbiq6z44lH0fwbkdWYukhyXZL9X8lNWCY,4099
|
22
21
|
enterprise_data/admin_analytics/database/tables/__init__.py,sha256=Z-c3P9hqR-dC9uYKe63qHkQG9Nms8cLE2jRN-4jeMM0,289
|
23
22
|
enterprise_data/admin_analytics/database/tables/base.py,sha256=1KyKsC18pW3m-5U-T6pdt5rIwsz6Wp3QFFbD3r6L6YQ,395
|
24
|
-
enterprise_data/admin_analytics/database/tables/fact_engagement_admin_dash.py,sha256=
|
23
|
+
enterprise_data/admin_analytics/database/tables/fact_engagement_admin_dash.py,sha256=cA-Y63LQVDD7UHv5rYdX2091R7W6clcT2DXO0VbMhVQ,7354
|
25
24
|
enterprise_data/admin_analytics/database/tables/fact_enrollment_admin_dash.py,sha256=q1XB7aDwFzllS5lBQNfS2y7drehqiK7o0J1eiYzqFOk,10854
|
26
25
|
enterprise_data/admin_analytics/database/tables/skills_daily_rollup_admin_dash.py,sha256=9PqLeVqByrz7R0qumRbwJlr5lzIWn7Fl7WEGM0aJVlw,3131
|
27
26
|
enterprise_data/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -31,17 +30,16 @@ enterprise_data/api/v0/serializers.py,sha256=dngZTk6DhRxApchQKCMp1B_c8aVnQtH0NCq
|
|
31
30
|
enterprise_data/api/v0/urls.py,sha256=vzJjqIo_S3AXWs9Us8XTaJc3FnxLbYzAkmLyuDQqum0,699
|
32
31
|
enterprise_data/api/v0/views.py,sha256=4RslZ4NZOU-844bnebEQ71ji2utRY7jEijqC45oQQD0,14380
|
33
32
|
enterprise_data/api/v1/__init__.py,sha256=1aAzAYU5hk-RW6cKUxa1645cbZMxn7GIZ7OMjWc9MKI,46
|
34
|
-
enterprise_data/api/v1/
|
35
|
-
enterprise_data/api/v1/
|
36
|
-
enterprise_data/api/v1/urls.py,sha256=zoztENyCI_8TcKpE88Q6pkKSHiWCC9DascjC9ul4arc,3975
|
33
|
+
enterprise_data/api/v1/serializers.py,sha256=JOpHjP7n-NgXsShT_Wf6VLVlF-supKw2PW0oZxiwwrI,10323
|
34
|
+
enterprise_data/api/v1/urls.py,sha256=vPBZ7p9hsj-lMowqbp29sd2hKhHtICCMVal6th4eQxg,3995
|
37
35
|
enterprise_data/api/v1/views/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
38
|
-
enterprise_data/api/v1/views/analytics_completions.py,sha256=
|
39
|
-
enterprise_data/api/v1/views/analytics_engagements.py,sha256=
|
40
|
-
enterprise_data/api/v1/views/analytics_enrollments.py,sha256=
|
41
|
-
enterprise_data/api/v1/views/analytics_leaderboard.py,sha256=
|
36
|
+
enterprise_data/api/v1/views/analytics_completions.py,sha256=4jg2I50TNn2Q6InLCI9eS1MBiFo4SMRsTx4kZJtNmpI,6268
|
37
|
+
enterprise_data/api/v1/views/analytics_engagements.py,sha256=W9DNalWIgoeo_KXqapDj2_ireRyXU47nKHw95sAEtxM,6306
|
38
|
+
enterprise_data/api/v1/views/analytics_enrollments.py,sha256=HXgoFwvXk-CZcf6qbVOfYSCnkfDxGvfHxl0j3RcKz7A,6290
|
39
|
+
enterprise_data/api/v1/views/analytics_leaderboard.py,sha256=zwd-2A2sctjAdx5BUjCqiwcZ5aNvM8wfm386pR-RwGQ,4114
|
42
40
|
enterprise_data/api/v1/views/base.py,sha256=Kkmd5zgEBAhvwS_GoGXSK6lgbDNwSPioYn-QbnizI3w,3416
|
43
41
|
enterprise_data/api/v1/views/enterprise_admin.py,sha256=DsR1oHFhe6LCIFjIJ4YLLZ7PUChvNlFfdZD-sxHoijY,7388
|
44
|
-
enterprise_data/api/v1/views/enterprise_learner.py,sha256=
|
42
|
+
enterprise_data/api/v1/views/enterprise_learner.py,sha256=E1eU0DgCIhe4Bx70uZku89-RwEKay4aYt7X0IbPhJzA,18650
|
45
43
|
enterprise_data/api/v1/views/enterprise_offers.py,sha256=VifxgqTLFLVw4extYPlHcN1N_yjXcsYsAlYEnAbpb10,1266
|
46
44
|
enterprise_data/fixtures/enterprise_enrollment.json,sha256=6onPXXR29pMdTdbl_mn81sDi3Re5jkLUZz2TPMB_1IY,5786
|
47
45
|
enterprise_data/fixtures/enterprise_user.json,sha256=6g8GvNY9j_fh1dvAU80bTAMI2F5vXCkb8a4UjsftMvQ,1970
|
@@ -114,14 +112,13 @@ enterprise_data/tests/test_models.py,sha256=MWBY-LY5TPBjZ4GlvpM-h4W-BvRKr2Rml8Bz
|
|
114
112
|
enterprise_data/tests/test_utils.py,sha256=vbmYM7DMN-lHS2p4yaa0Yd6uSGXd2qoZRDE9X3J4Sec,18385
|
115
113
|
enterprise_data/tests/test_views.py,sha256=UvDRNTxruy5zBK_KgUy2cBMbwlaTW_vkM0-TCXbQZiY,69667
|
116
114
|
enterprise_data/tests/admin_analytics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
117
|
-
enterprise_data/tests/admin_analytics/mock_analytics_data.py,sha256=
|
115
|
+
enterprise_data/tests/admin_analytics/mock_analytics_data.py,sha256=qN73YxoD1kAtKsQo94avR0DGMCsJClrpqFxxYC6reqE,15248
|
118
116
|
enterprise_data/tests/admin_analytics/mock_enrollments.py,sha256=u61tkqSdVKExURJF7arvBoXvbdkF39CkjxQiTvVMXIE,4467
|
119
117
|
enterprise_data/tests/admin_analytics/test_analytics_engagements.py,sha256=gfO6RI2gbDMhOD4p4h7l6sZpisglBreTPtJRqNxIgo8,10440
|
120
118
|
enterprise_data/tests/admin_analytics/test_analytics_enrollments.py,sha256=uFa6gnlf-CthctdYTWsw-pLLOxkELGiq8YFq5CyWFbs,10826
|
121
|
-
enterprise_data/tests/admin_analytics/test_analytics_leaderboard.py,sha256=
|
122
|
-
enterprise_data/tests/admin_analytics/test_data_loaders.py,sha256=
|
119
|
+
enterprise_data/tests/admin_analytics/test_analytics_leaderboard.py,sha256=iFT47BVlgf93u1K2l3_8HDcXY-9vWKp3iUaWM7V0C2U,5476
|
120
|
+
enterprise_data/tests/admin_analytics/test_data_loaders.py,sha256=0ZsxW4J5bFDsujLXa0PpIP2BjuKK4EDQRF7C6gfgCfA,1067
|
123
121
|
enterprise_data/tests/admin_analytics/test_enterprise_completions.py,sha256=6BsCnaXdTMaQXoqy4SWNxAkGimc75lBCdBHjR2MdOFU,7233
|
124
|
-
enterprise_data/tests/admin_analytics/test_utils.py,sha256=4qL_ZK-sGzbMMqiOrBrPmzdIPno7KohiaIfd7FMehic,5260
|
125
122
|
enterprise_data/tests/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
126
123
|
enterprise_data/tests/api/v0/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
127
124
|
enterprise_data/tests/api/v0/test_serializers.py,sha256=Gfty6gy6OQLN318uL1OCPhAZOqSUL50FWc0nC23VMnc,6257
|
@@ -170,8 +167,8 @@ enterprise_reporting/tests/test_send_enterprise_reports.py,sha256=WtL-RqGgu2x5PP
|
|
170
167
|
enterprise_reporting/tests/test_utils.py,sha256=Zt_TA0LVb-B6fQGkUkAKKVlUKKnQh8jnw1US1jKe7g8,9493
|
171
168
|
enterprise_reporting/tests/test_vertica_client.py,sha256=-R2yNCGUjRtoXwLMBloVFQkFYrJoo613VCr61gwI3kQ,140
|
172
169
|
enterprise_reporting/tests/utils.py,sha256=xms2LM7DV3wczXEfctOK1ddel1EE0J_YSr17UzbCDy4,1401
|
173
|
-
edx_enterprise_data-9.
|
174
|
-
edx_enterprise_data-9.
|
175
|
-
edx_enterprise_data-9.
|
176
|
-
edx_enterprise_data-9.
|
177
|
-
edx_enterprise_data-9.
|
170
|
+
edx_enterprise_data-9.2.0.dist-info/LICENSE,sha256=dql8h4yceoMhuzlcK0TT_i-NgTFNIZsgE47Q4t3dUYI,34520
|
171
|
+
edx_enterprise_data-9.2.0.dist-info/METADATA,sha256=Y3R-klk-iA_cgSs0EiuD_zzzPnx-4_JBwF9ZMrirwpg,1569
|
172
|
+
edx_enterprise_data-9.2.0.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
|
173
|
+
edx_enterprise_data-9.2.0.dist-info/top_level.txt,sha256=f5F2kU-dob6MqiHJpgZkFzoCD5VMhsdpkTV5n9Tvq3I,59
|
174
|
+
edx_enterprise_data-9.2.0.dist-info/RECORD,,
|
enterprise_data/__init__.py
CHANGED
@@ -1,24 +1,10 @@
|
|
1
|
-
"""
|
1
|
+
"""
|
2
|
+
Constants for admin analytics app.
|
3
|
+
"""
|
2
4
|
|
3
5
|
from enum import Enum
|
4
6
|
|
5
7
|
|
6
|
-
class Granularity(Enum):
|
7
|
-
"""Granularity choices"""
|
8
|
-
DAILY = 'Daily'
|
9
|
-
WEEKLY = 'Weekly'
|
10
|
-
MONTHLY = 'Monthly'
|
11
|
-
QUARTERLY = 'Quarterly'
|
12
|
-
|
13
|
-
|
14
|
-
class Calculation(Enum):
|
15
|
-
"""Calculation choices"""
|
16
|
-
TOTAL = 'Total'
|
17
|
-
RUNNING_TOTAL = 'Running Total'
|
18
|
-
MOVING_AVERAGE_3_PERIOD = 'Moving Average (3 Period)'
|
19
|
-
MOVING_AVERAGE_7_PERIOD = 'Moving Average (7 Period)'
|
20
|
-
|
21
|
-
|
22
8
|
class ResponseType(Enum):
|
23
9
|
"""Response type choices"""
|
24
10
|
JSON = 'json'
|
@@ -3,138 +3,13 @@ Utility functions for fetching data from the database.
|
|
3
3
|
"""
|
4
4
|
from logging import getLogger
|
5
5
|
|
6
|
-
import numpy
|
7
6
|
import pandas
|
8
7
|
|
9
|
-
from django.http import Http404
|
10
|
-
|
11
8
|
from enterprise_data.admin_analytics.database import run_query
|
12
|
-
from enterprise_data.utils import timer
|
13
9
|
|
14
10
|
LOGGER = getLogger(__name__)
|
15
11
|
|
16
12
|
|
17
|
-
def get_select_query(table: str, columns: list, enterprise_uuid: str) -> str:
|
18
|
-
"""
|
19
|
-
Generate a SELECT query for the given table and columns.
|
20
|
-
|
21
|
-
Arguments:
|
22
|
-
table (str): The table to query.
|
23
|
-
columns (list): The columns to select.
|
24
|
-
enterprise_uuid (str): The UUID of the enterprise customer.
|
25
|
-
|
26
|
-
Returns:
|
27
|
-
(str): The SELECT query.
|
28
|
-
"""
|
29
|
-
return f'SELECT {", ".join(columns)} FROM {table} WHERE enterprise_customer_uuid = "{enterprise_uuid}"'
|
30
|
-
|
31
|
-
|
32
|
-
def fetch_enrollment_data(enterprise_uuid: str):
|
33
|
-
"""
|
34
|
-
Fetch enrollment data from the database for the given enterprise customer.
|
35
|
-
|
36
|
-
Arguments:
|
37
|
-
enterprise_uuid (str): The UUID of the enterprise customer.
|
38
|
-
|
39
|
-
Returns:
|
40
|
-
(pandas.DataFrame): The enrollment data.
|
41
|
-
"""
|
42
|
-
enterprise_uuid = enterprise_uuid.replace('-', '')
|
43
|
-
|
44
|
-
columns = [
|
45
|
-
'enterprise_customer_name',
|
46
|
-
'enterprise_customer_uuid',
|
47
|
-
'lms_enrollment_id',
|
48
|
-
'user_id',
|
49
|
-
'email',
|
50
|
-
'course_key',
|
51
|
-
'courserun_key',
|
52
|
-
'course_id',
|
53
|
-
'course_subject',
|
54
|
-
'course_title',
|
55
|
-
'enterprise_enrollment_date',
|
56
|
-
'lms_enrollment_mode',
|
57
|
-
'enroll_type',
|
58
|
-
'program_title',
|
59
|
-
'date_certificate_awarded',
|
60
|
-
'grade_percent',
|
61
|
-
'cert_awarded',
|
62
|
-
'date_certificate_created_raw',
|
63
|
-
'passed_date_raw',
|
64
|
-
'passed_date',
|
65
|
-
'has_passed',
|
66
|
-
]
|
67
|
-
query = get_select_query(
|
68
|
-
table='fact_enrollment_admin_dash',
|
69
|
-
columns=columns,
|
70
|
-
enterprise_uuid=enterprise_uuid,
|
71
|
-
)
|
72
|
-
|
73
|
-
with timer('fetch_enrollment_data'):
|
74
|
-
results = run_query(query=query)
|
75
|
-
|
76
|
-
if not results:
|
77
|
-
raise Http404(f'No enrollment data found for enterprise {enterprise_uuid}')
|
78
|
-
|
79
|
-
LOGGER.info(f'[PLOTLY] Enrollment data fetched successfully. Records: {len(results)}')
|
80
|
-
enrollments = pandas.DataFrame(numpy.array(results), columns=columns)
|
81
|
-
LOGGER.info('[PLOTLY] Enrollment data converted to DataFrame.')
|
82
|
-
|
83
|
-
# Convert date columns to datetime.
|
84
|
-
enrollments['enterprise_enrollment_date'] = enrollments['enterprise_enrollment_date'].astype('datetime64[ns]')
|
85
|
-
enrollments['date_certificate_awarded'] = enrollments['date_certificate_awarded'].astype('datetime64[ns]')
|
86
|
-
enrollments['date_certificate_created_raw'] = enrollments['date_certificate_created_raw'].astype('datetime64[ns]')
|
87
|
-
enrollments['passed_date_raw'] = enrollments['passed_date_raw'].astype('datetime64[ns]')
|
88
|
-
enrollments['passed_date'] = enrollments['passed_date'].astype('datetime64[ns]')
|
89
|
-
|
90
|
-
return enrollments
|
91
|
-
|
92
|
-
|
93
|
-
def fetch_engagement_data(enterprise_uuid: str):
|
94
|
-
"""
|
95
|
-
Fetch engagement data from the database for the given enterprise customer.
|
96
|
-
|
97
|
-
Arguments:
|
98
|
-
enterprise_uuid (str): The UUID of the enterprise customer.
|
99
|
-
|
100
|
-
Returns:
|
101
|
-
(pandas.DataFrame): The engagement data.
|
102
|
-
"""
|
103
|
-
enterprise_uuid = enterprise_uuid.replace('-', '')
|
104
|
-
|
105
|
-
columns = [
|
106
|
-
'user_id',
|
107
|
-
'email',
|
108
|
-
'enterprise_customer_uuid',
|
109
|
-
'course_key',
|
110
|
-
'enroll_type',
|
111
|
-
'activity_date',
|
112
|
-
'course_title',
|
113
|
-
'course_subject',
|
114
|
-
'is_engaged',
|
115
|
-
'is_engaged_video',
|
116
|
-
'is_engaged_forum',
|
117
|
-
'is_engaged_problem',
|
118
|
-
'is_active',
|
119
|
-
'learning_time_seconds',
|
120
|
-
]
|
121
|
-
query = get_select_query(
|
122
|
-
table='fact_enrollment_engagement_day_admin_dash', columns=columns, enterprise_uuid=enterprise_uuid
|
123
|
-
)
|
124
|
-
|
125
|
-
with timer('fetch_engagement_data'):
|
126
|
-
results = run_query(query=query)
|
127
|
-
if not results:
|
128
|
-
raise Http404(f'No engagement data found for enterprise {enterprise_uuid}')
|
129
|
-
|
130
|
-
LOGGER.info(f'[PLOTLY] Engagement data fetched successfully. Records: {len(results)}')
|
131
|
-
engagement = pandas.DataFrame(numpy.array(results), columns=columns)
|
132
|
-
LOGGER.info('[PLOTLY] Engagement data converted to DataFrame.')
|
133
|
-
engagement['activity_date'] = engagement['activity_date'].astype('datetime64[ns]')
|
134
|
-
|
135
|
-
return engagement
|
136
|
-
|
137
|
-
|
138
13
|
def fetch_max_enrollment_datetime():
|
139
14
|
"""
|
140
15
|
Fetch the latest created date from the enterprise_learner_enrollment table.
|
@@ -109,3 +109,88 @@ class FactEngagementAdminDashQueries:
|
|
109
109
|
GROUP BY activity_date, enroll_type
|
110
110
|
ORDER BY activity_date;
|
111
111
|
"""
|
112
|
+
|
113
|
+
@staticmethod
|
114
|
+
def get_all_leaderboard_data_query():
|
115
|
+
"""
|
116
|
+
Get the query to fetch the leaderboard data.
|
117
|
+
|
118
|
+
Query should fetch the leaderboard data for the enterprise customer to show in the data table.
|
119
|
+
|
120
|
+
Returns:
|
121
|
+
(str): Query to fetch the leaderboard data.
|
122
|
+
"""
|
123
|
+
return """
|
124
|
+
WITH Engagement AS (
|
125
|
+
SELECT
|
126
|
+
email,
|
127
|
+
ROUND(SUM(learning_time_seconds) / 3600, 1) as learning_time_hours,
|
128
|
+
SUM(is_engaged) as session_count,
|
129
|
+
CASE
|
130
|
+
WHEN SUM(is_engaged) = 0 THEN 0.0
|
131
|
+
ELSE ROUND(SUM(learning_time_seconds) / 3600 / SUM(is_engaged), 1)
|
132
|
+
END AS average_session_length
|
133
|
+
FROM fact_enrollment_engagement_day_admin_dash
|
134
|
+
WHERE enterprise_customer_uuid=%(enterprise_customer_uuid)s AND
|
135
|
+
(activity_date BETWEEN %(start_date)s AND %(end_date)s) AND
|
136
|
+
is_engaged = 1
|
137
|
+
GROUP BY email
|
138
|
+
),
|
139
|
+
Completions AS (
|
140
|
+
SELECT email, count(course_key) as course_completion_count
|
141
|
+
FROM fact_enrollment_admin_dash
|
142
|
+
WHERE enterprise_customer_uuid=%(enterprise_customer_uuid)s AND
|
143
|
+
(passed_date BETWEEN %(start_date)s AND %(end_date)s) AND
|
144
|
+
has_passed = 1
|
145
|
+
GROUP BY email
|
146
|
+
)
|
147
|
+
SELECT
|
148
|
+
Engagement.email,
|
149
|
+
Engagement.learning_time_hours,
|
150
|
+
Engagement.session_count,
|
151
|
+
Engagement.average_session_length,
|
152
|
+
Completions.course_completion_count
|
153
|
+
FROM Engagement
|
154
|
+
LEFT JOIN Completions
|
155
|
+
ON Engagement.email = Completions.email
|
156
|
+
ORDER BY
|
157
|
+
Engagement.learning_time_hours DESC,
|
158
|
+
Engagement.session_count DESC,
|
159
|
+
Completions.course_completion_count DESC
|
160
|
+
LIMIT %(limit)s OFFSET %(offset)s;
|
161
|
+
"""
|
162
|
+
|
163
|
+
@staticmethod
|
164
|
+
def get_leaderboard_data_count_query():
|
165
|
+
"""
|
166
|
+
Get the query to fetch the leaderboard row count.
|
167
|
+
|
168
|
+
Query should fetch the count of rows for the leaderboard data for the enterprise customer.
|
169
|
+
|
170
|
+
Returns:
|
171
|
+
(str): Query to fetch the leaderboard row count.
|
172
|
+
"""
|
173
|
+
return """
|
174
|
+
WITH Engagement AS (
|
175
|
+
SELECT
|
176
|
+
email
|
177
|
+
FROM fact_enrollment_engagement_day_admin_dash
|
178
|
+
WHERE enterprise_customer_uuid=%(enterprise_customer_uuid)s AND
|
179
|
+
(activity_date BETWEEN %(start_date)s AND %(end_date)s) AND
|
180
|
+
is_engaged = 1
|
181
|
+
GROUP BY email
|
182
|
+
),
|
183
|
+
Completions AS (
|
184
|
+
SELECT email, count(course_key) as course_completion_count
|
185
|
+
FROM fact_enrollment_admin_dash
|
186
|
+
WHERE enterprise_customer_uuid=%(enterprise_customer_uuid)s AND
|
187
|
+
(passed_date BETWEEN %(start_date)s AND %(end_date)s) AND
|
188
|
+
has_passed = 1
|
189
|
+
GROUP BY email
|
190
|
+
)
|
191
|
+
SELECT
|
192
|
+
count(*)
|
193
|
+
FROM Engagement
|
194
|
+
LEFT JOIN Completions
|
195
|
+
ON Engagement.email = Completions.email
|
196
|
+
"""
|
@@ -152,3 +152,53 @@ class FactEngagementAdminDashTable(BaseTable):
|
|
152
152
|
},
|
153
153
|
as_dict=True,
|
154
154
|
)
|
155
|
+
|
156
|
+
def get_all_leaderboard_data(
|
157
|
+
self, enterprise_customer_uuid: UUID, start_date: date, end_date: date, limit: int, offset: int
|
158
|
+
):
|
159
|
+
"""
|
160
|
+
Get the leaderboard data for the given enterprise customer.
|
161
|
+
|
162
|
+
Arguments:
|
163
|
+
enterprise_customer_uuid (UUID): The UUID of the enterprise customer.
|
164
|
+
start_date (date): The start date.
|
165
|
+
end_date (date): The end date.
|
166
|
+
limit (int): The maximum number of records to return.
|
167
|
+
offset (int): The number of records to skip.
|
168
|
+
|
169
|
+
Returns:
|
170
|
+
list[dict]: The leaderboard data.
|
171
|
+
"""
|
172
|
+
return run_query(
|
173
|
+
query=self.queries.get_all_leaderboard_data_query(),
|
174
|
+
params={
|
175
|
+
'enterprise_customer_uuid': enterprise_customer_uuid,
|
176
|
+
'start_date': start_date,
|
177
|
+
'end_date': end_date,
|
178
|
+
'limit': limit,
|
179
|
+
'offset': offset,
|
180
|
+
},
|
181
|
+
as_dict=True,
|
182
|
+
)
|
183
|
+
|
184
|
+
def get_leaderboard_data_count(self, enterprise_customer_uuid: UUID, start_date: date, end_date: date):
|
185
|
+
"""
|
186
|
+
Get the total number of leaderboard records for the given enterprise customer.
|
187
|
+
|
188
|
+
Arguments:
|
189
|
+
enterprise_customer_uuid (UUID): The UUID of the enterprise customer.
|
190
|
+
start_date (date): The start date.
|
191
|
+
end_date (date): The end date.
|
192
|
+
|
193
|
+
Returns:
|
194
|
+
(int): The total number of leaderboard records.
|
195
|
+
"""
|
196
|
+
results = run_query(
|
197
|
+
query=self.queries.get_leaderboard_data_count_query(),
|
198
|
+
params={
|
199
|
+
'enterprise_customer_uuid': enterprise_customer_uuid,
|
200
|
+
'start_date': start_date,
|
201
|
+
'end_date': end_date,
|
202
|
+
}
|
203
|
+
)
|
204
|
+
return results[0][0]
|
@@ -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 ResponseType
|
9
9
|
from enterprise_data.models import (
|
10
10
|
EnterpriseAdminLearnerProgress,
|
11
11
|
EnterpriseAdminSummarizeInsights,
|
@@ -241,23 +241,8 @@ class AdvanceAnalyticsQueryParamSerializer(serializers.Serializer): # pylint: d
|
|
241
241
|
ResponseType.JSON.value,
|
242
242
|
ResponseType.CSV.value
|
243
243
|
]
|
244
|
-
GRANULARITY_CHOICES = [
|
245
|
-
Granularity.DAILY.value,
|
246
|
-
Granularity.WEEKLY.value,
|
247
|
-
Granularity.MONTHLY.value,
|
248
|
-
Granularity.QUARTERLY.value
|
249
|
-
]
|
250
|
-
CALCULATION_CHOICES = [
|
251
|
-
Calculation.TOTAL.value,
|
252
|
-
Calculation.RUNNING_TOTAL.value,
|
253
|
-
Calculation.MOVING_AVERAGE_3_PERIOD.value,
|
254
|
-
Calculation.MOVING_AVERAGE_7_PERIOD.value
|
255
|
-
]
|
256
|
-
|
257
244
|
start_date = serializers.DateField(required=False)
|
258
245
|
end_date = serializers.DateField(required=False)
|
259
|
-
granularity = serializers.CharField(required=False)
|
260
|
-
calculation = serializers.CharField(required=False)
|
261
246
|
response_type = serializers.CharField(required=False)
|
262
247
|
page = serializers.IntegerField(required=False, min_value=1)
|
263
248
|
page_size = serializers.IntegerField(required=False, min_value=2)
|
@@ -287,25 +272,3 @@ class AdvanceAnalyticsQueryParamSerializer(serializers.Serializer): # pylint: d
|
|
287
272
|
if value not in self.RESPONSE_TYPES:
|
288
273
|
raise serializers.ValidationError(f"response_type must be one of {self.RESPONSE_TYPES}")
|
289
274
|
return value
|
290
|
-
|
291
|
-
def validate_granularity(self, value):
|
292
|
-
"""
|
293
|
-
Validate the granularity value.
|
294
|
-
|
295
|
-
Raises:
|
296
|
-
serializers.ValidationError: If granularity is not one of the valid choices.
|
297
|
-
"""
|
298
|
-
if value not in self.GRANULARITY_CHOICES:
|
299
|
-
raise serializers.ValidationError(f"Granularity must be one of {self.GRANULARITY_CHOICES}")
|
300
|
-
return value
|
301
|
-
|
302
|
-
def validate_calculation(self, value):
|
303
|
-
"""
|
304
|
-
Validate the calculation value.
|
305
|
-
|
306
|
-
Raises:
|
307
|
-
serializers.ValidationError: If calculation is not one of the valid choices
|
308
|
-
"""
|
309
|
-
if value not in self.CALCULATION_CHOICES:
|
310
|
-
raise serializers.ValidationError(f"Calculation must be one of {self.CALCULATION_CHOICES}")
|
311
|
-
return value
|
enterprise_data/api/v1/urls.py
CHANGED
@@ -58,8 +58,8 @@ urlpatterns = [
|
|
58
58
|
),
|
59
59
|
re_path(
|
60
60
|
fr'^admin/analytics/(?P<enterprise_uuid>{UUID4_REGEX})/leaderboard$',
|
61
|
-
AdvanceAnalyticsLeaderboardView.as_view(),
|
62
|
-
name='enterprise-admin-analytics-leaderboard'
|
61
|
+
AdvanceAnalyticsLeaderboardView.as_view({'get': 'list'}),
|
62
|
+
name='enterprise-admin-analytics-leaderboard-list'
|
63
63
|
),
|
64
64
|
re_path(
|
65
65
|
fr'^admin/analytics/(?P<enterprise_uuid>{UUID4_REGEX})/enrollments/stats$',
|
@@ -15,7 +15,6 @@ from django.http import StreamingHttpResponse
|
|
15
15
|
|
16
16
|
from enterprise_data.admin_analytics.constants import ResponseType
|
17
17
|
from enterprise_data.admin_analytics.database.tables import FactEnrollmentAdminDashTable
|
18
|
-
from enterprise_data.api.v1.paginators import AdvanceAnalyticsPagination
|
19
18
|
from enterprise_data.api.v1.serializers import AdvanceAnalyticsQueryParamSerializer
|
20
19
|
from enterprise_data.api.v1.views.base import AnalyticsPaginationMixin
|
21
20
|
from enterprise_data.renderers import IndividualCompletionsCSVRenderer
|
@@ -33,7 +32,6 @@ class AdvanceAnalyticsCompletionsView(AnalyticsPaginationMixin, ViewSet):
|
|
33
32
|
2. `enterprise_data_api_v1.enterprise-learner-completion-stats`: Get completion stats data.
|
34
33
|
"""
|
35
34
|
authentication_classes = (JwtAuthentication,)
|
36
|
-
pagination_class = AdvanceAnalyticsPagination
|
37
35
|
http_method_names = ('get', )
|
38
36
|
|
39
37
|
@permission_required('can_access_enterprise', fn=lambda request, enterprise_uuid: enterprise_uuid)
|
@@ -15,7 +15,6 @@ from django.http import StreamingHttpResponse
|
|
15
15
|
|
16
16
|
from enterprise_data.admin_analytics.constants import ResponseType
|
17
17
|
from enterprise_data.admin_analytics.database.tables import FactEngagementAdminDashTable, FactEnrollmentAdminDashTable
|
18
|
-
from enterprise_data.api.v1.paginators import AdvanceAnalyticsPagination
|
19
18
|
from enterprise_data.api.v1.serializers import AdvanceAnalyticsQueryParamSerializer
|
20
19
|
from enterprise_data.api.v1.views.base import AnalyticsPaginationMixin
|
21
20
|
from enterprise_data.renderers import IndividualEngagementsCSVRenderer
|
@@ -33,7 +32,6 @@ class AdvanceAnalyticsEngagementView(AnalyticsPaginationMixin, ViewSet):
|
|
33
32
|
2. `enterprise_data_api_v1.enterprise-learner-engagement-stats`: Get engagement stats data.
|
34
33
|
"""
|
35
34
|
authentication_classes = (JwtAuthentication,)
|
36
|
-
pagination_class = AdvanceAnalyticsPagination
|
37
35
|
http_method_names = ('get', )
|
38
36
|
|
39
37
|
@permission_required('can_access_enterprise', fn=lambda request, enterprise_uuid: enterprise_uuid)
|
@@ -14,7 +14,6 @@ from django.http import StreamingHttpResponse
|
|
14
14
|
|
15
15
|
from enterprise_data.admin_analytics.constants import ResponseType
|
16
16
|
from enterprise_data.admin_analytics.database.tables import FactEnrollmentAdminDashTable
|
17
|
-
from enterprise_data.api.v1.paginators import AdvanceAnalyticsPagination
|
18
17
|
from enterprise_data.api.v1.serializers import AdvanceAnalyticsQueryParamSerializer
|
19
18
|
from enterprise_data.api.v1.views.base import AnalyticsPaginationMixin
|
20
19
|
from enterprise_data.renderers import IndividualEnrollmentsCSVRenderer
|
@@ -32,7 +31,6 @@ class AdvanceAnalyticsEnrollmentsView(AnalyticsPaginationMixin, ViewSet):
|
|
32
31
|
2. `enterprise_data_api_v1.enterprise-learner-enrollment-stats`: Get enrollment stats data.
|
33
32
|
"""
|
34
33
|
authentication_classes = (JwtAuthentication,)
|
35
|
-
pagination_class = AdvanceAnalyticsPagination
|
36
34
|
http_method_names = ('get', )
|
37
35
|
|
38
36
|
@permission_required('can_access_enterprise', fn=lambda request, enterprise_uuid: enterprise_uuid)
|