edx-enterprise-data 8.3.1__tar.gz → 8.5.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 (180) hide show
  1. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/CHANGELOG.rst +9 -0
  2. {edx_enterprise_data-8.3.1/edx_enterprise_data.egg-info → edx_enterprise_data-8.5.0}/PKG-INFO +1 -1
  3. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0/edx_enterprise_data.egg-info}/PKG-INFO +1 -1
  4. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/edx_enterprise_data.egg-info/SOURCES.txt +1 -0
  5. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/__init__.py +1 -1
  6. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/admin_analytics/data_loaders.py +45 -0
  7. edx_enterprise_data-8.5.0/enterprise_data/admin_analytics/utils.py +257 -0
  8. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/api/v1/serializers.py +11 -0
  9. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/api/v1/urls.py +10 -0
  10. edx_enterprise_data-8.5.0/enterprise_data/api/v1/views/enterprise_admin.py +261 -0
  11. edx_enterprise_data-8.5.0/enterprise_data/migrations/0044_enterpriseexecedlcmoduleperformance.py +87 -0
  12. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/models.py +94 -0
  13. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/tests/admin_analytics/test_data_loaders.py +30 -1
  14. edx_enterprise_data-8.5.0/enterprise_data/tests/admin_analytics/test_utils.py +241 -0
  15. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/tests/api/v1/views/test_enterprise_admin.py +49 -0
  16. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/tests/test_utils.py +28 -0
  17. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/utils.py +16 -0
  18. edx_enterprise_data-8.3.1/enterprise_data/admin_analytics/utils.py +0 -81
  19. edx_enterprise_data-8.3.1/enterprise_data/api/v1/views/enterprise_admin.py +0 -109
  20. edx_enterprise_data-8.3.1/enterprise_data/tests/admin_analytics/test_utils.py +0 -102
  21. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/LICENSE +0 -0
  22. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/MANIFEST.in +0 -0
  23. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/README.md +0 -0
  24. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/edx_enterprise_data.egg-info/dependency_links.txt +0 -0
  25. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/edx_enterprise_data.egg-info/not-zip-safe +0 -0
  26. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/edx_enterprise_data.egg-info/requires.txt +0 -0
  27. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/edx_enterprise_data.egg-info/top_level.txt +0 -0
  28. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/admin_analytics/__init__.py +0 -0
  29. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/admin_analytics/database.py +0 -0
  30. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/api/__init__.py +0 -0
  31. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/api/urls.py +0 -0
  32. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/api/v0/__init__.py +0 -0
  33. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/api/v0/serializers.py +0 -0
  34. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/api/v0/urls.py +0 -0
  35. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/api/v0/views.py +0 -0
  36. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/api/v1/__init__.py +0 -0
  37. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/api/v1/views/__init__.py +0 -0
  38. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/api/v1/views/base.py +0 -0
  39. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/api/v1/views/enterprise_learner.py +0 -0
  40. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/api/v1/views/enterprise_offers.py +0 -0
  41. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/apps.py +0 -0
  42. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/clients.py +0 -0
  43. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/constants.py +0 -0
  44. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/filters.py +0 -0
  45. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/fixtures/enterprise_enrollment.json +0 -0
  46. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/fixtures/enterprise_user.json +0 -0
  47. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/management/__init__.py +0 -0
  48. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/management/commands/__init__.py +0 -0
  49. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/management/commands/create_dummy_data.py +0 -0
  50. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/management/commands/create_dummy_data_lpr_v1.py +0 -0
  51. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/management/commands/create_enterprise_enrollment.py +0 -0
  52. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/management/commands/create_enterprise_learner_enrollment_lpr_v1.py +0 -0
  53. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/management/commands/create_enterprise_learner_lpr_v1.py +0 -0
  54. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/management/commands/create_enterprise_offer.py +0 -0
  55. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/management/commands/create_enterprise_user.py +0 -0
  56. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/management/commands/tests/__init__.py +0 -0
  57. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/management/commands/tests/test_create_dummy_data_lpr_v1.py +0 -0
  58. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/management/commands/tests/test_create_enterprise_enrollment.py +0 -0
  59. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/management/commands/tests/test_create_enterprise_learner_enrollment_lpr_v1.py +0 -0
  60. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/management/commands/tests/test_create_enterprise_learner_lpr_v1.py +0 -0
  61. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/management/commands/tests/test_create_enterprise_user.py +0 -0
  62. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/migrations/0001_initial.py +0 -0
  63. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/migrations/0002_auto_20180430_1358.py +0 -0
  64. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/migrations/0003_auto_20180501_0603.py +0 -0
  65. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/migrations/0004_auto_20180501_0928.py +0 -0
  66. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/migrations/0004_auto_20180508_1652.py +0 -0
  67. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/migrations/0005_auto_20180524_2204.py +0 -0
  68. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/migrations/0006_auto_20180612_0336.py +0 -0
  69. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/migrations/0007_auto_20180612_0534.py +0 -0
  70. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/migrations/0008_auto_20180614_0108.py +0 -0
  71. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/migrations/0009_auto_20180628_1152.py +0 -0
  72. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/migrations/0010_enterpriseenrollment_created.py +0 -0
  73. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/migrations/0011_enterpriseuser.py +0 -0
  74. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/migrations/0012_auto_20180831_1930.py +0 -0
  75. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/migrations/0013_auto_20180831_1931.py +0 -0
  76. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/migrations/0014_enterpriseuser_created.py +0 -0
  77. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/migrations/0015_auto_20180907_1757.py +0 -0
  78. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/migrations/0016_auto_20180924_2138.py +0 -0
  79. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/migrations/0017_enterpriseenrollment_unenrollment_timestamp.py +0 -0
  80. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/migrations/0018_enterprisedatafeaturerole_enterprisedataroleassignment.py +0 -0
  81. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/migrations/0019_add_enterprise_data_feature_roles.py +0 -0
  82. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/migrations/0020_add_role_based_access_control_switch.py +0 -0
  83. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/migrations/0021_auto_20190329_1241.py +0 -0
  84. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/migrations/0022_remove_role_based_access_control_switch.py +0 -0
  85. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/migrations/0023_enterpriselearner_enterpriselearnerenrollment.py +0 -0
  86. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/migrations/0024_auto_20210602_1811.py +0 -0
  87. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/migrations/0025_auto_20210703_1854.py +0 -0
  88. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/migrations/0026_auto_20210916_0414.py +0 -0
  89. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/migrations/0027_enterpriselearnerenrollment_total_learning_time_seconds.py +0 -0
  90. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/migrations/0028_enterpriselearnerenrollment_offer_id.py +0 -0
  91. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/migrations/0029_enterpriseoffer.py +0 -0
  92. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/migrations/0030_auto_20230609_1353.py +0 -0
  93. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/migrations/0031_auto_20230615_0705.py +0 -0
  94. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/migrations/0032_auto_20230704_0818.py +0 -0
  95. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/migrations/0033_enterpriseadminlearnerprogress_enterpriseadminsummarizeinsights.py +0 -0
  96. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/migrations/0034_auto_20230907_0834.py +0 -0
  97. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/migrations/0035_auto_20230907_1154.py +0 -0
  98. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/migrations/0036_enterprisesubsidybudget_subsidy_access_policy_display_name.py +0 -0
  99. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/migrations/0037_alter_enterpriseenrollment_consent_granted.py +0 -0
  100. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/migrations/0038_enterpriseoffer_export_timestamp.py +0 -0
  101. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/migrations/0039_auto_20240212_1403.py +0 -0
  102. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/migrations/0040_auto_20240718_0536_squashed_0043_alter_enterpriselearnerenrollment_enterprise_enrollment_id.py +0 -0
  103. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/migrations/__init__.py +0 -0
  104. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/paginators.py +0 -0
  105. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/renderers.py +0 -0
  106. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/settings/__init__.py +0 -0
  107. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/settings/test.py +0 -0
  108. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/signals.py +0 -0
  109. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/tests/__init__.py +0 -0
  110. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/tests/admin_analytics/__init__.py +0 -0
  111. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/tests/api/__init__.py +0 -0
  112. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/tests/api/v0/__init__.py +0 -0
  113. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/tests/api/v0/test_serializers.py +0 -0
  114. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/tests/api/v1/__init__.py +0 -0
  115. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/tests/api/v1/test_serializers.py +0 -0
  116. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/tests/api/v1/test_views.py +0 -0
  117. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/tests/api/v1/views/__init__.py +0 -0
  118. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/tests/factories.py +0 -0
  119. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/tests/mixins.py +0 -0
  120. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/tests/test_clients.py +0 -0
  121. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/tests/test_filters.py +0 -0
  122. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/tests/test_models.py +0 -0
  123. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/tests/test_views.py +0 -0
  124. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data/urls.py +0 -0
  125. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data_roles/__init__.py +0 -0
  126. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data_roles/admin.py +0 -0
  127. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data_roles/apps.py +0 -0
  128. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data_roles/constants.py +0 -0
  129. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data_roles/migrations/0001_initial.py +0 -0
  130. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data_roles/migrations/0002_add_enterprise_data_feature_roles.py +0 -0
  131. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data_roles/migrations/0003_add_role_based_access_control_switch.py +0 -0
  132. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data_roles/migrations/0004_enterprisedataroleassignment_enterprise_id.py +0 -0
  133. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data_roles/migrations/0005_turn_on_role_based_access_control_switch.py +0 -0
  134. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data_roles/migrations/0006_remove_role_based_access_control_switch.py +0 -0
  135. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data_roles/migrations/0007_enterprisedataroleassignment_applies_to_all_contexts.py +0 -0
  136. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data_roles/migrations/__init__.py +0 -0
  137. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data_roles/models.py +0 -0
  138. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data_roles/rules.py +0 -0
  139. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data_roles/tests/__init__.py +0 -0
  140. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data_roles/tests/factories.py +0 -0
  141. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_data_roles/tests/test_models.py +0 -0
  142. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_reporting/__init__.py +0 -0
  143. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_reporting/clients/__init__.py +0 -0
  144. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_reporting/clients/enterprise.py +0 -0
  145. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_reporting/clients/s3.py +0 -0
  146. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_reporting/clients/snowflake.py +0 -0
  147. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_reporting/clients/vertica.py +0 -0
  148. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_reporting/delivery_method.py +0 -0
  149. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_reporting/external_resource_link_report.py +0 -0
  150. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_reporting/fixtures/__init__.py +0 -0
  151. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_reporting/fixtures/enterprise_customer_reporting.json +0 -0
  152. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_reporting/reporter.py +0 -0
  153. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_reporting/send_enterprise_reports.py +0 -0
  154. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_reporting/tests/__init__.py +0 -0
  155. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_reporting/tests/test_clients.py +0 -0
  156. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_reporting/tests/test_delivery_method.py +0 -0
  157. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_reporting/tests/test_enterprise_client.py +0 -0
  158. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_reporting/tests/test_external_link_report.py +0 -0
  159. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_reporting/tests/test_reporter.py +0 -0
  160. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_reporting/tests/test_send_enterprise_reports.py +0 -0
  161. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_reporting/tests/test_utils.py +0 -0
  162. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_reporting/tests/test_vertica_client.py +0 -0
  163. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_reporting/tests/utils.py +0 -0
  164. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/enterprise_reporting/utils.py +0 -0
  165. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/requirements/base.in +0 -0
  166. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/requirements/base.txt +0 -0
  167. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/requirements/ci.txt +0 -0
  168. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/requirements/common_constraints.txt +0 -0
  169. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/requirements/constraints.txt +0 -0
  170. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/requirements/dev.txt +0 -0
  171. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/requirements/django.txt +0 -0
  172. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/requirements/pip.txt +0 -0
  173. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/requirements/pip_tools.txt +0 -0
  174. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/requirements/quality.txt +0 -0
  175. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/requirements/reporting.in +0 -0
  176. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/requirements/test-master.txt +0 -0
  177. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/requirements/test-reporting.txt +0 -0
  178. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/requirements/test.txt +0 -0
  179. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/setup.cfg +0 -0
  180. {edx_enterprise_data-8.3.1 → edx_enterprise_data-8.5.0}/setup.py +0 -0
