edx-enterprise-data 8.6.1__tar.gz → 8.8.0__tar.gz

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.
Files changed (188) hide show
  1. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/CHANGELOG.rst +9 -0
  2. {edx_enterprise_data-8.6.1/edx_enterprise_data.egg-info → edx_enterprise_data-8.8.0}/PKG-INFO +1 -1
  3. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0/edx_enterprise_data.egg-info}/PKG-INFO +1 -1
  4. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/edx_enterprise_data.egg-info/SOURCES.txt +6 -0
  5. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/__init__.py +1 -1
  6. edx_enterprise_data-8.8.0/enterprise_data/admin_analytics/completions_utils.py +261 -0
  7. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/admin_analytics/constants.py +9 -3
  8. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/admin_analytics/utils.py +50 -11
  9. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/api/v1/paginators.py +1 -1
  10. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/api/v1/serializers.py +44 -30
  11. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/api/v1/urls.py +21 -4
  12. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/api/v1/views/analytics_enrollments.py +42 -53
  13. edx_enterprise_data-8.8.0/enterprise_data/api/v1/views/analytics_leaderboard.py +120 -0
  14. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/api/v1/views/enterprise_admin.py +9 -5
  15. edx_enterprise_data-8.8.0/enterprise_data/api/v1/views/enterprise_completions.py +177 -0
  16. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/renderers.py +14 -0
  17. edx_enterprise_data-8.8.0/enterprise_data/tests/admin_analytics/mock_analytics_data.py +501 -0
  18. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/tests/admin_analytics/mock_enrollments.py +23 -7
  19. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/tests/admin_analytics/test_analytics_enrollments.py +23 -22
  20. edx_enterprise_data-8.8.0/enterprise_data/tests/admin_analytics/test_analytics_leaderboard.py +163 -0
  21. edx_enterprise_data-8.8.0/enterprise_data/tests/admin_analytics/test_enterprise_completions.py +202 -0
  22. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/tests/api/v1/views/test_enterprise_admin.py +4 -0
  23. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/LICENSE +0 -0
  24. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/MANIFEST.in +0 -0
  25. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/README.md +0 -0
  26. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/edx_enterprise_data.egg-info/dependency_links.txt +0 -0
  27. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/edx_enterprise_data.egg-info/not-zip-safe +0 -0
  28. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/edx_enterprise_data.egg-info/requires.txt +0 -0
  29. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/edx_enterprise_data.egg-info/top_level.txt +0 -0
  30. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/admin_analytics/__init__.py +0 -0
  31. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/admin_analytics/data_loaders.py +0 -0
  32. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/admin_analytics/database.py +0 -0
  33. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/api/__init__.py +0 -0
  34. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/api/urls.py +0 -0
  35. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/api/v0/__init__.py +0 -0
  36. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/api/v0/serializers.py +0 -0
  37. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/api/v0/urls.py +0 -0
  38. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/api/v0/views.py +0 -0
  39. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/api/v1/__init__.py +0 -0
  40. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/api/v1/views/__init__.py +0 -0
  41. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/api/v1/views/base.py +0 -0
  42. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/api/v1/views/enterprise_learner.py +0 -0
  43. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/api/v1/views/enterprise_offers.py +0 -0
  44. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/apps.py +0 -0
  45. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/clients.py +0 -0
  46. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/constants.py +0 -0
  47. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/filters.py +0 -0
  48. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/fixtures/enterprise_enrollment.json +0 -0
  49. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/fixtures/enterprise_user.json +0 -0
  50. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/management/__init__.py +0 -0
  51. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/management/commands/__init__.py +0 -0
  52. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/management/commands/create_dummy_data.py +0 -0
  53. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/management/commands/create_dummy_data_lpr_v1.py +0 -0
  54. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/management/commands/create_enterprise_enrollment.py +0 -0
  55. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/management/commands/create_enterprise_learner_enrollment_lpr_v1.py +0 -0
  56. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/management/commands/create_enterprise_learner_lpr_v1.py +0 -0
  57. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/management/commands/create_enterprise_offer.py +0 -0
  58. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/management/commands/create_enterprise_user.py +0 -0
  59. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/management/commands/tests/__init__.py +0 -0
  60. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/management/commands/tests/test_create_dummy_data_lpr_v1.py +0 -0
  61. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/management/commands/tests/test_create_enterprise_enrollment.py +0 -0
  62. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/management/commands/tests/test_create_enterprise_learner_enrollment_lpr_v1.py +0 -0
  63. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/management/commands/tests/test_create_enterprise_learner_lpr_v1.py +0 -0
  64. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/management/commands/tests/test_create_enterprise_user.py +0 -0
  65. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/migrations/0001_initial.py +0 -0
  66. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/migrations/0002_auto_20180430_1358.py +0 -0
  67. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/migrations/0003_auto_20180501_0603.py +0 -0
  68. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/migrations/0004_auto_20180501_0928.py +0 -0
  69. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/migrations/0004_auto_20180508_1652.py +0 -0
  70. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/migrations/0005_auto_20180524_2204.py +0 -0
  71. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/migrations/0006_auto_20180612_0336.py +0 -0
  72. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/migrations/0007_auto_20180612_0534.py +0 -0
  73. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/migrations/0008_auto_20180614_0108.py +0 -0
  74. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/migrations/0009_auto_20180628_1152.py +0 -0
  75. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/migrations/0010_enterpriseenrollment_created.py +0 -0
  76. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/migrations/0011_enterpriseuser.py +0 -0
  77. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/migrations/0012_auto_20180831_1930.py +0 -0
  78. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/migrations/0013_auto_20180831_1931.py +0 -0
  79. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/migrations/0014_enterpriseuser_created.py +0 -0
  80. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/migrations/0015_auto_20180907_1757.py +0 -0
  81. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/migrations/0016_auto_20180924_2138.py +0 -0
  82. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/migrations/0017_enterpriseenrollment_unenrollment_timestamp.py +0 -0
  83. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/migrations/0018_enterprisedatafeaturerole_enterprisedataroleassignment.py +0 -0
  84. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/migrations/0019_add_enterprise_data_feature_roles.py +0 -0
  85. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/migrations/0020_add_role_based_access_control_switch.py +0 -0
  86. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/migrations/0021_auto_20190329_1241.py +0 -0
  87. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/migrations/0022_remove_role_based_access_control_switch.py +0 -0
  88. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/migrations/0023_enterpriselearner_enterpriselearnerenrollment.py +0 -0
  89. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/migrations/0024_auto_20210602_1811.py +0 -0
  90. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/migrations/0025_auto_20210703_1854.py +0 -0
  91. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/migrations/0026_auto_20210916_0414.py +0 -0
  92. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/migrations/0027_enterpriselearnerenrollment_total_learning_time_seconds.py +0 -0
  93. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/migrations/0028_enterpriselearnerenrollment_offer_id.py +0 -0
  94. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/migrations/0029_enterpriseoffer.py +0 -0
  95. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/migrations/0030_auto_20230609_1353.py +0 -0
  96. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/migrations/0031_auto_20230615_0705.py +0 -0
  97. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/migrations/0032_auto_20230704_0818.py +0 -0
  98. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/migrations/0033_enterpriseadminlearnerprogress_enterpriseadminsummarizeinsights.py +0 -0
  99. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/migrations/0034_auto_20230907_0834.py +0 -0
  100. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/migrations/0035_auto_20230907_1154.py +0 -0
  101. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/migrations/0036_enterprisesubsidybudget_subsidy_access_policy_display_name.py +0 -0
  102. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/migrations/0037_alter_enterpriseenrollment_consent_granted.py +0 -0
  103. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/migrations/0038_enterpriseoffer_export_timestamp.py +0 -0
  104. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/migrations/0039_auto_20240212_1403.py +0 -0
  105. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/migrations/0040_auto_20240718_0536_squashed_0043_alter_enterpriselearnerenrollment_enterprise_enrollment_id.py +0 -0
  106. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/migrations/0044_enterpriseexecedlcmoduleperformance.py +0 -0
  107. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/migrations/__init__.py +0 -0
  108. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/models.py +0 -0
  109. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/paginators.py +0 -0
  110. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/settings/__init__.py +0 -0
  111. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/settings/test.py +0 -0
  112. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/signals.py +0 -0
  113. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/tests/__init__.py +0 -0
  114. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/tests/admin_analytics/__init__.py +0 -0
  115. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/tests/admin_analytics/test_data_loaders.py +0 -0
  116. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/tests/admin_analytics/test_utils.py +0 -0
  117. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/tests/api/__init__.py +0 -0
  118. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/tests/api/v0/__init__.py +0 -0
  119. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/tests/api/v0/test_serializers.py +0 -0
  120. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/tests/api/v1/__init__.py +0 -0
  121. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/tests/api/v1/test_serializers.py +0 -0
  122. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/tests/api/v1/test_views.py +0 -0
  123. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/tests/api/v1/views/__init__.py +0 -0
  124. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/tests/factories.py +0 -0
  125. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/tests/mixins.py +0 -0
  126. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/tests/test_clients.py +0 -0
  127. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/tests/test_filters.py +0 -0
  128. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/tests/test_models.py +0 -0
  129. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/tests/test_utils.py +0 -0
  130. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/tests/test_views.py +0 -0
  131. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/urls.py +0 -0
  132. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data/utils.py +0 -0
  133. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data_roles/__init__.py +0 -0
  134. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data_roles/admin.py +0 -0
  135. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data_roles/apps.py +0 -0
  136. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data_roles/constants.py +0 -0
  137. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data_roles/migrations/0001_initial.py +0 -0
  138. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data_roles/migrations/0002_add_enterprise_data_feature_roles.py +0 -0
  139. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data_roles/migrations/0003_add_role_based_access_control_switch.py +0 -0
  140. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data_roles/migrations/0004_enterprisedataroleassignment_enterprise_id.py +0 -0
  141. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data_roles/migrations/0005_turn_on_role_based_access_control_switch.py +0 -0
  142. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data_roles/migrations/0006_remove_role_based_access_control_switch.py +0 -0
  143. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data_roles/migrations/0007_enterprisedataroleassignment_applies_to_all_contexts.py +0 -0
  144. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data_roles/migrations/__init__.py +0 -0
  145. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data_roles/models.py +0 -0
  146. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data_roles/rules.py +0 -0
  147. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data_roles/tests/__init__.py +0 -0
  148. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data_roles/tests/factories.py +0 -0
  149. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_data_roles/tests/test_models.py +0 -0
  150. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_reporting/__init__.py +0 -0
  151. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_reporting/clients/__init__.py +0 -0
  152. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_reporting/clients/enterprise.py +0 -0
  153. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_reporting/clients/s3.py +0 -0
  154. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_reporting/clients/snowflake.py +0 -0
  155. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_reporting/clients/vertica.py +0 -0
  156. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_reporting/delivery_method.py +0 -0
  157. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_reporting/external_resource_link_report.py +0 -0
  158. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_reporting/fixtures/__init__.py +0 -0
  159. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_reporting/fixtures/enterprise_customer_reporting.json +0 -0
  160. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_reporting/reporter.py +0 -0
  161. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_reporting/send_enterprise_reports.py +0 -0
  162. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_reporting/tests/__init__.py +0 -0
  163. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_reporting/tests/test_clients.py +0 -0
  164. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_reporting/tests/test_delivery_method.py +0 -0
  165. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_reporting/tests/test_enterprise_client.py +0 -0
  166. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_reporting/tests/test_external_link_report.py +0 -0
  167. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_reporting/tests/test_reporter.py +0 -0
  168. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_reporting/tests/test_send_enterprise_reports.py +0 -0
  169. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_reporting/tests/test_utils.py +0 -0
  170. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_reporting/tests/test_vertica_client.py +0 -0
  171. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_reporting/tests/utils.py +0 -0
  172. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/enterprise_reporting/utils.py +0 -0
  173. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/requirements/base.in +0 -0
  174. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/requirements/base.txt +0 -0
  175. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/requirements/ci.txt +0 -0
  176. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/requirements/common_constraints.txt +0 -0
  177. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/requirements/constraints.txt +0 -0
  178. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/requirements/dev.txt +0 -0
  179. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/requirements/django.txt +0 -0
  180. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/requirements/pip.txt +0 -0
  181. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/requirements/pip_tools.txt +0 -0
  182. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/requirements/quality.txt +0 -0
  183. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/requirements/reporting.in +0 -0
  184. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/requirements/test-master.txt +0 -0
  185. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/requirements/test-reporting.txt +0 -0
  186. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/requirements/test.txt +0 -0
  187. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/setup.cfg +0 -0
  188. {edx_enterprise_data-8.6.1 → edx_enterprise_data-8.8.0}/setup.py +0 -0
