edx-enterprise-data 8.12.1__py3-none-any.whl → 8.13.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: edx-enterprise-data
3
- Version: 8.12.1
3
+ Version: 8.13.0
4
4
  Summary: Enterprise Reporting
5
5
  Home-page: https://github.com/openedx/edx-enterprise-data
6
6
  Author: edX
@@ -1,4 +1,4 @@
1
- enterprise_data/__init__.py,sha256=8TjCXGqGwt4y6eG7nZC6OYCp9Dut66Hr_TTySgVt3PE,124
1
+ enterprise_data/__init__.py,sha256=2_b9Iv3g3fvN4GMN2sCDmFoIxUoDt6VL8J1dMTvRDUE,124
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
@@ -12,17 +12,19 @@ enterprise_data/utils.py,sha256=_zK3BErjZSIkP_JNzK8m-DR5pRTnxKylP9I-vURaRcE,3009
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
14
  enterprise_data/admin_analytics/constants.py,sha256=7WturLuMISekgcHHlgj45PPdPrDTYM-l21lA8-Q_Tfc,1107
15
- enterprise_data/admin_analytics/data_loaders.py,sha256=4PSsIveNBtpFqwrLUjTl5oywHVYFAMdZrNRHRGMk4XY,6116
16
- enterprise_data/admin_analytics/utils.py,sha256=CQuTlg36AALJiopp4us-JN8oTXsw-jDXSJenbphLDME,12270
15
+ enterprise_data/admin_analytics/data_loaders.py,sha256=z5OHCmsjQuu1lBw43mSSCbdSQ40fmL_WtodV_Tcnj5U,4791
16
+ enterprise_data/admin_analytics/utils.py,sha256=DftyBaSKnf0f6E3adH8yVAvGNeQGROywufqnJxZ0W84,6295
17
17
  enterprise_data/admin_analytics/database/__init__.py,sha256=vNSWKf2VV5xMegN7htJJtxtQEb0ASLC6frE2w0ZpYpE,104
18
18
  enterprise_data/admin_analytics/database/utils.py,sha256=5u-d6ZQW95mF_r4bH8Xdi7DgpYAuDFOG_q0P-bjKXHU,1712
19
19
  enterprise_data/admin_analytics/database/queries/__init__.py,sha256=IC5TLOr_GnydbrVbl2mWhwO3aUbYeHuDmfPTLmwGhZA,218
20
20
  enterprise_data/admin_analytics/database/queries/fact_engagement_admin_dash.py,sha256=fq01Ni_sKnvSRoiPQfTnJ8TtRePQe5MBLmI5CpVy36o,747
21
21
  enterprise_data/admin_analytics/database/queries/fact_enrollment_admin_dash.py,sha256=uuhX3OIB5Cp0-5uxN604HNEUuzb3s9nH3VR4CiEXs18,5388
22
- enterprise_data/admin_analytics/database/tables/__init__.py,sha256=3ECKlKlz1wuTER5M57CMFvmLFp0oCnVQXAUeTN96Bs0,213
22
+ enterprise_data/admin_analytics/database/queries/skills_daily_rollup_admin_dash.py,sha256=PgWwvtVCK5lbiq6z44lH0fwbkdWYukhyXZL9X8lNWCY,4099
23
+ enterprise_data/admin_analytics/database/tables/__init__.py,sha256=Z-c3P9hqR-dC9uYKe63qHkQG9Nms8cLE2jRN-4jeMM0,289
23
24
  enterprise_data/admin_analytics/database/tables/base.py,sha256=1KyKsC18pW3m-5U-T6pdt5rIwsz6Wp3QFFbD3r6L6YQ,395
24
25
  enterprise_data/admin_analytics/database/tables/fact_engagement_admin_dash.py,sha256=EsG8KgRW84wtA_nJuUknjLYlDtaPSJf_9mWdkO2Bj2I,1293
25
26
  enterprise_data/admin_analytics/database/tables/fact_enrollment_admin_dash.py,sha256=ix5QvPnrUZMVs_Fdt742i9PAmrQTXuqHlfW3PJhSQWo,7282
27
+ enterprise_data/admin_analytics/database/tables/skills_daily_rollup_admin_dash.py,sha256=9PqLeVqByrz7R0qumRbwJlr5lzIWn7Fl7WEGM0aJVlw,3131
26
28
  enterprise_data/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
29
  enterprise_data/api/urls.py,sha256=POqc_KATHdnpMf9zHtpO46pKD5KAlAExtx7G6iylLcU,273
28
30
  enterprise_data/api/v0/__init__.py,sha256=1aAzAYU5hk-RW6cKUxa1645cbZMxn7GIZ7OMjWc9MKI,46
@@ -38,7 +40,7 @@ enterprise_data/api/v1/views/analytics_engagements.py,sha256=8H3Fk-hTqJaU3H5Lpu1
38
40
  enterprise_data/api/v1/views/analytics_enrollments.py,sha256=uVc36C0s9y1dPBfbYwFhXLqPlExvQvZeiJ4C45lb4ZQ,6447
39
41
  enterprise_data/api/v1/views/analytics_leaderboard.py,sha256=2DALqzUIbe4-ZGgHHIkYAKJ5L1ik2ruPtQNYtTdPba4,5974
40
42
  enterprise_data/api/v1/views/base.py,sha256=Kkmd5zgEBAhvwS_GoGXSK6lgbDNwSPioYn-QbnizI3w,3416
41
- enterprise_data/api/v1/views/enterprise_admin.py,sha256=d79WkyBbqLk6pKGWWrnbYdrbOgQztP03Q7NKRA5tZB4,8639
43
+ enterprise_data/api/v1/views/enterprise_admin.py,sha256=DsR1oHFhe6LCIFjIJ4YLLZ7PUChvNlFfdZD-sxHoijY,7388
42
44
  enterprise_data/api/v1/views/enterprise_completions.py,sha256=bJG2ZtTbLyiBrj64iJHQNHEKLrJCzl9OuJ7nDtw-9aY,8377
43
45
  enterprise_data/api/v1/views/enterprise_learner.py,sha256=yABjJje3CT8I8YOhWr1_tTkdKtnGJom8eu3EFz_-0BU,18517
44
46
  enterprise_data/api/v1/views/enterprise_offers.py,sha256=VifxgqTLFLVw4extYPlHcN1N_yjXcsYsAlYEnAbpb10,1266
@@ -113,14 +115,14 @@ enterprise_data/tests/test_models.py,sha256=MWBY-LY5TPBjZ4GlvpM-h4W-BvRKr2Rml8Bz
113
115
  enterprise_data/tests/test_utils.py,sha256=vbmYM7DMN-lHS2p4yaa0Yd6uSGXd2qoZRDE9X3J4Sec,18385
114
116
  enterprise_data/tests/test_views.py,sha256=UvDRNTxruy5zBK_KgUy2cBMbwlaTW_vkM0-TCXbQZiY,69667
115
117
  enterprise_data/tests/admin_analytics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
