edx-enterprise-data 8.0.0__tar.gz → 8.2.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 (179) hide show
  1. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/CHANGELOG.rst +8 -0
  2. {edx_enterprise_data-8.0.0/edx_enterprise_data.egg-info → edx_enterprise_data-8.2.0}/PKG-INFO +4 -1
  3. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0/edx_enterprise_data.egg-info}/PKG-INFO +4 -1
  4. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/edx_enterprise_data.egg-info/SOURCES.txt +15 -1
  5. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/edx_enterprise_data.egg-info/requires.txt +3 -0
  6. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/__init__.py +1 -1
  7. edx_enterprise_data-8.2.0/enterprise_data/admin_analytics/constants.py +15 -0
  8. edx_enterprise_data-8.2.0/enterprise_data/admin_analytics/data_loaders.py +137 -0
  9. edx_enterprise_data-8.2.0/enterprise_data/admin_analytics/database.py +31 -0
  10. edx_enterprise_data-8.2.0/enterprise_data/admin_analytics/utils.py +81 -0
  11. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/api/v1/serializers.py +20 -0
  12. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/api/v1/urls.py +13 -6
  13. edx_enterprise_data-8.2.0/enterprise_data/api/v1/views/base.py +26 -0
  14. edx_enterprise_data-8.2.0/enterprise_data/api/v1/views/enterprise_admin.py +109 -0
  15. edx_enterprise_data-8.0.0/enterprise_data/api/v1/views.py → edx_enterprise_data-8.2.0/enterprise_data/api/v1/views/enterprise_learner.py +5 -114
  16. edx_enterprise_data-8.2.0/enterprise_data/api/v1/views/enterprise_offers.py +41 -0
  17. edx_enterprise_data-8.2.0/enterprise_data/tests/admin_analytics/test_data_loaders.py +86 -0
  18. edx_enterprise_data-8.2.0/enterprise_data/tests/admin_analytics/test_utils.py +102 -0
  19. edx_enterprise_data-8.2.0/enterprise_data/tests/api/v1/views/test_enterprise_admin.py +82 -0
  20. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/tests/test_filters.py +1 -1
  21. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/tests/test_utils.py +73 -0
  22. edx_enterprise_data-8.2.0/enterprise_data/utils.py +84 -0
  23. edx_enterprise_data-8.2.0/enterprise_data_roles/migrations/__init__.py +0 -0
  24. edx_enterprise_data-8.2.0/enterprise_data_roles/tests/__init__.py +0 -0
  25. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_reporting/clients/__init__.py +2 -3
  26. edx_enterprise_data-8.2.0/enterprise_reporting/fixtures/__init__.py +0 -0
  27. edx_enterprise_data-8.2.0/enterprise_reporting/tests/__init__.py +0 -0
  28. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_reporting/tests/test_enterprise_client.py +2 -5
  29. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/requirements/base.in +3 -0
  30. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/requirements/base.txt +27 -12
  31. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/requirements/ci.txt +3 -3
  32. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/requirements/common_constraints.txt +11 -0
  33. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/requirements/constraints.txt +6 -0
  34. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/requirements/dev.txt +37 -29
  35. edx_enterprise_data-8.2.0/requirements/django.txt +1 -0
  36. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/requirements/pip.txt +2 -2
  37. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/requirements/quality.txt +40 -32
  38. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/requirements/test-master.txt +29 -14
  39. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/requirements/test-reporting.txt +14 -13
  40. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/requirements/test.txt +30 -15
  41. edx_enterprise_data-8.0.0/enterprise_data/utils.py +0 -37
  42. edx_enterprise_data-8.0.0/requirements/django.txt +0 -1
  43. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/LICENSE +0 -0
  44. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/MANIFEST.in +0 -0
  45. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/README.md +0 -0
  46. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/edx_enterprise_data.egg-info/dependency_links.txt +0 -0
  47. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/edx_enterprise_data.egg-info/not-zip-safe +0 -0
  48. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/edx_enterprise_data.egg-info/top_level.txt +0 -0
  49. {edx_enterprise_data-8.0.0/enterprise_data/api → edx_enterprise_data-8.2.0/enterprise_data/admin_analytics}/__init__.py +0 -0
  50. {edx_enterprise_data-8.0.0/enterprise_data/management → edx_enterprise_data-8.2.0/enterprise_data/api}/__init__.py +0 -0
  51. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/api/urls.py +0 -0
  52. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/api/v0/__init__.py +0 -0
  53. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/api/v0/serializers.py +0 -0
  54. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/api/v0/urls.py +0 -0
  55. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/api/v0/views.py +0 -0
  56. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/api/v1/__init__.py +0 -0
  57. {edx_enterprise_data-8.0.0/enterprise_data/management/commands → edx_enterprise_data-8.2.0/enterprise_data/api/v1/views}/__init__.py +0 -0
  58. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/apps.py +0 -0
  59. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/clients.py +0 -0
  60. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/constants.py +0 -0
  61. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/filters.py +0 -0
  62. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/fixtures/enterprise_enrollment.json +0 -0
  63. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/fixtures/enterprise_user.json +0 -0
  64. {edx_enterprise_data-8.0.0/enterprise_data/management/commands/tests → edx_enterprise_data-8.2.0/enterprise_data/management}/__init__.py +0 -0
  65. {edx_enterprise_data-8.0.0/enterprise_data/migrations → edx_enterprise_data-8.2.0/enterprise_data/management/commands}/__init__.py +0 -0
  66. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/management/commands/create_dummy_data.py +0 -0
  67. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/management/commands/create_dummy_data_lpr_v1.py +0 -0
  68. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/management/commands/create_enterprise_enrollment.py +0 -0
  69. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/management/commands/create_enterprise_learner_enrollment_lpr_v1.py +0 -0
  70. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/management/commands/create_enterprise_learner_lpr_v1.py +0 -0
  71. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/management/commands/create_enterprise_offer.py +0 -0
  72. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/management/commands/create_enterprise_user.py +0 -0
  73. {edx_enterprise_data-8.0.0/enterprise_data/settings → edx_enterprise_data-8.2.0/enterprise_data/management/commands/tests}/__init__.py +0 -0
  74. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/management/commands/tests/test_create_dummy_data_lpr_v1.py +0 -0
  75. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/management/commands/tests/test_create_enterprise_enrollment.py +0 -0
  76. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/management/commands/tests/test_create_enterprise_learner_enrollment_lpr_v1.py +0 -0
  77. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/management/commands/tests/test_create_enterprise_learner_lpr_v1.py +0 -0
  78. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/management/commands/tests/test_create_enterprise_user.py +0 -0
  79. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/migrations/0001_initial.py +0 -0
  80. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/migrations/0002_auto_20180430_1358.py +0 -0
  81. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/migrations/0003_auto_20180501_0603.py +0 -0
  82. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/migrations/0004_auto_20180501_0928.py +0 -0
  83. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/migrations/0004_auto_20180508_1652.py +0 -0
  84. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/migrations/0005_auto_20180524_2204.py +0 -0
  85. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/migrations/0006_auto_20180612_0336.py +0 -0
  86. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/migrations/0007_auto_20180612_0534.py +0 -0
  87. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/migrations/0008_auto_20180614_0108.py +0 -0
  88. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/migrations/0009_auto_20180628_1152.py +0 -0
  89. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/migrations/0010_enterpriseenrollment_created.py +0 -0
  90. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/migrations/0011_enterpriseuser.py +0 -0
  91. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/migrations/0012_auto_20180831_1930.py +0 -0
  92. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/migrations/0013_auto_20180831_1931.py +0 -0
  93. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/migrations/0014_enterpriseuser_created.py +0 -0
  94. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/migrations/0015_auto_20180907_1757.py +0 -0
  95. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/migrations/0016_auto_20180924_2138.py +0 -0
  96. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/migrations/0017_enterpriseenrollment_unenrollment_timestamp.py +0 -0
  97. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/migrations/0018_enterprisedatafeaturerole_enterprisedataroleassignment.py +0 -0
  98. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/migrations/0019_add_enterprise_data_feature_roles.py +0 -0
  99. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/migrations/0020_add_role_based_access_control_switch.py +0 -0
  100. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/migrations/0021_auto_20190329_1241.py +0 -0
  101. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/migrations/0022_remove_role_based_access_control_switch.py +0 -0
  102. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/migrations/0023_enterpriselearner_enterpriselearnerenrollment.py +0 -0
  103. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/migrations/0024_auto_20210602_1811.py +0 -0
  104. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/migrations/0025_auto_20210703_1854.py +0 -0
  105. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/migrations/0026_auto_20210916_0414.py +0 -0
  106. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/migrations/0027_enterpriselearnerenrollment_total_learning_time_seconds.py +0 -0
  107. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/migrations/0028_enterpriselearnerenrollment_offer_id.py +0 -0
  108. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/migrations/0029_enterpriseoffer.py +0 -0
  109. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/migrations/0030_auto_20230609_1353.py +0 -0
  110. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/migrations/0031_auto_20230615_0705.py +0 -0
  111. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/migrations/0032_auto_20230704_0818.py +0 -0
  112. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/migrations/0033_enterpriseadminlearnerprogress_enterpriseadminsummarizeinsights.py +0 -0
  113. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/migrations/0034_auto_20230907_0834.py +0 -0
  114. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/migrations/0035_auto_20230907_1154.py +0 -0
  115. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/migrations/0036_enterprisesubsidybudget_subsidy_access_policy_display_name.py +0 -0
  116. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/migrations/0037_alter_enterpriseenrollment_consent_granted.py +0 -0
  117. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/migrations/0038_enterpriseoffer_export_timestamp.py +0 -0
  118. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/migrations/0039_auto_20240212_1403.py +0 -0
  119. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/migrations/0040_auto_20240718_0536_squashed_0043_alter_enterpriselearnerenrollment_enterprise_enrollment_id.py +0 -0
  120. {edx_enterprise_data-8.0.0/enterprise_data/tests → edx_enterprise_data-8.2.0/enterprise_data/migrations}/__init__.py +0 -0
  121. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/models.py +0 -0
  122. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/paginators.py +0 -0
  123. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/renderers.py +0 -0
  124. {edx_enterprise_data-8.0.0/enterprise_data/tests/api → edx_enterprise_data-8.2.0/enterprise_data/settings}/__init__.py +0 -0
  125. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/settings/test.py +0 -0
  126. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/signals.py +0 -0
  127. {edx_enterprise_data-8.0.0/enterprise_data/tests/api/v0 → edx_enterprise_data-8.2.0/enterprise_data/tests}/__init__.py +0 -0
  128. {edx_enterprise_data-8.0.0/enterprise_data/tests/api/v1 → edx_enterprise_data-8.2.0/enterprise_data/tests/admin_analytics}/__init__.py +0 -0
  129. {edx_enterprise_data-8.0.0/enterprise_data_roles/migrations → edx_enterprise_data-8.2.0/enterprise_data/tests/api}/__init__.py +0 -0
  130. {edx_enterprise_data-8.0.0/enterprise_data_roles/tests → edx_enterprise_data-8.2.0/enterprise_data/tests/api/v0}/__init__.py +0 -0
  131. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/tests/api/v0/test_serializers.py +0 -0
  132. {edx_enterprise_data-8.0.0/enterprise_reporting/fixtures → edx_enterprise_data-8.2.0/enterprise_data/tests/api/v1}/__init__.py +0 -0
  133. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/tests/api/v1/test_serializers.py +0 -0
  134. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/tests/api/v1/test_views.py +0 -0
  135. {edx_enterprise_data-8.0.0/enterprise_reporting/tests → edx_enterprise_data-8.2.0/enterprise_data/tests/api/v1/views}/__init__.py +0 -0
  136. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/tests/factories.py +0 -0
  137. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/tests/mixins.py +0 -0
  138. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/tests/test_clients.py +0 -0
  139. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/tests/test_models.py +0 -0
  140. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/tests/test_views.py +0 -0
  141. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data/urls.py +0 -0
  142. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data_roles/__init__.py +0 -0
  143. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data_roles/admin.py +0 -0
  144. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data_roles/apps.py +0 -0
  145. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data_roles/constants.py +0 -0
  146. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data_roles/migrations/0001_initial.py +0 -0
  147. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data_roles/migrations/0002_add_enterprise_data_feature_roles.py +0 -0
  148. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data_roles/migrations/0003_add_role_based_access_control_switch.py +0 -0
  149. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data_roles/migrations/0004_enterprisedataroleassignment_enterprise_id.py +0 -0
  150. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data_roles/migrations/0005_turn_on_role_based_access_control_switch.py +0 -0
  151. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data_roles/migrations/0006_remove_role_based_access_control_switch.py +0 -0
  152. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data_roles/migrations/0007_enterprisedataroleassignment_applies_to_all_contexts.py +0 -0
  153. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data_roles/models.py +0 -0
  154. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data_roles/rules.py +0 -0
  155. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data_roles/tests/factories.py +0 -0
  156. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_data_roles/tests/test_models.py +0 -0
  157. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_reporting/__init__.py +0 -0
  158. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_reporting/clients/enterprise.py +0 -0
  159. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_reporting/clients/s3.py +0 -0
  160. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_reporting/clients/snowflake.py +0 -0
  161. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_reporting/clients/vertica.py +0 -0
  162. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_reporting/delivery_method.py +0 -0
  163. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_reporting/external_resource_link_report.py +3 -3
  164. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_reporting/fixtures/enterprise_customer_reporting.json +0 -0
  165. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_reporting/reporter.py +0 -0
  166. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_reporting/send_enterprise_reports.py +0 -0
  167. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_reporting/tests/test_clients.py +1 -1
  168. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_reporting/tests/test_delivery_method.py +0 -0
  169. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_reporting/tests/test_external_link_report.py +2 -2
  170. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_reporting/tests/test_reporter.py +0 -0
  171. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_reporting/tests/test_send_enterprise_reports.py +0 -0
  172. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_reporting/tests/test_utils.py +3 -3
  173. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_reporting/tests/test_vertica_client.py +0 -0
  174. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_reporting/tests/utils.py +0 -0
  175. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/enterprise_reporting/utils.py +1 -1
  176. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/requirements/pip_tools.txt +0 -0
  177. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/requirements/reporting.in +0 -0
  178. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/setup.cfg +0 -0
  179. {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.2.0}/setup.py +0 -0