@@ -15,6 +15,15 @@ Unreleased
15
15
  ----------
16
16
 
17
17
  =========================
18
+
19
+ [8.5.0] - 2024-08-12
20
+ ---------------------
21
+ * Added a new model and REST endpoint to get Exec Ed LC Module Performance data.
22
+
23
+ [8.4.0] - 2024-08-09
24
+ ---------------------
25
+ * feat: endpoint to get skills aggregated data for an enterprise customer
26
+
18
27
  [8.3.1] - 2024-08-06
19
28
  ---------------------
20
29
  * Dependency updates
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: edx-enterprise-data
3
- Version: 8.3.1
3
+ Version: 8.5.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.3.1
3
+ Version: 8.5.0
4
4
  Summary: Enterprise Reporting
5
5
  Home-page: https://github.com/openedx/edx-enterprise-data
6
6
  Author: edX
@@ -96,6 +96,7 @@ enterprise_data/migrations/0037_alter_enterpriseenrollment_consent_granted.py
96
96
  enterprise_data/migrations/0038_enterpriseoffer_export_timestamp.py
97
97
  enterprise_data/migrations/0039_auto_20240212_1403.py
98
98
  enterprise_data/migrations/0040_auto_20240718_0536_squashed_0043_alter_enterpriselearnerenrollment_enterprise_enrollment_id.py