116
- enterprise_data/tests/admin_analytics/mock_analytics_data.py,sha256=VZHhMKSQn5S-o1wNReSVyEnI_-6EkW-TUV5EvXCl5is,19536
118
+ enterprise_data/tests/admin_analytics/mock_analytics_data.py,sha256=LiuwYQXj44XW_mBIQz9gP3Izy8dwrkX-Yp9s8PA19e4,20866
117
119
  enterprise_data/tests/admin_analytics/mock_enrollments.py,sha256=LfuMo9Kn-OQD4z42G3BRuM5MXUUXXlaAMhTqfJf46XE,7266
118
120
  enterprise_data/tests/admin_analytics/test_analytics_engagements.py,sha256=KPXtBPaAOrzfff7W-xERSGx9KtZAJndLbIJx3gopSnE,15689
119
121
  enterprise_data/tests/admin_analytics/test_analytics_enrollments.py,sha256=GePv5E8BRGRWUHUwGaXvYIsN3dtDpNXUh-yfW5iBTi4,13781
120
122
  enterprise_data/tests/admin_analytics/test_analytics_leaderboard.py,sha256=VSEyDAHfWBJvqmx9yzd4NnPAqK3TqaKrMBWswMAdzfU,6206
121
- enterprise_data/tests/admin_analytics/test_data_loaders.py,sha256=o3denJ4aUS1pI5Crksl4C6m-NtCBm8ynoHBnLkf-v2U,4641
123
+ enterprise_data/tests/admin_analytics/test_data_loaders.py,sha256=b4BjN88FX9WjE6XJjkJZnoEvWVB_DovBGJ_wh-HgT9I,3514
122
124
  enterprise_data/tests/admin_analytics/test_enterprise_completions.py,sha256=afkHQFy4bvqZ0pq5Drl1t2nv8zxbgca2jzOQbihlPG0,7359
123
- enterprise_data/tests/admin_analytics/test_utils.py,sha256=y33HXy6BDOoftdcz3qYlOYhgx7JSXDki-OLzBdTpiwA,11449
125
+ enterprise_data/tests/admin_analytics/test_utils.py,sha256=4qL_ZK-sGzbMMqiOrBrPmzdIPno7KohiaIfd7FMehic,5260
124
126
  enterprise_data/tests/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
125
127
  enterprise_data/tests/api/v0/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
126
128
  enterprise_data/tests/api/v0/test_serializers.py,sha256=Gfty6gy6OQLN318uL1OCPhAZOqSUL50FWc0nC23VMnc,6257
@@ -128,7 +130,7 @@ enterprise_data/tests/api/v1/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NM
128
130
  enterprise_data/tests/api/v1/test_serializers.py,sha256=DwgEHcyOP3oqNUPB2O-NkJGeO_cYs9XJiq7791vJLZE,3682
129
131
  enterprise_data/tests/api/v1/test_views.py,sha256=rLqUHfar0HdBNtz33hQxd_0qUUgr7Ku3KwQSQ1B4Ypg,15213
130
132
  enterprise_data/tests/api/v1/views/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
131
- enterprise_data/tests/api/v1/views/test_enterprise_admin.py,sha256=i0A1JqG1YQTVRiXviFPbx385p7YhMxew72lgTZ7YNEg,6173
133
+ enterprise_data/tests/api/v1/views/test_enterprise_admin.py,sha256=ysdxVJU-beWQ7eCCTvnKR4br8VFz2lPg7w64z9mrado,9495
132
134
  enterprise_data_roles/__init__.py,sha256=toCpbypm2uDoWVw29_em9gPFNly8vNUS__C0b4TCqEg,112
133
135
  enterprise_data_roles/admin.py,sha256=QNP0VeWE092vZzpyxOA5UJK1nNGl5e71B1J0RCwo_nU,998
134
136
  enterprise_data_roles/apps.py,sha256=nKi8TyuQ5Q6WGtKs5QeXvUTc3N-YQjKhyBnm2EM3Bng,260
@@ -169,8 +171,8 @@ enterprise_reporting/tests/test_send_enterprise_reports.py,sha256=WtL-RqGgu2x5PP
169
171
  enterprise_reporting/tests/test_utils.py,sha256=Zt_TA0LVb-B6fQGkUkAKKVlUKKnQh8jnw1US1jKe7g8,9493
170
172
  enterprise_reporting/tests/test_vertica_client.py,sha256=-R2yNCGUjRtoXwLMBloVFQkFYrJoo613VCr61gwI3kQ,140
171
173
  enterprise_reporting/tests/utils.py,sha256=xms2LM7DV3wczXEfctOK1ddel1EE0J_YSr17UzbCDy4,1401
172
- edx_enterprise_data-8.12.1.dist-info/LICENSE,sha256=dql8h4yceoMhuzlcK0TT_i-NgTFNIZsgE47Q4t3dUYI,34520
173
- edx_enterprise_data-8.12.1.dist-info/METADATA,sha256=zAJ9YvmaCGHDChLF25Cf1zmK3Okmbq3qIG7UJ6E7xuw,1570
174
- edx_enterprise_data-8.12.1.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
175
- edx_enterprise_data-8.12.1.dist-info/top_level.txt,sha256=f5F2kU-dob6MqiHJpgZkFzoCD5VMhsdpkTV5n9Tvq3I,59
176
- edx_enterprise_data-8.12.1.dist-info/RECORD,,
174
+ edx_enterprise_data-8.13.0.dist-info/LICENSE,sha256=dql8h4yceoMhuzlcK0TT_i-NgTFNIZsgE47Q4t3dUYI,34520
175
+ edx_enterprise_data-8.13.0.dist-info/METADATA,sha256=Nh0uCmLiclIEZJZgq-fQduIOzYp3g9mq9NTLoeaPQkc,1570
176
+ edx_enterprise_data-8.13.0.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
177
+ edx_enterprise_data-8.13.0.dist-info/top_level.txt,sha256=f5F2kU-dob6MqiHJpgZkFzoCD5VMhsdpkTV5n9Tvq3I,59
178
+ edx_enterprise_data-8.13.0.dist-info/RECORD,,
@@ -2,4 +2,4 @@
2
2
  Enterprise data api application. This Django app exposes API endpoints used by enterprises.