@@ -15,6 +15,14 @@ Unreleased
15
15
  ----------
16
16
 
17
17
  =========================
18
+ [8.2.0] - 2024-07-25
19
+ ---------------------
20
+ * Added a new API endpoint to get admin analytics aggregated data on user enrollment and engagement.
21
+
22
+ [8.1.0] - 2024-07-22
23
+ ---------------------
24
+ * Upgrade python requirements
25
+
18
26
  [8.0.0] - 2024-07-18
19
27
  ---------------------
20
28
  * Fix migration for EnterpriseLearnerEnrollment model
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: edx-enterprise-data
3
- Version: 8.0.0
3
+ Version: 8.2.0
4
4
  Summary: Enterprise Reporting
5
5
  Home-page: https://github.com/openedx/edx-enterprise-data
6
6
  Author: edX
@@ -23,6 +23,9 @@ Requires-Dist: edx-opaque-keys
23
23
  Requires-Dist: edx-rbac
24
24
  Requires-Dist: edx-rest-api-client
25
25
  Requires-Dist: factory_boy
26
+ Requires-Dist: mysql-connector-python
27
+ Requires-Dist: numpy<=1.24.4
28
+ Requires-Dist: pandas<=2.0.3
26
29
  Requires-Dist: requests
27
30
  Requires-Dist: rules
