edx-enterprise-data 8.4.0__py3-none-any.whl → 8.6.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.4.0
3
+ Version: 8.6.0
4
4
  Summary: Enterprise Reporting
5
5
  Home-page: https://github.com/openedx/edx-enterprise-data
6
6
  Author: edX
@@ -1,18 +1,19 @@
1
- enterprise_data/__init__.py,sha256=V3f8VWSTHKMmk7VgKNYsFMadFosPMVNgWxHub9YqP9c,123
1
+ enterprise_data/__init__.py,sha256=EPPAk93ePtVZFtG1D6BcUQ1SPs07JiPQCLJhZ41gW-Y,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
- enterprise_data/models.py,sha256=3VHJ5eWfI0KZxh5u969E7uA2nE5VZqyUnk_VV--UTI4,19154
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=WVt0qy9Ippdnl404Zsq-MruB9oQfY6h87ZzpScYBeaw,1770
8
+ enterprise_data/renderers.py,sha256=eh-FZFbP_yWcfDemavUGB7vYIJA2PjW_dvM79qxYZz8,2085
9
9
  enterprise_data/signals.py,sha256=8eqNPnlvmfsKf19lGWv5xTIuBgQIqR8EZSp9UYzC8Rc,1024
10
10
  enterprise_data/urls.py,sha256=bqtKF5OEWEwrNmHG3os-pZNuNsmjlhxEqp7yM4TbPf4,243
11
11
  enterprise_data/utils.py,sha256=kNO4nW_GBpBiIBlVUkCb4Xo0k1oVshT8nDOBP5eWoV8,2643
12
12
  enterprise_data/admin_analytics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
+ enterprise_data/admin_analytics/constants.py,sha256=6Gc9rP-J3nGgAk3fhzNlyR1HMq8Apjxs9ViWJiYrri4,722
13
14
  enterprise_data/admin_analytics/data_loaders.py,sha256=x1XNYdtJV1G9cv0SeBZqYitRV8-GlJXtEZ2cc2OJU7M,5415
14
15
  enterprise_data/admin_analytics/database.py,sha256=mNS_9xE5h6O7oMMzr6kr6LDTTSNvKzo8vaM-YG8tOd8,1312
15
- enterprise_data/admin_analytics/utils.py,sha256=J6E9JhZedWkJQ9TJJF_z95sL5Oka1_x7gs_st4Z35Yo,8420
16
+ enterprise_data/admin_analytics/utils.py,sha256=65HjDdNJkQTHqS6KatHE4UpEzKDMj2_v21O-YBf130Q,10023
16
17
  enterprise_data/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
18
  enterprise_data/api/urls.py,sha256=POqc_KATHdnpMf9zHtpO46pKD5KAlAExtx7G6iylLcU,273
18
19
  enterprise_data/api/v0/__init__.py,sha256=1aAzAYU5hk-RW6cKUxa1645cbZMxn7GIZ7OMjWc9MKI,46
@@ -20,11 +21,13 @@ enterprise_data/api/v0/serializers.py,sha256=dngZTk6DhRxApchQKCMp1B_c8aVnQtH0NCq
20
21
  enterprise_data/api/v0/urls.py,sha256=vzJjqIo_S3AXWs9Us8XTaJc3FnxLbYzAkmLyuDQqum0,699
21
22
  enterprise_data/api/v0/views.py,sha256=4RslZ4NZOU-844bnebEQ71ji2utRY7jEijqC45oQQD0,14380
22
23
  enterprise_data/api/v1/__init__.py,sha256=1aAzAYU5hk-RW6cKUxa1645cbZMxn7GIZ7OMjWc9MKI,46
23
- enterprise_data/api/v1/serializers.py,sha256=AOHumabnzwK59nytoOZEWhA3f9EL_CV-bXPsYSH3LI8,8263
24
- enterprise_data/api/v1/urls.py,sha256=m5B01cZuInVsJkRdUloHC45fHxSJKQMSzBSCgU2GpkA,1855
24
+ enterprise_data/api/v1/paginators.py,sha256=OHbuBP7hAFJ_ce0UAMfJ1pARMMzqvzVYiYeFMw3xZLU,3592
25
+ enterprise_data/api/v1/serializers.py,sha256=oNLxBbHa6CJ7d7mdA0hpmLFjKMe-S6nOIguQkEu_D4Y,11723
26
+ enterprise_data/api/v1/urls.py,sha256=wMw-h0NlkOgS1HYUm2FqGOLA-BHBdFZYaL-QNZn0T60,2635
25
27
  enterprise_data/api/v1/views/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
