edx-enterprise-data 8.8.0__tar.gz → 8.8.1__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.8.0 → edx_enterprise_data-8.8.1}/CHANGELOG.rst +4 -0
  2. {edx_enterprise_data-8.8.0/edx_enterprise_data.egg-info → edx_enterprise_data-8.8.1}/PKG-INFO +1 -1
  3. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1/edx_enterprise_data.egg-info}/PKG-INFO +1 -1
  4. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/__init__.py +1 -1
  5. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/admin_analytics/data_loaders.py +7 -3
  6. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/admin_analytics/utils.py +6 -0
  7. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/api/v1/views/analytics_enrollments.py +60 -37
  8. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/api/v1/views/analytics_leaderboard.py +23 -2
  9. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/api/v1/views/enterprise_admin.py +20 -19
  10. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/api/v1/views/enterprise_completions.py +48 -27
  11. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/tests/admin_analytics/mock_analytics_data.py +18 -8
  12. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/utils.py +16 -0
  13. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/LICENSE +0 -0
  14. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/MANIFEST.in +0 -0
  15. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/README.md +0 -0
  16. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/edx_enterprise_data.egg-info/SOURCES.txt +0 -0
  17. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/edx_enterprise_data.egg-info/dependency_links.txt +0 -0
  18. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/edx_enterprise_data.egg-info/not-zip-safe +0 -0
  19. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/edx_enterprise_data.egg-info/requires.txt +0 -0
  20. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/edx_enterprise_data.egg-info/top_level.txt +0 -0
  21. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/admin_analytics/__init__.py +0 -0
  22. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/admin_analytics/completions_utils.py +0 -0
  23. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/admin_analytics/constants.py +0 -0
  24. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/admin_analytics/database.py +0 -0
  25. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/api/__init__.py +0 -0
  26. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/api/urls.py +0 -0
  27. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/api/v0/__init__.py +0 -0
  28. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/api/v0/serializers.py +0 -0
  29. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/api/v0/urls.py +0 -0
  30. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/api/v0/views.py +0 -0
  31. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/api/v1/__init__.py +0 -0
  32. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/api/v1/paginators.py +0 -0
  33. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/api/v1/serializers.py +0 -0
  34. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/api/v1/urls.py +0 -0
  35. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/api/v1/views/__init__.py +0 -0
  36. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/api/v1/views/base.py +0 -0
  37. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/api/v1/views/enterprise_learner.py +0 -0
  38. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/api/v1/views/enterprise_offers.py +0 -0
  39. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/apps.py +0 -0
  40. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/clients.py +0 -0
  41. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/constants.py +0 -0
  42. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/filters.py +0 -0
  43. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/fixtures/enterprise_enrollment.json +0 -0
  44. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/fixtures/enterprise_user.json +0 -0
  45. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/management/__init__.py +0 -0
  46. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/management/commands/__init__.py +0 -0
  47. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/management/commands/create_dummy_data.py +0 -0
  48. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/management/commands/create_dummy_data_lpr_v1.py +0 -0
  49. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/management/commands/create_enterprise_enrollment.py +0 -0
  50. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/management/commands/create_enterprise_learner_enrollment_lpr_v1.py +0 -0
  51. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/management/commands/create_enterprise_learner_lpr_v1.py +0 -0
  52. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/management/commands/create_enterprise_offer.py +0 -0
  53. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/management/commands/create_enterprise_user.py +0 -0
  54. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/management/commands/tests/__init__.py +0 -0
  55. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/management/commands/tests/test_create_dummy_data_lpr_v1.py +0 -0
  56. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/management/commands/tests/test_create_enterprise_enrollment.py +0 -0
  57. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/management/commands/tests/test_create_enterprise_learner_enrollment_lpr_v1.py +0 -0
  58. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/management/commands/tests/test_create_enterprise_learner_lpr_v1.py +0 -0
  59. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/management/commands/tests/test_create_enterprise_user.py +0 -0
  60. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/migrations/0001_initial.py +0 -0
  61. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/migrations/0002_auto_20180430_1358.py +0 -0
  62. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/migrations/0003_auto_20180501_0603.py +0 -0
  63. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/migrations/0004_auto_20180501_0928.py +0 -0
  64. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/migrations/0004_auto_20180508_1652.py +0 -0
  65. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/migrations/0005_auto_20180524_2204.py +0 -0
  66. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/migrations/0006_auto_20180612_0336.py +0 -0
  67. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/migrations/0007_auto_20180612_0534.py +0 -0
  68. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/migrations/0008_auto_20180614_0108.py +0 -0
  69. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/migrations/0009_auto_20180628_1152.py +0 -0
  70. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/migrations/0010_enterpriseenrollment_created.py +0 -0
  71. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/migrations/0011_enterpriseuser.py +0 -0
  72. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/migrations/0012_auto_20180831_1930.py +0 -0
  73. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/migrations/0013_auto_20180831_1931.py +0 -0
  74. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/migrations/0014_enterpriseuser_created.py +0 -0
  75. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/migrations/0015_auto_20180907_1757.py +0 -0
  76. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/migrations/0016_auto_20180924_2138.py +0 -0
  77. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/migrations/0017_enterpriseenrollment_unenrollment_timestamp.py +0 -0
  78. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/migrations/0018_enterprisedatafeaturerole_enterprisedataroleassignment.py +0 -0
  79. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/migrations/0019_add_enterprise_data_feature_roles.py +0 -0
  80. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/migrations/0020_add_role_based_access_control_switch.py +0 -0
  81. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/migrations/0021_auto_20190329_1241.py +0 -0
  82. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/migrations/0022_remove_role_based_access_control_switch.py +0 -0
  83. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/migrations/0023_enterpriselearner_enterpriselearnerenrollment.py +0 -0
  84. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/migrations/0024_auto_20210602_1811.py +0 -0
  85. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/migrations/0025_auto_20210703_1854.py +0 -0
  86. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/migrations/0026_auto_20210916_0414.py +0 -0
  87. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/migrations/0027_enterpriselearnerenrollment_total_learning_time_seconds.py +0 -0
  88. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/migrations/0028_enterpriselearnerenrollment_offer_id.py +0 -0
  89. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/migrations/0029_enterpriseoffer.py +0 -0
  90. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/migrations/0030_auto_20230609_1353.py +0 -0
  91. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/migrations/0031_auto_20230615_0705.py +0 -0
  92. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/migrations/0032_auto_20230704_0818.py +0 -0
  93. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/migrations/0033_enterpriseadminlearnerprogress_enterpriseadminsummarizeinsights.py +0 -0
  94. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/migrations/0034_auto_20230907_0834.py +0 -0
  95. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/migrations/0035_auto_20230907_1154.py +0 -0
  96. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/migrations/0036_enterprisesubsidybudget_subsidy_access_policy_display_name.py +0 -0
  97. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/migrations/0037_alter_enterpriseenrollment_consent_granted.py +0 -0
  98. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/migrations/0038_enterpriseoffer_export_timestamp.py +0 -0
  99. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/migrations/0039_auto_20240212_1403.py +0 -0
  100. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/migrations/0040_auto_20240718_0536_squashed_0043_alter_enterpriselearnerenrollment_enterprise_enrollment_id.py +0 -0
  101. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/migrations/0044_enterpriseexecedlcmoduleperformance.py +0 -0
  102. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/migrations/__init__.py +0 -0
  103. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/models.py +0 -0
  104. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/paginators.py +0 -0
  105. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/renderers.py +0 -0
  106. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/settings/__init__.py +0 -0
  107. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/settings/test.py +0 -0
  108. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/signals.py +0 -0
  109. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/tests/__init__.py +0 -0
  110. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/tests/admin_analytics/__init__.py +0 -0
  111. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/tests/admin_analytics/mock_enrollments.py +0 -0
  112. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/tests/admin_analytics/test_analytics_enrollments.py +0 -0
  113. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/tests/admin_analytics/test_analytics_leaderboard.py +0 -0
  114. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/tests/admin_analytics/test_data_loaders.py +0 -0
  115. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/tests/admin_analytics/test_enterprise_completions.py +0 -0
  116. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/tests/admin_analytics/test_utils.py +0 -0
  117. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/tests/api/__init__.py +0 -0
  118. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/tests/api/v0/__init__.py +0 -0
  119. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/tests/api/v0/test_serializers.py +0 -0
  120. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/tests/api/v1/__init__.py +0 -0
  121. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/tests/api/v1/test_serializers.py +0 -0
  122. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/tests/api/v1/test_views.py +0 -0
  123. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/tests/api/v1/views/__init__.py +0 -0
  124. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/tests/api/v1/views/test_enterprise_admin.py +0 -0
  125. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/tests/factories.py +0 -0
  126. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/tests/mixins.py +0 -0
  127. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/tests/test_clients.py +0 -0
  128. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/tests/test_filters.py +0 -0
  129. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/tests/test_models.py +0 -0
  130. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/tests/test_utils.py +0 -0
  131. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/tests/test_views.py +0 -0
  132. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data/urls.py +0 -0
  133. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data_roles/__init__.py +0 -0
  134. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data_roles/admin.py +0 -0
  135. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data_roles/apps.py +0 -0
  136. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data_roles/constants.py +0 -0
  137. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data_roles/migrations/0001_initial.py +0 -0
  138. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data_roles/migrations/0002_add_enterprise_data_feature_roles.py +0 -0
  139. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data_roles/migrations/0003_add_role_based_access_control_switch.py +0 -0
  140. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data_roles/migrations/0004_enterprisedataroleassignment_enterprise_id.py +0 -0
  141. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data_roles/migrations/0005_turn_on_role_based_access_control_switch.py +0 -0
  142. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data_roles/migrations/0006_remove_role_based_access_control_switch.py +0 -0
  143. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data_roles/migrations/0007_enterprisedataroleassignment_applies_to_all_contexts.py +0 -0
  144. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data_roles/migrations/__init__.py +0 -0
  145. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data_roles/models.py +0 -0
  146. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data_roles/rules.py +0 -0
  147. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data_roles/tests/__init__.py +0 -0
  148. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data_roles/tests/factories.py +0 -0
  149. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_data_roles/tests/test_models.py +0 -0
  150. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_reporting/__init__.py +0 -0
  151. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_reporting/clients/__init__.py +0 -0
  152. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_reporting/clients/enterprise.py +0 -0
  153. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_reporting/clients/s3.py +0 -0
  154. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_reporting/clients/snowflake.py +0 -0
  155. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_reporting/clients/vertica.py +0 -0
  156. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_reporting/delivery_method.py +0 -0
  157. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_reporting/external_resource_link_report.py +0 -0
  158. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_reporting/fixtures/__init__.py +0 -0
  159. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_reporting/fixtures/enterprise_customer_reporting.json +0 -0
  160. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_reporting/reporter.py +0 -0
  161. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_reporting/send_enterprise_reports.py +0 -0
  162. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_reporting/tests/__init__.py +0 -0
  163. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_reporting/tests/test_clients.py +0 -0
  164. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_reporting/tests/test_delivery_method.py +0 -0
  165. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_reporting/tests/test_enterprise_client.py +0 -0
  166. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_reporting/tests/test_external_link_report.py +0 -0
  167. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_reporting/tests/test_reporter.py +0 -0
  168. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_reporting/tests/test_send_enterprise_reports.py +0 -0
  169. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_reporting/tests/test_utils.py +0 -0
  170. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_reporting/tests/test_vertica_client.py +0 -0
  171. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_reporting/tests/utils.py +0 -0
  172. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/enterprise_reporting/utils.py +0 -0
  173. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/requirements/base.in +0 -0
  174. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/requirements/base.txt +0 -0
  175. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/requirements/ci.txt +0 -0
  176. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/requirements/common_constraints.txt +0 -0
  177. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/requirements/constraints.txt +0 -0
  178. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/requirements/dev.txt +0 -0
  179. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/requirements/django.txt +0 -0
  180. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/requirements/pip.txt +0 -0
  181. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/requirements/pip_tools.txt +0 -0
  182. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/requirements/quality.txt +0 -0
  183. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/requirements/reporting.in +0 -0
  184. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/requirements/test-master.txt +0 -0
  185. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/requirements/test-reporting.txt +0 -0
  186. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/requirements/test.txt +0 -0
  187. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/setup.cfg +0 -0
  188. {edx_enterprise_data-8.8.0 → edx_enterprise_data-8.8.1}/setup.py +0 -0