@@ -16,6 +16,15 @@ Unreleased
16
16
 
17
17
  =========================
18
18
 
19
+ [8.8.0] - 2024-08-15
20
+ ---------------------
21
+ * feat: Add API endpoints for advance analytics leaderboard data
22
+ * refactor: Use `response_type` and `chart_type` in advance analytics enrollments API endpoints
23
+
24
+ [8.7.0] - 2024-08-13
25
+ ---------------------
26
+ * feat: add endpoints to get completion data for an enterprise customer
27
+
19
28
  [8.6.1] - 2024-08-12
20
29
  ---------------------
21
30
  * Dependency updates
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: edx-enterprise-data
3
- Version: 8.6.1
3
+ Version: 8.8.0
4
4
  Summary: Enterprise Reporting
5
5
  Home-page: https://github.com/openedx/edx-enterprise-data
6
6
  Author: edX
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: edx-enterprise-data
3
- Version: 8.6.1
3
+ Version: 8.8.0
4
4
  Summary: Enterprise Reporting
5
5
  Home-page: https://github.com/openedx/edx-enterprise-data
6
6
  Author: edX
@@ -21,6 +21,7 @@ enterprise_data/signals.py
21
21
  enterprise_data/urls.py
22
22
  enterprise_data/utils.py
23
23
  enterprise_data/admin_analytics/__init__.py