28
31
  Provides-Extra: reporting
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: edx-enterprise-data
3
- Version: 8.0.0
3
+ Version: 8.2.0
4
4
  Summary: Enterprise Reporting
5
5
  Home-page: https://github.com/openedx/edx-enterprise-data
6
6
  Author: edX
@@ -23,6 +23,9 @@ Requires-Dist: edx-opaque-keys
23
23
  Requires-Dist: edx-rbac
24
24
  Requires-Dist: edx-rest-api-client
25
25
  Requires-Dist: factory_boy
26
+ Requires-Dist: mysql-connector-python
27
+ Requires-Dist: numpy<=1.24.4
28
+ Requires-Dist: pandas<=2.0.3
26
29
  Requires-Dist: requests
27
30
  Requires-Dist: rules
28
31
  Provides-Extra: reporting
@@ -20,6 +20,11 @@ enterprise_data/renderers.py
20
20
  enterprise_data/signals.py
21
21
  enterprise_data/urls.py
22
22
  enterprise_data/utils.py
23
+ enterprise_data/admin_analytics/__init__.py
24
+ enterprise_data/admin_analytics/constants.py
25
+ enterprise_data/admin_analytics/data_loaders.py
26
+ enterprise_data/admin_analytics/database.py
27
+ enterprise_data/admin_analytics/utils.py
23
28
  enterprise_data/api/__init__.py