@@ -16,6 +16,10 @@ Unreleased
16
16
 
17
17
  =========================
18
18
 
19
+ [8.8.1] - 2024-08-16
20
+ ---------------------
21
+ * refactor: Add logs and time measurements for different code blocks
22
+
19
23
  [8.8.0] - 2024-08-15
20
24
  ---------------------
21
25
  * feat: Add API endpoints for advance analytics leaderboard data
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: edx-enterprise-data
3
- Version: 8.8.0
3
+ Version: 8.8.1
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.8.0
3
+ Version: 8.8.1
4
4
  Summary: Enterprise Reporting
5
5
  Home-page: https://github.com/openedx/edx-enterprise-data
6
6
  Author: edX
@@ -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.8.0"
5
+ __version__ = "8.8.1"
@@ -7,6 +7,7 @@ import pandas
7
7
  from django.http import Http404
8
8
 
9
9
  from enterprise_data.admin_analytics.database import run_query
10
+ from enterprise_data.utils import timer
10
11
 
11
12
 
12
13
  def get_select_query(table: str, columns: list, enterprise_uuid: str) -> str:
@@ -65,7 +66,8 @@ def fetch_enrollment_data(enterprise_uuid: str):
65
66
  enterprise_uuid=enterprise_uuid,
66
67
  )