24
+ enterprise_data/admin_analytics/completions_utils.py
24
25
  enterprise_data/admin_analytics/constants.py
25
26
  enterprise_data/admin_analytics/data_loaders.py
26
27
  enterprise_data/admin_analytics/database.py
@@ -37,8 +38,10 @@ enterprise_data/api/v1/serializers.py
37
38
  enterprise_data/api/v1/urls.py
38
39
  enterprise_data/api/v1/views/__init__.py
39
40
  enterprise_data/api/v1/views/analytics_enrollments.py
41
+ enterprise_data/api/v1/views/analytics_leaderboard.py
40
42
  enterprise_data/api/v1/views/base.py
41
43
  enterprise_data/api/v1/views/enterprise_admin.py
44
+ enterprise_data/api/v1/views/enterprise_completions.py
42
45
  enterprise_data/api/v1/views/enterprise_learner.py
43
46
  enterprise_data/api/v1/views/enterprise_offers.py
44
47
  enterprise_data/fixtures/enterprise_enrollment.json
@@ -112,9 +115,12 @@ enterprise_data/tests/test_models.py
112
115
  enterprise_data/tests/test_utils.py
113
116
  enterprise_data/tests/test_views.py
114
117
  enterprise_data/tests/admin_analytics/__init__.py
118
+ enterprise_data/tests/admin_analytics/mock_analytics_data.py
115
119
  enterprise_data/tests/admin_analytics/mock_enrollments.py
116
120
  enterprise_data/tests/admin_analytics/test_analytics_enrollments.py
121
+ enterprise_data/tests/admin_analytics/test_analytics_leaderboard.py
117
122
  enterprise_data/tests/admin_analytics/test_data_loaders.py
123
+ enterprise_data/tests/admin_analytics/test_enterprise_completions.py
118
124
  enterprise_data/tests/admin_analytics/test_utils.py
119
125
  enterprise_data/tests/api/__init__.py
120
126
  enterprise_data/tests/api/v0/__init__.py