3
3
  """
4
4
 
5
- __version__ = "8.12.1"
5
+ __version__ = "8.13.0"
@@ -147,51 +147,3 @@ def fetch_max_enrollment_datetime():
147
147
  if not results:
148
148
  return None
149
149
  return pandas.to_datetime(results[0][0])
150
-
151
-
152
- def fetch_skills_data(enterprise_uuid: str):
153
- """
154
- Fetch skills data from the database for the given enterprise customer.
155
-
156
- Arguments:
157
- enterprise_uuid (str): The UUID of the enterprise customer.
158
-
159
- Returns:
160
- (pandas.DataFrame): The skills data.
161
- """
162
-
163
- enterprise_uuid = enterprise_uuid.replace('-', '')
164
-
165
- cols = [
166
- 'course_number',
167
- 'skill_type',
168
- 'skill_name',
169
- 'skill_url',
170
- 'confidence',
171
- 'skill_rank',
172
- 'course_title',
173
- 'course_key',
174
- 'level_type',
175
- 'primary_subject_name',
176
- 'date',
177
- 'enterprise_customer_uuid',
178
- 'enterprise_customer_name',
179
- 'enrolls',
180
- 'completions',
181
- ]
182
- query = get_select_query(
183
- table='skills_daily_rollup_admin_dash', columns=cols, enterprise_uuid=enterprise_uuid
184
- )
185
-
186
- with timer('fetch_skills_data'):
187
- skills = run_query(query=query)
188
-
189
- if not skills:
190
- raise Http404(f'No skills data found for enterprise {enterprise_uuid}')
191
-
192
- LOGGER.info(f'[PLOTLY] Skills data fetched successfully. Records: {len(skills)}')
193
- skills = pandas.DataFrame(numpy.array(skills), columns=cols)
194
- LOGGER.info('[PLOTLY] Skills data converted to DataFrame.')
195
- skills['date'] = skills['date'].astype('datetime64[ns]')
196
-
197
- return skills
@@ -0,0 +1,118 @@
1
+ """
2
+ Module containing queries for the skills_daily_rollup_admin_dash table.
3
+ """
4
+
5
+
6
+ class SkillsDailyRollupAdminDashQueries:
7
+ """
8
+ Queries related to the skills_daily_rollup_admin_dash table.
9
+ """
10
+ @staticmethod
11
+ def get_top_skills():
12
+ """
13
+ Get the query to fetch the top skills for an enterprise customer.
14
+ """
15
+ return """
16
+ SELECT
17
+ skill_name,
18
+ skill_type,
19
+ SUM(enrolls) AS enrolls,
20
+ SUM(completions) AS completions
21
+ FROM
22
+ skills_daily_rollup_admin_dash
23
+ WHERE
24
+ enterprise_customer_uuid=%(enterprise_customer_uuid)s AND
25
+ date BETWEEN %(start_date)s AND %(end_date)s
26
+ GROUP BY
27
+ skill_name, skill_type
28
+ ORDER BY
29
+ enrolls DESC, completions DESC;
30
+ """
31
+
32
+ @staticmethod
33
+ def get_top_skills_by_enrollment():
34
+ """
35
+ Get the query to fetch the top skills by enrollment for an enterprise customer.
36
+ """
37
+ return """
38
+ WITH TopSkills AS (
39
+ -- Get top 10 skills by total enrollments
40
+ SELECT
41
+ skill_name
42
+ FROM
43
+ skills_daily_rollup_admin_dash
44
+ WHERE
45
+ enterprise_customer_uuid=%(enterprise_customer_uuid)s
46
+ AND date BETWEEN %(start_date)s AND %(end_date)s
47
+ GROUP BY
48
+ skill_name
49
+ ORDER BY
50
+ SUM(enrolls) DESC
51
+ LIMIT 10
52
+ )
53
+ SELECT
54
+ sd.skill_name,
55
+ CASE
56
+ WHEN sd.primary_subject_name IN (
57
+ 'business-management', 'computer-science',
58
+ 'data-analysis-statistics', 'engineering', 'communication'
59
+ ) THEN sd.primary_subject_name
60
+ ELSE 'other'
61
+ END AS subject_name,
62
+ SUM(sd.enrolls) AS count
63
+ FROM
64
+ skills_daily_rollup_admin_dash AS sd
65
+ JOIN
66
+ TopSkills AS ts ON sd.skill_name = ts.skill_name
67
+ WHERE
68
+ sd.enterprise_customer_uuid=%(enterprise_customer_uuid)s
69
+ AND date BETWEEN %(start_date)s AND %(end_date)s
70
+ GROUP BY
71
+ sd.skill_name, subject_name
72
+ ORDER BY
73
+ subject_name;
74
+ """
75
+
76
+ @staticmethod
77
+ def get_top_skills_by_completion():
78
+ """
79
+ Get the query to fetch the top skills by completion for an enterprise customer.
80
+ """
81
+ return """
82
+ WITH TopSkills AS (
83
+ -- Get top 10 skills by total completions
84
+ SELECT
85
+ skill_name
86
+ FROM
87
+ skills_daily_rollup_admin_dash
88
+ WHERE
89
+ enterprise_customer_uuid=%(enterprise_customer_uuid)s
90
+ AND date BETWEEN %(start_date)s AND %(end_date)s
91
+ GROUP BY
92
+ skill_name
93
+ ORDER BY
94
+ SUM(completions) DESC
95
+ LIMIT 10
96
+ )
97
+ SELECT
98
+ sd.skill_name,
99
+ CASE
100
+ WHEN sd.primary_subject_name IN (
101
+ 'business-management', 'computer-science',
102
+ 'data-analysis-statistics', 'engineering', 'communication'
103
+ ) THEN sd.primary_subject_name
104
+ ELSE 'other'
105
+ END AS subject_name,
106
+ SUM(sd.completions) AS count
107
+ FROM
108
+ skills_daily_rollup_admin_dash AS sd
109
+ JOIN
110
+ TopSkills AS ts ON sd.skill_name = ts.skill_name
111
+ WHERE
112
+ sd.enterprise_customer_uuid=%(enterprise_customer_uuid)s
113
+ AND date BETWEEN %(start_date)s AND %(end_date)s
114
+ GROUP BY
115
+ sd.skill_name, subject_name
116
+ ORDER BY
117
+ subject_name;
118
+ """
@@ -3,3 +3,4 @@ This module contains the database queries for the admin analytics.
3
3
  """
4
4
  from .fact_engagement_admin_dash import FactEngagementAdminDashTable
5
5
  from .fact_enrollment_admin_dash import FactEnrollmentAdminDashTable