67
68
 
68
- results = run_query(query=query)
69
+ with timer('fetch_enrollment_data'):
70
+ results = run_query(query=query)
69
71
  if not results:
70
72
  raise Http404(f'No enrollment data found for enterprise {enterprise_uuid}')
71
73
 
@@ -113,7 +115,8 @@ def fetch_engagement_data(enterprise_uuid: str):
113
115
  table='fact_enrollment_engagement_day_admin_dash', columns=columns, enterprise_uuid=enterprise_uuid
114
116
  )
115
117
 
116
- results = run_query(query=query)
118
+ with timer('fetch_engagement_data'):
119
+ results = run_query(query=query)
117
120
  if not results:
118
121
  raise Http404(f'No engagement data found for enterprise {enterprise_uuid}')
119
122
 
@@ -171,7 +174,8 @@ def fetch_skills_data(enterprise_uuid: str):
171
174
  table='skills_daily_rollup_admin_dash', columns=cols, enterprise_uuid=enterprise_uuid
172
175
  )
173
176
 
174
- skills = run_query(query=query)
177
+ with timer('fetch_skills_data'):
178
+ skills = run_query(query=query)
175
179
 
176
180
  if not skills:
177
181
  raise Http404(f'No skills data found for enterprise {enterprise_uuid}')