@@ -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.6.1"
5
+ __version__ = "8.8.0"
@@ -0,0 +1,261 @@
1
+ """This module contains utility functions for completions analytics."""
2
+ from enterprise_data.utils import date_filter
3
+
4
+
5
+ def date_aggregation(level, group, date, df, type_="count"):
6
+ """Perform date aggregation on a DataFrame.
7
+
8
+ This function aggregates data based on the specified level of aggregation (e.g., daily, weekly, monthly, quarterly)
9
+ and returns the aggregated data.
10
+
11
+ Args:
12
+ level (str): The level of aggregation. Possible values are "Daily", "Weekly", "Monthly", and "Quarterly".
13
+ group (list): A list of column names to group the data by.
14
+ date (str): The name of the date column in the DataFrame.
15
+ df (pandas.DataFrame): The DataFrame containing the data to be aggregated.
16
+ type_ (str, optional): The type of aggregation to perform. Possible values
17
+ are "count" and "sum". Defaults to "count".
18
+
19
+ Returns:
20
+ pandas.DataFrame: The aggregated data.
21
+
22
+ """
23
+ if type_ == "count":
24
+ if level == "Daily":
25
+ df = df.groupby(group).size().reset_index()
26
+ group.append("count")
27
+ df.columns = group
28
+ elif level == "Weekly":
29
+ df[date] = df[date].dt.to_period("W").dt.start_time
30
+ df = df.groupby(group).size().reset_index()
31
+ group.append("count")
32
+ df.columns = group
33
+ elif level == "Monthly":
34
+ df[date] = df[date].dt.to_period("M").dt.start_time
35
+ df = df.groupby(group).size().reset_index()
36
+ group.append("count")
37
+ df.columns = group
38
+ elif level == "Quarterly":
39
+ df[date] = df[date].dt.to_period("Q").dt.start_time
40
+ df = df.groupby(group).size().reset_index()
41
+ group.append("count")
42
+ df.columns = group
43
+ elif type_ == "sum":
44
+ if level == "Daily":
45
+ df = df.groupby(group).sum().reset_index()
46
+ group.append("sum")
47
+ df.columns = group
48
+ elif level == "Weekly":
49
+ df[date] = df[date].dt.to_period("W").dt.start_time
50
+ df = df.groupby(group).sum().reset_index()
51
+ group.append("sum")
52
+ df.columns = group
53
+ elif level == "Monthly":
54
+ df[date] = df[date].dt.to_period("M").dt.start_time
55
+ df = df.groupby(group).sum().reset_index()
56
+ group.append("sum")
57
+ df.columns = group
58
+ elif level == "Quarterly":
59
+ df[date] = df[date].dt.to_period("Q").dt.start_time
60
+ df = df.groupby(group).sum().reset_index()
61
+ group.append("sum")
62
+ df.columns = group
63
+
64
+ return df
65
+
66
+
67
+ def calculation(calc, df, type_="count"):
68
+ """Perform a calculation on the given DataFrame based on the specified calculation type.
69
+
70
+ Args:
71
+ calc (str): The calculation type. Possible values are "Total", "Running Total",
72
+ "Moving Average (3 Period)", and "Moving Average (7 Period)".
73
+ df (pandas.DataFrame): The filtered enrollments data.
74
+ type_ (str, optional): The type of calculation to perform. Default is "count".
75
+
76
+ Returns:
77
+ pandas.DataFrame: The aggregated data after performing the calculation.
78
+ """
79
+ if type_ == "count":
80
+ if calc == "Total":
81
+ pass
82
+ elif calc == "Running Total":
83
+ df["count"] = df.groupby("enroll_type")["count"].cumsum()
84
+ elif calc == "Moving Average (3 Period)":
85
+ df["count"] = (
86
+ df.groupby("enroll_type")["count"]
87
+ .rolling(3)
88
+ .mean()
89
+ .droplevel(level=[0])
90
+ )
91
+ elif calc == "Moving Average (7 Period)":
92
+ df["count"] = (
93
+ df.groupby("enroll_type")["count"]
94
+ .rolling(7)
95
+ .mean()
96
+ .droplevel(level=[0])
97
+ )
98
+ elif type_ == "sum":
99
+ if calc == "Total":
100
+ pass
101
+ elif calc == "Running Total":
102
+ df["sum"] = df.groupby("enroll_type")["sum"].cumsum()
103
+ elif calc == "Moving Average (3 Period)":
104
+ df["sum"] = (
105
+ df.groupby("enroll_type")["sum"].rolling(3).mean().droplevel(level=[0])
106
+ )
107
+ elif calc == "Moving Average (7 Period)":
108
+ df["sum"] = (
109
+ df.groupby("enroll_type")["sum"].rolling(7).mean().droplevel(level=[0])
110
+ )
111
+
112
+ return df
113
+
114
+
115
+ def get_completions_over_time(start_date, end_date, dff, date_agg, calc):
116
+ """Get agreggated data for completions over time graph.
117
+
118
+ Args:
119
+ start_date (datetime): The start date for the date filter.
120
+ end_date (datetime): The end date for the date filter.
121
+ dff (pandas.DataFrame): enrollments data
122
+ date_agg (str): It denotes the granularity of the aggregated date which can be Daily, Weekly, Monthly, Quarterly
123
+ calc (str): Calculations denoiated the period for the running averages. It can be Total, Running Total, Moving
124
+ Average (3 Period), Moving Average (7 Period)
125
+ """
126
+
127
+ dff = dff[dff["has_passed"] == 1]
128
+
129
+ # Date filtering.
130
+ dff = date_filter(start=start_date, end=end_date, data_frame=dff, date_column="passed_date")
131
+
132
+ # Date aggregation.
133
+ dff = date_aggregation(
134
+ level=date_agg, group=["passed_date", "enroll_type"], date="passed_date", df=dff
135
+ )
136
+
137
+ # Calculating metric.
138
+ dff = calculation(calc=calc, df=dff)
139
+
140
+ return dff
141
+
142
+
143
+ def get_top_courses_by_completions(start_date, end_date, dff):
144
+ """Get top 10 courses by completions.
145
+
146
+ Args:
147
+ start_date (datetime): The start date for the date filter.
148
+ end_date (datetime): The end date for the date filter.
149
+ dff (pandas.DataFrame): Enrollments data
150
+ """
151
+
152
+ dff = dff[dff["has_passed"] == 1]
153
+
154
+ # Date filtering.
155
+ dff = date_filter(start=start_date, end=end_date, data_frame=dff, date_column="passed_date")
156
+
157
+ courses = list(
158
+ dff.groupby(["course_key"]).size().sort_values(ascending=False)[:10].index
159
+ )
160
+
161
+ dff = (
162
+ dff[dff.course_key.isin(courses)]
163
+ .groupby(["course_key", "course_title", "enroll_type"])
164
+ .size()
165
+ .reset_index()
166
+ )
167
+ dff.columns = ["course_key", "course_title", "enroll_type", "count"]
168
+
169
+ return dff
170
+
171
+
172
+ def get_top_subjects_by_completions(start_date, end_date, dff):
173
+ """Get top 10 subjects by completions.
174
+
175
+ Args:
176
+ start_date (datetime): The start date for the date filter.
177
+ end_date (datetime): The end date for the date filter.
178
+ dff (pandas.DataFrame): Enrollments data
179
+ """
180
+
181
+ dff = dff[dff["has_passed"] == 1]
182
+
183
+ # Date filtering.
184
+ dff = date_filter(start=start_date, end=end_date, data_frame=dff, date_column="passed_date")
185
+
186
+ subjects = list(
187
+ dff.groupby(["course_subject"]).size().sort_values(ascending=False)[:10].index
188
+ )
189
+
190
+ dff = (
191
+ dff[dff.course_subject.isin(subjects)]
192
+ .groupby(["course_subject", "enroll_type"])
193
+ .size()
194
+ .reset_index()
195
+ )
196
+ dff.columns = ["course_subject", "enroll_type", "count"]
197
+
198
+ return dff
199
+
200
+
201
+ def get_csv_data_for_completions_over_time(
202
+ start_date, end_date, enrollments, date_agg, calc
203
+ ):
204
+ """Get csv data for completions over time graph.
205
+
206
+ Args:
207
+ start_date (datetime): The start date for the date filter.
208
+ end_date (datetime): The end date for the date filter.
209
+ enrollments (pandas.DataFrame): Filtered enrollments data
210
+ date_agg (str): it denotes the granularity of the aggregated date which can be Daily, Weekly, Monthly, Quarterly
211
+ calc (str): calculations denoiated the period for the running averages. It can be Total, Running Total, Moving
212
+ Average (3 Period), Moving Average (7 Period)
213
+
214
+ Returns:
215
+ dict: csv data
216
+ """
217
+
218
+ dff = get_completions_over_time(start_date, end_date, enrollments, date_agg, calc)
219
+ dff = dff.pivot(index="passed_date", columns="enroll_type", values="count")
220
+ filename = (
221
+ f"Completions Timeseries, {start_date} - {end_date} ({date_agg} {calc}).csv"
222
+ )
223
+ return {"filename": filename, "data": dff}
224
+
225
+
226
+ def get_csv_data_for_top_courses_by_completions(start_date, end_date, enrollments):
227
+ """Get csv data for top 10 courses by completions.
228
+
229
+ Args:
230
+ start_date (datetime): The start date for the date filter.
231
+ end_date (datetime): The end date for the date filter.
232
+ enrollments (pandas.DataFrame): Filtered enrollments data
233
+
234
+ Returns:
235
+ dict: csv data
236
+ """
237
+
238
+ dff = get_top_courses_by_completions(start_date, end_date, enrollments)
239
+ dff = dff.pivot(
240
+ index=["course_key", "course_title"], columns="enroll_type", values="count"
241
+ )
242
+ filename = f"Top 10 Courses by Completions, {start_date} - {end_date}.csv"
243
+ return {"filename": filename, "data": dff}
244
+
245
+
246
+ def get_csv_data_for_top_subjects_by_completions(start_date, end_date, enrollments):
247
+ """Get csv data for top 10 subjects by completions.
248
+
249
+ Args:
250
+ start_date (datetime): The start date for the date filter.
251
+ end_date (datetime): The end date for the date filter.
252
+ enrollments (pandas.DataFrame): Filtered enrollments data
253
+
254
+ Returns:
255
+ dict: csv data
256
+ """
257
+
258
+ dff = get_top_subjects_by_completions(start_date, end_date, enrollments)
259
+ dff = dff.pivot(index="course_subject", columns="enroll_type", values="count")
260
+ filename = f"Top 10 Subjects by Completions, {start_date} - {end_date}.csv"
261
+ return {"filename": filename, "data": dff}
@@ -3,7 +3,7 @@
3
3
  from enum import Enum
