edx-enterprise-data 8.0.0__tar.gz → 8.3.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.
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/CHANGELOG.rst +12 -0
- {edx_enterprise_data-8.0.0/edx_enterprise_data.egg-info → edx_enterprise_data-8.3.0}/PKG-INFO +4 -1
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0/edx_enterprise_data.egg-info}/PKG-INFO +4 -1
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/edx_enterprise_data.egg-info/SOURCES.txt +14 -1
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/edx_enterprise_data.egg-info/requires.txt +3 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/__init__.py +1 -1
- edx_enterprise_data-8.3.0/enterprise_data/admin_analytics/data_loaders.py +137 -0
- edx_enterprise_data-8.3.0/enterprise_data/admin_analytics/database.py +50 -0
- edx_enterprise_data-8.3.0/enterprise_data/admin_analytics/utils.py +81 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/api/v1/serializers.py +20 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/api/v1/urls.py +13 -6
- edx_enterprise_data-8.3.0/enterprise_data/api/v1/views/base.py +26 -0
- edx_enterprise_data-8.3.0/enterprise_data/api/v1/views/enterprise_admin.py +109 -0
- edx_enterprise_data-8.0.0/enterprise_data/api/v1/views.py → edx_enterprise_data-8.3.0/enterprise_data/api/v1/views/enterprise_learner.py +5 -114
- edx_enterprise_data-8.3.0/enterprise_data/api/v1/views/enterprise_offers.py +41 -0
- edx_enterprise_data-8.3.0/enterprise_data/tests/admin_analytics/test_data_loaders.py +86 -0
- edx_enterprise_data-8.3.0/enterprise_data/tests/admin_analytics/test_utils.py +102 -0
- edx_enterprise_data-8.3.0/enterprise_data/tests/api/v1/views/test_enterprise_admin.py +82 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/tests/test_filters.py +1 -1
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/tests/test_utils.py +73 -0
- edx_enterprise_data-8.3.0/enterprise_data/utils.py +84 -0
- edx_enterprise_data-8.3.0/enterprise_data_roles/migrations/__init__.py +0 -0
- edx_enterprise_data-8.3.0/enterprise_data_roles/tests/__init__.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_reporting/clients/__init__.py +2 -3
- edx_enterprise_data-8.3.0/enterprise_reporting/fixtures/__init__.py +0 -0
- edx_enterprise_data-8.3.0/enterprise_reporting/tests/__init__.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_reporting/tests/test_enterprise_client.py +2 -5
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/requirements/base.in +3 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/requirements/base.txt +27 -12
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/requirements/ci.txt +3 -3
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/requirements/common_constraints.txt +11 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/requirements/constraints.txt +6 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/requirements/dev.txt +37 -29
- edx_enterprise_data-8.3.0/requirements/django.txt +1 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/requirements/pip.txt +2 -2
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/requirements/quality.txt +40 -32
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/requirements/test-master.txt +29 -14
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/requirements/test-reporting.txt +14 -13
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/requirements/test.txt +30 -15
- edx_enterprise_data-8.0.0/enterprise_data/utils.py +0 -37
- edx_enterprise_data-8.0.0/requirements/django.txt +0 -1
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/LICENSE +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/MANIFEST.in +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/README.md +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/edx_enterprise_data.egg-info/dependency_links.txt +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/edx_enterprise_data.egg-info/not-zip-safe +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/edx_enterprise_data.egg-info/top_level.txt +0 -0
- {edx_enterprise_data-8.0.0/enterprise_data/api → edx_enterprise_data-8.3.0/enterprise_data/admin_analytics}/__init__.py +0 -0
- {edx_enterprise_data-8.0.0/enterprise_data/management → edx_enterprise_data-8.3.0/enterprise_data/api}/__init__.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/api/urls.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/api/v0/__init__.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/api/v0/serializers.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/api/v0/urls.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/api/v0/views.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/api/v1/__init__.py +0 -0
- {edx_enterprise_data-8.0.0/enterprise_data/management/commands → edx_enterprise_data-8.3.0/enterprise_data/api/v1/views}/__init__.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/apps.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/clients.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/constants.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/filters.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/fixtures/enterprise_enrollment.json +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/fixtures/enterprise_user.json +0 -0
- {edx_enterprise_data-8.0.0/enterprise_data/management/commands/tests → edx_enterprise_data-8.3.0/enterprise_data/management}/__init__.py +0 -0
- {edx_enterprise_data-8.0.0/enterprise_data/migrations → edx_enterprise_data-8.3.0/enterprise_data/management/commands}/__init__.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/management/commands/create_dummy_data.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/management/commands/create_dummy_data_lpr_v1.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/management/commands/create_enterprise_enrollment.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/management/commands/create_enterprise_learner_enrollment_lpr_v1.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/management/commands/create_enterprise_learner_lpr_v1.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/management/commands/create_enterprise_offer.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/management/commands/create_enterprise_user.py +0 -0
- {edx_enterprise_data-8.0.0/enterprise_data/settings → edx_enterprise_data-8.3.0/enterprise_data/management/commands/tests}/__init__.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/management/commands/tests/test_create_dummy_data_lpr_v1.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/management/commands/tests/test_create_enterprise_enrollment.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/management/commands/tests/test_create_enterprise_learner_enrollment_lpr_v1.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/management/commands/tests/test_create_enterprise_learner_lpr_v1.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/management/commands/tests/test_create_enterprise_user.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/migrations/0001_initial.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/migrations/0002_auto_20180430_1358.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/migrations/0003_auto_20180501_0603.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/migrations/0004_auto_20180501_0928.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/migrations/0004_auto_20180508_1652.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/migrations/0005_auto_20180524_2204.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/migrations/0006_auto_20180612_0336.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/migrations/0007_auto_20180612_0534.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/migrations/0008_auto_20180614_0108.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/migrations/0009_auto_20180628_1152.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/migrations/0010_enterpriseenrollment_created.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/migrations/0011_enterpriseuser.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/migrations/0012_auto_20180831_1930.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/migrations/0013_auto_20180831_1931.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/migrations/0014_enterpriseuser_created.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/migrations/0015_auto_20180907_1757.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/migrations/0016_auto_20180924_2138.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/migrations/0017_enterpriseenrollment_unenrollment_timestamp.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/migrations/0018_enterprisedatafeaturerole_enterprisedataroleassignment.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/migrations/0019_add_enterprise_data_feature_roles.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/migrations/0020_add_role_based_access_control_switch.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/migrations/0021_auto_20190329_1241.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/migrations/0022_remove_role_based_access_control_switch.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/migrations/0023_enterpriselearner_enterpriselearnerenrollment.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/migrations/0024_auto_20210602_1811.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/migrations/0025_auto_20210703_1854.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/migrations/0026_auto_20210916_0414.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/migrations/0027_enterpriselearnerenrollment_total_learning_time_seconds.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/migrations/0028_enterpriselearnerenrollment_offer_id.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/migrations/0029_enterpriseoffer.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/migrations/0030_auto_20230609_1353.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/migrations/0031_auto_20230615_0705.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/migrations/0032_auto_20230704_0818.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/migrations/0033_enterpriseadminlearnerprogress_enterpriseadminsummarizeinsights.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/migrations/0034_auto_20230907_0834.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/migrations/0035_auto_20230907_1154.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/migrations/0036_enterprisesubsidybudget_subsidy_access_policy_display_name.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/migrations/0037_alter_enterpriseenrollment_consent_granted.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/migrations/0038_enterpriseoffer_export_timestamp.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/migrations/0039_auto_20240212_1403.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/migrations/0040_auto_20240718_0536_squashed_0043_alter_enterpriselearnerenrollment_enterprise_enrollment_id.py +0 -0
- {edx_enterprise_data-8.0.0/enterprise_data/tests → edx_enterprise_data-8.3.0/enterprise_data/migrations}/__init__.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/models.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/paginators.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/renderers.py +0 -0
- {edx_enterprise_data-8.0.0/enterprise_data/tests/api → edx_enterprise_data-8.3.0/enterprise_data/settings}/__init__.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/settings/test.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/signals.py +0 -0
- {edx_enterprise_data-8.0.0/enterprise_data/tests/api/v0 → edx_enterprise_data-8.3.0/enterprise_data/tests}/__init__.py +0 -0
- {edx_enterprise_data-8.0.0/enterprise_data/tests/api/v1 → edx_enterprise_data-8.3.0/enterprise_data/tests/admin_analytics}/__init__.py +0 -0
- {edx_enterprise_data-8.0.0/enterprise_data_roles/migrations → edx_enterprise_data-8.3.0/enterprise_data/tests/api}/__init__.py +0 -0
- {edx_enterprise_data-8.0.0/enterprise_data_roles/tests → edx_enterprise_data-8.3.0/enterprise_data/tests/api/v0}/__init__.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/tests/api/v0/test_serializers.py +0 -0
- {edx_enterprise_data-8.0.0/enterprise_reporting/fixtures → edx_enterprise_data-8.3.0/enterprise_data/tests/api/v1}/__init__.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/tests/api/v1/test_serializers.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/tests/api/v1/test_views.py +0 -0
- {edx_enterprise_data-8.0.0/enterprise_reporting/tests → edx_enterprise_data-8.3.0/enterprise_data/tests/api/v1/views}/__init__.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/tests/factories.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/tests/mixins.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/tests/test_clients.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/tests/test_models.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/tests/test_views.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/urls.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data_roles/__init__.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data_roles/admin.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data_roles/apps.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data_roles/constants.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data_roles/migrations/0001_initial.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data_roles/migrations/0002_add_enterprise_data_feature_roles.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data_roles/migrations/0003_add_role_based_access_control_switch.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data_roles/migrations/0004_enterprisedataroleassignment_enterprise_id.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data_roles/migrations/0005_turn_on_role_based_access_control_switch.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data_roles/migrations/0006_remove_role_based_access_control_switch.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data_roles/migrations/0007_enterprisedataroleassignment_applies_to_all_contexts.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data_roles/models.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data_roles/rules.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data_roles/tests/factories.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data_roles/tests/test_models.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_reporting/__init__.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_reporting/clients/enterprise.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_reporting/clients/s3.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_reporting/clients/snowflake.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_reporting/clients/vertica.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_reporting/delivery_method.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_reporting/external_resource_link_report.py +3 -3
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_reporting/fixtures/enterprise_customer_reporting.json +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_reporting/reporter.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_reporting/send_enterprise_reports.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_reporting/tests/test_clients.py +1 -1
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_reporting/tests/test_delivery_method.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_reporting/tests/test_external_link_report.py +2 -2
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_reporting/tests/test_reporter.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_reporting/tests/test_send_enterprise_reports.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_reporting/tests/test_utils.py +3 -3
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_reporting/tests/test_vertica_client.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_reporting/tests/utils.py +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_reporting/utils.py +1 -1
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/requirements/pip_tools.txt +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/requirements/reporting.in +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/setup.cfg +0 -0
- {edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/setup.py +0 -0
@@ -15,6 +15,18 @@ Unreleased
|
|
15
15
|
----------
|
16
16
|
|
17
17
|
=========================
|
18
|
+
[8.3.0] - 2024-07-25
|
19
|
+
---------------------
|
20
|
+
* refactor: Refactor code to avoid error conditions.
|
21
|
+
|
22
|
+
[8.2.0] - 2024-07-25
|
23
|
+
---------------------
|
24
|
+
* Added a new API endpoint to get admin analytics aggregated data on user enrollment and engagement.
|
25
|
+
|
26
|
+
[8.1.0] - 2024-07-22
|
27
|
+
---------------------
|
28
|
+
* Upgrade python requirements
|
29
|
+
|
18
30
|
[8.0.0] - 2024-07-18
|
19
31
|
---------------------
|
20
32
|
* Fix migration for EnterpriseLearnerEnrollment model
|
{edx_enterprise_data-8.0.0/edx_enterprise_data.egg-info → edx_enterprise_data-8.3.0}/PKG-INFO
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: edx-enterprise-data
|
3
|
-
Version: 8.
|
3
|
+
Version: 8.3.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
|
{edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0/edx_enterprise_data.egg-info}/PKG-INFO
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: edx-enterprise-data
|
3
|
-
Version: 8.
|
3
|
+
Version: 8.3.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
|
{edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/edx_enterprise_data.egg-info/SOURCES.txt
RENAMED
@@ -20,6 +20,10 @@ 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/data_loaders.py
|
25
|
+
enterprise_data/admin_analytics/database.py
|
26
|
+
enterprise_data/admin_analytics/utils.py
|
23
27
|
enterprise_data/api/__init__.py
|
24
28
|
enterprise_data/api/urls.py
|
25
29
|
enterprise_data/api/v0/__init__.py
|
@@ -29,7 +33,11 @@ enterprise_data/api/v0/views.py
|
|
29
33
|
enterprise_data/api/v1/__init__.py
|
30
34
|
enterprise_data/api/v1/serializers.py
|
31
35
|
enterprise_data/api/v1/urls.py
|
32
|
-
enterprise_data/api/v1/views.py
|
36
|
+
enterprise_data/api/v1/views/__init__.py
|
37
|
+
enterprise_data/api/v1/views/base.py
|
38
|
+
enterprise_data/api/v1/views/enterprise_admin.py
|
39
|
+
enterprise_data/api/v1/views/enterprise_learner.py
|
40
|
+
enterprise_data/api/v1/views/enterprise_offers.py
|
33
41
|
enterprise_data/fixtures/enterprise_enrollment.json
|
34
42
|
enterprise_data/fixtures/enterprise_user.json
|
35
43
|
enterprise_data/management/__init__.py
|
@@ -99,12 +107,17 @@ enterprise_data/tests/test_filters.py
|
|
99
107
|
enterprise_data/tests/test_models.py
|
100
108
|
enterprise_data/tests/test_utils.py
|
101
109
|
enterprise_data/tests/test_views.py
|
110
|
+
enterprise_data/tests/admin_analytics/__init__.py
|
111
|
+
enterprise_data/tests/admin_analytics/test_data_loaders.py
|
112
|
+
enterprise_data/tests/admin_analytics/test_utils.py
|
102
113
|
enterprise_data/tests/api/__init__.py
|
103
114
|
enterprise_data/tests/api/v0/__init__.py
|
104
115
|
enterprise_data/tests/api/v0/test_serializers.py
|
105
116
|
enterprise_data/tests/api/v1/__init__.py
|
106
117
|
enterprise_data/tests/api/v1/test_serializers.py
|
107
118
|
enterprise_data/tests/api/v1/test_views.py
|
119
|
+
enterprise_data/tests/api/v1/views/__init__.py
|
120
|
+
enterprise_data/tests/api/v1/views/test_enterprise_admin.py
|
108
121
|
enterprise_data_roles/__init__.py
|
109
122
|
enterprise_data_roles/admin.py
|
110
123
|
enterprise_data_roles/apps.py
|
@@ -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,50 @@
|
|
1
|
+
"""
|
2
|
+
Utility functions for interacting with the database.
|
3
|
+
"""
|
4
|
+
from contextlib import closing
|
5
|
+
from logging import getLogger
|
6
|
+
|
7
|
+
from mysql.connector import connect
|
8
|
+
|
9
|
+
from django.conf import settings
|
10
|
+
|
11
|
+
from enterprise_data.utils import timeit
|
12
|
+
|
13
|
+
LOGGER = getLogger(__name__)
|
14
|
+
|
15
|
+
|
16
|
+
def get_db_connection(database=settings.ENTERPRISE_REPORTING_DB_ALIAS):
|
17
|
+
"""
|
18
|
+
Get a connection to the database.
|
19
|
+
|
20
|
+
Returns:
|
21
|
+
(mysql.connector.connection.MySQLConnection): The database connection.
|
22
|
+
"""
|
23
|
+
return connect(
|
24
|
+
host=settings.DATABASES[database]['HOST'],
|
25
|
+
port=settings.DATABASES[database]['PORT'],
|
26
|
+
database=settings.DATABASES[database]['NAME'],
|
27
|
+
user=settings.DATABASES[database]['USER'],
|
28
|
+
password=settings.DATABASES[database]['PASSWORD'],
|
29
|
+
)
|
30
|
+
|
31
|
+
|
32
|
+
@timeit
|
33
|
+
def run_query(query):
|
34
|
+
"""
|
35
|
+
Run a query on the database and return the results.
|
36
|
+
|
37
|
+
Arguments:
|
38
|
+
query (str): The query to run.
|
39
|
+
|
40
|
+
Returns:
|
41
|
+
(list): The results of the query.
|
42
|
+
"""
|
43
|
+
try:
|
44
|
+
with closing(get_db_connection()) as connection:
|
45
|
+
with closing(connection.cursor()) as cursor:
|
46
|
+
cursor.execute(query)
|
47
|
+
return cursor.fetchall()
|
48
|
+
except Exception:
|
49
|
+
LOGGER.exception(f'[run_query]: run_query failed for query "{query}".')
|
50
|
+
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
|
{edx_enterprise_data-8.0.0 → edx_enterprise_data-8.3.0}/enterprise_data/api/v1/serializers.py
RENAMED
@@ -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
|
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
|
-
|
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
|
-
|
25
|
+
enterprise_offers_views.EnterpriseOfferViewSet,
|
24
26
|
'enterprise-offers',
|
25
27
|
)
|
26
28
|
router.register(
|
27
29
|
r'enterprise/(?P<enterprise_id>.+)/users',
|
28
|
-
|
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
|
-
|
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
|
-
|
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)
|