@@ -3,6 +3,7 @@ Utility functions for fetching data from the database.
3
3
  """
4
4
  from datetime import datetime, timedelta
5
5
  from enum import Enum
6
+ from logging import getLogger
6
7
 
7
8
  from edx_django_utils.cache import TieredCache, get_cache_key
8
9
 
@@ -15,6 +16,8 @@ from enterprise_data.admin_analytics.data_loaders import (
15
16
  )
16
17
  from enterprise_data.utils import date_filter, primary_subject_truncate
17
18
 
19
+ LOGGER = getLogger(__name__)
20
+
18
21
 
19
22
  class ChartType(Enum):
20
23
  """
@@ -144,6 +147,7 @@ def fetch_and_cache_enrollments_data(enterprise_id, cache_expiry):
144
147
  cached_response = TieredCache.get_cached_response(cache_key)
145
148
 
146
149
  if cached_response.is_found:
150
+ LOGGER.info(f"Enrollments data found in cache for Enterprise [{enterprise_id}]")
147
151
  return cached_response.value
148
152
  else:
149
153
  enrollments = fetch_enrollment_data(enterprise_id)
@@ -171,6 +175,7 @@ def fetch_and_cache_engagements_data(enterprise_id, cache_expiry):
171
175
  cached_response = TieredCache.get_cached_response(cache_key)
172
176
 
173
177
  if cached_response.is_found:
178
+ LOGGER.info(f"Engagements data found in cache for Enterprise [{enterprise_id}]")
174
179
  return cached_response.value
175
180
  else:
176
181
  engagements = fetch_engagement_data(enterprise_id)
@@ -198,6 +203,7 @@ def fetch_and_cache_skills_data(enterprise_id, cache_expiry):
198
203
  cached_response = TieredCache.get_cached_response(cache_key)
199
204
 
200
205
  if cached_response.is_found:
206
+ LOGGER.info(f"Skills data found in cache for Enterprise [{enterprise_id}]")
201
207
  return cached_response.value
202
208
  else:
203
209
  skills = fetch_skills_data(enterprise_id)
@@ -1,5 +1,6 @@
1
1
  """Advance Analytics for Enrollments"""
2
2
  from datetime import datetime
3
+ from logging import getLogger
3
4
 
4
5
  from edx_rbac.decorators import permission_required
5
6
  from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication
@@ -21,7 +22,9 @@ from enterprise_data.api.v1.serializers import (
21
22
  AdvanceAnalyticsQueryParamSerializer,
22
23
  )
23
24
  from enterprise_data.renderers import IndividualEnrollmentsCSVRenderer
24
- from enterprise_data.utils import date_filter
25
+ from enterprise_data.utils import date_filter, timer
26
+
27
+ LOGGER = getLogger(__name__)
25
28
 
26
29
 
27
30
  class AdvanceAnalyticsIndividualEnrollmentsView(APIView):
@@ -46,6 +49,13 @@ class AdvanceAnalyticsIndividualEnrollmentsView(APIView):
46
49
  end_date = serializer.data.get('end_date', datetime.now())
47
50
  response_type = request.query_params.get('response_type', ResponseType.JSON.value)
48
51
 
52
+ LOGGER.info(
53
+ "Individual enrollments data requested for enterprise [%s] from [%s] to [%s]",
54
+ enterprise_uuid,
55
+ start_date,
56
+ end_date,
57
+ )
58
+
49
59
  # filter enrollments by date
50
60
  enrollments = date_filter(start_date, end_date, enrollments_df, "enterprise_enrollment_date")
51
61
 
@@ -62,6 +72,13 @@ class AdvanceAnalyticsIndividualEnrollmentsView(APIView):
62
72
  enrollments["enterprise_enrollment_date"] = enrollments["enterprise_enrollment_date"].dt.date
63
73
  enrollments = enrollments.sort_values(by="enterprise_enrollment_date", ascending=False).reset_index(drop=True)
64
74
 
75
+ LOGGER.info(
76
+ "Individual enrollments data prepared for enterprise [%s] from [%s] to [%s]",
77
+ enterprise_uuid,
78
+ start_date,
79
+ end_date,
80
+ )
81
+
65
82
  if response_type == ResponseType.CSV.value:
66
83
  filename = f"""individual_enrollments, {start_date} - {end_date}.csv"""