4
4
 
5
5
 
6
- class GRANULARITY(Enum):
6
+ class Granularity(Enum):
7
7
  """Granularity choices"""
8
8
  DAILY = 'Daily'
9
9
  WEEKLY = 'Weekly'
@@ -11,7 +11,7 @@ class GRANULARITY(Enum):
11
11
  QUARTERLY = 'Quarterly'
12
12
 
13
13
 
14
- class CALCULATION(Enum):
14
+ class Calculation(Enum):
15
15
  """Calculation choices"""
16
16
  TOTAL = 'Total'
17
17
  RUNNING_TOTAL = 'Running Total'
@@ -19,9 +19,15 @@ class CALCULATION(Enum):
19
19
  MOVING_AVERAGE_7_PERIOD = 'Moving Average (7 Period)'
20
20
 
21
21
 
22
- class ENROLLMENT_CSV(Enum):
22
+ class EnrollmentChart(Enum):
23
23
  """CSV choices"""
24
24
  ENROLLMENTS_OVER_TIME = 'enrollments_over_time'
25
25
  TOP_COURSES_BY_ENROLLMENTS = 'top_courses_by_enrollments'
26
26
  TOP_SUBJECTS_BY_ENROLLMENTS = 'top_subjects_by_enrollments'
27
27
  INDIVIDUAL_ENROLLMENTS = 'individual_enrollments'