+ enterprise_data/api/v1/views/analytics_enrollments.py,sha256=SvuK53i_4S3etqktoJ0h5ky4dntD176u6DXZuqHTsEg,16352
26
29
  enterprise_data/api/v1/views/base.py,sha256=FTAxlz5EzvAY657wzVgzhJPFSCHHzct7IDcvm71Smt8,866
27
- enterprise_data/api/v1/views/enterprise_admin.py,sha256=hBUj5XPyyBwpyNED62lLlV1etRK9vHRS6aYnZLTF2w0,8081
30
+ enterprise_data/api/v1/views/enterprise_admin.py,sha256=RTSRyPfHvbzV_ihSbGjYi0VuE6AjaYZIyqpAKTMYa5Q,8980
28
31
  enterprise_data/api/v1/views/enterprise_learner.py,sha256=yABjJje3CT8I8YOhWr1_tTkdKtnGJom8eu3EFz_-0BU,18517
29
32
  enterprise_data/api/v1/views/enterprise_offers.py,sha256=VifxgqTLFLVw4extYPlHcN1N_yjXcsYsAlYEnAbpb10,1266
30
33
  enterprise_data/fixtures/enterprise_enrollment.json,sha256=6onPXXR29pMdTdbl_mn81sDi3Re5jkLUZz2TPMB_1IY,5786
@@ -85,6 +88,7 @@ enterprise_data/migrations/0037_alter_enterpriseenrollment_consent_granted.py,sh
85
88
  enterprise_data/migrations/0038_enterpriseoffer_export_timestamp.py,sha256=8St3DsKb-VI3LGGC-Mc7OvmFn67kVob0HKCPGKcjvu4,436
86
89
  enterprise_data/migrations/0039_auto_20240212_1403.py,sha256=rMiJcYx26ZGqPdQzgiWkUgbcRhD-tp9BgXdgt67vC2Q,1002
87
90
  enterprise_data/migrations/0040_auto_20240718_0536_squashed_0043_alter_enterpriselearnerenrollment_enterprise_enrollment_id.py,sha256=Kq_ResqoAsJjtyIysHjs4VaDxNh6p4fPMMyh93GIJxA,1693
91
+ enterprise_data/migrations/0044_enterpriseexecedlcmoduleperformance.py,sha256=1aJ0wIu9K2cT3AJdBIHuNCUpJysPWmq_37Ucp5ntEO8,6122
88
92
  enterprise_data/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
89
93
  enterprise_data/settings/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
90
94
  enterprise_data/settings/test.py,sha256=4-flfrlf81AthGx9wTaT5PscyoOWyhsDDqbzBl-z7Eg,4191
@@ -97,6 +101,8 @@ enterprise_data/tests/test_models.py,sha256=MWBY-LY5TPBjZ4GlvpM-h4W-BvRKr2Rml8Bz
97
101
  enterprise_data/tests/test_utils.py,sha256=vbmYM7DMN-lHS2p4yaa0Yd6uSGXd2qoZRDE9X3J4Sec,18385
98
102
  enterprise_data/tests/test_views.py,sha256=UvDRNTxruy5zBK_KgUy2cBMbwlaTW_vkM0-TCXbQZiY,69667
99
103
  enterprise_data/tests/admin_analytics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
104
+ enterprise_data/tests/admin_analytics/mock_enrollments.py,sha256=ApRzq6LxdAqGNNZqGoPSdLbGCf6R-z8qL8FoRf4wvvs,6712
105
+ enterprise_data/tests/admin_analytics/test_analytics_enrollments.py,sha256=WNdJCM52zUJikBTl3VakPIvNiVNvUed-8vk275njSdY,14847
100
106
  enterprise_data/tests/admin_analytics/test_data_loaders.py,sha256=o3denJ4aUS1pI5Crksl4C6m-NtCBm8ynoHBnLkf-v2U,4641
101
107
  enterprise_data/tests/admin_analytics/test_utils.py,sha256=y33HXy6BDOoftdcz3qYlOYhgx7JSXDki-OLzBdTpiwA,11449
