Skip to content

Commit

Permalink
checkpoint: it mostly works
Browse files Browse the repository at this point in the history
  • Loading branch information
iloveagent57 committed Nov 25, 2024
1 parent 35490e6 commit bb5c4f4
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 27 deletions.
64 changes: 64 additions & 0 deletions enterprise_access/apps/api_client/lms_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ class LmsApiClient(BaseOAuthClient):
pending_enterprise_learner_endpoint = enterprise_api_base_url + 'pending-enterprise-learner/'
enterprise_group_membership_endpoint = enterprise_api_base_url + 'enterprise-group/'

def enterprise_customer_url(self, enterprise_customer_uuid):
return os.path.join(
self.enterprise_customer_endpoint,
f"{enterprise_customer_uuid}/",
)

def enterprise_group_endpoint(self, group_uuid):
return os.path.join(
self.enterprise_api_base_url + 'enterprise-group/',
Expand All @@ -63,6 +69,12 @@ def enterprise_group_members_endpoint(self, group_uuid):
"learners/",
)

def enterprise_customer_bulk_enrollment_url(self, enterprise_customer_uuid):
return os.path.join(
self.enterprise_customer_url(enterprise_customer_uuid),
"enroll_learners_in_courses/",
)

def get_enterprise_customer_data(self, enterprise_customer_uuid=None, enterprise_customer_slug=None):
"""
Gets the data for an EnterpriseCustomer for the given uuid or slug.
Expand Down Expand Up @@ -408,6 +420,58 @@ def update_pending_learner_status(self, enterprise_group_uuid, learner_email):
logger.exception('failed to update group membership status. [%s]', url)
return None

def bulk_enroll_enterprise_learners(self, enterprise_customer_uuid, enrollments_info):
"""
Calls the Enterprise Bulk Enrollment API to enroll learners in courses.
Arguments:
enterprise_customer_uuid (UUID): UUID representation of the customer that the enrollment will be linked to
enrollment_info (list[dicts]): List of enrollment information required to enroll.
Each entry must contain key/value pairs as follows:
user_id: ID of the learner to be enrolled
course_run_key: the course run key to be enrolled in by the user
[transaction_id,license_uuid]: uuid representation of the subsidy identifier
that allows the enrollment
is_default_auto_enrollment (optional): boolean indicating whether the enrollment
is the realization of a default enrollment intention.
Example::
[
{
'user_id': 1234,
'course_run_key': 'course-v2:edX+FunX+Fun_Course',
'transaction_id': '84kdbdbade7b4fcb838f8asjke8e18ae',
},
{
'user_id': 1234,
'course_run_key': 'course-v2:edX+FunX+Fun_Course',
'license_uuid': '00001111de7b4fcb838f8asjke8effff',
'is_default_auto_enrollment': True,
},
...
]
Returns:
response (dict): JSON response data
Raises:
requests.exceptions.HTTPError: if service is down/unavailable or status code comes back >= 300,
the method will log and throw an HTTPError exception.
"""
bulk_enrollment_url = self.enterprise_customer_bulk_enrollment_url(enterprise_customer_uuid)
options = {'enrollments_info': enrollments_info}
response = self.client.post(
bulk_enrollment_url,
json=options,
)
try:
response.raise_for_status()
return response.json()
except requests.exceptions.HTTPError as exc:
logger.error(
f'Failed to generate enterprise enrollments for enterprise: {enterprise_customer_uuid} '
f'with options: {options}. Failed with error: {exc} and payload %s',
response.json(),
)
raise exc


class LmsUserApiClient(BaseUserApiClient):
"""
Expand Down
43 changes: 43 additions & 0 deletions enterprise_access/apps/api_client/tests/test_lms_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,49 @@ def test_get_pending_enterprise_group_memberships(self, mock_oauth_client, mock_
)
assert pending_enterprise_group_memberships == expected_return

@mock.patch('requests.Response.json')
@mock.patch('enterprise_access.apps.api_client.base_oauth.OAuthAPIClient')
def test_bulk_enroll_enterprise_learners(self, mock_oauth_client, mock_json):
"""
Tests that the ``bulk_enroll_enterprise_learners`` endpoint can be
requested via the LmsApiClient.
"""
mock_oauth_client.return_value.post.return_value = requests.Response()
mock_oauth_client.return_value.post.return_value.status_code = 200

mock_json.return_value = {
'successes': [{'what': 'ever'}],
'failures': [],
}

enrollments_info = [
{
'user_id': 1234,
'course_run_key': 'course-v2:edX+FunX+Fun_Course',
'transaction_id': '84kdbdbade7b4fcb838f8asjke8e18ae',
},
{
'user_id': 1234,
'course_run_key': 'course-v2:edX+FunX+Fun_Course',
'license_uuid': '00001111de7b4fcb838f8asjke8effff',
'is_default_auto_enrollment': True,
},
]

client = LmsApiClient()
response_payload = client.bulk_enroll_enterprise_learners(
str(TEST_ENTERPRISE_UUID),
enrollments_info,
)

url = (
'http://edx-platform.example.com/enterprise/api/v1/enterprise-customer/'
f'{TEST_ENTERPRISE_UUID}/enroll_learners_in_courses/'
)
mock_oauth_client.return_value.post.assert_called_with(
url,
json={'enrollments_info': enrollments_info},
)