28
+
29
+
30
+ class ResponseType(Enum):
31
+ """Response type choices"""
32
+ JSON = 'json'
33
+ CSV = 'csv'
@@ -1,13 +1,18 @@
1
1
  """
2
2
  Utility functions for fetching data from the database.
3
3
  """
4
- from datetime import datetime
4
+ from datetime import datetime, timedelta
5
5
  from enum import Enum
6
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
10
- from enterprise_data.admin_analytics.data_loaders import fetch_engagement_data, fetch_enrollment_data, fetch_skills_data
9
+ from enterprise_data.admin_analytics.constants import Calculation, Granularity
10
+ from enterprise_data.admin_analytics.data_loaders import (
11
+ fetch_engagement_data,
12
+ fetch_enrollment_data,
13
+ fetch_max_enrollment_datetime,
14
+ fetch_skills_data,
15
+ )
11
16
  from enterprise_data.utils import date_filter, primary_subject_truncate
12
17
 
13
18
 
@@ -18,6 +23,40 @@ class ChartType(Enum):
18
23
  BUBBLE = 'bubble'
19
24
  TOP_SKILLS_ENROLLMENT = 'top_skills_enrollment'
20
25
  TOP_SKILLS_COMPLETION = 'top_skills_completion'
26
+ COMPLETIONS_OVER_TIME = 'completions_over_time'
27
+ TOP_COURSES_BY_COMPLETIONS = 'top_courses_by_completions'
28
+ TOP_SUBJECTS_BY_COMPLETIONS = 'top_subjects_by_completions'
29
+
30
+
31
+ def fetch_enrollments_cache_expiry_timestamp():
32
+ """Calculate cache expiry timestamp"""
33
+ # TODO: Implement correct cache expiry logic for `enrollments` data.
34
+ # Current cache expiry logic is based on `enterprise_learner_enrollment` table,
35
+ # Which has nothing to do with the `enrollments` data. Instead cache expiry should
36
+ # be based on `fact_enrollment_admin_dash` table. Currently we have no timestamp in
37
+ # `fact_enrollment_admin_dash` table that can be used for cache expiry. Add a new
38
+ # column in the table for this purpose and then use that column for cache expiry.
39
+ last_updated_at = fetch_max_enrollment_datetime()
40
+ cache_expiry = (
41
+ last_updated_at + timedelta(days=1) if last_updated_at else datetime.now()
42
+ )
43
+ return cache_expiry
44
+
45
+
46
+ def fetch_engagements_cache_expiry_timestamp():
47
+ """Calculate cache expiry timestamp"""
48
+ # TODO: Implement correct cache expiry logic for `engagements` data.
49
+ # Current cache expiry logic is based on `enterprise_learner_enrollment` table,
50
+ # Which has nothing to do with the `engagements` data. Instead cache expiry should
51
+ # be based on `fact_enrollment_engagement_day_admin_dash` table. Currently we have
52
+ # no timestamp in `fact_enrollment_engagement_day_admin_dash` table that can be used
53
+ # for cache expiry. Add a new column in the table for this purpose and then use that
54
+ # column for cache expiry.
55
+ last_updated_at = fetch_max_enrollment_datetime()
56
+ cache_expiry = (
57
+ last_updated_at + timedelta(days=1) if last_updated_at else datetime.now()
58
+ )
59
+ return cache_expiry
21
60
 
22
61
 
23
62
  def granularity_aggregation(level, group, date, data_frame, aggregation_type="count"):