99
+ enterprise_data/migrations/0044_enterpriseexecedlcmoduleperformance.py
99
100
  enterprise_data/migrations/__init__.py
100
101
  enterprise_data/settings/__init__.py
101
102
  enterprise_data/settings/test.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.3.1"
5
+ __version__ = "8.5.0"
@@ -135,3 +135,48 @@ def fetch_max_enrollment_datetime():
135
135
  if not results:
136
136
  return None
137
137
  return pandas.to_datetime(results[0][0])
138
+
139
+
140
+ def fetch_skills_data(enterprise_uuid: str):
141
+ """
142
+ Fetch skills data from the database for the given enterprise customer.
143
+
144
+ Arguments:
145
+ enterprise_uuid (str): The UUID of the enterprise customer.
146
+
147
+ Returns:
148
+ (pandas.DataFrame): The skills data.
149
+ """
150
+
151
+ enterprise_uuid = enterprise_uuid.replace('-', '')
152
+
153
+ cols = [
154
+ 'course_number',
155
+ 'skill_type',
156
+ 'skill_name',
157
+ 'skill_url',
158
+ 'confidence',
159
+ 'skill_rank',
160
+ 'course_title',
161
+ 'course_key',
162
+ 'level_type',
163
+ 'primary_subject_name',
164
+ 'date',
165
+ 'enterprise_customer_uuid',
166
+ 'enterprise_customer_name',
167
+ 'enrolls',
168
+ 'completions',
169
+ ]
170
+ query = get_select_query(
171
+ table='skills_daily_rollup_admin_dash', columns=cols, enterprise_uuid=enterprise_uuid
172
+ )
173
+
174
+ skills = run_query(query=query)
175
+
176
+ if not skills:
177
+ raise Http404(f'No skills data found for enterprise {enterprise_uuid}')
178
+
179
+ skills = pandas.DataFrame(numpy.array(skills), columns=cols)
180
+ skills['date'] = skills['date'].astype('datetime64[ns]')
181
+
182
+ return skills
@@ -0,0 +1,257 @@
1
+ """
2
+ Utility functions for fetching data from the database.
3
+ """
4
+ from datetime import datetime
5
+ from enum import Enum
6
+
7
+ from edx_django_utils.cache import TieredCache, get_cache_key
8
+
9
+ from enterprise_data.admin_analytics.data_loaders import fetch_engagement_data, fetch_enrollment_data, fetch_skills_data
10
+ from enterprise_data.utils import date_filter, primary_subject_truncate
11
+
12
+
13
+ class ChartType(Enum):
14
+ """
15
+ Chart types.
16
+ """
17
+ BUBBLE = 'bubble'
18
+ TOP_SKILLS_ENROLLMENT = 'top_skills_enrollment'
19
+ TOP_SKILLS_COMPLETION = 'top_skills_completion'
20
+
21
+
22
+ def get_cache_timeout(cache_expiry):
23
+ """
24
+ Helper method to calculate cache timeout in seconds.
25
+
26
+ Arguments:
27
+ cache_expiry (datetime): Datetime object denoting the cache expiry.
28
+
29
+ Returns:
30
+ (int): Cache timeout in seconds.
31
+ """
32
+ now = datetime.now()
33
+ cache_timeout = 0
34
+ if cache_expiry > now:
35
+ # Calculate cache expiry in seconds from now.
36
+ cache_timeout = (cache_expiry - now).seconds
37
+
38
+ return cache_timeout
39
+
40
+
41
+ def fetch_and_cache_enrollments_data(enterprise_id, cache_expiry):
42
+ """
43
+ Helper method to fetch and cache enrollments data.
44
+
45
+ Arguments:
46
+ enterprise_id (str): UUID of the enterprise customer in string format.
47
+ cache_expiry (datetime): Datetime object denoting the cache expiry.
48
+
49
+ Returns:
50
+ (pandas.DataFrame): The enrollments data.
51
+ """
52
+ cache_key = get_cache_key(
53
+ resource='enterprise-admin-analytics-aggregates-enrollments',
54
+ enterprise_customer=enterprise_id,
55
+ )
56
+ cached_response = TieredCache.get_cached_response(cache_key)
57
+
58
+ if cached_response.is_found:
59
+ return cached_response.value
60
+ else:
61
+ enrollments = fetch_enrollment_data(enterprise_id)
62
+ TieredCache.set_all_tiers(
63
+ cache_key, enrollments, get_cache_timeout(cache_expiry)
64
+ )
65
+ return enrollments
66
+
67
+
68
+ def fetch_and_cache_engagements_data(enterprise_id, cache_expiry):
69
+ """
70
+ Helper method to fetch and cache engagements data.
71
+
72
+ Arguments:
73
+ enterprise_id (str): UUID of the enterprise customer in string format.
74
+ cache_expiry (datetime): Datetime object denoting the cache expiry.
75
+
76
+ Returns:
77
+ (pandas.DataFrame): The engagements data.
78
+ """
79
+ cache_key = get_cache_key(
80
+ resource='enterprise-admin-analytics-aggregates-engagements',
81
+ enterprise_customer=enterprise_id,
82
+ )
83
+ cached_response = TieredCache.get_cached_response(cache_key)
84
+
85
+ if cached_response.is_found:
86
+ return cached_response.value
87
+ else:
88
+ engagements = fetch_engagement_data(enterprise_id)
89
+ TieredCache.set_all_tiers(
90
+ cache_key, engagements, get_cache_timeout(cache_expiry)
91
+ )
92
+ return engagements
93
+
94
+
95
+ def fetch_and_cache_skills_data(enterprise_id, cache_expiry):
96
+ """
97
+ Helper method to fetch and cache skills data.
98
+
99
+ Arguments:
100
+ enterprise_id (str): UUID of the enterprise customer in string format.
101
+ cache_expiry (datetime): Datetime object denoting the cache expiry.
102
+
103
+ Returns:
104
+ (pandas.DataFrame): The skills data.
105
+ """
106
+ cache_key = get_cache_key(
107
+ resource='enterprise-admin-analytics-aggregate-skills',
108
+ enterprise_customer=enterprise_id,
109
+ )
110
+ cached_response = TieredCache.get_cached_response(cache_key)
111
+
112
+ if cached_response.is_found:
113
+ return cached_response.value
114
+ else:
115
+ skills = fetch_skills_data(enterprise_id)
116
+ TieredCache.set_all_tiers(
117
+ cache_key, skills, get_cache_timeout(cache_expiry)
118
+ )
119
+ return skills
120
+
121
+
122
+ def get_skills_bubble_chart_df(skills_filtered):
123
+ """ Get the skills data for the bubble chart.
124
+
125
+ Args:
126
+ skills_filtered (list): The skills data.
127
+
128
+ Returns:
129
+ (pandas.DataFrame): The skills data for the bubble chart.
130
+ """
131
+
132
+ # Group by skill_name and skill_type, and aggregate enrolls and completions
133
+ skills_aggregated = (
134
+ skills_filtered.groupby(['skill_name', 'skill_type'], as_index=False)
135
+ .agg(enrolls=('enrolls', 'sum'), completions=('completions', 'sum'))
136
+ )
137
+
138
+ # Convert enrolls and completions to integers
139
+ skills_aggregated['enrolls'] = skills_aggregated['enrolls'].astype(int)
140
+ skills_aggregated['completions'] = skills_aggregated['completions'].astype(int)
141
+
142
+ # Sort the dataframe by enrolls and completions in descending order
143
+ skills_aggregated = skills_aggregated.sort_values(by=['enrolls', 'completions'], ascending=False)
144
+
145
+ return skills_aggregated
146
+
147
+
148
+ def get_top_skills_enrollment(skills_filtered):
149
+ """ Get the top skills by enrolls.
150
+
151
+ Args:
152
+ skills_filtered (pandas.DataFrame): The skills data.
153
+
154
+ Returns:
155
+ (pandas.DataFrame): The top skills by enrolls data
156
+ """
157
+
158
+ # Get the top 10 skills by enrolls
159
+ top_skills = (
160
+ skills_filtered.groupby('skill_name')
161
+ .enrolls.sum()
162
+ .sort_values(ascending=False)
163
+ .head(10)
164
+ .index
165
+ )
166
+
167
+ # Apply primary_subject_truncate to the primary_subject_name column
168
+ skills_filtered['primary_subject_name'] = skills_filtered['primary_subject_name'].apply(primary_subject_truncate)
169
+
170
+ # Filter data for the top skills and aggregate enrolls by skill_name and primary_subject_name
171
+ top_skills_enrollment_data = (
172
+ skills_filtered[skills_filtered.skill_name.isin(top_skills)]
173
+ .groupby(['skill_name', 'primary_subject_name'], as_index=False)
174
+ .agg(count=('enrolls', 'sum'))
175
+ )
176
+
177
+ # Sort the dataframe by primary_subject_name
178
+ top_skills_enrollment_data = top_skills_enrollment_data.sort_values(by="primary_subject_name")
179
+
180
+ return top_skills_enrollment_data
181
+
182
+
183
+ def get_top_skills_completion(skills_filtered):
184
+ """ Get the top skills by completions.
185
+
186
+ Args:
187
+ skills_filtered (pandas.DataFrame): The skills data.
188
+
189
+ Returns:
190
+ (pandas.DataFrame): The top skills by completions
191
+ """
192
+
193
+ # Get the top 10 skills by completions
194
+ top_skills = (
195
+ skills_filtered.groupby('skill_name')
196
+ .completions.sum()
197
+ .sort_values(ascending=False)
198
+ .head(10)
199
+ .index
200
+ )
201
+
202
+ # Apply primary_subject_truncate to the primary_subject_name column
203
+ skills_filtered['primary_subject_name'] = skills_filtered['primary_subject_name'].apply(primary_subject_truncate)
204
+
205
+ # Filter data for the top skills and aggregate completions by skill_name and primary_subject_name
206
+ top_skills_completion_data = (
207
+ skills_filtered[skills_filtered.skill_name.isin(top_skills)]
208
+ .groupby(['skill_name', 'primary_subject_name'], as_index=False)
209
+ .agg(count=('completions', 'sum'))
210
+ )
211
+
212
+ # Sort the dataframe by primary_subject_name
213
+ top_skills_completion_data = top_skills_completion_data.sort_values(by='primary_subject_name')
214
+
215
+ return top_skills_completion_data
216
+
217
+
218
+ def get_skills_chart_data(chart_type, start_date, end_date, skills):
219
+ """
220
+ Get chart data for skill charts.
221
+
222
+ Arguments:
223
+ chart_type (ChartType): The type of chart to generate.
224
+ start_date (datetime): The start date for the date filter.
225
+ end_date (datetime): The end date for the date filter.
226
+ skills (pandas.DataFrame): The skills data.
227
+ """
228
+ skills_filtered = date_filter(start=start_date, end=end_date, data_frame=skills.copy(), date_column='date')
229
+ if chart_type == ChartType.BUBBLE:
230
+ return get_skills_bubble_chart_df(skills_filtered=skills_filtered.copy())
231
+ elif chart_type == ChartType.TOP_SKILLS_ENROLLMENT:
232
+ return get_top_skills_enrollment(skills_filtered=skills_filtered.copy())
233
+ elif chart_type == ChartType.TOP_SKILLS_COMPLETION:
234
+ return get_top_skills_completion(skills_filtered=skills_filtered.copy())
235
+ else:
236
+ raise ValueError(f"Invalid chart type: {chart_type}")
237
+
238
+
239
+ def get_top_skills_csv_data(skills, start_date, end_date):
240
+ """ Get the top skills data for CSV download.
241
+
242
+ Args:
243
+ skills (pandas.DataFrame): The skills data.
244
+ start_date (str): The start date for the date filter.
245
+ end_date (str): The end date for the date filter.
246
+
247
+ Returns:
248
+ (pandas.DataFrame): The top skills data for CSV download.
249
+ """
250
+ dff = get_skills_chart_data(
251
+ chart_type=ChartType.BUBBLE,
252
+ start_date=start_date,
253
+ end_date=end_date,
254
+ skills=skills.copy(),
255
+ )
256
+ dff = dff.sort_values(by='enrolls', ascending=False)
257
+ return dff
@@ -8,6 +8,7 @@ from rest_framework import serializers
8
8
  from enterprise_data.models import (
9
9
  EnterpriseAdminLearnerProgress,
10
10
  EnterpriseAdminSummarizeInsights,
11
+ EnterpriseExecEdLCModulePerformance,
11
12
  EnterpriseLearner,
12
13
  EnterpriseLearnerEnrollment,
13
14
  EnterpriseOffer,
@@ -216,3 +217,13 @@ class AdminAnalyticsAggregatesQueryParamsSerializer(serializers.Serializer): #
216
217
  if attrs['start_date'] > attrs['end_date']:
217
218
  raise serializers.ValidationError("start_date should be less than or equal to end_date.")
218
219
  return attrs
220
+
221
+
222
+ class EnterpriseExecEdLCModulePerformanceSerializer(serializers.ModelSerializer):
223
+ """
224
+ Serializer for EnterpriseExecEdLCModulePerformance model.
225
+ """
226
+
227
+ class Meta:
228
+ model = EnterpriseExecEdLCModulePerformance
229
+ fields = '__all__'
@@ -35,6 +35,11 @@ router.register(
35
35
  enterprise_learner_views.EnterpriseLearnerCompletedCoursesViewSet,
36
36
  'enterprise-learner-completed-courses',
37
37
  )
38
+ router.register(
39
+ r'enterprise/(?P<enterprise_id>.+)/module-performance',
40
+ enterprise_admin_views.EnterpriseExecEdLCModulePerformanceViewSet,
41
+ 'enterprise-admin-module-performance',
42
+ )
38
43
 
39
44
  urlpatterns = [
40
45
  re_path(
@@ -47,6 +52,11 @@ urlpatterns = [
47
52
  enterprise_admin_views.EnterpriseAdminAnalyticsAggregatesView.as_view(),
48
53
  name='enterprise-admin-analytics-aggregates'
49
54
  ),
55
+ re_path(
56
+ fr'^admin/anlaytics/(?P<enterprise_id>{UUID4_REGEX})/skills/stats',
57
+ enterprise_admin_views.EnterpriseAdminAnalyticsSkillsView.as_view(),
58
+ name='enterprise-admin-analytics-skills'
59
+ ),
50
60
  ]
51
61
 
52
62
  urlpatterns += router.urls
@@ -0,0 +1,261 @@
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 import filters, viewsets
9
+ from rest_framework.response import Response
10
+ from rest_framework.status import HTTP_200_OK, HTTP_404_NOT_FOUND
11
+ from rest_framework.views import APIView
12
+
13
+ from django.http import HttpResponse
14
+
15
+ from enterprise_data.admin_analytics.data_loaders import fetch_max_enrollment_datetime
16
+ from enterprise_data.admin_analytics.utils import (
17
+ ChartType,
18
+ fetch_and_cache_engagements_data,
19
+ fetch_and_cache_enrollments_data,
20
+ fetch_and_cache_skills_data,
21
+ get_skills_chart_data,
22
+ get_top_skills_csv_data,
23
+ )
24
+ from enterprise_data.api.v1 import serializers
25
+ from enterprise_data.models import (
26
+ EnterpriseAdminLearnerProgress,
27
+ EnterpriseAdminSummarizeInsights,
28
+ EnterpriseExecEdLCModulePerformance,
29
+ )
30
+ from enterprise_data.utils import date_filter
31
+
32
+ from .base import EnterpriseViewSetMixin
33
+
34
+
35
+ class EnterpriseAdminInsightsView(APIView):
36
+ """
37
+ API for getting the enterprise admin insights.
38
+ """
39
+
40
+ authentication_classes = (JwtAuthentication,)
41
+ http_method_names = ["get"]
42
+
43
+ @permission_required(
44
+ "can_access_enterprise", fn=lambda request, enterprise_id: enterprise_id
45
+ )
46
+ def get(self, request, enterprise_id):
47
+ """
48
+ HTTP GET endpoint to retrieve the enterprise admin insights
49
+ """
50
+ response_data = {}
51
+ learner_progress = {}
52
+ learner_engagement = {}
53
+
54
+ try:
55
+ learner_progress = EnterpriseAdminLearnerProgress.objects.get(
56
+ enterprise_customer_uuid=enterprise_id
57
+ )
58
+ learner_progress = serializers.EnterpriseAdminLearnerProgressSerializer(
59
+ learner_progress
60
+ ).data
61
+ response_data["learner_progress"] = learner_progress
62
+ except EnterpriseAdminLearnerProgress.DoesNotExist:
63
+ pass
64
+
65
+ try:
66
+ learner_engagement = EnterpriseAdminSummarizeInsights.objects.get(
67
+ enterprise_customer_uuid=enterprise_id
68
+ )
69
+ learner_engagement = serializers.EnterpriseAdminSummarizeInsightsSerializer(
70
+ learner_engagement
71
+ ).data
72
+ response_data["learner_engagement"] = learner_engagement
73
+ except EnterpriseAdminSummarizeInsights.DoesNotExist:
74
+ pass
75
+
76
+ status = HTTP_200_OK
77
+ if learner_progress == {} and learner_engagement == {}:
78
+ status = HTTP_404_NOT_FOUND
79
+
80
+ return Response(data=response_data, status=status)
81
+
82
+
83
+ class EnterpriseAdminAnalyticsAggregatesView(APIView):
84
+ """
85
+ API for getting the enterprise admin analytics aggregates.
86
+ """
87
+
88
+ authentication_classes = (JwtAuthentication,)
89
+ http_method_names = ["get"]
90
+
91
+ @permission_required(
92
+ "can_access_enterprise", fn=lambda request, enterprise_id: enterprise_id
93
+ )
94
+ def get(self, request, enterprise_id):
95
+ """
96
+ HTTP GET endpoint to retrieve the enterprise admin aggregate data.
97
+ """
98
+ serializer = serializers.AdminAnalyticsAggregatesQueryParamsSerializer(
99
+ data=request.GET
100
+ )
101
+ serializer.is_valid(raise_exception=True)
102
+
103
+ last_updated_at = fetch_max_enrollment_datetime()
104
+ cache_expiry = (
105
+ last_updated_at + timedelta(days=1) if last_updated_at else datetime.now()
106
+ )
107
+
108
+ enrollment = fetch_and_cache_enrollments_data(
109
+ enterprise_id, cache_expiry
110
+ ).copy()
111
+ engagement = fetch_and_cache_engagements_data(
112
+ enterprise_id, cache_expiry
113
+ ).copy()
114
+ # Use start and end date if provided by the client, if client has not provided then use
115
+ # 1. minimum enrollment date from the data as the start_date
116
+ # 2. today's date as the end_date
117
+ start_date = serializer.data.get(
118
+ 'start_date', enrollment.enterprise_enrollment_date.min()
119
+ )
120
+ end_date = serializer.data.get('end_date', datetime.now())
121
+
122
+ # Date filtering.
123
+ dff = date_filter(
124
+ start=start_date,
125
+ end=end_date,
126
+ data_frame=enrollment.copy(),
127
+ date_column='enterprise_enrollment_date',
128
+ )
129
+
130
+ enrolls = len(dff)
131
+ courses = len(dff.course_key.unique())
132
+
133
+ dff = date_filter(
134
+ start=start_date,
135
+ end=end_date,
136
+ data_frame=enrollment.copy(),
137
+ date_column='passed_date',
138
+ )
139
+
140
+ completions = dff.has_passed.sum()
141
+
142
+ # Date filtering.
143
+ dff = date_filter(
144
+ start=start_date,
145
+ end=end_date,
146
+ data_frame=engagement.copy(),
147
+ date_column='activity_date',
148
+ )
149
+
150
+ hours = round(dff.learning_time_seconds.sum() / 60 / 60, 1)
151
+ sessions = dff.is_engaged.sum()
152
+
153
+ return Response(
154
+ data={
155
+ 'enrolls': enrolls,
156
+ 'courses': courses,
157
+ 'completions': completions,
158
+ 'hours': hours,
159
+ 'sessions': sessions,
160
+ 'last_updated_at': last_updated_at.date() if last_updated_at else None,
161
+ 'min_enrollment_date': enrollment.enterprise_enrollment_date.min().date(),
162
+ 'max_enrollment_date': enrollment.enterprise_enrollment_date.max().date(),
163
+ },
164
+ status=HTTP_200_OK,
165
+ )
166
+
167
+
168
+ class EnterpriseAdminAnalyticsSkillsView(APIView):
169
+ """
170
+ API for getting the enterprise admin analytics skills data.
171
+ """
172
+ authentication_classes = (JwtAuthentication,)
173
+ http_method_names = ['get']
174
+
175
+ @permission_required(
176
+ 'can_access_enterprise', fn=lambda request, enterprise_id: enterprise_id
177
+ )
178
+ def get(self, request, enterprise_id):
179
+ """HTTP GET endpoint to retrieve the enterprise admin skills aggregated data.
180
+
181
+ Args:
182
+ request (HttpRequest): request object
183
+ enterprise_id (str): UUID of the enterprise customer
184
+
185
+ Returns:
186
+ response(HttpResponse): response object
187
+ """
188
+ serializer = serializers.AdminAnalyticsAggregatesQueryParamsSerializer(
189
+ data=request.GET
190
+ )
191
+ serializer.is_valid(raise_exception=True)
192
+
193
+ start_date = serializer.data.get("start_date")
194
+ end_date = serializer.data.get("end_date", datetime.now())
195
+
196
+ last_updated_at = fetch_max_enrollment_datetime()
197
+ cache_expiry = (
198
+ last_updated_at + timedelta(days=1) if last_updated_at else datetime.now()
199
+ )
200
+ skills = fetch_and_cache_skills_data(enterprise_id, cache_expiry).copy()
201
+
202
+ if request.GET.get("format") == "csv":
203
+ csv_data = get_top_skills_csv_data(skills, start_date, end_date)
204
+ response = HttpResponse(content_type='text/csv')
205
+ filename = f"Skills by Enrollment and Completion, {start_date} - {end_date}.csv"
206
+ response['Content-Disposition'] = f'attachment; filename="{filename}"'
207
+ csv_data.to_csv(path_or_buf=response, index=False)
208
+ return response
209
+
210
+ top_skills = get_skills_chart_data(
211
+ chart_type=ChartType.BUBBLE,
212
+ start_date=start_date,
213
+ end_date=end_date,
214
+ skills=skills,
215
+ )
216
+ top_skills_enrollments = get_skills_chart_data(
217
+ chart_type=ChartType.TOP_SKILLS_ENROLLMENT,
218
+ start_date=start_date,
219
+ end_date=end_date,
220
+ skills=skills,
221
+ )
222
+ top_skills_by_completions = get_skills_chart_data(
223
+ chart_type=ChartType.TOP_SKILLS_COMPLETION,
224
+ start_date=start_date,
225
+ end_date=end_date,
226
+ skills=skills,
227
+ )
228
+
229
+ response_data = {
230
+ "top_skills": top_skills.to_dict(orient="records"),
231
+ "top_skills_by_enrollments": top_skills_enrollments.to_dict(
232
+ orient="records"
233
+ ),
234
+ "top_skills_by_completions": top_skills_by_completions.to_dict(
235
+ orient="records"
236
+ ),
237
+ }
238
+
239
+ return Response(data=response_data, status=HTTP_200_OK)
240
+
241
+
242
+ class EnterpriseExecEdLCModulePerformanceViewSet(EnterpriseViewSetMixin, viewsets.ReadOnlyModelViewSet):
243
+ """
244
+ View to for getting enterprise exec ed learner module performance records.
245
+ """
246
+ serializer_class = serializers.EnterpriseExecEdLCModulePerformanceSerializer
247
+ filter_backends = (filters.OrderingFilter, filters.SearchFilter)
248
+ ordering_fields = '__all__'
249
+ ordering = ('last_access',)
250
+ search_fields = (
251
+ 'username',
252
+ 'course_name'
253
+ )
254
+
255
+ def get_queryset(self):
256
+ """
257
+ Return the queryset of EnterpriseExecEdLCModulePerformance objects.
258
+ """
259
+ return EnterpriseExecEdLCModulePerformance.objects.filter(
260
+ enterprise_customer_uuid=self.kwargs['enterprise_id'],
261
+ )