6
+ from .skills_daily_rollup_admin_dash import SkillsDailyRollupAdminDashTable
@@ -0,0 +1,94 @@
1
+ """
2
+ Module for interacting with the skills_daily_rollup_admin_dash table.
3
+ """
4
+ from datetime import date
5
+ from uuid import UUID
6
+
7
+ from enterprise_data.admin_analytics.database.queries.skills_daily_rollup_admin_dash import (
8
+ SkillsDailyRollupAdminDashQueries,
9
+ )
10
+ from enterprise_data.admin_analytics.database.tables.base import BaseTable
11
+ from enterprise_data.admin_analytics.database.utils import run_query
12
+
13
+
14
+ class SkillsDailyRollupAdminDashTable(BaseTable):
15
+ """
16
+ Class for communicating with the skills_daily_rollup_admin_dash table.
17
+ """
18
+ queries = SkillsDailyRollupAdminDashQueries()
19
+
20
+ def get_top_skills(self, enterprise_customer_uuid: UUID, start_date: date, end_date: date):
21
+ """
22
+ Get the top skills for the given enterprise customer.
23
+
24
+ Arguments:
25
+ enterprise_customer_uuid (UUID): The UUID of the enterprise customer.
26
+ start_date (date): The start date.
27
+ end_date (date): The end date.
28
+
29
+ Returns:
30
+ list<dict>: A list of dictionaries containing the skill_name, skill_type, enrolls and completions.
31
+ """
32
+ return run_query(
33
+ query=self.queries.get_top_skills(),
34
+ params={
35
+ 'enterprise_customer_uuid': enterprise_customer_uuid,
36
+ 'start_date': start_date,
37
+ 'end_date': end_date,
38
+ },
39
+ as_dict=True,
40
+ )
41
+
42
+ def get_top_skills_by_enrollment(
43
+ self,
44
+ enterprise_customer_uuid: UUID,
45
+ start_date: date,
46
+ end_date: date
47
+ ):
48
+ """
49
+ Get the top skills by enrollments for the given enterprise customer.
50
+
51
+ Arguments:
52
+ enterprise_customer_uuid (UUID): The UUID of the enterprise customer.
53
+ start_date (date): The start date.
54
+ end_date (date): The end date.
55
+
56
+ Returns:
57
+ list<dict>: A list of dictionaries containing the skill_name, subject_name, count.
58
+ """
59
+ return run_query(
60
+ query=self.queries.get_top_skills_by_enrollment(),
61
+ params={
62
+ 'enterprise_customer_uuid': enterprise_customer_uuid,
63
+ 'start_date': start_date,
64
+ 'end_date': end_date,
65
+ },
66
+ as_dict=True,
67
+ )
68
+
69
+ def get_top_skills_by_completion(
70
+ self,
71
+ enterprise_customer_uuid: UUID,
72
+ start_date: date,
73
+ end_date: date
74
+ ):
75
+ """
76
+ Get the top skills by completion for the given enterprise customer.
77
+
78
+ Arguments:
79
+ enterprise_customer_uuid (UUID): The UUID of the enterprise customer.
80
+ start_date (date): The start date.
81
+ end_date (date): The end date.
82
+
83
+ Returns:
84
+ list<dict>: A list of dictionaries containing the skill_name, subject_name, count.
85
+ """
86
+ return run_query(
87
+ query=self.queries.get_top_skills_by_completion(),
88
+ params={
89
+ 'enterprise_customer_uuid': enterprise_customer_uuid,
90
+ 'start_date': start_date,
91
+ 'end_date': end_date,
92
+ },
93
+ as_dict=True,
94
+ )
@@ -12,9 +12,7 @@ from enterprise_data.admin_analytics.data_loaders import (
12
12
  fetch_engagement_data,
13
13
  fetch_enrollment_data,
14
14
  fetch_max_enrollment_datetime,
15
- fetch_skills_data,
16
15
  )
17
- from enterprise_data.utils import date_filter, primary_subject_truncate
18
16
 
19
17
  LOGGER = getLogger(__name__)
20
18
 
@@ -23,9 +21,6 @@ class ChartType(Enum):
23
21
  """
24
22
  Chart types.
25
23
  """
26
- BUBBLE = 'bubble'
27
- TOP_SKILLS_ENROLLMENT = 'top_skills_enrollment'
28
- TOP_SKILLS_COMPLETION = 'top_skills_completion'
29
24
  COMPLETIONS_OVER_TIME = 'completions_over_time'
30
25
  TOP_COURSES_BY_COMPLETIONS = 'top_courses_by_completions'
31
26
  TOP_SUBJECTS_BY_COMPLETIONS = 'top_subjects_by_completions'
@@ -183,169 +178,3 @@ def fetch_and_cache_engagements_data(enterprise_id, cache_expiry):
183
178
  cache_key, engagements, get_cache_timeout(cache_expiry)
184
179
  )
185
180
  return engagements
186
-
187
-
188
- def fetch_and_cache_skills_data(enterprise_id, cache_expiry):
189
- """
190
- Helper method to fetch and cache skills data.
191
-
192
- Arguments:
193
- enterprise_id (str): UUID of the enterprise customer in string format.
194
- cache_expiry (datetime): Datetime object denoting the cache expiry.
195
-
196
- Returns:
197
- (pandas.DataFrame): The skills data.
198
- """
199
- cache_key = get_cache_key(
200
- resource='enterprise-admin-analytics-aggregate-skills',
201
- enterprise_customer=enterprise_id,
202
- )
203
- cached_response = TieredCache.get_cached_response(cache_key)
204
-
205
- if cached_response.is_found:
206
- LOGGER.info(f"Skills data found in cache for Enterprise [{enterprise_id}]")
207
- return cached_response.value
208
- else:
209
- skills = fetch_skills_data(enterprise_id)
210
- TieredCache.set_all_tiers(
211
- cache_key, skills, get_cache_timeout(cache_expiry)
212
- )
213
- return skills
214
-
215
-
216
- def get_skills_bubble_chart_df(skills_filtered):
217
- """ Get the skills data for the bubble chart.
218
-
219
- Args:
220
- skills_filtered (pandas.DataFrame): The skills data.
221
-
222
- Returns:
223
- (pandas.DataFrame): The skills data for the bubble chart.
224
- """
225
-
226
- # Group by skill_name and skill_type, and aggregate enrolls and completions
227
- skills_aggregated = (
228
- skills_filtered.groupby(['skill_name', 'skill_type'], as_index=False)
229
- .agg(enrolls=('enrolls', 'sum'), completions=('completions', 'sum'))
230
- )
231
-
232
- # Convert enrolls and completions to integers
233
- skills_aggregated['enrolls'] = skills_aggregated['enrolls'].astype(int)
234
- skills_aggregated['completions'] = skills_aggregated['completions'].astype(int)
235
-
236
- # Sort the dataframe by enrolls and completions in descending order
237
- skills_aggregated = skills_aggregated.sort_values(by=['enrolls', 'completions'], ascending=False)
238
-
239
- return skills_aggregated
240
-
241
-
242
- def get_top_skills_enrollment(skills_filtered):
243
- """ Get the top skills by enrolls.
244
-
245
- Args:
246
- skills_filtered (pandas.DataFrame): The skills data.
247
-
248
- Returns:
249
- (pandas.DataFrame): The top skills by enrolls data
250
- """
251
-
252
- # Get the top 10 skills by enrolls
253
- top_skills = (
254
- skills_filtered.groupby('skill_name')
255
- .enrolls.sum()
256
- .sort_values(ascending=False)
257
- .head(10)
258
- .index
259
- )
260
-
261
- # Apply primary_subject_truncate to the primary_subject_name column
262
- skills_filtered['primary_subject_name'] = skills_filtered['primary_subject_name'].apply(primary_subject_truncate)
263
-
264
- # Filter data for the top skills and aggregate enrolls by skill_name and primary_subject_name
265
- top_skills_enrollment_data = (
266
- skills_filtered[skills_filtered.skill_name.isin(top_skills)]
267
- .groupby(['skill_name', 'primary_subject_name'], as_index=False)
268
- .agg(count=('enrolls', 'sum'))
269
- )
270
-
271
- # Sort the dataframe by primary_subject_name
272
- top_skills_enrollment_data = top_skills_enrollment_data.sort_values(by="primary_subject_name")
273
-
274
- return top_skills_enrollment_data
275
-
276
-
277
- def get_top_skills_completion(skills_filtered):
278
- """ Get the top skills by completions.
279
-
280
- Args:
281
- skills_filtered (pandas.DataFrame): The skills data.
282
-
283
- Returns:
284
- (pandas.DataFrame): The top skills by completions
285
- """
286
-
287
- # Get the top 10 skills by completions
288
- top_skills = (
289
- skills_filtered.groupby('skill_name')
290
- .completions.sum()
291
- .sort_values(ascending=False)
292
- .head(10)
293
- .index
294
- )
295
-
296
- # Apply primary_subject_truncate to the primary_subject_name column
297
- skills_filtered['primary_subject_name'] = skills_filtered['primary_subject_name'].apply(primary_subject_truncate)
298
-
299
- # Filter data for the top skills and aggregate completions by skill_name and primary_subject_name
300
- top_skills_completion_data = (
301
- skills_filtered[skills_filtered.skill_name.isin(top_skills)]
302
- .groupby(['skill_name', 'primary_subject_name'], as_index=False)
303
- .agg(count=('completions', 'sum'))
304
- )
305
-
306
- # Sort the dataframe by primary_subject_name
307
- top_skills_completion_data = top_skills_completion_data.sort_values(by='primary_subject_name')
308
-
309
- return top_skills_completion_data
310
-
311
-
312
- def get_skills_chart_data(chart_type, start_date, end_date, skills):
313
- """
314
- Get chart data for skill charts.
315
-
316
- Arguments:
317
- chart_type (ChartType): The type of chart to generate.
318
- start_date (datetime): The start date for the date filter.
319
- end_date (datetime): The end date for the date filter.
320
- skills (pandas.DataFrame): The skills data.
321
- """
322
- skills_filtered = date_filter(start=start_date, end=end_date, data_frame=skills.copy(), date_column='date')
323
- if chart_type == ChartType.BUBBLE:
324
- return get_skills_bubble_chart_df(skills_filtered=skills_filtered.copy())
325
- elif chart_type == ChartType.TOP_SKILLS_ENROLLMENT:
326
- return get_top_skills_enrollment(skills_filtered=skills_filtered.copy())
327
- elif chart_type == ChartType.TOP_SKILLS_COMPLETION:
328
- return get_top_skills_completion(skills_filtered=skills_filtered.copy())
329
- else:
330
- raise ValueError(f"Invalid chart type: {chart_type}")
331
-
332
-
333
- def get_top_skills_csv_data(skills, start_date, end_date):
334
- """ Get the top skills data for CSV download.
335
-
336
- Args:
337
- skills (pandas.DataFrame): The skills data.
338
- start_date (str): The start date for the date filter.
339
- end_date (str): The end date for the date filter.
340
-
341
- Returns:
342
- (pandas.DataFrame): The top skills data for CSV download.
343
- """
344
- dff = get_skills_chart_data(
345
- chart_type=ChartType.BUBBLE,
346
- start_date=start_date,
347
- end_date=end_date,
348
- skills=skills.copy(),
349
- )
350
- dff = dff.sort_values(by='enrolls', ascending=False)
351
- return dff
@@ -1,7 +1,7 @@
1
1
  """
2
2
  Views for enterprise admin api v1.
3
3
  """