24
29
  enterprise_data/api/urls.py
25
30
  enterprise_data/api/v0/__init__.py
@@ -29,7 +34,11 @@ enterprise_data/api/v0/views.py
29
34
  enterprise_data/api/v1/__init__.py
30
35
  enterprise_data/api/v1/serializers.py
31
36
  enterprise_data/api/v1/urls.py
32
- enterprise_data/api/v1/views.py
37
+ enterprise_data/api/v1/views/__init__.py
38
+ enterprise_data/api/v1/views/base.py
39
+ enterprise_data/api/v1/views/enterprise_admin.py
40
+ enterprise_data/api/v1/views/enterprise_learner.py
41
+ enterprise_data/api/v1/views/enterprise_offers.py
33
42
  enterprise_data/fixtures/enterprise_enrollment.json
34
43
  enterprise_data/fixtures/enterprise_user.json
35
44
  enterprise_data/management/__init__.py
@@ -99,12 +108,17 @@ enterprise_data/tests/test_filters.py
99
108
  enterprise_data/tests/test_models.py
100
109
  enterprise_data/tests/test_utils.py
101
110
  enterprise_data/tests/test_views.py
111
+ enterprise_data/tests/admin_analytics/__init__.py
112
+ enterprise_data/tests/admin_analytics/test_data_loaders.py
113
+ enterprise_data/tests/admin_analytics/test_utils.py
102
114
  enterprise_data/tests/api/__init__.py
103
115
  enterprise_data/tests/api/v0/__init__.py
104
116
  enterprise_data/tests/api/v0/test_serializers.py
105
117
  enterprise_data/tests/api/v1/__init__.py
106
118
  enterprise_data/tests/api/v1/test_serializers.py
107
119
  enterprise_data/tests/api/v1/test_views.py
120
+ enterprise_data/tests/api/v1/views/__init__.py
121
+ enterprise_data/tests/api/v1/views/test_enterprise_admin.py
108
122
  enterprise_data_roles/__init__.py
109
123
  enterprise_data_roles/admin.py
110
124
  enterprise_data_roles/apps.py
@@ -9,6 +9,9 @@ edx-opaque-keys
9
9
  edx-rbac
10
10
  edx-rest-api-client
11
11
  factory_boy
12
+ mysql-connector-python
13
+ numpy<=1.24.4
14
+ pandas<=2.0.3
12
15
  requests
13
16
  rules
14
17
 