@@ -25,9 +64,9 @@ def granularity_aggregation(level, group, date, data_frame, aggregation_type="co
25
64
  df = data_frame
26
65
 
27
66
  period_mapping = {
28
- GRANULARITY.WEEKLY.value: "W",
29
- GRANULARITY.MONTHLY.value: "M",
30
- GRANULARITY.QUARTERLY.value: "Q"
67
+ Granularity.WEEKLY.value: "W",
68
+ Granularity.MONTHLY.value: "M",
69
+ Granularity.QUARTERLY.value: "Q"
31
70
  }
32
71
 
33
72
  if level in period_mapping:
@@ -49,15 +88,15 @@ def calculation_aggregation(calc, data_frame, aggregation_type="count"):
49
88
  df = data_frame
50
89
 
51
90
  window_mapping = {
52
- CALCULATION.MOVING_AVERAGE_3_PERIOD.value: 3,
53
- CALCULATION.MOVING_AVERAGE_7_PERIOD.value: 7,
91
+ Calculation.MOVING_AVERAGE_3_PERIOD.value: 3,
92
+ Calculation.MOVING_AVERAGE_7_PERIOD.value: 7,
54
93
  }
55
94
 
56
95
  aggregation_column = "count" if aggregation_type == "count" else "sum"
57
96
 
58
- if calc == CALCULATION.RUNNING_TOTAL.value:
97
+ if calc == Calculation.RUNNING_TOTAL.value:
59
98
  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]:
99
+ elif calc in [Calculation.MOVING_AVERAGE_3_PERIOD.value, Calculation.MOVING_AVERAGE_7_PERIOD.value]:
61
100
  df[aggregation_column] = (
62
101
  df.groupby("enroll_type")[aggregation_column]
63
102
  .rolling(window_mapping[calc])
@@ -172,7 +211,7 @@ def get_skills_bubble_chart_df(skills_filtered):
172
211
  """ Get the skills data for the bubble chart.
173
212
 
174
213
  Args:
175
- skills_filtered (list): The skills data.
214
+ skills_filtered (pandas.DataFrame): The skills data.
176
215
 
177
216
  Returns:
178
217
  (pandas.DataFrame): The skills data for the bubble chart.
@@ -72,7 +72,7 @@ class AdvanceAnalyticsPagination(PageNumberPagination):
72
72
  max_page_size (int): The maximum allowed page size.
73
73
  """
74
74
  page_size_query_param = "page_size"
75
- page_size = 10
75
+ page_size = 50
76
76
  max_page_size = 100
77
77
 
78
78
  def paginate_queryset(self, queryset, request, view=None):
@@ -5,7 +5,7 @@ from uuid import UUID
5
5
 
6
6
  from rest_framework import serializers
7
7
 
8
- from enterprise_data.admin_analytics.constants import CALCULATION, ENROLLMENT_CSV, GRANULARITY
8
+ from enterprise_data.admin_analytics.constants import Calculation, EnrollmentChart, Granularity, ResponseType
9
9
  from enterprise_data.models import (
10
10
  EnterpriseAdminLearnerProgress,
11
11
  EnterpriseAdminSummarizeInsights,
@@ -206,6 +206,11 @@ class AdminAnalyticsAggregatesQueryParamsSerializer(serializers.Serializer): #
206
206
  """
207
207
  start_date = serializers.DateField(required=False)
208
208
  end_date = serializers.DateField(required=False)
209
+ granularity = serializers.CharField(required=False)
210
+ calculation = serializers.CharField(required=False)
211
+ response_type = serializers.CharField(required=False)
212
+ page = serializers.IntegerField(required=False)
213
+ chart_type = serializers.CharField(required=False)
209
214
 
210
215
  def validate(self, attrs):
211
216
  """
@@ -232,23 +237,28 @@ class EnterpriseExecEdLCModulePerformanceSerializer(serializers.ModelSerializer)
232
237
 
233
238
  class AdvanceAnalyticsQueryParamSerializer(serializers.Serializer): # pylint: disable=abstract-method
234
239
  """Serializer for validating query params"""
240
+ RESPONSE_TYPES = [
241
+ ResponseType.JSON.value,
242
+ ResponseType.CSV.value
243
+ ]
235
244
  GRANULARITY_CHOICES = [
236
- GRANULARITY.DAILY.value,
237
- GRANULARITY.WEEKLY.value,
238
- GRANULARITY.MONTHLY.value,
239
- GRANULARITY.QUARTERLY.value
245
+ Granularity.DAILY.value,
246
+ Granularity.WEEKLY.value,
247
+ Granularity.MONTHLY.value,
248
+ Granularity.QUARTERLY.value
240
249
  ]
241
250
  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
251
+ Calculation.TOTAL.value,
252
+ Calculation.RUNNING_TOTAL.value,
253
+ Calculation.MOVING_AVERAGE_3_PERIOD.value,
254
+ Calculation.MOVING_AVERAGE_7_PERIOD.value
246
255
  ]
247
256
 
248
257
  start_date = serializers.DateField(required=False)
249
258
  end_date = serializers.DateField(required=False)
250
259
  granularity = serializers.CharField(required=False)
251
260
  calculation = serializers.CharField(required=False)
261
+ response_type = serializers.CharField(required=False)
252
262
 
253
263
  def validate(self, attrs):
254
264
  """
@@ -265,6 +275,17 @@ class AdvanceAnalyticsQueryParamSerializer(serializers.Serializer): # pylint: d
265
275
 
266
276
  return attrs
267
277
 
278
+ def validate_response_type(self, value):
279
+ """
280
+ Validate the response_type value.
281
+
282
+ Raises:
283
+ serializers.ValidationError: If response_type is not one of the valid choices in `RESPONSE_TYPES`.
284
+ """
285
+ if value not in self.RESPONSE_TYPES:
286
+ raise serializers.ValidationError(f"response_type must be one of {self.RESPONSE_TYPES}")
287
+ return value
288
+
268
289
  def validate_granularity(self, value):
269
290
  """
270
291
  Validate the granularity value.
@@ -288,32 +309,25 @@ class AdvanceAnalyticsQueryParamSerializer(serializers.Serializer): # pylint: d
288
309
  return value
289
310
 
290
311
 
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
312
+ class AdvanceAnalyticsEnrollmentStatsSerializer(
313
+ AdvanceAnalyticsQueryParamSerializer
314
+ ): # pylint: disable=abstract-method
315
+ """Serializer for validating Advance Analytics Enrollments Stats API"""
316
+ CHART_TYPES = [
317
+ EnrollmentChart.ENROLLMENTS_OVER_TIME.value,
318
+ EnrollmentChart.TOP_COURSES_BY_ENROLLMENTS.value,
319
+ EnrollmentChart.TOP_SUBJECTS_BY_ENROLLMENTS.value
295
320
  ]
296
321
 
297
- csv_type = serializers.CharField(required=False)
322
+ chart_type = serializers.CharField(required=False)
298
323
 
299
- def validate_csv_type(self, value):
324
+ def validate_chart_type(self, value):
300
325
  """
301
- Validate the csv_type value.
326
+ Validate the chart_type value.
302
327
 
303
328
  Raises:
304
- serializers.ValidationError: If csv_type is not one of the valid choices
329
+ serializers.ValidationError: If chart_type is not one of the valid choices
305
330
  """
306
- if value not in self.CSV_TYPES:
307
- raise serializers.ValidationError(f"csv_type must be one of {self.CSV_TYPES}")
331
+ if value not in self.CHART_TYPES:
332
+ raise serializers.ValidationError(f"chart_type must be one of {self.CHART_TYPES}")
308
333
  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
- ]
@@ -8,12 +8,14 @@ from rest_framework.routers import DefaultRouter
8
8
  from django.urls import re_path