102
108
  enterprise_data/tests/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -147,8 +153,8 @@ enterprise_reporting/tests/test_send_enterprise_reports.py,sha256=WtL-RqGgu2x5PP
147
153
  enterprise_reporting/tests/test_utils.py,sha256=Zt_TA0LVb-B6fQGkUkAKKVlUKKnQh8jnw1US1jKe7g8,9493
148
154
  enterprise_reporting/tests/test_vertica_client.py,sha256=-R2yNCGUjRtoXwLMBloVFQkFYrJoo613VCr61gwI3kQ,140
149
155
  enterprise_reporting/tests/utils.py,sha256=xms2LM7DV3wczXEfctOK1ddel1EE0J_YSr17UzbCDy4,1401
150
- edx_enterprise_data-8.4.0.dist-info/LICENSE,sha256=dql8h4yceoMhuzlcK0TT_i-NgTFNIZsgE47Q4t3dUYI,34520
151
- edx_enterprise_data-8.4.0.dist-info/METADATA,sha256=o0b8zqSTviYu9WVM9aDEK6ozyC_PBJlKxUQJC3fNZic,1569
152
- edx_enterprise_data-8.4.0.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
153
- edx_enterprise_data-8.4.0.dist-info/top_level.txt,sha256=f5F2kU-dob6MqiHJpgZkFzoCD5VMhsdpkTV5n9Tvq3I,59
154
- edx_enterprise_data-8.4.0.dist-info/RECORD,,
156
+ edx_enterprise_data-8.6.0.dist-info/LICENSE,sha256=dql8h4yceoMhuzlcK0TT_i-NgTFNIZsgE47Q4t3dUYI,34520
157
+ edx_enterprise_data-8.6.0.dist-info/METADATA,sha256=UT5OiAzT8dXg3nZ9psbT8eqAbGjEnDD1GjRb8YEitv8,1569
158
+ edx_enterprise_data-8.6.0.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
159
+ edx_enterprise_data-8.6.0.dist-info/top_level.txt,sha256=f5F2kU-dob6MqiHJpgZkFzoCD5VMhsdpkTV5n9Tvq3I,59
160
+ edx_enterprise_data-8.6.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.4.0"
5
+ __version__ = "8.6.0"
@@ -0,0 +1,27 @@
1
+ """Advanced Analytics Constants"""
2
+
3
+ from enum import Enum
4
+
5
+
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
+ class ENROLLMENT_CSV(Enum):
23
+ """CSV choices"""
24
+ ENROLLMENTS_OVER_TIME = 'enrollments_over_time'
25
+ TOP_COURSES_BY_ENROLLMENTS = 'top_courses_by_enrollments'
26
+ TOP_SUBJECTS_BY_ENROLLMENTS = 'top_subjects_by_enrollments'
27
+ INDIVIDUAL_ENROLLMENTS = 'individual_enrollments'
@@ -6,6 +6,7 @@ from enum import Enum
6
6
 
7
7
  from edx_django_utils.cache import TieredCache, get_cache_key
8
8
 
9
+ from enterprise_data.admin_analytics.constants import CALCULATION, GRANULARITY
9
10
  from enterprise_data.admin_analytics.data_loaders import fetch_engagement_data, fetch_enrollment_data, fetch_skills_data
10
11
  from enterprise_data.utils import date_filter, primary_subject_truncate
11
12
 
@@ -19,6 +20,54 @@ class ChartType(Enum):
19
20
  TOP_SKILLS_COMPLETION = 'top_skills_completion'
20
21
 
21
22
 