@@ -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.0.0"
5
+ __version__ = "8.2.0"
@@ -0,0 +1,15 @@
1
+ """
2
+ Constants for admin analytics.
3
+ """
4
+ import mysql.connector
5
+
6
+ from django.conf import settings
7
+
8
+ DATABASE_CONNECTION_CONFIG = {
9
+ 'host': settings.DATABASES[settings.ENTERPRISE_REPORTING_DB_ALIAS]['HOST'],
10
+ 'port': settings.DATABASES[settings.ENTERPRISE_REPORTING_DB_ALIAS]['PORT'],
11
+ 'database': settings.DATABASES[settings.ENTERPRISE_REPORTING_DB_ALIAS]['NAME'],
12
+ 'user': settings.DATABASES[settings.ENTERPRISE_REPORTING_DB_ALIAS]['USER'],
13
+ 'password': settings.DATABASES[settings.ENTERPRISE_REPORTING_DB_ALIAS]['PASSWORD'],
14
+ }
15
+ DATABASE_CONNECTOR = mysql.connector.connect
@@ -0,0 +1,137 @@
1
+ """
2
+ Utility functions for fetching data from the database.
3
+ """
4
+ import numpy
5
+ import pandas
6
+
7
+ from django.http import Http404
8
+
9
+ from enterprise_data.admin_analytics.database import run_query
10
+
11
+
12
+ def get_select_query(table: str, columns: list, enterprise_uuid: str) -> str:
13
+ """
14
+ Generate a SELECT query for the given table and columns.
15
+
16
+ Arguments:
17
+ table (str): The table to query.
18
+ columns (list): The columns to select.
19
+ enterprise_uuid (str): The UUID of the enterprise customer.
20
+
21
+ Returns:
22
+ (str): The SELECT query.
23
+ """
24
+ return f'SELECT {", ".join(columns)} FROM {table} WHERE enterprise_customer_uuid = "{enterprise_uuid}"'
25
+
26
+
27
+ def fetch_enrollment_data(enterprise_uuid: str):
28
+ """
29
+ Fetch enrollment data from the database for the given enterprise customer.
30
+
31
+ Arguments:
32
+ enterprise_uuid (str): The UUID of the enterprise customer.
33
+
34
+ Returns:
35
+ (pandas.DataFrame): The enrollment data.
36
+ """
37
+ enterprise_uuid = enterprise_uuid.replace('-', '')
38
+
39
+ columns = [
40
+ 'enterprise_customer_name',
41
+ 'enterprise_customer_uuid',
42
+ 'lms_enrollment_id',
43
+ 'user_id',
44
+ 'email',
45
+ 'course_key',
46
+ 'courserun_key',
47
+ 'course_id',
48
+ 'course_subject',
49
+ 'course_title',
50
+ 'enterprise_enrollment_date',
51
+ 'lms_enrollment_mode',
52
+ 'enroll_type',
53
+ 'program_title',
54
+ 'date_certificate_awarded',
55
+ 'grade_percent',
56
+ 'cert_awarded',
57
+ 'date_certificate_created_raw',
58
+ 'passed_date_raw',
59
+ 'passed_date',
60
+ 'has_passed',
61
+ ]
62
+ query = get_select_query(
63
+ table='fact_enrollment_admin_dash',
64
+ columns=columns,
65
+ enterprise_uuid=enterprise_uuid,
66
+ )
67
+
68
+ results = run_query(query=query)
69
+ if not results:
70
+ raise Http404(f'No enrollment data found for enterprise {enterprise_uuid}')
71
+
72
+ enrollments = pandas.DataFrame(numpy.array(results), columns=columns)
73
+
74
+ # Convert date columns to datetime.
75
+ enrollments['enterprise_enrollment_date'] = enrollments['enterprise_enrollment_date'].astype('datetime64[ns]')
76
+ enrollments['date_certificate_awarded'] = enrollments['date_certificate_awarded'].astype('datetime64[ns]')
77
+ enrollments['date_certificate_created_raw'] = enrollments['date_certificate_created_raw'].astype('datetime64[ns]')
78
+ enrollments['passed_date_raw'] = enrollments['passed_date_raw'].astype('datetime64[ns]')
79
+ enrollments['passed_date'] = enrollments['passed_date'].astype('datetime64[ns]')
80
+
81
+ return enrollments
82
+
83
+
84
+ def fetch_engagement_data(enterprise_uuid: str):
85
+ """
86
+ Fetch engagement data from the database for the given enterprise customer.
87
+
88
+ Arguments:
89
+ enterprise_uuid (str): The UUID of the enterprise customer.
90
+
91
+ Returns:
92
+ (pandas.DataFrame): The engagement data.
93
+ """
94
+ enterprise_uuid = enterprise_uuid.replace('-', '')
95
+
96
+ columns = [
97
+ 'user_id',
98
+ 'email',
99
+ 'enterprise_customer_uuid',
100
+ 'course_key',
101
+ 'enroll_type',
102
+ 'activity_date',
103
+ 'course_title',
104
+ 'course_subject',
105
+ 'is_engaged',
106
+ 'is_engaged_video',
107
+ 'is_engaged_forum',
108
+ 'is_engaged_problem',
109
+ 'is_active',
110
+ 'learning_time_seconds',
111
+ ]
112
+ query = get_select_query(
113
+ table='fact_enrollment_engagement_day_admin_dash', columns=columns, enterprise_uuid=enterprise_uuid
114
+ )
115
+
116
+ results = run_query(query=query)
117
+ if not results:
118
+ raise Http404(f'No engagement data found for enterprise {enterprise_uuid}')
119
+
120
+ engagement = pandas.DataFrame(numpy.array(results), columns=columns)
121
+ engagement['activity_date'] = engagement['activity_date'].astype('datetime64[ns]')
122
+
123
+ return engagement
124
+
125
+
126
+ def fetch_max_enrollment_datetime():
127
+ """
128
+ Fetch the latest created date from the enterprise_learner_enrollment table.
129
+
130
+ created will be same for all records as this is added at the time of data load. Which is when the async process
131
+ populates the data in the table. We can use this to get the latest data load time.
132
+ """
133
+ query = "SELECT MAX(created) FROM enterprise_learner_enrollment"
134
+ results = run_query(query)
135
+ if not results:
136
+ return None
137
+ return pandas.to_datetime(results[0][0])
@@ -0,0 +1,31 @@
1
+ """
2
+ Utility functions for interacting with the database.
3
+ """
4
+ from contextlib import closing
5
+ from logging import getLogger
6
+
7
+ from enterprise_data.admin_analytics.constants import DATABASE_CONNECTION_CONFIG, DATABASE_CONNECTOR
8
+ from enterprise_data.utils import timeit
9
+
10
+ LOGGER = getLogger(__name__)
11
+
12
+
13
+ @timeit
14
+ def run_query(query):
15
+ """
16
+ Run a query on the database and return the results.
17
+
18
+ Arguments:
19
+ query (str): The query to run.
20
+
21
+ Returns:
22
+ (list): The results of the query.
23
+ """
24
+ try:
25
+ with closing(DATABASE_CONNECTOR(**DATABASE_CONNECTION_CONFIG)) as connection:
26
+ with closing(connection.cursor()) as cursor:
27
+ cursor.execute(query)
28
+ return cursor.fetchall()
29
+ except Exception:
30
+ LOGGER.exception(f'[run_query]: run_query failed for query "{query}".')
31
+ raise
@@ -0,0 +1,81 @@
1
+ """
2
+ Utility functions for fetching data from the database.
3
+ """
4
+ from datetime import datetime
5
+
6
+ from edx_django_utils.cache import TieredCache, get_cache_key
7
+
8
+ from enterprise_data.admin_analytics.data_loaders import fetch_engagement_data, fetch_enrollment_data
9
+
10
+
11
+ def get_cache_timeout(cache_expiry):
12
+ """
13
+ Helper method to calculate cache timeout in seconds.
14
+
15
+ Arguments:
16
+ cache_expiry (datetime): Datetime object denoting the cache expiry.
17
+
18
+ Returns:
19
+ (int): Cache timeout in seconds.
20
+ """
21
+ now = datetime.now()
22
+ cache_timeout = 0
23
+ if cache_expiry > now:
24
+ # Calculate cache expiry in seconds from now.
25
+ cache_timeout = (cache_expiry - now).seconds
26
+
27
+ return cache_timeout
28
+
29
+
30
+ def fetch_and_cache_enrollments_data(enterprise_id, cache_expiry):
31
+ """
32
+ Helper method to fetch and cache enrollments data.
33
+
34
+ Arguments:
35
+ enterprise_id (str): UUID of the enterprise customer in string format.
36
+ cache_expiry (datetime): Datetime object denoting the cache expiry.
37
+
38
+ Returns:
39
+ (pandas.DataFrame): The enrollments data.
40
+ """
41
+ cache_key = get_cache_key(
42
+ resource='enterprise-admin-analytics-aggregates-enrollments',
43
+ enterprise_customer=enterprise_id,
44
+ )
45
+ cached_response = TieredCache.get_cached_response(cache_key)
46
+
47
+ if cached_response.is_found:
48
+ return cached_response.value
49
+ else:
50
+ enrollments = fetch_enrollment_data(enterprise_id)
51
+ TieredCache.set_all_tiers(
52
+ cache_key, enrollments, get_cache_timeout(cache_expiry)
53
+ )
54
+ return enrollments
55
+
56
+
57
+ def fetch_and_cache_engagements_data(enterprise_id, cache_expiry):
58
+ """
59
+ Helper method to fetch and cache engagements data.
60
+
61
+ Arguments:
62
+ enterprise_id (str): UUID of the enterprise customer in string format.
63
+ cache_expiry (datetime): Datetime object denoting the cache expiry.
64
+
65
+ Returns:
66
+ (pandas.DataFrame): The engagements data.
67
+ """
68
+ cache_key = get_cache_key(
69
+ resource='enterprise-admin-analytics-aggregates-engagements',
70
+ enterprise_customer=enterprise_id,
71
+ )
72
+ cached_response = TieredCache.get_cached_response(cache_key)
73
+
74
+ if cached_response.is_found:
75
+ return cached_response.value
76
+ else:
77
+ engagements = fetch_engagement_data(enterprise_id)
78
+ TieredCache.set_all_tiers(
79
+ cache_key, engagements, get_cache_timeout(cache_expiry)
80
+ )
81
+ return engagements
@@ -196,3 +196,23 @@ class EnterpriseAdminSummarizeInsightsSerializer(serializers.ModelSerializer):
196
196
  class Meta:
197
197
  model = EnterpriseAdminSummarizeInsights
198
198
  fields = '__all__'
199
+
200
+
201
+ class AdminAnalyticsAggregatesQueryParamsSerializer(serializers.Serializer): # pylint: disable=abstract-method
202
+ """
203
+ Serializer for validating admin analytics query params.
204
+ """
205
+ start_date = serializers.DateField(required=False)
206
+ end_date = serializers.DateField(required=False)
207
+
208
+ def validate(self, attrs):
209
+ """
210
+ Validate the query params.
211
+
212
+ Raises:
213
+ serializers.ValidationError: If start_date is greater than end_date.
214
+ """
215
+ if 'start_date' in attrs and 'end_date' in attrs:
216
+ if attrs['start_date'] > attrs['end_date']:
217
+ raise serializers.ValidationError("start_date should be less than or equal to end_date.")
218
+ return attrs
@@ -7,7 +7,9 @@ from rest_framework.routers import DefaultRouter
7
7
 
8
8
  from django.urls import re_path
9
9
 
10
- from enterprise_data.api.v1 import views
10
+ from enterprise_data.api.v1.views import enterprise_admin as enterprise_admin_views
11
+ from enterprise_data.api.v1.views import enterprise_learner as enterprise_learner_views
12
+ from enterprise_data.api.v1.views import enterprise_offers as enterprise_offers_views
11
13
  from enterprise_data.constants import UUID4_REGEX