9
9
 
10
10
  from enterprise_data.api.v1.views import enterprise_admin as enterprise_admin_views
11
+ from enterprise_data.api.v1.views import enterprise_completions as enterprise_completions_views
11
12
  from enterprise_data.api.v1.views import enterprise_learner as enterprise_learner_views
12
13
  from enterprise_data.api.v1.views import enterprise_offers as enterprise_offers_views
13
14
  from enterprise_data.api.v1.views.analytics_enrollments import (
14
15
  AdvanceAnalyticsEnrollmentStatsView,
15
16
  AdvanceAnalyticsIndividualEnrollmentsView,
16
17
  )
18
+ from enterprise_data.api.v1.views.analytics_leaderboard import AdvanceAnalyticsLeaderboardView
17
19
  from enterprise_data.constants import UUID4_REGEX
18
20
 
19
21
  app_name = 'enterprise_data_api_v1'
@@ -52,25 +54,40 @@ urlpatterns = [
52
54
  name='enterprise-admin-insights'
53
55
  ),
54
56
  re_path(
55
- fr'^admin/anlaytics/(?P<enterprise_id>{UUID4_REGEX})$',
57
+ fr'^admin/analytics/(?P<enterprise_id>{UUID4_REGEX})$',
56
58
  enterprise_admin_views.EnterpriseAdminAnalyticsAggregatesView.as_view(),
57
59
  name='enterprise-admin-analytics-aggregates'
58
60
  ),
59
61
  re_path(
60
- fr'^admin/anlaytics/(?P<enterprise_uuid>{UUID4_REGEX})/enrollments/stats$',
62
+ fr'^admin/analytics/(?P<enterprise_uuid>{UUID4_REGEX})/leaderboard$',
63
+ AdvanceAnalyticsLeaderboardView.as_view(),
64
+ name='enterprise-admin-analytics-leaderboard'
65
+ ),
66
+ re_path(
67
+ fr'^admin/analytics/(?P<enterprise_uuid>{UUID4_REGEX})/enrollments/stats$',
61
68
  AdvanceAnalyticsEnrollmentStatsView.as_view(),
62
69
  name='enterprise-admin-analytics-enrollments-stats'
63
70
  ),
64
71
  re_path(
65
- fr'^admin/anlaytics/(?P<enterprise_uuid>{UUID4_REGEX})/enrollments$',
72
+ fr'^admin/analytics/(?P<enterprise_uuid>{UUID4_REGEX})/enrollments$',
66
73
  AdvanceAnalyticsIndividualEnrollmentsView.as_view(),
67
74
  name='enterprise-admin-analytics-enrollments'
68
75
  ),
69
76
  re_path(
70
- fr'^admin/anlaytics/(?P<enterprise_id>{UUID4_REGEX})/skills/stats',
77
+ fr'^admin/analytics/(?P<enterprise_id>{UUID4_REGEX})/skills/stats',
71
78
  enterprise_admin_views.EnterpriseAdminAnalyticsSkillsView.as_view(),
72
79
  name='enterprise-admin-analytics-skills'
73
80
  ),
81
+ re_path(
82
+ fr'^admin/analytics/(?P<enterprise_id>{UUID4_REGEX})/completions/stats$',
83
+ enterprise_completions_views.EnterrpiseAdminCompletionsStatsView.as_view(),
84
+ name='enterprise-admin-analytics-completions-stats'
85
+ ),
86
+ re_path(
87
+ fr'^admin/analytics/(?P<enterprise_id>{UUID4_REGEX})/completions$',
88
+ enterprise_completions_views.EnterrpiseAdminCompletionsView.as_view(),
89
+ name='enterprise-admin-analytics-completions'
90
+ ),
74
91
  ]
75
92
 
76
93
  urlpatterns += router.urls