67
84
  return StreamingHttpResponse(
@@ -112,49 +129,55 @@ class AdvanceAnalyticsEnrollmentStatsView(APIView):
112
129
  response_type = serializer.data.get('response_type', ResponseType.JSON.value)
113
130
  chart_type = serializer.data.get('chart_type')
114
131
 
132
+ # TODO: Add validation that if response_type is CSV then chart_type must be provided
133
+
115
134
  if response_type == ResponseType.JSON.value:
116
- data = {
117
- "enrollments_over_time": self.construct_enrollments_over_time(
118
- enrollments_df.copy(),
119
- start_date,
120
- end_date,
121
- granularity,
122
- calculation,
123
- ),
124
- "top_courses_by_enrollments": self.construct_top_courses_by_enrollments(
125
- enrollments_df.copy(),
126
- start_date,
127
- end_date,
128
- ),
129
- "top_subjects_by_enrollments": self.construct_top_subjects_by_enrollments(
130
- enrollments_df.copy(),
131
- start_date,
132
- end_date,
133
- ),
134
- }
135
+ with timer('construct_enrollment_all_stats'):
136
+ data = {
137
+ "enrollments_over_time": self.construct_enrollments_over_time(
138
+ enrollments_df.copy(),
139
+ start_date,
140
+ end_date,
141
+ granularity,
142
+ calculation,
143
+ ),
144
+ "top_courses_by_enrollments": self.construct_top_courses_by_enrollments(
145
+ enrollments_df.copy(),
146
+ start_date,
147
+ end_date,
148
+ ),
149
+ "top_subjects_by_enrollments": self.construct_top_subjects_by_enrollments(
150
+ enrollments_df.copy(),
151
+ start_date,
152
+ end_date,
153
+ ),
154
+ }
135
155
  return Response(data)
136
156
 
137
157
  if response_type == ResponseType.CSV.value:
138
158
  if chart_type == EnrollmentChart.ENROLLMENTS_OVER_TIME.value:
139
- return self.construct_enrollments_over_time_csv(
140
- enrollments_df.copy(),
141
- start_date,
142
- end_date,
143
- granularity,
144
- calculation,
145
- )
159
+ with timer('construct_enrollments_over_time_csv'):
160
+ return self.construct_enrollments_over_time_csv(
161
+ enrollments_df.copy(),
162
+ start_date,
163
+ end_date,
164
+ granularity,
165
+ calculation,
166
+ )
146
167
  elif chart_type == EnrollmentChart.TOP_COURSES_BY_ENROLLMENTS.value:
147
- return self.construct_top_courses_by_enrollments_csv(
148
- enrollments_df.copy(),
149
- start_date,
150
- end_date,
151
- )
168
+ with timer('construct_top_courses_by_enrollments_csv'):
169
+ return self.construct_top_courses_by_enrollments_csv(
170
+ enrollments_df.copy(),
171
+ start_date,
172
+ end_date,
173
+ )
152
174
  elif chart_type == EnrollmentChart.TOP_SUBJECTS_BY_ENROLLMENTS.value:
153
- return self.construct_top_subjects_by_enrollments_csv(
154
- enrollments_df.copy(),
155
- start_date,
156
- end_date,
157
- )
175
+ with timer('construct_top_subjects_by_enrollments_csv'):
176
+ return self.construct_top_subjects_by_enrollments_csv(
177
+ enrollments_df.copy(),
178
+ start_date,
179
+ end_date,
180
+ )
158
181
 
159
182
  def enrollments_over_time_common(self, enrollments_df, start_date, end_date, granularity, calculation):
160
183
  """
@@ -1,5 +1,6 @@
1
1
  """Advance Analytics for Leaderboard"""
2
2
  from datetime import datetime
3
+ from logging import getLogger
3
4
 
4
5
  import numpy as np
5
6
  import pandas as pd
@@ -21,6 +22,8 @@ from enterprise_data.api.v1.serializers import AdvanceAnalyticsQueryParamSeriali
21
22
  from enterprise_data.renderers import LeaderboardCSVRenderer
22
23
  from enterprise_data.utils import date_filter
23
24
 
25
+ LOGGER = getLogger(__name__)
26
+
24
27
 
25
28
  class AdvanceAnalyticsLeaderboardView(APIView):
26
29
  """
@@ -46,6 +49,13 @@ class AdvanceAnalyticsLeaderboardView(APIView):
46
49
  end_date = serializer.data.get('end_date', datetime.now())
47
50
  response_type = serializer.data.get('response_type', ResponseType.JSON.value)
48
51
 
52
+ LOGGER.info(
53
+ "Leaderboard data requested for enterprise [%s] from [%s] to [%s]",
54
+ enterprise_uuid,
55
+ start_date,
56
+ end_date,
57
+ )
58
+
49
59
  # only include learners who have passed the course
50
60
  enrollments_df = enrollments_df[enrollments_df["has_passed"] == 1]
51
61
 
@@ -67,8 +77,12 @@ class AdvanceAnalyticsLeaderboardView(APIView):
67
77
  engage["learning_time_hours"] = round(
68
78
  engage["learning_time_seconds"].astype("float") / 60 / 60, 1
69
79
  )
70
- engage["average_session_length"] = round(
71
- engage["learning_time_hours"] / engage["daily_sessions"].astype("float"), 1
80
+
81
+ # if daily_sessions is 0, set average_session_length to 0 becuase otherwise it will be `inf`
82
+ engage["average_session_length"] = np.where(
83
+ engage["daily_sessions"] == 0,
84
+ 0,
85
+ round(engage["learning_time_hours"] / engage["daily_sessions"].astype("float"), 1)
72
86
  )
73
87
 
74
88
  leaderboard_df = engage.merge(completions, on="email", how="left")
@@ -85,6 +99,13 @@ class AdvanceAnalyticsLeaderboardView(APIView):
85
99
  # convert `nan` values to `None` because `nan` is not JSON serializable
86
100
  leaderboard_df = leaderboard_df.replace(np.nan, None)
87
101
 
102
+ LOGGER.info(
103
+ "Leaderboard data prepared for enterprise [%s] from [%s] to [%s]",
104
+ enterprise_uuid,
105
+ start_date,
106
+ end_date,
107
+ )
108
+
88
109
  if response_type == ResponseType.CSV.value:
89
110
  filename = f"""Leaderboard, {start_date} - {end_date}.csv"""
90
111
  leaderboard_df = leaderboard_df[
@@ -27,7 +27,7 @@ from enterprise_data.models import (
27
27
  EnterpriseAdminSummarizeInsights,
28
28
  EnterpriseExecEdLCModulePerformance,
29
29
  )
30
- from enterprise_data.utils import date_filter
30
+ from enterprise_data.utils import date_filter, timer
31
31
 
32
32
  from .base import EnterpriseViewSetMixin
33
33
 
@@ -211,24 +211,25 @@ class EnterpriseAdminAnalyticsSkillsView(APIView):
211
211
  csv_data.to_csv(path_or_buf=response, index=False)
212
212
  return response
213
213
 
214
- top_skills = get_skills_chart_data(
215
- chart_type=ChartType.BUBBLE,
216
- start_date=start_date,
217
- end_date=end_date,
218
- skills=skills,
219
- )
220
- top_skills_enrollments = get_skills_chart_data(
221
- chart_type=ChartType.TOP_SKILLS_ENROLLMENT,
222
- start_date=start_date,
223
- end_date=end_date,
224
- skills=skills,
225
- )
226
- top_skills_by_completions = get_skills_chart_data(
227
- chart_type=ChartType.TOP_SKILLS_COMPLETION,
228
- start_date=start_date,
229
- end_date=end_date,
230
- skills=skills,
231
- )
214
+ with timer('skills_all_charts_data'):
215
+ top_skills = get_skills_chart_data(
216
+ chart_type=ChartType.BUBBLE,
217
+ start_date=start_date,
218
+ end_date=end_date,
219
+ skills=skills,
220
+ )
221
+ top_skills_enrollments = get_skills_chart_data(
222
+ chart_type=ChartType.TOP_SKILLS_ENROLLMENT,
223
+ start_date=start_date,
224
+ end_date=end_date,
225
+ skills=skills,
226
+ )
227
+ top_skills_by_completions = get_skills_chart_data(
228
+ chart_type=ChartType.TOP_SKILLS_COMPLETION,
229
+ start_date=start_date,
230
+ end_date=end_date,
231
+ skills=skills,
232
+ )
232
233
 
233
234
  response_data = {
234
235
  "top_skills": top_skills.to_dict(orient="records"),
@@ -1,6 +1,7 @@
1
1
  """Views for enterprise admin completions analytics."""
2
2
  import datetime
3
3
  from datetime import datetime, timedelta
4
+ from logging import getLogger
4
5
 
5
6
  from edx_rbac.decorators import permission_required
6
7
  from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication
@@ -23,7 +24,9 @@ from enterprise_data.admin_analytics.data_loaders import fetch_max_enrollment_da
23
24
  from enterprise_data.admin_analytics.utils import ChartType, fetch_and_cache_enrollments_data
24
25
  from enterprise_data.api.v1 import serializers
25
26
  from enterprise_data.api.v1.paginators import AdvanceAnalyticsPagination
26
- from enterprise_data.utils import date_filter
27
+ from enterprise_data.utils import date_filter, timer
28
+
29
+ LOGGER = getLogger(__name__)
27
30
 
28
31
 
29
32
  class EnterrpiseAdminCompletionsStatsView(APIView):
@@ -67,39 +70,43 @@ class EnterrpiseAdminCompletionsStatsView(APIView):
67
70
  csv_data = {}
68
71
 
69
72
  if chart_type == ChartType.COMPLETIONS_OVER_TIME.value:
70
- csv_data = get_csv_data_for_completions_over_time(
71
- start_date=start_date,
72
- end_date=end_date,
73
- enrollments=enrollments.copy(),
74
- date_agg=serializer.data.get('granularity', Granularity.DAILY.value),
75
- calc=serializer.data.get('calculation', Calculation.TOTAL.value),
76
- )
73
+ with timer('completions_over_time_csv_data'):
74
+ csv_data = get_csv_data_for_completions_over_time(
75
+ start_date=start_date,
76
+ end_date=end_date,
77
+ enrollments=enrollments.copy(),
78
+ date_agg=serializer.data.get('granularity', Granularity.DAILY.value),
79
+ calc=serializer.data.get('calculation', Calculation.TOTAL.value),
80
+ )
77
81
  elif chart_type == ChartType.TOP_COURSES_BY_COMPLETIONS.value:
78
- csv_data = get_csv_data_for_top_courses_by_completions(
79
- start_date=start_date, end_date=end_date, enrollments=enrollments.copy()
80
- )
82
+ with timer('top_courses_by_completions_csv_data'):
83
+ csv_data = get_csv_data_for_top_courses_by_completions(
84
+ start_date=start_date, end_date=end_date, enrollments=enrollments.copy()
85
+ )
81
86
  elif chart_type == ChartType.TOP_SUBJECTS_BY_COMPLETIONS.value:
82
- csv_data = get_csv_data_for_top_subjects_by_completions(
83
- start_date=start_date, end_date=end_date, enrollments=enrollments.copy()
84
- )
87
+ with timer('top_subjects_by_completions_csv_data'):
88
+ csv_data = get_csv_data_for_top_subjects_by_completions(
89
+ start_date=start_date, end_date=end_date, enrollments=enrollments.copy()
90
+ )
85
91
  filename = csv_data['filename']
86
92
  response['Content-Disposition'] = f'attachment; filename="{filename}"'
87
93
  csv_data['data'].to_csv(path_or_buf=response)
88
94
  return response
89
95
 
90
- completions_over_time = get_completions_over_time(
91
- start_date=start_date,
92
- end_date=end_date,
93
- dff=enrollments.copy(),
94
- date_agg=serializer.data.get('granularity', Granularity.DAILY.value),
95
- calc=serializer.data.get('calculation', Calculation.TOTAL.value),
96
- )
97
- top_courses_by_completions = get_top_courses_by_completions(
98
- start_date=start_date, end_date=end_date, dff=enrollments.copy()
99
- )
100
- top_subjects_by_completions = get_top_subjects_by_completions(
101
- start_date=start_date, end_date=end_date, dff=enrollments.copy()
102
- )
96
+ with timer('completions_all_charts_data'):
97
+ completions_over_time = get_completions_over_time(
98
+ start_date=start_date,
99
+ end_date=end_date,
100
+ dff=enrollments.copy(),
101
+ date_agg=serializer.data.get('granularity', Granularity.DAILY.value),
102
+ calc=serializer.data.get('calculation', Calculation.TOTAL.value),
103
+ )
104
+ top_courses_by_completions = get_top_courses_by_completions(
105
+ start_date=start_date, end_date=end_date, dff=enrollments.copy()
106
+ )
107
+ top_subjects_by_completions = get_top_subjects_by_completions(
108
+ start_date=start_date, end_date=end_date, dff=enrollments.copy()
109
+ )
103
110
 
104
111
  return Response(
105
112
  data={
@@ -153,6 +160,13 @@ class EnterrpiseAdminCompletionsView(APIView):
153
160
  )
154
161
  end_date = serializer.data.get('end_date', datetime.now())
155
162
 
163
+ LOGGER.info(
164
+ "Completions data requested for enterprise [%s] from [%s] to [%s]",
165
+ enterprise_id,
166
+ start_date,
167
+ end_date,
168
+ )
169
+
156
170
  dff = enrollments[enrollments['has_passed'] == 1]
157
171
 
158
172
  # Date filtering
@@ -162,6 +176,13 @@ class EnterrpiseAdminCompletionsView(APIView):
162
176
  dff['passed_date'] = dff['passed_date'].dt.date
163
177
  dff = dff.sort_values(by="passed_date", ascending=False).reset_index(drop=True)
164
178
 
179
+ LOGGER.info(
180
+ "Completions data prepared for enterprise [%s] from [%s] to [%s]",
181
+ enterprise_id,
182
+ start_date,
183
+ end_date,
184
+ )
185
+
165
186
  if serializer.data.get('response_type') == 'csv':
166
187
  response = HttpResponse(content_type='text/csv')
167
188
  filename = f"Individual Completions, {start_date} - {end_date}.csv"
@@ -374,14 +374,24 @@ def enrollments_csv_content():
374
374
 
375
375
  def leaderboard_csv_content():
376
376
  """Return the CSV content of leaderboard."""
377
+ # return (
378
+ # b'email,learning_time_hours,daily_sessions,average_session_length,course_completions\r\n'
379
+ # b'paul77@example.org,4.4,1,4.4,\r\nseth57@example.org,2.7,1,2.7,\r\n'
380
+ # b'weaverpatricia@example.net,2.6,1,2.6,\r\nwebertodd@example.com,1.5,1,1.5,\r\n'
381
+ # b'yferguson@example.net,1.3,1,1.3,\r\nyallison@example.org,1.2,1,1.2,\r\n'
382
+ # b'padillamichelle@example.org,1.0,1,1.0,\r\ncaseyjohnny@example.com,0.0,0,,\r\n'
383
+ # b'crystal86@example.net,0.0,0,,\r\ngraceperez@example.com,0.0,0,,\r\n'
384
+ # b'mackwilliam@example.com,0.0,0,,\r\nsamanthaclarke@example.org,0.0,0,,\r\n'
385
+ # )
386
+
377
387
  return (
378
388
  b'email,learning_time_hours,daily_sessions,average_session_length,course_completions\r\n'
379
389
  b'paul77@example.org,4.4,1,4.4,\r\nseth57@example.org,2.7,1,2.7,\r\n'
380
390
  b'weaverpatricia@example.net,2.6,1,2.6,\r\nwebertodd@example.com,1.5,1,1.5,\r\n'
381
391
  b'yferguson@example.net,1.3,1,1.3,\r\nyallison@example.org,1.2,1,1.2,\r\n'
382
- b'padillamichelle@example.org,1.0,1,1.0,\r\ncaseyjohnny@example.com,0.0,0,,\r\n'
383
- b'crystal86@example.net,0.0,0,,\r\ngraceperez@example.com,0.0,0,,\r\n'
384
- b'mackwilliam@example.com,0.0,0,,\r\nsamanthaclarke@example.org,0.0,0,,\r\n'
392
+ b'padillamichelle@example.org,1.0,1,1.0,\r\ncaseyjohnny@example.com,0.0,0,0.0,\r\n'
393
+ b'crystal86@example.net,0.0,0,0.0,\r\ngraceperez@example.com,0.0,0,0.0,\r\n'
394
+ b'mackwilliam@example.com,0.0,0,0.0,\r\nsamanthaclarke@example.org,0.0,0,0.0,\r\n'
385
395
  )
386
396
 
387
397
 
@@ -447,7 +457,7 @@ LEADERBOARD_RESPONSE = [
447
457
  "daily_sessions": 0,
448
458
  "learning_time_seconds": 0,
449
459
  "learning_time_hours": 0.0,
450
- "average_session_length": None,
460
+ "average_session_length": 0.0,
451
461
  "course_completions": None,
452
462
  },
453
463
  {
@@ -455,7 +465,7 @@ LEADERBOARD_RESPONSE = [
455
465
  "daily_sessions": 0,
456
466
  "learning_time_seconds": 0,
457
467
  "learning_time_hours": 0.0,
458
- "average_session_length": None,
468
+ "average_session_length": 0.0,
459
469
  "course_completions": None,
460
470
  },
461
471
  {
@@ -463,7 +473,7 @@ LEADERBOARD_RESPONSE = [
463
473
  "daily_sessions": 0,
464
474
  "learning_time_seconds": 21,
465
475
  "learning_time_hours": 0.0,
466
- "average_session_length": None,
476
+ "average_session_length": 0.0,
467
477
  "course_completions": None,
468
478
  },
469
479
  {
@@ -471,7 +481,7 @@ LEADERBOARD_RESPONSE = [
471
481
  "daily_sessions": 0,
472
482
  "learning_time_seconds": 0,
473
483
  "learning_time_hours": 0.0,
474
- "average_session_length": None,
484
+ "average_session_length": 0.0,
475
485
  "course_completions": None,
476
486
  },
477
487
  {
@@ -479,7 +489,7 @@ LEADERBOARD_RESPONSE = [
479
489
  "daily_sessions": 0,
480
490
  "learning_time_seconds": 29,
481
491
  "learning_time_hours": 0.0,
482
- "average_session_length": None,
492
+ "average_session_length": 0.0,
483
493
  "course_completions": None,
484
494
  },
485
495
  ]
@@ -4,6 +4,7 @@ Utility functions for Enterprise Data app.
4
4
  import hashlib
5
5
  import random
6
6
  import time
7
+ from contextlib import contextmanager
7
8
  from datetime import timedelta
8
9
  from functools import wraps
9
10
  from logging import getLogger
@@ -68,6 +69,21 @@ def timeit(func):
68
69
  return wrapper
69
70
 
70
71
 
72
+ @contextmanager
73
+ def timer(prefix):
74
+ """
75
+ Context manager to measure the time taken by a block of code.
76
+
77
+ Arguments:
78
+ prefix (str): The prefix to print in the log.
79
+ """
80
+ start = time.time()
81
+ yield
82
+ end = time.time()
83
+ difference = end - start
84
+ print(f"TIMER:: {prefix} took {difference:.20f} seconds")
85
+
86
+
71
87
  def date_filter(start, end, data_frame, date_column):
72
88
  """
73
89
  Filter a pandas DataFrame by date range.