12
14
 
13
15
  app_name = 'enterprise_data_api_v1'
@@ -15,31 +17,36 @@ app_name = 'enterprise_data_api_v1'
15
17
  router = DefaultRouter()
16
18
  router.register(
17
19
  r'enterprise/(?P<enterprise_id>.+)/enrollments',
18
- views.EnterpriseLearnerEnrollmentViewSet,
20
+ enterprise_learner_views.EnterpriseLearnerEnrollmentViewSet,
19
21
  'enterprise-learner-enrollment',
20
22
  )
21
23
  router.register(
22
24
  r'enterprise/(?P<enterprise_id>.+)/offers',
23
- views.EnterpriseOfferViewSet,
25
+ enterprise_offers_views.EnterpriseOfferViewSet,
24
26
  'enterprise-offers',
25
27
  )
26
28
  router.register(
27
29
  r'enterprise/(?P<enterprise_id>.+)/users',
28
- views.EnterpriseLearnerViewSet,
30
+ enterprise_learner_views.EnterpriseLearnerViewSet,
29
31
  'enterprise-learner',
30
32
  )
31
33
  router.register(
32
34
  r'enterprise/(?P<enterprise_id>.+)/learner_completed_courses',
33
- views.EnterpriseLearnerCompletedCoursesViewSet,
35
+ enterprise_learner_views.EnterpriseLearnerCompletedCoursesViewSet,
34
36
  'enterprise-learner-completed-courses',
35
37
  )
36
38
 
37
39
  urlpatterns = [
38
40
  re_path(
39
41
  fr'^admin/insights/(?P<enterprise_id>{UUID4_REGEX})$',
40
- views.EnterpriseAdminInsightsView.as_view(),
42
+ enterprise_admin_views.EnterpriseAdminInsightsView.as_view(),
41
43
  name='enterprise-admin-insights'
42
44
  ),
45
+ re_path(
46
+ fr'^admin/anlaytics/(?P<enterprise_id>{UUID4_REGEX})$',
47
+ enterprise_admin_views.EnterpriseAdminAnalyticsAggregatesView.as_view(),
48
+ name='enterprise-admin-analytics-aggregates'
49
+ ),
43
50
  ]
44
51
 
45
52
  urlpatterns += router.urls
