Skip to content

Commit

Permalink
feat: incorporate enterprise customer logic into BFF
Browse files Browse the repository at this point in the history
  • Loading branch information
brobro10000 committed Feb 19, 2025
1 parent 942bd29 commit 0646616
Show file tree
Hide file tree
Showing 9 changed files with 143 additions and 26 deletions.
27 changes: 25 additions & 2 deletions enterprise_access/apps/api/v1/tests/test_bff_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,25 @@ def setUp(self):
# Mock base response data
self.mock_common_response_data = {
'enterprise_customer': self.expected_enterprise_customer,
'all_linked_enterprise_customer_users': [
{
'id': 1,
'active': True,
'enterprise_customer': self.expected_enterprise_customer,
'user_id': 3,
},
{
'id': 2,
'active': False,
'enterprise_customer': {
**self.mock_enterprise_customer_2,
'disable_search': False,
'show_integration_warning': True,
},
'user_id': 6,
},
],
'should_update_active_enterprise_customer_user': self.mock_should_update_active_enterprise_customer_user,
'enterprise_customer_user_subsidies': {
'subscriptions': {
'customer_agreement': None,
Expand Down Expand Up @@ -242,7 +261,6 @@ def test_dashboard_empty_state_with_permissions(
expected_response_data = {
'detail': f'Missing: {BFF_READ_PERMISSION}',
}

self.assertEqual(response.status_code, expected_status_code)
self.assertEqual(response.json(), expected_response_data)

Expand Down Expand Up @@ -552,16 +570,19 @@ def test_dashboard_with_subscriptions_license_auto_apply(
else []
)
mock_enterprise_customer_with_auto_apply = {
**self.mock_enterprise_customer,
**self.expected_enterprise_customer,
'identity_provider': mock_identity_provider,
'identity_providers': mock_identity_providers,
'show_integration_warning': bool(identity_provider)
}
mock_linked_enterprise_customer_users = (
[]
if is_staff_request_user
else [{
'id': 1,
'active': True,
'enterprise_customer': mock_enterprise_customer_with_auto_apply,
'user_id': 3,
}]
)
mock_enterprise_learner_response_data = {
Expand Down Expand Up @@ -658,6 +679,8 @@ def test_dashboard_with_subscriptions_license_auto_apply(
'identity_providers': mock_identity_providers,
'show_integration_warning': expected_show_integration_warning,
},
'all_linked_enterprise_customer_users': mock_linked_enterprise_customer_users,
'should_update_active_enterprise_customer_user': False,
'enterprise_customer_user_subsidies': {
'subscriptions': {
'customer_agreement': expected_customer_agreement,
Expand Down
23 changes: 12 additions & 11 deletions enterprise_access/apps/api_client/lms_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,13 @@ class LmsApiClient(BaseOAuthClient):
"""
API client for calls to the LMS service.
"""
enterprise_api_base_url = settings.LMS_URL + '/enterprise/api/v1/'
enterprise_learner_endpoint = enterprise_api_base_url + 'enterprise-learner/'
enterprise_customer_endpoint = enterprise_api_base_url + 'enterprise-customer/'
pending_enterprise_learner_endpoint = enterprise_api_base_url + 'pending-enterprise-learner/'
enterprise_group_membership_endpoint = enterprise_api_base_url + 'enterprise-group/'
pending_enterprise_admin_endpoint = enterprise_api_base_url + 'pending-enterprise-admin/'
enterprise_base_url = settings.LMS_URL + '/enterprise/'
enterprise_api_v1_base_url = enterprise_base_url + 'api/v1/'
enterprise_learner_endpoint = enterprise_api_v1_base_url + 'enterprise-learner/'
enterprise_customer_endpoint = enterprise_api_v1_base_url + 'enterprise-customer/'
pending_enterprise_learner_endpoint = enterprise_api_v1_base_url + 'pending-enterprise-learner/'
enterprise_group_membership_endpoint = enterprise_api_v1_base_url + 'enterprise-group/'
pending_enterprise_admin_endpoint = enterprise_api_v1_base_url + 'pending-enterprise-admin/'

def enterprise_customer_url(self, enterprise_customer_uuid):
return os.path.join(
Expand All @@ -60,7 +61,7 @@ def enterprise_customer_url(self, enterprise_customer_uuid):

def enterprise_group_endpoint(self, group_uuid):
return os.path.join(
self.enterprise_api_base_url + 'enterprise-group/',
self.enterprise_api_v1_base_url + 'enterprise-group/',
f"{group_uuid}/",
)

Expand Down Expand Up @@ -553,12 +554,12 @@ class LmsUserApiClient(BaseUserApiClient):
"""
API client for user-specific calls to the LMS service.
"""
enterprise_api_base_url = f"{settings.LMS_URL}/enterprise/api/v1/"
enterprise_base_url = settings.LMS_URL + "/enterprise/"
enterprise_api_v1_base_url = enterprise_base_url + "api/v1/"
enterprise_learner_portal_api_base_url = f"{settings.LMS_URL}/enterprise_learner_portal/api/v1/"

enterprise_learner_endpoint = f"{enterprise_api_base_url}enterprise-learner/"
enterprise_learner_endpoint = f"{enterprise_api_v1_base_url}enterprise-learner/"
default_enterprise_enrollment_intentions_learner_status_endpoint = (
f'{enterprise_api_base_url}default-enterprise-enrollment-intentions/learner-status/'
f'{enterprise_api_v1_base_url}default-enterprise-enrollment-intentions/learner-status/'
)
enterprise_course_enrollments_endpoint = (
f'{enterprise_learner_portal_api_base_url}enterprise_course_enrollments/'
Expand Down
30 changes: 19 additions & 11 deletions enterprise_access/apps/bffs/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

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
from enterprise_access.cache_utils import request_cache, versioned_cache_key

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -41,13 +41,13 @@ def enterprise_course_enrollments_cache_key(enterprise_customer_uuid, lms_user_i
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):
def get_and_cache_enterprise_customer_users(request, **kwargs):
"""
Retrieves and caches enterprise learner data.
"""
username = request.user.username
cache_key = enterprise_customer_users_cache_key(username)
cached_response = TieredCache.get_cached_response(cache_key)
cached_response = request_cache(namespace=REQUEST_CACHE_NAMESPACE).get_cached_response(cache_key)
if cached_response.is_found:
logger.info(
f'enterprise_customer_users cache hit for username {username}'
Expand All @@ -59,7 +59,11 @@ def get_and_cache_enterprise_customer_users(request, timeout=settings.ENTERPRISE
username=username,
**kwargs,
)
TieredCache.set_all_tiers(cache_key, response_payload, timeout)
logger.info(
'Fetched enterprise customer user for username %s',
username,
)
request_cache(namespace=REQUEST_CACHE_NAMESPACE).set(cache_key, response_payload)
return response_payload


Expand Down Expand Up @@ -258,30 +262,33 @@ def _determine_enterprise_customer_for_display(
Determine the enterprise customer user for display.
Returns:
The enterprise customer user for display.
tuple(Dict, boolean): The enterprise customer user for display, and a boolean to determine
whether to update the active enterprise customer to the return value.
"""

if not enterprise_customer_slug and not enterprise_customer_uuid:
# No enterprise customer specified in the request, so return the active enterprise customer
return active_enterprise_customer
return active_enterprise_customer, False

# If the requested enterprise does not match the active enterprise customer user's slug/uuid
# and there is a linked enterprise customer user for the requested enterprise, return the
# linked enterprise customer.
# linked enterprise customer. By returning true, we are updating the current active enterprise
# customer to the requested_enterprise_customer
request_matches_active_enterprise_customer = _request_matches_active_enterprise_customer(
active_enterprise_customer=active_enterprise_customer,
enterprise_customer_slug=enterprise_customer_slug,
enterprise_customer_uuid=enterprise_customer_uuid,
)
if not request_matches_active_enterprise_customer and requested_enterprise_customer:
return requested_enterprise_customer
return requested_enterprise_customer, True

# If the request user is staff and the requested enterprise does not match the active enterprise
# customer user's slug/uuid, return the staff-enterprise customer.
if staff_enterprise_customer:
return staff_enterprise_customer
return staff_enterprise_customer, False

# Otherwise, return the active enterprise customer.
return active_enterprise_customer
return active_enterprise_customer, False


def _request_matches_active_enterprise_customer(
Expand Down Expand Up @@ -360,7 +367,7 @@ def transform_enterprise_customer_users_data(data, request, enterprise_customer_
enterprise_customer_user_for_requested_customer.get('enterprise_customer')
if enterprise_customer_user_for_requested_customer else None
)
enterprise_customer = _determine_enterprise_customer_for_display(
enterprise_customer, should_update_active_enterprise_customer_user = _determine_enterprise_customer_for_display(
enterprise_customer_slug=enterprise_customer_slug,
enterprise_customer_uuid=enterprise_customer_uuid,
active_enterprise_customer=active_enterprise_customer,
Expand All @@ -373,4 +380,5 @@ def transform_enterprise_customer_users_data(data, request, enterprise_customer_
'active_enterprise_customer': active_enterprise_customer,
'all_linked_enterprise_customer_users': enterprise_customer_users,
'staff_enterprise_customer': staff_enterprise_customer,
'should_update_active_enterprise_customer_user': should_update_active_enterprise_customer_user,
}
35 changes: 34 additions & 1 deletion enterprise_access/apps/bffs/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ def active_enterprise_customer(self):
def all_linked_enterprise_customer_users(self):
return self.data.get('all_linked_enterprise_customer_users')

@property
def should_update_active_enterprise_customer_user(self):
return self.data.get('should_update_active_enterprise_customer_user')

@property
def is_request_user_linked_to_enterprise_customer(self):
"""
Expand Down Expand Up @@ -222,13 +226,42 @@ def _initialize_enterprise_customer_users(self):
self.enterprise_customer_uuid,
self.enterprise_customer_slug,
)

# Sets the active enterprise customer metadata in the LMS
if should_update_active_enterprise_customer_user := transformed_data.get(
'should_update_active_enterprise_customer_user', False
):
enterprise_customer = transformed_data.get('enterprise_customer')
enterprise_customer_uuid = enterprise_customer.get('uuid')
try:
transformed_data.update({
'active_enterprise_customer': enterprise_customer,
'all_linked_enterprise_customer_users': [
{
**enterprise_customer_user,
"active": (
enterprise_customer_user["enterprise_customer"]["uuid"] == enterprise_customer_uuid
)
}
for enterprise_customer_user in transformed_data.get('all_linked_enterprise_customer_users', [])
]
})
except Exception as exc: # pylint: disable=broad-except
logger.exception(
f"Failed to update active active enterprise user "
f"{enterprise_customer_uuid} and learner {self.lms_user_id}: {exc} "
)
self.add_error(
developer_message=f"Unable to update the active enterprise customer user to "
f"{self.enterprise_customer_uuid}",
user_message="Unable to update your active enterprise"
)
# Update the context data with the transformed enterprise customer users data
self.data.update({
'enterprise_customer': transformed_data.get('enterprise_customer'),
'active_enterprise_customer': transformed_data.get('active_enterprise_customer'),
'all_linked_enterprise_customer_users': transformed_data.get('all_linked_enterprise_customer_users', []),
'staff_enterprise_customer': transformed_data.get('staff_enterprise_customer'),
'should_update_active_enterprise_customer_user': should_update_active_enterprise_customer_user
})

def add_error(self, status_code=None, **kwargs):
Expand Down
5 changes: 4 additions & 1 deletion enterprise_access/apps/bffs/response_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ def build(self):
dict: A dictionary containing the response data.
"""
self.response_data['enterprise_customer'] = self.context.enterprise_customer
self.response_data['all_linked_enterprise_customer_users'] = self.context.all_linked_enterprise_customer_users
self.response_data['should_update_active_enterprise_customer_user'] = (
self.context.should_update_active_enterprise_customer_user
)
self.response_data['enterprise_features'] = self.context.enterprise_features
return self.response_data, self.status_code

Expand Down Expand Up @@ -121,7 +125,6 @@ def build(self):
'enterprise_course_enrollments': self.enterprise_course_enrollments,
'all_enrollments_by_status': self.all_enrollments_by_status,
})

# Serialize and validate the response
try:
serializer = LearnerDashboardResponseSerializer(data=self.response_data)
Expand Down
12 changes: 12 additions & 0 deletions enterprise_access/apps/bffs/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,12 +152,24 @@ class EnterpriseCustomerSerializer(BaseBffSerializer):
show_integration_warning = serializers.BooleanField()


class EnterpriseCustomerUserSerializer(BaseBffSerializer):
"""
Serializer for all linked enterprise customer users
"""
id = serializers.IntegerField()
user_id = serializers.IntegerField()
enterprise_customer = EnterpriseCustomerSerializer()
active = serializers.BooleanField()


class BaseResponseSerializer(BaseBffSerializer):
"""
Serializer for base response.
"""

enterprise_customer = EnterpriseCustomerSerializer(required=False, allow_null=True)
all_linked_enterprise_customer_users = EnterpriseCustomerUserSerializer(many=True, allow_empty=True, default=list)
should_update_active_enterprise_customer_user = serializers.BooleanField()
errors = ErrorSerializer(many=True, required=False, default=list)
warnings = WarningSerializer(many=True, required=False, default=list)
enterprise_features = serializers.DictField(required=False, default=dict)
Expand Down
3 changes: 3 additions & 0 deletions enterprise_access/apps/bffs/tests/test_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ def test_handler_context_init(self, mock_get_enterprise_customers_for_user, rais
'enterprise_customer': self.mock_enterprise_customer_2,
}
],
'should_update_active_enterprise_customer_user': False,
}

self.assertEqual(context.data, expected_data)
Expand Down Expand Up @@ -125,11 +126,13 @@ def test_handler_context_init_staff_user_unlinked(
'active_enterprise_customer': None,
'staff_enterprise_customer': self.mock_enterprise_customer,
'all_linked_enterprise_customer_users': [],
'should_update_active_enterprise_customer_user': False,
}
if raises_exception:
expected_data.update({
'enterprise_customer': None,
'staff_enterprise_customer': None,
'should_update_active_enterprise_customer_user': False,
})
self.assertEqual(context.data, expected_data)
expected_errors = (
Expand Down
13 changes: 13 additions & 0 deletions enterprise_access/apps/bffs/tests/test_response_builders.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from unittest import mock
from unittest.mock import MagicMock

import ddt
from rest_framework import status
Expand All @@ -22,6 +23,8 @@ class TestBaseResponseBuilder(TestHandlerContextMixin):
def test_base_build_error(self, mock_handler_context):
mock_context_data = {
'enterprise_customer': self.mock_enterprise_customer,
'all_linked_enterprise_customer_users': self.mock_all_linked_enterprise_customer_users,
'should_update_active_enterprise_customer_user': self.mock_should_update_active_enterprise_customer_user,
}
mock_handler_context.return_value = self.get_mock_handler_context(
data=mock_context_data,
Expand All @@ -32,6 +35,8 @@ def test_base_build_error(self, mock_handler_context):
expected_response_data = {
'enterprise_customer': self.mock_enterprise_customer,
'enterprise_features': {'feature_flag': True},
'all_linked_enterprise_customer_users': self.mock_all_linked_enterprise_customer_users,
'should_update_active_enterprise_customer_user': self.mock_should_update_active_enterprise_customer_user,
}
self.assertEqual(response_data, expected_response_data)
self.assertEqual(status_code, status.HTTP_200_OK)
Expand Down Expand Up @@ -59,6 +64,8 @@ def test_base_build_error(self, mock_handler_context):
def test_add_errors_warnings_to_response(self, mock_handler_context, errors, warnings):
mock_context_data = {
'enterprise_customer': self.mock_enterprise_customer,
'all_linked_enterprise_customer_users': self.mock_all_linked_enterprise_customer_users,
'should_update_active_enterprise_customer_user': self.mock_should_update_active_enterprise_customer_user,
}
mock_handler_context.return_value = self.get_mock_handler_context(
data=mock_context_data,
Expand All @@ -68,6 +75,8 @@ def test_add_errors_warnings_to_response(self, mock_handler_context, errors, war
expected_output = {
'enterprise_customer': self.mock_enterprise_customer,
'enterprise_features': {'feature_flag': True},
'all_linked_enterprise_customer_users': self.mock_all_linked_enterprise_customer_users,
'should_update_active_enterprise_customer_user': self.mock_should_update_active_enterprise_customer_user,
'errors': [],
'warnings': [],
}
Expand Down Expand Up @@ -129,6 +138,8 @@ def setUp(self):
def test_build(self, mock_handler_context, has_subscriptions_data):
mock_context_data = {
'enterprise_customer': self.mock_enterprise_customer,
'all_linked_enterprise_customer_users': self.mock_all_linked_enterprise_customer_users,
'should_update_active_enterprise_customer_user': self.mock_should_update_active_enterprise_customer_user,
}
mock_handler_context.return_value = self.get_mock_handler_context(
data=mock_context_data,
Expand Down Expand Up @@ -165,6 +176,8 @@ def test_build(self, mock_handler_context, has_subscriptions_data):
'enterprise_customer_user_subsidies': {
'subscriptions': mock_subscriptions_data,
},
'all_linked_enterprise_customer_users': self.mock_all_linked_enterprise_customer_users,
'should_update_active_enterprise_customer_user': self.mock_should_update_active_enterprise_customer_user,
'errors': [],
'warnings': [],
}
Expand Down
Loading

0 comments on commit 0646616

Please sign in to comment.