23
+ def granularity_aggregation(level, group, date, data_frame, aggregation_type="count"):
24
+ """Aggregate data based on granularity"""
25
+ df = data_frame
26
+
27
+ period_mapping = {
28
+ GRANULARITY.WEEKLY.value: "W",
29
+ GRANULARITY.MONTHLY.value: "M",
30
+ GRANULARITY.QUARTERLY.value: "Q"
31
+ }
32
+
33
+ if level in period_mapping:
34
+ df[date] = df[date].dt.to_period(period_mapping[level]).dt.start_time
35
+
36
+ agg_column_name = "count"
37
+ if aggregation_type == "count":
38
+ df = df.groupby(group).size().reset_index()
39
+ elif aggregation_type == "sum":
40
+ df = df.groupby(group).sum().reset_index()
41
+ agg_column_name = "sum"
42
+
43
+ df.columns = group + [agg_column_name]
44
+ return df
45
+
46
+
47
+ def calculation_aggregation(calc, data_frame, aggregation_type="count"):
48
+ """Aggregate data based on calculation"""
49
+ df = data_frame
50
+
51
+ window_mapping = {
52
+ CALCULATION.MOVING_AVERAGE_3_PERIOD.value: 3,
53
+ CALCULATION.MOVING_AVERAGE_7_PERIOD.value: 7,
54
+ }
55
+
56
+ aggregation_column = "count" if aggregation_type == "count" else "sum"
57
+
58
+ if calc == CALCULATION.RUNNING_TOTAL.value:
59
+ df[aggregation_column] = df.groupby("enroll_type")[aggregation_column].cumsum()
60
+ elif calc in [CALCULATION.MOVING_AVERAGE_3_PERIOD.value, CALCULATION.MOVING_AVERAGE_7_PERIOD.value]:
61
+ df[aggregation_column] = (
62
+ df.groupby("enroll_type")[aggregation_column]
63
+ .rolling(window_mapping[calc])
64
+ .mean()
65
+ .droplevel(level=[0])
66
+ )
67
+
68
+ return df
69
+
70
+
22
71
  def get_cache_timeout(cache_expiry):