class TestLmsUserApiClient(TestCase):
"""
Expand Down
68 changes: 43 additions & 25 deletions enterprise_access/apps/bffs/handlers.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
""""
Handlers for bffs app.
"""

import json
import logging

from django.utils.functional import cached_property

from enterprise_access.apps.api_client.license_manager_client import LicenseManagerUserApiClient
from enterprise_access.apps.api_client.lms_client import LmsUserApiClient
from enterprise_access.apps.api_client.lms_client import LmsApiClient, LmsUserApiClient
from enterprise_access.apps.bffs.context import HandlerContext
from enterprise_access.apps.bffs.mixins import BaseLearnerDataMixin
from enterprise_access.apps.bffs.serializers import EnterpriseCustomerUserSubsidiesSerializer
Expand Down Expand Up @@ -229,7 +231,7 @@ def transform_subscriptions_result(self, subscriptions_result):
'subscription_licenses_by_status': subscription_licenses_by_status,
}

@property
@cached_property
def current_active_license(self):
"""
Returns an activated license for the user iff the related subscription plan is current,
Expand Down Expand Up @@ -412,35 +414,51 @@ def enroll_in_redeemable_default_enterprise_enrollment_intentions(self):
needs_enrollment = enrollment_statuses.get('needs_enrollment', {})
needs_enrollment_enrollable = needs_enrollment.get('enrollable', [])

activated_subscription_licenses = self.subscription_licenses_by_status.get('activated', [])

if not (needs_enrollment_enrollable or activated_subscription_licenses):
# Skip enrollment if there are no:
# - default enterprise enrollment intentions that should be enrolled OR
# - activated subscription licenses
if not (needs_enrollment_enrollable and self.current_active_license):
return

redeemable_default_courses = []
license_uuids_by_course_run_key = {}
for enrollment_intention in needs_enrollment_enrollable:
for subscription_license in activated_subscription_licenses:
subscription_plan = subscription_license.get('subscription_plan', {})
subscription_catalog = subscription_plan.get('enterprise_catalog_uuid')
applicable_catalog_to_enrollment_intention = enrollment_intention.get(
'applicable_enterprise_catalog_uuids'
)
if subscription_catalog in applicable_catalog_to_enrollment_intention:
redeemable_default_courses.append((enrollment_intention, subscription_license))
break
subscription_plan = self.current_active_license.get('subscription_plan', {})
subscription_catalog = subscription_plan.get('enterprise_catalog_uuid')
applicable_catalog_to_enrollment_intention = enrollment_intention.get(
'applicable_enterprise_catalog_uuids'
)
if subscription_catalog in applicable_catalog_to_enrollment_intention:
course_run_key = enrollment_intention['course_run_key']
license_uuids_by_course_run_key[course_run_key] = self.current_active_license['uuid']
break

bulk_enrollment_payload = []
for course_run_key, license_uuid in license_uuids_by_course_run_key.items():
bulk_enrollment_payload.append({
'user_id': self.context.lms_user_id,
'course_run_key': course_run_key,
'license_uuid': license_uuid,
'is_default_auto_enrollment': True,
})

client = LmsApiClient()
response_payload = client.bulk_enroll_enterprise_learners(
self.context.enterprise_customer_uuid,
bulk_enrollment_payload,
)

if response_payload['failures']:
self.add_error(
user_message='There were failures realizing default enrollments',
developer_message='Default realization enrollment failures: ' + json.dumps(failures),
)

for redeemable_course, subscription_license in redeemable_default_courses:
# TODO: enroll in redeemable courses (stubbed)
if not self.context.data.get('default_enterprise_enrollment_realizations'):
self.context.data['default_enterprise_enrollment_realizations'] = []
if not self.context.data.get('default_enterprise_enrollment_realizations'):
self.context.data['default_enterprise_enrollment_realizations'] = []

for enrollment in response_payload['successes']:
course_run_key = enrollment.get('course_run_key')
self.context.data['default_enterprise_enrollment_realizations'].append({
'course_key': redeemable_course.get('key'),
'course_key': course_run_key,
'enrollment_status': 'enrolled',
'subscription_license_uuid': subscription_license.get('uuid'),
'subscription_license_uuid': license_uuids_by_course_run_key.get(course_run_key),
})


Expand Down
6 changes: 4 additions & 2 deletions enterprise_access/apps/bffs/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,9 @@ class CustomerAgreementSerializer(BaseBffSerializer):
disable_expiration_notifications = serializers.BooleanField()
enable_auto_applied_subscriptions_with_universal_link = serializers.BooleanField()
subscription_for_auto_applied_licenses = serializers.UUIDField(allow_null=True)
has_custom_license_expiration_messaging_v2 = serializers.BooleanField(required=False, default=False)
has_custom_license_expiration_messaging_v2 = serializers.BooleanField(
required=False, allow_null=True, default=False,
)
button_label_in_modal_v2 = serializers.CharField(required=False, allow_null=True)
expired_subscription_modal_messaging_v2 = serializers.CharField(required=False, allow_null=True)
modal_header_text_v2 = serializers.CharField(required=False, allow_null=True)
Expand Down Expand Up @@ -261,7 +263,7 @@ class EnterpriseCourseEnrollmentSerializer(BaseBffSerializer):
org_name = serializers.CharField()
course_run_status = serializers.CharField()
display_name = serializers.CharField()
emails_enabled = serializers.BooleanField()
emails_enabled = serializers.BooleanField(required=False, allow_null=True)
certificate_download_url = serializers.CharField(allow_null=True)
created = serializers.DateTimeField()
start_date = serializers.DateTimeField(allow_null=True)
Expand Down

0 comments on commit bb5c4f4

Please sign in to comment.