From 40586ced369de396e4f1500086c26071bc238b18 Mon Sep 17 00:00:00 2001 From: Adam Stankiewicz Date: Mon, 25 Nov 2024 11:35:08 -0500 Subject: [PATCH 1/8] perf: add caching to service calls initiated from BFF --- enterprise_access/apps/bffs/api.py | 100 ++++++++++++++++++++++++ enterprise_access/apps/bffs/handlers.py | 65 ++++++++++----- 2 files changed, 147 insertions(+), 18 deletions(-) diff --git a/enterprise_access/apps/bffs/api.py b/enterprise_access/apps/bffs/api.py index f80e8954..d4793dfe 100644 --- a/enterprise_access/apps/bffs/api.py +++ b/enterprise_access/apps/bffs/api.py @@ -6,6 +6,7 @@ from django.conf import settings from edx_django_utils.cache import TieredCache +from enterprise_access.apps.api_client.license_manager_client import LicenseManagerUserApiClient from enterprise_access.apps.api_client.lms_client import LmsApiClient, LmsUserApiClient from enterprise_access.cache_utils import versioned_cache_key @@ -24,6 +25,18 @@ def enterprise_customer_cache_key(enterprise_customer_slug, enterprise_customer_ return versioned_cache_key('enterprise_customer', enterprise_customer_slug, enterprise_customer_uuid) +def subscription_licenses_cache_key(enterprise_customer_uuid): + return versioned_cache_key('get_subscription_licenses_for_learner', enterprise_customer_uuid) + + +def default_enterprise_enrollment_intentions_cache_key(enterprise_customer_uuid): + return versioned_cache_key('get_default_enterprise_enrollment_intentions', enterprise_customer_uuid) + + +def enterprise_course_enrollments_cache_key(enterprise_customer_uuid): + return versioned_cache_key('get_enterprise_course_enrollments', enterprise_customer_uuid) + + def get_and_cache_enterprise_customer_users(request, **kwargs): """ Retrieves and caches enterprise learner data. @@ -74,6 +87,93 @@ def get_and_cache_enterprise_customer( return response_payload +def get_and_cache_subscription_licenses_for_learner(request, enterprise_customer_uuid, **kwargs): + """ + Retrieves and caches subscription licenses for a learner. + """ + cache_key = subscription_licenses_cache_key(enterprise_customer_uuid) + cached_response = TieredCache.get_cached_response(cache_key) + if cached_response.is_found: + logger.info( + f'subscription_licenses cache hit for enterprise_customer_uuid {enterprise_customer_uuid}' + ) + return cached_response.value + + client = LicenseManagerUserApiClient(request) + response_payload = client.get_subscription_licenses_for_learner( + enterprise_customer_uuid=enterprise_customer_uuid, + **kwargs, + ) + TieredCache.set_all_tiers(cache_key, response_payload, settings.LICENSE_MANAGER_CLIENT_TIMEOUT) + return response_payload + + +def get_and_cache_default_enterprise_enrollment_intentions(request, enterprise_customer_uuid): + """ + Retrieves and caches default enterprise enrollment intentions for a learner. + """ + cache_key = default_enterprise_enrollment_intentions_cache_key(enterprise_customer_uuid) + cached_response = TieredCache.get_cached_response(cache_key) + if cached_response.is_found: + logger.info( + f'default_enterprise_enrollment_intentions cache hit ' + f'for enterprise_customer_uuid {enterprise_customer_uuid}' + ) + return cached_response.value + + client = LmsUserApiClient(request) + response_payload = client.get_default_enterprise_enrollment_intentions_learner_status( + enterprise_customer_uuid=enterprise_customer_uuid, + ) + TieredCache.set_all_tiers(cache_key, response_payload, settings.LMS_CLIENT_TIMEOUT) + return response_payload + + +def get_and_cache_enterprise_course_enrollments(request, enterprise_customer_uuid, **kwargs): + """ + Retrieves and caches enterprise course enrollments for a learner. + """ + cache_key = enterprise_course_enrollments_cache_key(enterprise_customer_uuid) + cached_response = TieredCache.get_cached_response(cache_key) + if cached_response.is_found: + logger.info( + f'enterprise_course_enrollments cache hit for enterprise_customer_uuid {enterprise_customer_uuid}' + ) + return cached_response.value + + client = LmsUserApiClient(request) + response_payload = client.get_enterprise_course_enrollments( + enterprise_customer_uuid=enterprise_customer_uuid, + **kwargs, + ) + TieredCache.set_all_tiers(cache_key, response_payload, settings.LMS_CLIENT_TIMEOUT) + return response_payload + + +def invalidate_default_enterprise_enrollment_intentions(enterprise_customer_uuid): + """ + Invalidates the default enterprise enrollment intentions cache for a learner. + """ + cache_key = default_enterprise_enrollment_intentions_cache_key(enterprise_customer_uuid) + TieredCache.delete_all_tiers(cache_key) + + +def invalidate_enterprise_course_enrollments(enterprise_customer_uuid): + """ + Invalidates the enterprise course enrollments cache for a learner. + """ + cache_key = enterprise_course_enrollments_cache_key(enterprise_customer_uuid) + TieredCache.delete_all_tiers(cache_key) + + +def invalidate_subscription_licenses_cache(enterprise_customer_uuid): + """ + Invalidates the subscription licenses cache for a learner. + """ + cache_key = subscription_licenses_cache_key(enterprise_customer_uuid) + TieredCache.delete_all_tiers(cache_key) + + def _get_active_enterprise_customer(enterprise_customer_users): """ Get the active enterprise customer user from the list of enterprise customer users. diff --git a/enterprise_access/apps/bffs/handlers.py b/enterprise_access/apps/bffs/handlers.py index 561df15d..0d91ded3 100644 --- a/enterprise_access/apps/bffs/handlers.py +++ b/enterprise_access/apps/bffs/handlers.py @@ -6,6 +6,14 @@ from enterprise_access.apps.api_client.license_manager_client import LicenseManagerUserApiClient from enterprise_access.apps.api_client.lms_client import LmsApiClient, LmsUserApiClient +from enterprise_access.apps.bffs.api import ( + get_and_cache_default_enterprise_enrollment_intentions, + get_and_cache_enterprise_course_enrollments, + get_and_cache_subscription_licenses_for_learner, + invalidate_default_enterprise_enrollment_intentions, + invalidate_enterprise_course_enrollments, + invalidate_subscription_licenses_cache +) from enterprise_access.apps.bffs.context import HandlerContext from enterprise_access.apps.bffs.mixins import BaseLearnerDataMixin from enterprise_access.apps.bffs.serializers import EnterpriseCustomerUserSubsidiesSerializer @@ -66,8 +74,7 @@ def __init__(self, context): super().__init__(context) # API Clients - self.license_manager_client = LicenseManagerUserApiClient(self.context.request) - self.lms_user_api_client = LmsUserApiClient(self.context.request) + self.license_manager_user_api_client = LicenseManagerUserApiClient(self.context.request) def load_and_process(self): """ @@ -174,7 +181,8 @@ def load_subscription_licenses(self): Load subscription licenses for the learner. """ try: - subscriptions_result = self.license_manager_client.get_subscription_licenses_for_learner( + subscriptions_result = get_and_cache_subscription_licenses_for_learner( + request=self.context.request, enterprise_customer_uuid=self.context.enterprise_customer_uuid, include_revoked=True, current_plans_only=False, @@ -288,7 +296,13 @@ def check_and_activate_assigned_license(self): if activation_key: try: # Perform side effect: Activate the assigned license - activated_license = self.license_manager_client.activate_license(activation_key) + activated_license = self.license_manager_user_api_client.activate_license(activation_key) + + # Invalidate the subscription licenses cache as the cached data changed + # with the now-activated license. + invalidate_subscription_licenses_cache( + enterprise_customer_uuid=self.context.enterprise_customer_uuid + ) except Exception as e: # pylint: disable=broad-exception-caught logger.exception(f"Error activating license {subscription_license.get('uuid')}") self.add_error( @@ -370,16 +384,21 @@ def check_and_auto_apply_license(self): try: # Perform side effect: Auto-apply license - auto_applied_license = self.license_manager_client.auto_apply_license(customer_agreement.get('uuid')) - if auto_applied_license: - # Update the context with the auto-applied license data - transformed_auto_applied_licenses = self.transform_subscription_licenses([auto_applied_license]) - licenses = self.subscription_licenses + transformed_auto_applied_licenses - subscription_licenses_by_status['activated'] = transformed_auto_applied_licenses - self.context.data['enterprise_customer_user_subsidies']['subscriptions'].update({ - 'subscription_licenses': licenses, - 'subscription_licenses_by_status': subscription_licenses_by_status, - }) + auto_applied_license = self.license_manager_user_api_client.auto_apply_license( + customer_agreement.get('uuid') + ) + # Invalidate the subscription licenses cache as the cached data changed with the auto-applied license. + invalidate_subscription_licenses_cache( + enterprise_customer_uuid=self.context.enterprise_customer_uuid + ) + # Update the context with the auto-applied license data + transformed_auto_applied_licenses = self.transform_subscription_licenses([auto_applied_license]) + licenses = self.subscription_licenses + transformed_auto_applied_licenses + subscription_licenses_by_status['activated'] = transformed_auto_applied_licenses + self.context.data['enterprise_customer_user_subsidies']['subscriptions'].update({ + 'subscription_licenses': licenses, + 'subscription_licenses_by_status': subscription_licenses_by_status, + }) except Exception as e: # pylint: disable=broad-exception-caught logger.exception("Error auto-applying license") self.add_error( @@ -391,12 +410,12 @@ def load_default_enterprise_enrollment_intentions(self): """ Load default enterprise course enrollments (stubbed) """ - client = self.lms_user_api_client try: - default_enrollment_intentions = client.get_default_enterprise_enrollment_intentions_learner_status( + default_enterprise_enrollment_intentions = get_and_cache_default_enterprise_enrollment_intentions( + request=self.context.request, enterprise_customer_uuid=self.context.enterprise_customer_uuid, ) - self.context.data['default_enterprise_enrollment_intentions'] = default_enrollment_intentions + self.context.data['default_enterprise_enrollment_intentions'] = default_enterprise_enrollment_intentions except Exception as e: # pylint: disable=broad-exception-caught logger.exception("Error loading default enterprise courses") self.add_error( @@ -466,6 +485,15 @@ def enroll_in_redeemable_default_enterprise_enrollment_intentions(self): 'subscription_license_uuid': license_uuids_by_course_run_key.get(course_run_key), }) + # Invalidate the default enterprise enrollment intentions and enterprise course enrollments cache + # as the previously redeemable enrollment intentions have been processed/enrolled. + invalidate_default_enterprise_enrollment_intentions( + enterprise_customer_uuid=self.context.enterprise_customer_uuid + ) + invalidate_enterprise_course_enrollments( + enterprise_customer_uuid=self.context.enterprise_customer_uuid + ) + class DashboardHandler(BaseLearnerPortalHandler): """ @@ -501,7 +529,8 @@ def load_enterprise_course_enrollments(self): list: A list of enterprise course enrollments. """ try: - enterprise_course_enrollments = self.lms_user_api_client.get_enterprise_course_enrollments( + enterprise_course_enrollments = get_and_cache_enterprise_course_enrollments( + request=self.context.request, enterprise_customer_uuid=self.context.enterprise_customer_uuid, is_active=True, ) From 312d80fd08c7cfbc64f04d8b041cefd9fb60a604 Mon Sep 17 00:00:00 2001 From: Adam Stankiewicz Date: Mon, 25 Nov 2024 11:39:15 -0500 Subject: [PATCH 2/8] chore: rename --- enterprise_access/apps/bffs/api.py | 4 ++-- enterprise_access/apps/bffs/handlers.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/enterprise_access/apps/bffs/api.py b/enterprise_access/apps/bffs/api.py index d4793dfe..aa4b9195 100644 --- a/enterprise_access/apps/bffs/api.py +++ b/enterprise_access/apps/bffs/api.py @@ -150,7 +150,7 @@ def get_and_cache_enterprise_course_enrollments(request, enterprise_customer_uui return response_payload -def invalidate_default_enterprise_enrollment_intentions(enterprise_customer_uuid): +def invalidate_default_enterprise_enrollment_intentions_cache(enterprise_customer_uuid): """ Invalidates the default enterprise enrollment intentions cache for a learner. """ @@ -158,7 +158,7 @@ def invalidate_default_enterprise_enrollment_intentions(enterprise_customer_uuid TieredCache.delete_all_tiers(cache_key) -def invalidate_enterprise_course_enrollments(enterprise_customer_uuid): +def invalidate_enterprise_course_enrollments_cache(enterprise_customer_uuid): """ Invalidates the enterprise course enrollments cache for a learner. """ diff --git a/enterprise_access/apps/bffs/handlers.py b/enterprise_access/apps/bffs/handlers.py index 0d91ded3..abc0b243 100644 --- a/enterprise_access/apps/bffs/handlers.py +++ b/enterprise_access/apps/bffs/handlers.py @@ -10,8 +10,8 @@ get_and_cache_default_enterprise_enrollment_intentions, get_and_cache_enterprise_course_enrollments, get_and_cache_subscription_licenses_for_learner, - invalidate_default_enterprise_enrollment_intentions, - invalidate_enterprise_course_enrollments, + invalidate_default_enterprise_enrollment_intentions_cache, + invalidate_enterprise_course_enrollments_cache, invalidate_subscription_licenses_cache ) from enterprise_access.apps.bffs.context import HandlerContext @@ -487,10 +487,10 @@ def enroll_in_redeemable_default_enterprise_enrollment_intentions(self): # Invalidate the default enterprise enrollment intentions and enterprise course enrollments cache # as the previously redeemable enrollment intentions have been processed/enrolled. - invalidate_default_enterprise_enrollment_intentions( + invalidate_default_enterprise_enrollment_intentions_cache( enterprise_customer_uuid=self.context.enterprise_customer_uuid ) - invalidate_enterprise_course_enrollments( + invalidate_enterprise_course_enrollments_cache( enterprise_customer_uuid=self.context.enterprise_customer_uuid ) From c6dabd40af01af3f14163929c8b49d3f9f7b0d18 Mon Sep 17 00:00:00 2001 From: Adam Stankiewicz Date: Mon, 25 Nov 2024 11:41:28 -0500 Subject: [PATCH 3/8] chore: clean up --- enterprise_access/apps/bffs/context.py | 1 - 1 file changed, 1 deletion(-) diff --git a/enterprise_access/apps/bffs/context.py b/enterprise_access/apps/bffs/context.py index 380ba12d..b142ddab 100644 --- a/enterprise_access/apps/bffs/context.py +++ b/enterprise_access/apps/bffs/context.py @@ -50,7 +50,6 @@ def __init__(self, request): # API clients self.lms_api_client = LmsApiClient() - self.lms_user_api_client = LmsUserApiClient(request) # Initialize common context data self._initialize_common_context_data() From e252f430077801269e9443dcc3aaeefb18877e0a Mon Sep 17 00:00:00 2001 From: Adam Stankiewicz Date: Mon, 25 Nov 2024 11:47:49 -0500 Subject: [PATCH 4/8] chore: quality --- enterprise_access/apps/bffs/context.py | 2 +- enterprise_access/apps/bffs/handlers.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/enterprise_access/apps/bffs/context.py b/enterprise_access/apps/bffs/context.py index b142ddab..7f484b7e 100644 --- a/enterprise_access/apps/bffs/context.py +++ b/enterprise_access/apps/bffs/context.py @@ -5,7 +5,7 @@ from rest_framework import status -from enterprise_access.apps.api_client.lms_client import LmsApiClient, LmsUserApiClient +from enterprise_access.apps.api_client.lms_client import LmsApiClient from enterprise_access.apps.bffs import serializers from enterprise_access.apps.bffs.api import ( get_and_cache_enterprise_customer_users, diff --git a/enterprise_access/apps/bffs/handlers.py b/enterprise_access/apps/bffs/handlers.py index abc0b243..6c58a752 100644 --- a/enterprise_access/apps/bffs/handlers.py +++ b/enterprise_access/apps/bffs/handlers.py @@ -5,7 +5,7 @@ import logging from enterprise_access.apps.api_client.license_manager_client import LicenseManagerUserApiClient -from enterprise_access.apps.api_client.lms_client import LmsApiClient, LmsUserApiClient +from enterprise_access.apps.api_client.lms_client import LmsApiClient from enterprise_access.apps.bffs.api import ( get_and_cache_default_enterprise_enrollment_intentions, get_and_cache_enterprise_course_enrollments, From d5fd69b9d5b6084a473cc42ed95fc30ca7087adf Mon Sep 17 00:00:00 2001 From: Adam Stankiewicz Date: Mon, 25 Nov 2024 11:51:30 -0500 Subject: [PATCH 5/8] chore: remove unused helper property for LmsApiClient in HandlerContext --- enterprise_access/apps/bffs/context.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/enterprise_access/apps/bffs/context.py b/enterprise_access/apps/bffs/context.py index 7f484b7e..79922e7c 100644 --- a/enterprise_access/apps/bffs/context.py +++ b/enterprise_access/apps/bffs/context.py @@ -48,9 +48,6 @@ def __init__(self, request): self._enterprise_features = {} self.data = {} # Stores processed data for the response - # API clients - self.lms_api_client = LmsApiClient() - # Initialize common context data self._initialize_common_context_data() From 35aa22f0195ae33c7594a856ea0e71119d8770b7 Mon Sep 17 00:00:00 2001 From: Adam Stankiewicz Date: Mon, 25 Nov 2024 12:17:44 -0500 Subject: [PATCH 6/8] perf: update cache timeout settings --- enterprise_access/apps/bffs/api.py | 33 ++++++++++++++----- enterprise_access/apps/bffs/context.py | 1 - .../apps/content_metadata/api.py | 10 +++--- .../subsidy_access_policy/customer_api.py | 8 +++-- enterprise_access/settings/base.py | 16 ++++++--- 5 files changed, 46 insertions(+), 22 deletions(-) diff --git a/enterprise_access/apps/bffs/api.py b/enterprise_access/apps/bffs/api.py index aa4b9195..d9a68ed5 100644 --- a/enterprise_access/apps/bffs/api.py +++ b/enterprise_access/apps/bffs/api.py @@ -37,7 +37,7 @@ def enterprise_course_enrollments_cache_key(enterprise_customer_uuid): return versioned_cache_key('get_enterprise_course_enrollments', enterprise_customer_uuid) -def get_and_cache_enterprise_customer_users(request, **kwargs): +def get_and_cache_enterprise_customer_users(request, timeout=settings.ENTERPRISE_USER_RECORD_CACHE_TIMEOUT, **kwargs): """ Retrieves and caches enterprise learner data. """ @@ -55,13 +55,14 @@ def get_and_cache_enterprise_customer_users(request, **kwargs): username=username, **kwargs, ) - TieredCache.set_all_tiers(cache_key, response_payload, settings.LMS_CLIENT_TIMEOUT) + TieredCache.set_all_tiers(cache_key, response_payload, timeout) return response_payload def get_and_cache_enterprise_customer( enterprise_customer_slug=None, enterprise_customer_uuid=None, + timeout=settings.ENTERPRISE_USER_RECORD_CACHE_TIMEOUT, ): """ Retrieves and caches enterprise customer data. @@ -83,11 +84,16 @@ def get_and_cache_enterprise_customer( enterprise_customer_uuid=enterprise_customer_uuid, enterprise_customer_slug=enterprise_customer_slug, ) - TieredCache.set_all_tiers(cache_key, response_payload, settings.LMS_CLIENT_TIMEOUT) + TieredCache.set_all_tiers(cache_key, response_payload, timeout) return response_payload -def get_and_cache_subscription_licenses_for_learner(request, enterprise_customer_uuid, **kwargs): +def get_and_cache_subscription_licenses_for_learner( + request, + enterprise_customer_uuid, + timeout=settings.SUBSCRIPTION_LICENSES_LEARNER_CACHE_TIMEOUT, + **kwargs +): """ Retrieves and caches subscription licenses for a learner. """ @@ -104,11 +110,15 @@ def get_and_cache_subscription_licenses_for_learner(request, enterprise_customer enterprise_customer_uuid=enterprise_customer_uuid, **kwargs, ) - TieredCache.set_all_tiers(cache_key, response_payload, settings.LICENSE_MANAGER_CLIENT_TIMEOUT) + TieredCache.set_all_tiers(cache_key, response_payload, timeout) return response_payload -def get_and_cache_default_enterprise_enrollment_intentions(request, enterprise_customer_uuid): +def get_and_cache_default_enterprise_enrollment_intentions( + request, + enterprise_customer_uuid, + timeout=settings.DEFAULT_ENTERPRISE_ENROLLMENT_INTENTIONS_CACHE_TIMEOUT, +): """ Retrieves and caches default enterprise enrollment intentions for a learner. """ @@ -125,11 +135,16 @@ def get_and_cache_default_enterprise_enrollment_intentions(request, enterprise_c response_payload = client.get_default_enterprise_enrollment_intentions_learner_status( enterprise_customer_uuid=enterprise_customer_uuid, ) - TieredCache.set_all_tiers(cache_key, response_payload, settings.LMS_CLIENT_TIMEOUT) + TieredCache.set_all_tiers(cache_key, response_payload, timeout) return response_payload -def get_and_cache_enterprise_course_enrollments(request, enterprise_customer_uuid, **kwargs): +def get_and_cache_enterprise_course_enrollments( + request, + enterprise_customer_uuid, + timeout=settings.ENTERPRISE_COURSE_ENROLLMENTS_CACHE_TIMEOUT, + **kwargs +): """ Retrieves and caches enterprise course enrollments for a learner. """ @@ -146,7 +161,7 @@ def get_and_cache_enterprise_course_enrollments(request, enterprise_customer_uui enterprise_customer_uuid=enterprise_customer_uuid, **kwargs, ) - TieredCache.set_all_tiers(cache_key, response_payload, settings.LMS_CLIENT_TIMEOUT) + TieredCache.set_all_tiers(cache_key, response_payload, timeout) return response_payload diff --git a/enterprise_access/apps/bffs/context.py b/enterprise_access/apps/bffs/context.py index 79922e7c..4dedfaa3 100644 --- a/enterprise_access/apps/bffs/context.py +++ b/enterprise_access/apps/bffs/context.py @@ -5,7 +5,6 @@ from rest_framework import status -from enterprise_access.apps.api_client.lms_client import LmsApiClient from enterprise_access.apps.bffs import serializers from enterprise_access.apps.bffs.api import ( get_and_cache_enterprise_customer_users, diff --git a/enterprise_access/apps/content_metadata/api.py b/enterprise_access/apps/content_metadata/api.py index f4ee7d2f..ea9a5513 100644 --- a/enterprise_access/apps/content_metadata/api.py +++ b/enterprise_access/apps/content_metadata/api.py @@ -15,10 +15,12 @@ logger = logging.getLogger(__name__) -DEFAULT_CACHE_TIMEOUT = getattr(settings, 'CONTENT_METADATA_CACHE_TIMEOUT', 60 * 5) - -def get_and_cache_catalog_content_metadata(enterprise_catalog_uuid, content_keys, timeout=None): +def get_and_cache_catalog_content_metadata( + enterprise_catalog_uuid, + content_keys, + timeout=settings.CONTENT_METADATA_CACHE_TIMEOUT, +): """ Returns the metadata corresponding to the requested ``content_keys`` within the provided ``enterprise_catalog_uuid``, @@ -70,7 +72,7 @@ def get_and_cache_catalog_content_metadata(enterprise_catalog_uuid, content_keys cache_key = cache_keys_by_content_key.get(fetched_record.get('key')) content_metadata_to_cache[cache_key] = fetched_record - cache.set_many(content_metadata_to_cache, timeout or DEFAULT_CACHE_TIMEOUT) + cache.set_many(content_metadata_to_cache, timeout) # Add to our results list everything we just had to fetch metadata_results_list.extend(fetched_metadata) diff --git a/enterprise_access/apps/subsidy_access_policy/customer_api.py b/enterprise_access/apps/subsidy_access_policy/customer_api.py index 0949b52b..bc752844 100644 --- a/enterprise_access/apps/subsidy_access_policy/customer_api.py +++ b/enterprise_access/apps/subsidy_access_policy/customer_api.py @@ -13,10 +13,12 @@ logger = logging.getLogger(__name__) -DEFAULT_CACHE_TIMEOUT = settings.ENTERPRISE_USER_RECORD_CACHE_TIMEOUT - -def get_and_cache_enterprise_learner_record(enterprise_customer_uuid, learner_id, timeout=DEFAULT_CACHE_TIMEOUT): +def get_and_cache_enterprise_learner_record( + enterprise_customer_uuid, + learner_id, + timeout=settings.ENTERPRISE_USER_RECORD_CACHE_TIMEOUT, +): """ Fetches the enterprise learner record from the Lms client if it exists. Uses the `learner_id` and `enterprise_customer_uuid` to determine if diff --git a/enterprise_access/settings/base.py b/enterprise_access/settings/base.py index 175efc02..ea84b161 100644 --- a/enterprise_access/settings/base.py +++ b/enterprise_access/settings/base.py @@ -467,6 +467,7 @@ def root(*path_fragments): ENTERPRISE_SUBSIDY_URL = '' ENTERPRISE_ACCESS_URL = '' +# API Client timeouts LICENSE_MANAGER_CLIENT_TIMEOUT = os.environ.get('LICENSE_MANAGER_CLIENT_TIMEOUT', 45) LMS_CLIENT_TIMEOUT = os.environ.get('LMS_CLIENT_TIMEOUT', 45) ECOMMERCE_CLIENT_TIMEOUT = os.environ.get('ECOMMERCE_CLIENT_TIMEOUT', 45) @@ -487,6 +488,7 @@ def root(*path_fragments): BRAZE_ASSIGNMENT_CANCELLED_NOTIFICATION_CAMPAIGN = '' BRAZE_ASSIGNMENT_AUTOMATIC_CANCELLATION_NOTIFICATION_CAMPAIGN = '' +# Braze configuration BRAZE_API_URL = '' BRAZE_API_KEY = os.environ.get('BRAZE_API_KEY', '') BRAZE_APP_ID = os.environ.get('BRAZE_APP_ID', '') @@ -505,11 +507,15 @@ def root(*path_fragments): SIMPLE_HISTORY_DATE_INDEX = False # Cache timeouts -SUBSIDY_RECORD_CACHE_TIMEOUT = 60 * 5 -ENTERPRISE_USER_RECORD_CACHE_TIMEOUT = 60 * 5 -SUBSIDY_AGGREGATES_CACHE_TIMEOUT = 60 * 10 - -ALL_ENTERPRISE_GROUP_MEMBERS_CACHE_TIMEOUT = 60 * 5 +DEFAULT_CACHE_TIMEOUT = 60 * 5 # 5 minutes +CONTENT_METADATA_CACHE_TIMEOUT = 60 * 30 # 30 minutes +ENTERPRISE_USER_RECORD_CACHE_TIMEOUT = 60 * 10 # 10 minutes +SUBSIDY_AGGREGATES_CACHE_TIMEOUT = 60 * 10 # 10 minutes +SUBSCRIPTION_LICENSES_LEARNER_CACHE_TIMEOUT = 60 * 2 # 2 minutes +SUBSIDY_RECORD_CACHE_TIMEOUT = DEFAULT_CACHE_TIMEOUT +DEFAULT_ENTERPRISE_ENROLLMENT_INTENTIONS_CACHE_TIMEOUT = DEFAULT_CACHE_TIMEOUT +ENTERPRISE_COURSE_ENROLLMENTS_CACHE_TIMEOUT = DEFAULT_CACHE_TIMEOUT +ALL_ENTERPRISE_GROUP_MEMBERS_CACHE_TIMEOUT = DEFAULT_CACHE_TIMEOUT BRAZE_GROUP_EMAIL_FORCE_REMIND_ALL_PENDING_LEARNERS = False BRAZE_GROUPS_EMAIL_AUTO_REMINDER_DAY_5_CAMPAIGN = '' From be1e5504489282b10256e6cb5cf055d18f263eaf Mon Sep 17 00:00:00 2001 From: Adam Stankiewicz Date: Tue, 26 Nov 2024 15:54:49 -0500 Subject: [PATCH 7/8] fix: update cache keys to be user-specific --- .../apps/api/v1/tests/test_bff_views.py | 2 +- enterprise_access/apps/bffs/api.py | 42 ++++++++++++------- enterprise_access/apps/bffs/handlers.py | 27 +++++++----- 3 files changed, 43 insertions(+), 28 deletions(-) diff --git a/enterprise_access/apps/api/v1/tests/test_bff_views.py b/enterprise_access/apps/api/v1/tests/test_bff_views.py index e0bbd917..9402ecca 100644 --- a/enterprise_access/apps/api/v1/tests/test_bff_views.py +++ b/enterprise_access/apps/api/v1/tests/test_bff_views.py @@ -183,7 +183,7 @@ def setUp(self): ) @ddt.unpack @mock_dashboard_dependencies - def test_dashboard_empty_state( + def test_dashboard_empty_state_with_permissions( self, mock_get_enterprise_customers_for_user, mock_get_subscription_licenses_for_learner, diff --git a/enterprise_access/apps/bffs/api.py b/enterprise_access/apps/bffs/api.py index d9a68ed5..5445b33f 100644 --- a/enterprise_access/apps/bffs/api.py +++ b/enterprise_access/apps/bffs/api.py @@ -25,16 +25,20 @@ def enterprise_customer_cache_key(enterprise_customer_slug, enterprise_customer_ return versioned_cache_key('enterprise_customer', enterprise_customer_slug, enterprise_customer_uuid) -def subscription_licenses_cache_key(enterprise_customer_uuid): - return versioned_cache_key('get_subscription_licenses_for_learner', enterprise_customer_uuid) +def subscription_licenses_cache_key(enterprise_customer_uuid, lms_user_id): + return versioned_cache_key('get_subscription_licenses_for_learner', enterprise_customer_uuid, lms_user_id) -def default_enterprise_enrollment_intentions_cache_key(enterprise_customer_uuid): - return versioned_cache_key('get_default_enterprise_enrollment_intentions', enterprise_customer_uuid) +def default_enterprise_enrollment_intentions_learner_status_cache_key(enterprise_customer_uuid, lms_user_id): + return versioned_cache_key( + 'get_default_enterprise_enrollment_intentions_learner_status', + enterprise_customer_uuid, + lms_user_id + ) -def enterprise_course_enrollments_cache_key(enterprise_customer_uuid): - return versioned_cache_key('get_enterprise_course_enrollments', enterprise_customer_uuid) +def enterprise_course_enrollments_cache_key(enterprise_customer_uuid, lms_user_id): + return versioned_cache_key('get_enterprise_course_enrollments', enterprise_customer_uuid, lms_user_id) def get_and_cache_enterprise_customer_users(request, timeout=settings.ENTERPRISE_USER_RECORD_CACHE_TIMEOUT, **kwargs): @@ -97,7 +101,7 @@ def get_and_cache_subscription_licenses_for_learner( """ Retrieves and caches subscription licenses for a learner. """ - cache_key = subscription_licenses_cache_key(enterprise_customer_uuid) + cache_key = subscription_licenses_cache_key(enterprise_customer_uuid, request.user.id) cached_response = TieredCache.get_cached_response(cache_key) if cached_response.is_found: logger.info( @@ -114,7 +118,7 @@ def get_and_cache_subscription_licenses_for_learner( return response_payload -def get_and_cache_default_enterprise_enrollment_intentions( +def get_and_cache_default_enterprise_enrollment_intentions_learner_status( request, enterprise_customer_uuid, timeout=settings.DEFAULT_ENTERPRISE_ENROLLMENT_INTENTIONS_CACHE_TIMEOUT, @@ -122,7 +126,10 @@ def get_and_cache_default_enterprise_enrollment_intentions( """ Retrieves and caches default enterprise enrollment intentions for a learner. """ - cache_key = default_enterprise_enrollment_intentions_cache_key(enterprise_customer_uuid) + cache_key = default_enterprise_enrollment_intentions_learner_status_cache_key( + enterprise_customer_uuid, + request.user.id, + ) cached_response = TieredCache.get_cached_response(cache_key) if cached_response.is_found: logger.info( @@ -148,7 +155,7 @@ def get_and_cache_enterprise_course_enrollments( """ Retrieves and caches enterprise course enrollments for a learner. """ - cache_key = enterprise_course_enrollments_cache_key(enterprise_customer_uuid) + cache_key = enterprise_course_enrollments_cache_key(enterprise_customer_uuid, request.user.id) cached_response = TieredCache.get_cached_response(cache_key) if cached_response.is_found: logger.info( @@ -165,27 +172,30 @@ def get_and_cache_enterprise_course_enrollments( return response_payload -def invalidate_default_enterprise_enrollment_intentions_cache(enterprise_customer_uuid): +def invalidate_default_enterprise_enrollment_intentions_learner_status_cache(enterprise_customer_uuid, lms_user_id): """ Invalidates the default enterprise enrollment intentions cache for a learner. """ - cache_key = default_enterprise_enrollment_intentions_cache_key(enterprise_customer_uuid) + cache_key = default_enterprise_enrollment_intentions_learner_status_cache_key( + enterprise_customer_uuid, + lms_user_id, + ) TieredCache.delete_all_tiers(cache_key) -def invalidate_enterprise_course_enrollments_cache(enterprise_customer_uuid): +def invalidate_enterprise_course_enrollments_cache(enterprise_customer_uuid, lms_user_id): """ Invalidates the enterprise course enrollments cache for a learner. """ - cache_key = enterprise_course_enrollments_cache_key(enterprise_customer_uuid) + cache_key = enterprise_course_enrollments_cache_key(enterprise_customer_uuid, lms_user_id) TieredCache.delete_all_tiers(cache_key) -def invalidate_subscription_licenses_cache(enterprise_customer_uuid): +def invalidate_subscription_licenses_cache(enterprise_customer_uuid, lms_user_id): """ Invalidates the subscription licenses cache for a learner. """ - cache_key = subscription_licenses_cache_key(enterprise_customer_uuid) + cache_key = subscription_licenses_cache_key(enterprise_customer_uuid, lms_user_id) TieredCache.delete_all_tiers(cache_key) diff --git a/enterprise_access/apps/bffs/handlers.py b/enterprise_access/apps/bffs/handlers.py index 6c58a752..8400cfe9 100644 --- a/enterprise_access/apps/bffs/handlers.py +++ b/enterprise_access/apps/bffs/handlers.py @@ -7,10 +7,10 @@ from enterprise_access.apps.api_client.license_manager_client import LicenseManagerUserApiClient from enterprise_access.apps.api_client.lms_client import LmsApiClient from enterprise_access.apps.bffs.api import ( - get_and_cache_default_enterprise_enrollment_intentions, + get_and_cache_default_enterprise_enrollment_intentions_learner_status, get_and_cache_enterprise_course_enrollments, get_and_cache_subscription_licenses_for_learner, - invalidate_default_enterprise_enrollment_intentions_cache, + invalidate_default_enterprise_enrollment_intentions_learner_status_cache, invalidate_enterprise_course_enrollments_cache, invalidate_subscription_licenses_cache ) @@ -301,7 +301,8 @@ def check_and_activate_assigned_license(self): # Invalidate the subscription licenses cache as the cached data changed # with the now-activated license. invalidate_subscription_licenses_cache( - enterprise_customer_uuid=self.context.enterprise_customer_uuid + enterprise_customer_uuid=self.context.enterprise_customer_uuid, + lms_user_id=self.context.lms_user_id, ) except Exception as e: # pylint: disable=broad-exception-caught logger.exception(f"Error activating license {subscription_license.get('uuid')}") @@ -389,7 +390,8 @@ def check_and_auto_apply_license(self): ) # Invalidate the subscription licenses cache as the cached data changed with the auto-applied license. invalidate_subscription_licenses_cache( - enterprise_customer_uuid=self.context.enterprise_customer_uuid + enterprise_customer_uuid=self.context.enterprise_customer_uuid, + lms_user_id=self.context.lms_user_id, ) # Update the context with the auto-applied license data transformed_auto_applied_licenses = self.transform_subscription_licenses([auto_applied_license]) @@ -411,10 +413,11 @@ def load_default_enterprise_enrollment_intentions(self): Load default enterprise course enrollments (stubbed) """ try: - default_enterprise_enrollment_intentions = get_and_cache_default_enterprise_enrollment_intentions( - request=self.context.request, - enterprise_customer_uuid=self.context.enterprise_customer_uuid, - ) + default_enterprise_enrollment_intentions =\ + get_and_cache_default_enterprise_enrollment_intentions_learner_status( + request=self.context.request, + enterprise_customer_uuid=self.context.enterprise_customer_uuid, + ) self.context.data['default_enterprise_enrollment_intentions'] = default_enterprise_enrollment_intentions except Exception as e: # pylint: disable=broad-exception-caught logger.exception("Error loading default enterprise courses") @@ -487,11 +490,13 @@ def enroll_in_redeemable_default_enterprise_enrollment_intentions(self): # Invalidate the default enterprise enrollment intentions and enterprise course enrollments cache # as the previously redeemable enrollment intentions have been processed/enrolled. - invalidate_default_enterprise_enrollment_intentions_cache( - enterprise_customer_uuid=self.context.enterprise_customer_uuid + invalidate_default_enterprise_enrollment_intentions_learner_status_cache( + enterprise_customer_uuid=self.context.enterprise_customer_uuid, + lms_user_id=self.context.lms_user_id, ) invalidate_enterprise_course_enrollments_cache( - enterprise_customer_uuid=self.context.enterprise_customer_uuid + enterprise_customer_uuid=self.context.enterprise_customer_uuid, + lms_user_id=self.context.lms_user_id, ) From 4eaa45d302a2243e86a2abd42cbba8c489be0417 Mon Sep 17 00:00:00 2001 From: Adam Stankiewicz Date: Tue, 26 Nov 2024 16:01:59 -0500 Subject: [PATCH 8/8] chore: move LmsApiClient to a class attr --- enterprise_access/apps/bffs/handlers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/enterprise_access/apps/bffs/handlers.py b/enterprise_access/apps/bffs/handlers.py index 8400cfe9..90014e05 100644 --- a/enterprise_access/apps/bffs/handlers.py +++ b/enterprise_access/apps/bffs/handlers.py @@ -75,6 +75,7 @@ def __init__(self, context): # API Clients self.license_manager_user_api_client = LicenseManagerUserApiClient(self.context.request) + self.lms_api_client = LmsApiClient() def load_and_process(self): """ @@ -458,9 +459,8 @@ def enroll_in_redeemable_default_enterprise_enrollment_intentions(self): 'is_default_auto_enrollment': True, }) - client = LmsApiClient() try: - response_payload = client.bulk_enroll_enterprise_learners( + response_payload = self.lms_api_client.bulk_enroll_enterprise_learners( self.context.enterprise_customer_uuid, bulk_enrollment_payload, )