23
72
  """
24
73
  Helper method to calculate cache timeout in seconds.
@@ -0,0 +1,121 @@
1
+ """Custom paginator for the Advance Analytics API."""
2
+
3
+ import math
4
+ from dataclasses import dataclass
5
+ from typing import Any
6
+
7
+ from rest_framework.exceptions import NotFound
8
+ from rest_framework.pagination import PageNumberPagination
9
+ from rest_framework.response import Response
10
+
11
+
12
+ @dataclass
13
+ class Page:
14
+ """
15
+ A class representing a single page of paginated data.
16
+
17
+ Attributes:
18
+ data (Any): The data contained in the current page.
19
+ count (int): The total number of items across all pages.
20
+ num_pages (int): The total number of pages.
21
+ current_page (int): The current page number.
22
+ """
23
+ data: Any
24
+ count: int
25
+ num_pages: int
26
+ current_page: int
27
+
28
+ def has_next(self):
29
+ """
30
+ Check if there is a next page.
31
+
32
+ Returns:
33
+ bool: True if there is a next page, False otherwise.
34
+ """
35
+ return self.current_page < self.num_pages
36
+
37
+ def has_previous(self):
38
+ """
39
+ Check if there is a previous page.
40
+
41
+ Returns:
42
+ bool: True if there is a previous page, False otherwise.
43
+ """
44
+ return self.current_page > 1
45
+
46
+ def next_page_number(self):
47
+ """
48
+ Get the next page number.
49
+
50
+ Returns:
51
+ int: The next page number.
52
+ """
53
+ return self.current_page + 1
54
+
55
+ def previous_page_number(self):
56
+ """
57
+ Get the previous page number.
58
+
59
+ Returns:
60
+ int: The previous page number.
61
+ """
62
+ return self.current_page - 1
63
+
64
+
65
+ class AdvanceAnalyticsPagination(PageNumberPagination):
66
+ """
67
+ Custom pagination class for advanced analytics.
68
+
69
+ Attributes:
70
+ page_size_query_param (str): The query parameter for the page size.
71
+ page_size (int): The default page size.
72
+ max_page_size (int): The maximum allowed page size.
73
+ """
74
+ page_size_query_param = "page_size"
75
+ page_size = 10
76
+ max_page_size = 100
77
+
78
+ def paginate_queryset(self, queryset, request, view=None):
79
+ """
80
+ Paginate a given dataframe based on the request parameters.
81
+
82
+ Args:
83
+ queryset (pd.DataFrame): The dataframe to paginate.
84
+ request (Request): The request object containing query parameters.
85
+ view (View, optional): The view that is calling the paginator.
86
+
87
+ Returns:
88
+ Page: A Page object. `data` attribute of the object will contain the paginated data.
89
+ """
90
+ dataframe = queryset
91
+
92
+ self.request = request # pylint: disable=attribute-defined-outside-init
93
+ page_size = self.get_page_size(request)
94
+ if not page_size:
95
+ return None
96
+
97
+ total_rows = dataframe.shape[0]
98
+ num_pages = math.ceil(total_rows / page_size)
99
+
100
+ page_number = int(request.query_params.get(self.page_query_param) or 1)
101
+ if page_number <= 0 or page_number > num_pages:
102
+ raise NotFound('Invalid page.')
103
+
104
+ start_index = (page_number - 1) * page_size
105
+ end_index = min(start_index + page_size, total_rows)
106
+ data_frame_page = dataframe.iloc[start_index:end_index]
107
+
108
+ # pylint: disable=attribute-defined-outside-init
109
+ self.page = Page(data_frame_page, total_rows, num_pages, page_number)
110
+
111
+ return self.page
112
+
113
+ def get_paginated_response(self, data):
114
+ return Response({
115
+ 'next': self.get_next_link(),
116
+ 'previous': self.get_previous_link(),
117
+ 'count': self.page.count,
118
+ 'num_pages': self.page.num_pages,
119
+ 'current_page': self.page.current_page,
120
+ 'results': data
121
+ })
@@ -5,9 +5,11 @@ from uuid import UUID
5
5
 
6
6
  from rest_framework import serializers
7
7
 
8
+ from enterprise_data.admin_analytics.constants import CALCULATION, ENROLLMENT_CSV, GRANULARITY
8
9
  from enterprise_data.models import (
9
10
  EnterpriseAdminLearnerProgress,
10
11
  EnterpriseAdminSummarizeInsights,
12
+ EnterpriseExecEdLCModulePerformance,
11
13
  EnterpriseLearner,
12
14
  EnterpriseLearnerEnrollment,
13
15
  EnterpriseOffer,
@@ -216,3 +218,102 @@ class AdminAnalyticsAggregatesQueryParamsSerializer(serializers.Serializer): #
216
218
  if attrs['start_date'] > attrs['end_date']:
217
219
  raise serializers.ValidationError("start_date should be less than or equal to end_date.")
218
220
  return attrs
221
+
222
+
223
+ class EnterpriseExecEdLCModulePerformanceSerializer(serializers.ModelSerializer):
224
+ """
225
+ Serializer for EnterpriseExecEdLCModulePerformance model.
226
+ """
227
+
228
+ class Meta:
229
+ model = EnterpriseExecEdLCModulePerformance
230
+ fields = '__all__'
231
+
232
+
233
+ class AdvanceAnalyticsQueryParamSerializer(serializers.Serializer): # pylint: disable=abstract-method
234
+ """Serializer for validating query params"""
235
+ GRANULARITY_CHOICES = [
236
+ GRANULARITY.DAILY.value,
237
+ GRANULARITY.WEEKLY.value,
238
+ GRANULARITY.MONTHLY.value,
239
+ GRANULARITY.QUARTERLY.value
240
+ ]
241
+ CALCULATION_CHOICES = [
242
+ CALCULATION.TOTAL.value,
243
+ CALCULATION.RUNNING_TOTAL.value,
244
+ CALCULATION.MOVING_AVERAGE_3_PERIOD.value,
245
+ CALCULATION.MOVING_AVERAGE_7_PERIOD.value
246
+ ]
247
+
248
+ start_date = serializers.DateField(required=False)
249
+ end_date = serializers.DateField(required=False)
250
+ granularity = serializers.CharField(required=False)
251
+ calculation = serializers.CharField(required=False)
252
+
253
+ def validate(self, attrs):
254
+ """
255
+ Validate the query params.
256
+
257
+ Raises:
258
+ serializers.ValidationError: If start_date is greater than end_date.
259
+ """
260
+ start_date = attrs.get('start_date')
261
+ end_date = attrs.get('end_date')
262
+
263
+ if start_date and end_date and start_date > end_date:
264
+ raise serializers.ValidationError("start_date should be less than or equal to end_date.")
265
+
266
+ return attrs
267
+
268
+ def validate_granularity(self, value):
269
+ """
270
+ Validate the granularity value.
271
+
272
+ Raises:
273
+ serializers.ValidationError: If granularity is not one of the valid choices.
274
+ """
275
+ if value not in self.GRANULARITY_CHOICES:
276
+ raise serializers.ValidationError(f"Granularity must be one of {self.GRANULARITY_CHOICES}")
277
+ return value
278
+
279
+ def validate_calculation(self, value):
280
+ """
281
+ Validate the calculation value.
282
+
283
+ Raises:
284
+ serializers.ValidationError: If calculation is not one of the valid choices
285
+ """
286
+ if value not in self.CALCULATION_CHOICES:
287
+ raise serializers.ValidationError(f"Calculation must be one of {self.CALCULATION_CHOICES}")
288
+ return value
289
+
290
+
291
+ class AdvanceAnalyticsEnrollmentSerializer(AdvanceAnalyticsQueryParamSerializer): # pylint: disable=abstract-method
292
+ """Serializer for validating Advance Analytics Enrollments API"""
293
+ CSV_TYPES = [
294
+ ENROLLMENT_CSV.INDIVIDUAL_ENROLLMENTS.value
295
+ ]
296
+
297
+ csv_type = serializers.CharField(required=False)
298
+
299
+ def validate_csv_type(self, value):
300
+ """
301
+ Validate the csv_type value.
302
+
303
+ Raises:
304
+ serializers.ValidationError: If csv_type is not one of the valid choices
305
+ """
306
+ if value not in self.CSV_TYPES:
307
+ raise serializers.ValidationError(f"csv_type must be one of {self.CSV_TYPES}")
308
+ return value
309
+
310
+
311
+ class AdvanceAnalyticsEnrollmentStatsSerializer(
312
+ AdvanceAnalyticsEnrollmentSerializer
313
+ ): # pylint: disable=abstract-method
314
+ """Serializer for validating Advance Analytics Enrollments Stats API"""
315
+ CSV_TYPES = [
316
+ ENROLLMENT_CSV.ENROLLMENTS_OVER_TIME.value,
317
+ ENROLLMENT_CSV.TOP_COURSES_BY_ENROLLMENTS.value,
318
+ ENROLLMENT_CSV.TOP_SUBJECTS_BY_ENROLLMENTS.value
319
+ ]
@@ -10,6 +10,10 @@ from django.urls import re_path
10
10
  from enterprise_data.api.v1.views import enterprise_admin as enterprise_admin_views
11
11
  from enterprise_data.api.v1.views import enterprise_learner as enterprise_learner_views
12
12
  from enterprise_data.api.v1.views import enterprise_offers as enterprise_offers_views
13
+ from enterprise_data.api.v1.views.analytics_enrollments import (
14
+ AdvanceAnalyticsEnrollmentStatsView,
15
+ AdvanceAnalyticsIndividualEnrollmentsView,
16
+ )
13
17
  from enterprise_data.constants import UUID4_REGEX
14
18
 
15
19
  app_name = 'enterprise_data_api_v1'
@@ -35,6 +39,11 @@ router.register(
35
39
  enterprise_learner_views.EnterpriseLearnerCompletedCoursesViewSet,
36
40
  'enterprise-learner-completed-courses',
37
41
  )
42
+ router.register(
43
+ r'enterprise/(?P<enterprise_id>.+)/module-performance',
44
+ enterprise_admin_views.EnterpriseExecEdLCModulePerformanceViewSet,
45
+ 'enterprise-admin-module-performance',
46
+ )
38
47
 
39
48
  urlpatterns = [
40
49
  re_path(
@@ -47,6 +56,16 @@ urlpatterns = [
47
56
  enterprise_admin_views.EnterpriseAdminAnalyticsAggregatesView.as_view(),
48
57
  name='enterprise-admin-analytics-aggregates'
49
58
  ),
59
+ re_path(
60
+ fr'^admin/anlaytics/(?P<enterprise_uuid>{UUID4_REGEX})/enrollments/stats$',
61
+ AdvanceAnalyticsEnrollmentStatsView.as_view(),
62
+ name='enterprise-admin-analytics-enrollments-stats'
63
+ ),
64
+ re_path(
65
+ fr'^admin/anlaytics/(?P<enterprise_uuid>{UUID4_REGEX})/enrollments$',
66
+ AdvanceAnalyticsIndividualEnrollmentsView.as_view(),
67
+ name='enterprise-admin-analytics-enrollments'
68
+ ),
50
69
  re_path(
51
70
  fr'^admin/anlaytics/(?P<enterprise_id>{UUID4_REGEX})/skills/stats',
52
71
  enterprise_admin_views.EnterpriseAdminAnalyticsSkillsView.as_view(),