@@ -0,0 +1,26 @@
1
+ """
2
+ Base views for enterprise data api v1.
3
+ """
4
+ from edx_rbac.mixins import PermissionRequiredMixin
5
+ from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication
6
+ from edx_rest_framework_extensions.paginators import DefaultPagination
7
+
8
+ from enterprise_data.constants import ANALYTICS_API_VERSION_1
9
+
10
+
11
+ class EnterpriseViewSetMixin(PermissionRequiredMixin):
12
+ """
13
+ Base class for all Enterprise view sets.
14
+ """
15
+ authentication_classes = (JwtAuthentication,)
16
+ pagination_class = DefaultPagination
17
+ permission_required = 'can_access_enterprise'
18
+ API_VERSION = ANALYTICS_API_VERSION_1
19
+
20
+ def paginate_queryset(self, queryset):
21
+ """
22
+ Allows no_page query param to skip pagination
23
+ """
24
+ if 'no_page' in self.request.query_params:
25
+ return None
26
+ return super().paginate_queryset(queryset)
@@ -0,0 +1,109 @@
1
+ """
2
+ Views for enterprise admin api v1.
3
+ """
4
+ from datetime import datetime, timedelta
5
+
6
+ from edx_rbac.decorators import permission_required
7
+ from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication
8
+ from rest_framework.response import Response
9
+ from rest_framework.status import HTTP_200_OK, HTTP_404_NOT_FOUND
10
+ from rest_framework.views import APIView
11
+
12
+ from enterprise_data.admin_analytics.data_loaders import fetch_max_enrollment_datetime
13
+ from enterprise_data.admin_analytics.utils import fetch_and_cache_engagements_data, fetch_and_cache_enrollments_data
14
+ from enterprise_data.api.v1 import serializers
15
+ from enterprise_data.models import EnterpriseAdminLearnerProgress, EnterpriseAdminSummarizeInsights
16
+ from enterprise_data.utils import date_filter
17
+
18
+
19
+ class EnterpriseAdminInsightsView(APIView):
20
+ """
21
+ API for getting the enterprise admin insights.
22
+ """
23
+ authentication_classes = (JwtAuthentication,)
24
+ http_method_names = ['get']
25
+
26
+ @permission_required('can_access_enterprise', fn=lambda request, enterprise_id: enterprise_id)
27
+ def get(self, request, enterprise_id):
28
+ """
29
+ HTTP GET endpoint to retrieve the enterprise admin insights
30
+ """
31
+ response_data = {}
32
+ learner_progress = {}
33
+ learner_engagement = {}
34
+
35
+ try:
36
+ learner_progress = EnterpriseAdminLearnerProgress.objects.get(enterprise_customer_uuid=enterprise_id)
37
+ learner_progress = serializers.EnterpriseAdminLearnerProgressSerializer(learner_progress).data
38
+ response_data['learner_progress'] = learner_progress
39
+ except EnterpriseAdminLearnerProgress.DoesNotExist:
40
+ pass
41
+
42
+ try:
43
+ learner_engagement = EnterpriseAdminSummarizeInsights.objects.get(enterprise_customer_uuid=enterprise_id)
44
+ learner_engagement = serializers.EnterpriseAdminSummarizeInsightsSerializer(learner_engagement).data
45
+ response_data['learner_engagement'] = learner_engagement
46
+ except EnterpriseAdminSummarizeInsights.DoesNotExist:
47
+ pass
48
+
49
+ status = HTTP_200_OK
50
+ if learner_progress == {} and learner_engagement == {}:
51
+ status = HTTP_404_NOT_FOUND
52
+
53
+ return Response(data=response_data, status=status)
54
+
55
+
56
+ class EnterpriseAdminAnalyticsAggregatesView(APIView):
57
+ """
58
+ API for getting the enterprise admin analytics aggregates.
59
+ """
60
+ authentication_classes = (JwtAuthentication,)
61
+ http_method_names = ['get']
62
+
63
+ @permission_required('can_access_enterprise', fn=lambda request, enterprise_id: enterprise_id)
64
+ def get(self, request, enterprise_id):
65
+ """
66
+ HTTP GET endpoint to retrieve the enterprise admin aggregate data.
67
+ """
68
+ serializer = serializers.AdminAnalyticsAggregatesQueryParamsSerializer(data=request.GET)
69
+ serializer.is_valid(raise_exception=True)
70
+
71
+ last_updated_at = fetch_max_enrollment_datetime()
72
+ cache_expiry = last_updated_at + timedelta(days=1) if last_updated_at else datetime.now()
73
+
74
+ enrollment = fetch_and_cache_enrollments_data(enterprise_id, cache_expiry).copy()
75
+ engagement = fetch_and_cache_engagements_data(enterprise_id, cache_expiry).copy()
76
+ # Use start and end date if provided by the client, if client has not provided then use
77
+ # 1. minimum enrollment date from the data as the start_date
78
+ # 2. today's date as the end_date
79
+ start_date = serializer.data.get('start_date', enrollment.enterprise_enrollment_date.min())
80
+ end_date = serializer.data.get('end_date', datetime.now())
81
+
82
+ # Date filtering.
83
+ dff = date_filter(
84
+ start=start_date, end=end_date, data_frame=enrollment.copy(), date_column='enterprise_enrollment_date'
85
+ )
86
+
87
+ enrolls = len(dff)
88
+ courses = len(dff.course_key.unique())
89
+
90
+ dff = date_filter(start=start_date, end=end_date, data_frame=enrollment.copy(), date_column='passed_date')
91
+
92
+ completions = dff.has_passed.sum()
93
+
94
+ # Date filtering.
95
+ dff = date_filter(start=start_date, end=end_date, data_frame=engagement.copy(), date_column='activity_date')
96
+
97
+ hours = round(dff.learning_time_seconds.sum() / 60 / 60, 1)
98
+ sessions = dff.is_engaged.sum()
99
+
100
+ return Response(data={
101
+ 'enrolls': enrolls,
102
+ 'courses': courses,
103
+ 'completions': completions,
104
+ 'hours': hours,
105
+ 'sessions': sessions,
106
+ 'last_updated_at': last_updated_at.date() if last_updated_at else None,
107
+ 'min_enrollment_date': enrollment.enterprise_enrollment_date.min().date(),
108
+ 'max_enrollment_date': enrollment.enterprise_enrollment_date.max().date(),
109
+ }, status=HTTP_200_OK)