4
- from datetime import datetime, timedelta
4
+ from datetime import datetime
5
5
 
6
6
  from edx_rbac.decorators import permission_required
7
7
  from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication
@@ -10,16 +10,11 @@ from rest_framework.response import Response
10
10
  from rest_framework.status import HTTP_200_OK, HTTP_404_NOT_FOUND
11
11
  from rest_framework.views import APIView
12
12
 
13
- from django.http import HttpResponse
14
-
15
13
  from enterprise_data.admin_analytics.data_loaders import fetch_max_enrollment_datetime
16
- from enterprise_data.admin_analytics.database.tables import FactEngagementAdminDashTable, FactEnrollmentAdminDashTable
17
- from enterprise_data.admin_analytics.utils import (
18
- ChartType,
19
- fetch_and_cache_enrollments_data,
20
- fetch_and_cache_skills_data,
21
- get_skills_chart_data,
22
- get_top_skills_csv_data,
14
+ from enterprise_data.admin_analytics.database.tables import (
15
+ FactEngagementAdminDashTable,
16
+ FactEnrollmentAdminDashTable,
17
+ SkillsDailyRollupAdminDashTable,
23
18
  )
24
19
  from enterprise_data.api.v1 import serializers
25
20
  from enterprise_data.models import (
@@ -157,61 +152,35 @@ class EnterpriseAdminAnalyticsSkillsView(APIView):
157
152
  Returns:
158
153
  response(HttpResponse): response object
159
154
  """
160
- serializer = serializers.AdminAnalyticsAggregatesQueryParamsSerializer(
161
- data=request.GET
162
- )
155
+ enterprise_id = enterprise_id.replace('-', '')
156
+ serializer = serializers.AdvanceAnalyticsQueryParamSerializer(data=request.GET)
163
157
  serializer.is_valid(raise_exception=True)
164
- last_updated_at = fetch_max_enrollment_datetime()
165
- cache_expiry = (
166
- last_updated_at + timedelta(days=1) if last_updated_at else datetime.now()
167
- )
168
158
 
169
- enrollment = fetch_and_cache_enrollments_data(
170
- enterprise_id, cache_expiry
171
- ).copy()
159
+ if (start_date := serializer.data.get('start_date')) is None:
160
+ start_date, _ = FactEnrollmentAdminDashTable().get_enrollment_date_range(enterprise_id)
172
161
 
173
- start_date = serializer.data.get('start_date', enrollment.enterprise_enrollment_date.min())
174
162
  end_date = serializer.data.get('end_date', datetime.now())
175
163
 
176
- skills = fetch_and_cache_skills_data(enterprise_id, cache_expiry).copy()
177
-
178
- if serializer.data.get('response_type') == 'csv':
179
- csv_data = get_top_skills_csv_data(skills, start_date, end_date)
180
- response = HttpResponse(content_type='text/csv')
181
- filename = f"Skills by Enrollment and Completion, {start_date} - {end_date}.csv"
182
- response['Content-Disposition'] = f'attachment; filename="{filename}"'
183
- response['Access-Control-Expose-Headers'] = 'Content-Disposition'
184
- csv_data.to_csv(path_or_buf=response, index=False)
185
- return response
186
-
187
- with timer('skills_all_charts_data'):
188
- top_skills = get_skills_chart_data(
189
- chart_type=ChartType.BUBBLE,
190
- start_date=start_date,
191
- end_date=end_date,
192
- skills=skills,
193
- )
194
- top_skills_enrollments = get_skills_chart_data(
195
- chart_type=ChartType.TOP_SKILLS_ENROLLMENT,
196
- start_date=start_date,
197
- end_date=end_date,
198
- skills=skills,
164
+ with timer('top_skills'):
165
+ skills = SkillsDailyRollupAdminDashTable().get_top_skills(enterprise_id, start_date, end_date)
166
+
167
+ with timer('top_skills_by_enrollments'):
168
+ top_skills_by_enrollments = SkillsDailyRollupAdminDashTable().get_top_skills_by_enrollment(
169
+ enterprise_id,
170
+ start_date,
171
+ end_date
199
172
  )
200
- top_skills_by_completions = get_skills_chart_data(
201
- chart_type=ChartType.TOP_SKILLS_COMPLETION,
202
- start_date=start_date,
203
- end_date=end_date,
204
- skills=skills,
173
+ with timer('top_skills_by_completions'):
174
+ top_skills_by_completions = SkillsDailyRollupAdminDashTable().get_top_skills_by_completion(
175
+ enterprise_id,
176
+ start_date,
177
+ end_date
205
178
  )
206
179
 
207
180
  response_data = {
208
- "top_skills": top_skills.to_dict(orient="records"),
209
- "top_skills_by_enrollments": top_skills_enrollments.to_dict(
210
- orient="records"
211
- ),
212
- "top_skills_by_completions": top_skills_by_completions.to_dict(
213
- orient="records"
214
- ),
181
+ "top_skills": skills,
182
+ "top_skills_by_enrollments": top_skills_by_enrollments,
183
+ "top_skills_by_completions": top_skills_by_completions,
215
184
  }
216
185
 
217
186
  return Response(data=response_data, status=HTTP_200_OK)
@@ -537,3 +537,59 @@ def engagements_csv_content():
537
537
  b'paul77@example.org,Synergized reciprocal encoding,2021-07-26,business-management,4.4\r\n'
538
538
  b'samanthaclarke@example.org,Synergized reciprocal encoding,2021-07-19,business-management,0.0\r\n'
539
539
  )
540
+
541
+
542
+ TOP_SKILLS = [
543
+ {
544
+ "skill_name": "Python (Programming Language)",
545
+ "skill_type": "Specialized Skill",
546
+ "enrolls": 19027.0,
547
+ "completions": 3004.0
548
+ },
549
+ {
550
+ "skill_name": "Data Science",
551
+ "skill_type": "Specialized Skill",
552
+ "enrolls": 13756.0,
553
+ "completions": 1517.0
554
+ },
555
+ {
556
+ "skill_name": "Algorithms",
557
+ "skill_type": "Specialized Skill",
558
+ "enrolls": 12776.0,
559
+ "completions": 1640.0
560
+ },
561
+ ]
562
+ TOP_SKILLS_BY_ENROLLMENTS = [
563
+ {
564
+ "skill_name": "Python (Programming Language)",
565
+ "subject_name": "business-management",
566
+ "count": 313.0
567
+ },
568
+ {
569
+ "skill_name": "Machine Learning",
570
+ "subject_name": "business-management",
571
+ "count": 442.0
572
+ },
573
+ {
574
+ "skill_name": "Computer Science",
575
+ "subject_name": "business-management",
576
+ "count": 39.0
577
+ },
578
+ ]
579
+ TOP_SKILLS_BY_COMPLETIONS = [
580
+ {
581
+ "skill_name": "Python (Programming Language)",
582
+ "subject_name": "business-management",
583
+ "count": 21.0
584
+ },
585
+ {
586
+ "skill_name": "SQL (Programming Language)",
587
+ "subject_name": "business-management",
588
+ "count": 11.0
589
+ },
590
+ {
591
+ "skill_name": "Algorithms",
592
+ "subject_name": "business-management",
593
+ "count": 15.0
594
+ },
595
+ ]
@@ -13,13 +13,8 @@ from enterprise_data.admin_analytics.data_loaders import (
13
13
  fetch_engagement_data,
14
14
  fetch_enrollment_data,
15
15
  fetch_max_enrollment_datetime,
16
- fetch_skills_data,
17
- )
18
- from enterprise_data.tests.test_utils import (
19
- get_dummy_engagements_data,
20
- get_dummy_enrollments_data,
21
- get_dummy_skills_data,
22
16
  )
17
+ from enterprise_data.tests.test_utils import get_dummy_engagements_data, get_dummy_enrollments_data
23
18
 
24
19
 
25
20
  class TestDataLoaders(TestCase):
@@ -89,27 +84,3 @@ class TestDataLoaders(TestCase):
89
84
  with pytest.raises(Http404) as error:
90
85
  fetch_enrollment_data(enterprise_uuid)
91
86
  error.value.message = f'No enrollment data found for enterprise {enterprise_uuid}'
92
-
93
- def test_fetch_skills_data(self):
94
- """
95
- Validate the fetch_skills_data function.
96
- """
97
- with patch('enterprise_data.admin_analytics.data_loaders.run_query') as mock_run_query:
98
- enterprise_uuid = str(uuid4())
99
- mock_run_query.return_value = [
100
- list(item.values()) for item in get_dummy_skills_data(enterprise_uuid)
101
- ]
102
-
103
- skills_data = fetch_skills_data(enterprise_uuid)
104
- self.assertEqual(skills_data.shape, (10, 15))
105
-
106
- def test_fetch_skills_data_empty_data(self):
107
- """
108
- Validate the fetch_skills_data function behavior when no data is returned from the query.
109
- """
110
- with patch('enterprise_data.admin_analytics.data_loaders.run_query') as mock_run_query:
111
- mock_run_query.return_value = []
112
- enterprise_uuid = str(uuid4())
113
- with pytest.raises(Http404) as error:
114
- fetch_skills_data(enterprise_uuid)
115
- error.value.message = f'No skills data found for enterprise {enterprise_uuid}'
@@ -3,7 +3,6 @@ Test the utility functions in the admin_analytics app.
3
3
  """
4
4
  from datetime import datetime, timedelta
5
5
 
6
- import pandas
7
6
  from mock import patch
8
7
 
9
8
  from django.test import TestCase
@@ -11,13 +10,8 @@ from django.test import TestCase
11
10
  from enterprise_data.admin_analytics.utils import (
12
11
  fetch_and_cache_engagements_data,
13
12
  fetch_and_cache_enrollments_data,
14
- fetch_and_cache_skills_data,
15
13
  get_cache_timeout,
16
- get_skills_bubble_chart_df,
17
- get_top_skills_completion,
18
- get_top_skills_enrollment,
19
14
  )
20
- from enterprise_data.utils import date_filter
21
15
 
22
16
 
23
17
  class TestUtils(TestCase):
@@ -106,136 +100,3 @@ class TestUtils(TestCase):
106
100
  self.assertEqual(enrollments, 'cached-engagements')
107
101
  self.assertEqual(mock_tiered_cache.get_cached_response.call_count, 1)
108
102
  self.assertEqual(mock_tiered_cache.set_all_tiers.call_count, 0)
109
-
110
- def test_fetch_and_cache_skills_data(self):
111
- """
112
- Validate the fetch_and_cache_skills_data function.
113
- """
114
- with patch('enterprise_data.admin_analytics.utils.fetch_skills_data') as mock_fetch_skills_data:
115
- with patch('enterprise_data.admin_analytics.utils.TieredCache') as mock_tiered_cache:
116
- # Simulate the scenario where the data is not found in the cache.
117
- mock_tiered_cache.get_cached_response.return_value.is_found = False
118
- mock_fetch_skills_data.return_value = 'skills'
119
- skills = fetch_and_cache_skills_data('enterprise_id', datetime.now() + timedelta(seconds=10))
120
- self.assertEqual(skills, 'skills')
121
- self.assertEqual(mock_tiered_cache.get_cached_response.call_count, 1)
122
- self.assertEqual(mock_tiered_cache.set_all_tiers.call_count, 1)
123
-
124
- def test_fetch_and_cache_skills_data_with_data_cache_found(self):
125
- """
126
- Validate the fetch_and_cache_skills_data function.
127
- """
128
- with patch('enterprise_data.admin_analytics.utils.fetch_skills_data') as mock_fetch_skills_data:
129
- with patch('enterprise_data.admin_analytics.utils.TieredCache') as mock_tiered_cache:
130
- # Simulate the scenario where the data is found in the cache.
131
- mock_tiered_cache.get_cached_response.return_value.is_found = True
132
- mock_tiered_cache.get_cached_response.return_value.value = 'cached-skills'
133
- mock_fetch_skills_data.return_value = 'skills'
134
-
135
- skills = fetch_and_cache_skills_data('enterprise_id', datetime.now() + timedelta(seconds=10))
136
- self.assertEqual(skills, 'cached-skills')
137
- self.assertEqual(mock_tiered_cache.get_cached_response.call_count, 1)
138
- self.assertEqual(mock_tiered_cache.set_all_tiers.call_count, 0)
139
-
140
- def test_get_skills_bubble_chart_df(self):
141
- """
142
- Validate the get_skills_bubble_chart_df function.
143
- """
144
- # Mock skills data
145
- skills = pandas.DataFrame({
146
- 'skill_name': ['Skill A', 'Skill B', 'Skill C'],
147
- 'skill_type': ['Type 1', 'Type 2', 'Type 1'],
148
- 'enrolls': [100, 200, 150],
149
- 'completions': [50, 100, 75],
150
- 'date': [datetime.now(), datetime.now(), datetime.now()],
151
- })
152
-
153
- # Define the expected result
154
- expected_result = pandas.DataFrame({
155
- 'skill_name': ['Skill B', 'Skill C', 'Skill A'],
156
- 'skill_type': ['Type 2', 'Type 1', 'Type 1'],
157
- 'enrolls': [200, 150, 100],
158
- 'completions': [100, 75, 50]
159
- })
160
- # Reset the index for the expected result
161
- expected_result = expected_result.reset_index(drop=True)
162
-
163
- start_date = datetime.now() - timedelta(days=10)
164
- end_date = datetime.now()
165
- filtered_skills = date_filter(start_date, end_date, skills, 'date')
166
- # Call the function
167
- result = get_skills_bubble_chart_df(filtered_skills)
168
-
169
- # Reset the index for the result
170
- result = result.reset_index(drop=True)
171
-
172
- # Assert the result
173
- pandas.testing.assert_frame_equal(result, expected_result)
174
-
175
- def test_get_top_skills_enrollment(self):
176
- """
177
- Validate the get_top_skills_enrollment function.
178
- """
179
- # Mock skills data
180
- skills = pandas.DataFrame({
181
- 'primary_subject_name': ['engineering', 'communication', 'engineering', 'other'],
182
- 'skill_name': ['Skill A', 'Skill B', 'Skill A', 'Skill D'],
183
- 'enrolls': [100, 200, 150, 300],
184
- 'completions': [50, 100, 75, 150],
185
- 'date': [datetime.now(), datetime.now(), datetime.now(), datetime.now()],
186
- })
187
-
188
- # Define the expected result
189
- expected_result = pandas.DataFrame({
190
- 'skill_name': ['Skill B', 'Skill A', 'Skill D'],
191
- 'primary_subject_name': ['communication', 'engineering', 'other'],
192
- 'count': [200, 250, 300]
193
- })
194
- # Reset the index for the expected result
195
- expected_result = expected_result.reset_index(drop=True)
196
- start_date = datetime.now() - timedelta(days=10)
197
- end_date = datetime.now()
198
- filtered_skills = date_filter(start_date, end_date, skills, 'date')
199
-
200
- # Call the function
201
- result = get_top_skills_enrollment(filtered_skills)
202
-
203
- # Reset the index for the result
204
- result = result.reset_index(drop=True)
205
-
206
- # Assert the result
207
- pandas.testing.assert_frame_equal(result, expected_result)
208
-
209
- def test_get_top_skills_completion(self):
210
- """
211
- Validate the get_top_skills_completion function.
212
- """
213
- # Mock skills data
214
- skills = pandas.DataFrame({
215
- 'skill_name': ['Skill A', 'Skill B', 'Skill C', 'Skill D'],
216
- 'primary_subject_name': ['communication', 'engineering', 'communication', 'other'],
217
- 'enrolls': [100, 200, 150, 300],
218
- 'completions': [50, 100, 75, 150],
219
- 'date': [datetime.now(), datetime.now(), datetime.now(), datetime.now()]
220
- })
221
-
222
- # Define the expected result
223
- expected_result = pandas.DataFrame({
224
- 'skill_name': ['Skill A', 'Skill C', 'Skill B', 'Skill D'],
225
- 'primary_subject_name': ['communication', 'communication', 'engineering', 'other'],
226
- 'count': [50, 75, 100, 150]
227
- })
228
- # Reset the index for the expected result
229
- expected_result = expected_result.reset_index(drop=True)
230
- start_date = datetime.now() - timedelta(days=10)
231
- end_date = datetime.now()
232
- filtered_skills = date_filter(start_date, end_date, skills, 'date')
233
-
234
- # Call the function
235
- result = get_top_skills_completion(filtered_skills)
236
-
237
- # Reset the index for the result
238
- result = result.reset_index(drop=True)
239
-
240
- # Assert the result
241
- pandas.testing.assert_frame_equal(result, expected_result)
@@ -3,7 +3,6 @@ Test cases for enterprise_admin views
3
3
  """
4
4
  from datetime import datetime
5
5
  from unittest import mock
6
- from uuid import uuid4
7
6
 
8
7
  import ddt
9
8
  from mock import patch
@@ -16,13 +15,13 @@ from enterprise_data.admin_analytics.database.queries import (
16
15
  FactEngagementAdminDashQueries,
17
16
  FactEnrollmentAdminDashQueries,
18
17
  )
19
- from enterprise_data.tests.mixins import JWTTestMixin
20
- from enterprise_data.tests.test_utils import (
21
- UserFactory,
22
- get_dummy_enrollments_data,
23
- get_dummy_enterprise_api_data,
24
- get_dummy_skills_data,
18
+ from enterprise_data.tests.admin_analytics.mock_analytics_data import (
19
+ TOP_SKILLS,
20
+ TOP_SKILLS_BY_COMPLETIONS,
21
+ TOP_SKILLS_BY_ENROLLMENTS,
25
22
  )
23
+ from enterprise_data.tests.mixins import JWTTestMixin
24
+ from enterprise_data.tests.test_utils import UserFactory, get_dummy_enterprise_api_data
26
25
  from enterprise_data_roles.constants import ENTERPRISE_DATA_ADMIN_ROLE
27
26
  from enterprise_data_roles.models import EnterpriseDataFeatureRole, EnterpriseDataRoleAssignment
28
27
 
@@ -108,51 +107,141 @@ class TestEnterpriseAdminAnalyticsAggregatesView(JWTTestMixin, APITransactionTes
108
107
 
109
108
 
110
109
  @ddt.ddt
111
- @mark.django_db
112
- class TestEnterpriseAdminAnalyticsSkillsView(JWTTestMixin, APITransactionTestCase):
113
- """
114
- Tests for EnterpriseAdminAnalyticsSkillsView.
115
- """
110
+ class TestSkillsStatsAPI(JWTTestMixin, APITransactionTestCase):
111
+ """Tests for EnterpriseAdminAnalyticsSkillsView."""
116
112
 
117
113
  def setUp(self):
118
114
  """
119
115
  Setup method.
120
116
  """
121
117
  super().setUp()
122
- self.user = UserFactory()
123
- self.enterprise_id = uuid4()
124
- self.set_jwt_cookie(context=f'{self.enterprise_id}')
118
+ self.user = UserFactory(is_staff=True)
119
+ role, __ = EnterpriseDataFeatureRole.objects.get_or_create(
120
+ name=ENTERPRISE_DATA_ADMIN_ROLE
121
+ )
122
+ self.role_assignment = EnterpriseDataRoleAssignment.objects.create(
123
+ role=role, user=self.user
124
+ )
125
+ self.client.force_authenticate(user=self.user)
125
126
 
126
- def _mock_run_query(self, query):
127
+ self.enterprise_uuid = "ee5e6b3a069a4947bb8dd2dbc323396c"
128
+ self.set_jwt_cookie()
129
+
130
+ self.url = reverse(
131
+ "v1:enterprise-admin-analytics-skills",
132
+ kwargs={"enterprise_id": self.enterprise_uuid},
133
+ )
134
+
135
+ @patch('enterprise_data.api.v1.views.enterprise_admin.FactEnrollmentAdminDashTable.get_enrollment_date_range')
136
+ @patch('enterprise_data.api.v1.views.enterprise_admin.SkillsDailyRollupAdminDashTable.get_top_skills')
137
+ @patch('enterprise_data.api.v1.views.enterprise_admin.SkillsDailyRollupAdminDashTable.get_top_skills_by_enrollment')
138
+ @patch('enterprise_data.api.v1.views.enterprise_admin.SkillsDailyRollupAdminDashTable.get_top_skills_by_completion')
139
+ def test_get(
140
+ self,
141
+ mock_get_top_skills_by_completion,
142
+ mock_get_top_skills_by_enrollment,
143
+ mock_get_top_skills,
144
+ mock_get_enrollment_date_range
145
+ ):
127
146
  """
128
- mock implementation of run_query.
147
+ Test the GET method for the EnterpriseAdminAnalyticsSkillsView works.
129
148
  """
130
- if 'skills_daily_rollup_admin_dash' in query:
131
- return [
132
- list(item.values()) for item in get_dummy_skills_data(self.enterprise_id)
133
- ]
134
- elif 'fact_enrollment_admin_dash' in query:
135
- return [
136
- list(item.values()) for item in get_dummy_enrollments_data(self.enterprise_id, 15)
137
- ]
138
- else:
139
- return [
140
- list(item.values()) for item in get_dummy_skills_data(self.enterprise_id)
141
- ]
142
-
143
- def test_get_admin_analytics_skills(self):
149
+ mock_get_enrollment_date_range.return_value = ("2020-04-03", "2024-07-04")
150
+ mock_get_top_skills.return_value = TOP_SKILLS
151
+ mock_get_top_skills_by_enrollment.return_value = TOP_SKILLS_BY_ENROLLMENTS
152
+ mock_get_top_skills_by_completion.return_value = TOP_SKILLS_BY_COMPLETIONS
153
+
154
+ response = self.client.get(self.url)
155
+ assert response.status_code == status.HTTP_200_OK
156
+ data = response.json()
157
+ assert data == {
158
+ "top_skills": [
159
+ {
160
+ "skill_name": "Python (Programming Language)",
161
+ "skill_type": "Specialized Skill",
162
+ "enrolls": 19027.0,
163
+ "completions": 3004.0,
164
+ },
165
+ {
166
+ "skill_name": "Data Science",
167
+ "skill_type": "Specialized Skill",
168
+ "enrolls": 13756.0,
169
+ "completions": 1517.0,
170
+ },
171
+ {
172
+ "skill_name": "Algorithms",
173
+ "skill_type": "Specialized Skill",
174
+ "enrolls": 12776.0,
175
+ "completions": 1640.0,
176
+ },
177
+ ],
178
+ "top_skills_by_enrollments": [
179
+ {
180
+ "skill_name": "Python (Programming Language)",
181
+ "subject_name": "business-management",
182
+ "count": 313.0,
183
+ },
184
+ {
185
+ "skill_name": "Machine Learning",
186
+ "subject_name": "business-management",
187
+ "count": 442.0,
188
+ },
189
+ {
190
+ "skill_name": "Computer Science",
191
+ "subject_name": "business-management",
192
+ "count": 39.0,
193
+ },
194
+ ],
195
+ "top_skills_by_completions": [
196
+ {
197
+ "skill_name": "Python (Programming Language)",
198
+ "subject_name": "business-management",
199
+ "count": 21.0,
200
+ },
201
+ {
202
+ "skill_name": "SQL (Programming Language)",
203
+ "subject_name": "business-management",
204
+ "count": 11.0,
205
+ },
206
+ {
207
+ "skill_name": "Algorithms",
208
+ "subject_name": "business-management",
209
+ "count": 15.0,
210
+ },
211
+ ],
212
+ }
213
+
214
+ @ddt.data(
215
+ {
216
+ "params": {"start_date": 1},
217
+ "error": {
218
+ "start_date": [
219
+ "Date has wrong format. Use one of these formats instead: YYYY-MM-DD."
220
+ ]
221
+ },
222
+ },
223
+ {
224
+ "params": {"end_date": 2},
225
+ "error": {
226
+ "end_date": [
227
+ "Date has wrong format. Use one of these formats instead: YYYY-MM-DD."
228
+ ]
229
+ },
230
+ },
231
+ {
232
+ "params": {"start_date": "2024-01-01", "end_date": "2023-01-01"},
233
+ "error": {
234
+ "non_field_errors": [
235
+ "start_date should be less than or equal to end_date."
236
+ ]
237
+ },
238
+ },
239
+ )
240
+ @ddt.unpack
241
+ def test_get_invalid_query_params(self, params, error):
144
242
  """
145
- Test to get admin analytics skills.
243
+ Test the GET method return correct error if any query param value is incorrect.
146
244
  """
147
- params = {'start_date': '2021-01-01', 'end_date': '2025-12-31'}
148
- url = reverse('v1:enterprise-admin-analytics-skills', kwargs={'enterprise_id': self.enterprise_id})
149
- with patch('enterprise_data.admin_analytics.data_loaders.run_query', side_effect=self._mock_run_query):
150
- response = self.client.get(url, params)
151
- assert response.status_code == status.HTTP_200_OK
152
- data = response.json()
153
- assert 'top_skills' in data
154
- assert 'top_skills_by_enrollments' in data
155
- assert 'top_skills_by_completions' in data
156
- assert len(data['top_skills']) == 10
157
- assert len(data['top_skills_by_enrollments']) == 10
158
- assert len(data['top_skills_by_completions']) == 10
245
+ response = self.client.get(self.url, params)
246
+ assert response.status_code == status.HTTP_400_BAD_REQUEST
247
+ assert response.json() == error