Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: update customer serializer #2168

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 44 additions & 1 deletion enterprise/api/v1/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
CourseEnrollmentDowngradeError,
CourseEnrollmentPermissionError,
get_integrations_for_customers,
get_active_sso_configurations_for_customer,
get_last_course_run_end_date,
has_course_run_available_for_enrollment,
track_enrollment,
Expand Down Expand Up @@ -227,7 +228,8 @@ class Meta:
'enable_learner_portal_sidebar_message', 'learner_portal_sidebar_content',
'enable_pathways', 'enable_programs', 'enable_demo_data_for_analytics_and_lpr', 'enable_academies',
'enable_one_academy', 'active_integrations', 'show_videos_in_learner_portal_search_results',
'default_language', 'country', 'enable_slug_login',
'default_language', 'country', 'enable_slug_login', 'active_sso_configurations',
'subscriptions', 'coupons', 'offers', 'has_active_offers', 'has_active_coupons', 'has_active_subscriptions'
)

identity_providers = EnterpriseCustomerIdentityProviderSerializer(many=True, read_only=True)
Expand All @@ -237,10 +239,51 @@ class Meta:
enterprise_notification_banner = serializers.SerializerMethodField()
admin_users = serializers.SerializerMethodField()
active_integrations = serializers.SerializerMethodField()
active_sso_configurations = serializers.SerializerMethodField()
subscriptions = serializers.SerializerMethodField()
coupons = serializers.SerializerMethodField()
offers = serializers.SerializerMethodField()
has_active_offers = serializers.SerializerMethodField()
has_active_coupons = serializers.SerializerMethodField()
has_active_subscriptions = serializers.SerializerMethodField()

def get_offers(self, obj):
# we want to get both active and inactive offers to display data for the card
return obj.offers_for_customer

def get_coupons(self, obj):
# we want to get both active and inactive coupons to display data for the card
return obj.coupons_for_customer

def get_subscriptions(self, obj):
# we want to get both active and inactive subs to display data for the card
return obj.subscriptions_for_customer

def get_has_active_offers(self, obj):
# loop through offers and check if there is at least one
# active offer and return boolean. this is used for the
# checkmark on the support tools customer data table.
return

def get_has_active_coupons(self, obj):
# loop through coupons and check if there is at least one
# active coupon and return boolean. this is used for the
# checkmark on the support tools customer data table.
return

def get_has_active_subscriptions(self, obj):
# loop through subs and check if there is at least one
# active sub and return boolean. this is used for the
# checkmark on the support tools customer data table.
return

def get_active_sso_configurations(self, obj):
return get_active_sso_configurations_for_customer(obj.uuid)

def get_active_integrations(self, obj):
return get_integrations_for_customers(obj.uuid)


def get_branding_configuration(self, obj):
"""
Return the serialized branding configuration object OR default object if null
Expand Down
10 changes: 10 additions & 0 deletions enterprise/api/v1/views/enterprise_customer_api_credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,16 @@ def retrieve(self, request, *args, **kwargs):

URL: /enterprise/api/v1/enterprise-customer-api-credentials/{enterprise_uuid}
"""

'''
Note: obtaining data for all API credentials created for a specific enterprise customer
is not straightforward because the data is not stored on the enterprise customer. One potential
method is to iterate through all users linked to the enterprise customer to identify the creator
of the API credentials. However, this approach could impact performance, raise security concerns, and
expand the scope of this task. As an alternative, I recommend displaying a checkmark on the customer
data table and API credentials card to indicate if API credentials are enabled for the enterprise customer.
'''

user_application = Application.objects.get(user=request.user)
serializer = self.get_serializer(instance=user_application)
return Response(serializer.data, status=status.HTTP_200_OK)
Expand Down
30 changes: 30 additions & 0 deletions enterprise/api_client/ecommerce.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,36 @@ def create_manual_enrollment_orders(self, enrollments):
str(exc)
)

def get_coupons(self, enterprise_customer_uuid):
"""
Get coupons for the enterprise customer.

Returns:
array of coupons.
"""
api_url = urljoin(f"{self.API_BASE_URL}/", f"enterprise/coupons/{enterprise_customer_uuid}/overview/")
# format each result to include start date/end date name, and uuid
try:
response = self.client.get(api_url)
return response.json().get('results')
except:
LOGGER.exception(f'failed to fetch at url {api_url}')

def get_offers(self, enterprise_customer_uuid):
"""
Get offers for the enterprise customer.

Returns:
array of offers.
"""
# format each result to include start date/end date, name, and uuid
api_url = urljoin(f"{self.API_BASE_URL}/", f"enterprise/{enterprise_customer_uuid}/enterprise-admin-offers/")
try:
response = self.client.get(api_url)
return response.json().get('results')
except:
LOGGER.exception(f'failed to fetch at url {api_url}')


class NoAuthEcommerceClient(NoAuthAPIClient):
"""
Expand Down
35 changes: 35 additions & 0 deletions enterprise/api_client/license_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""
API client for calls to the license-manager service.
"""
import logging

import requests
from django.conf import settings
from enterprise.api_client.client import BackendServiceAPIClient
from urllib.parse import urljoin

logger = logging.getLogger(__name__)

class LicenseManagerApiClient(BackendServiceAPIClient):
"""
API client for calls to the license-manager service.
"""
LICENSE_MANAGER_BASE_URL = urljoin(f"{settings.LICENSE_MANAGER_URL}/", "api/v1/")
SUBSCRIPTIONS_ENDPOINT = LICENSE_MANAGER_BASE_URL + 'subscriptions/?enterprise_customer_uuid='

def get_customer_subscriptions(self, customer_uuid):
"""
Call license-manager API for data about a SubscriptionPlan.

Arguments:
subscription_uuid (UUID): UUID of the SubscriptionPlan in license-manager
Returns:
dict: Dictionary representation of json returned from API
"""
try:
endpoint = self.SUBSCRIPTIONS_ENDPOINT + str(customer_uuid)
response = self.client.get(endpoint)
return response.json().get('results')
except requests.exceptions.HTTPError as exc:
logger.exception(exc)
raise
55 changes: 55 additions & 0 deletions enterprise/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
from enterprise.api_client.discovery import CourseCatalogApiClient, get_course_catalog_api_service_client
from enterprise.api_client.ecommerce import EcommerceApiClient
from enterprise.api_client.enterprise_catalog import EnterpriseCatalogApiClient
from enterprise.api_client.license_manager import LicenseManagerApiClient
from enterprise.api_client.lms import EnrollmentApiClient, ThirdPartyAuthApiClient
from enterprise.api_client.sso_orchestrator import EnterpriseSSOOrchestratorApiClient
from enterprise.api_client.xpert_ai import chat_completion
Expand Down Expand Up @@ -759,6 +760,60 @@ def catalog_contains_course(self, course_run_id):

return False

@property
def subscriptions_for_customer(self):
"""
Helper method to get subscriptions for customer.

Arguments:
customer_uuid (UUID): uuid of an enterprise customer
Returns:
list: a list of subscriptions.
"""
try:
subscriptions = LicenseManagerApiClient().get_customer_subscriptions(self.uuid)
except:
raise
return subscriptions

@property
def coupons_for_customer(self):
"""
Helper method to get active coupons for customer.

Arguments:
customer_uuid (UUID): uuid of an enterprise customer
Returns:
list: a list of active coupons
"""
ecommerce_service_worker = get_ecommerce_worker_user()
try:
ecommerce_api_client = EcommerceApiClient(ecommerce_service_worker)
except:
LOGGER.exception('failed')
else:
coupons = ecommerce_api_client.get_coupons(self.uuid)
return coupons

@property
def offers_for_customer(self):
"""
Helper method to get offers for customer.

Arguments:
customer_uuid (UUID): uuid of an enterprise customer
Returns:
list: a list of offers
"""
ecommerce_service_worker = get_ecommerce_worker_user()
try:
ecommerce_api_client = EcommerceApiClient(ecommerce_service_worker)
except:
LOGGER.exception('failed')
else:
offers = ecommerce_api_client.get_offers(self.uuid)
return offers

def enroll_user_pending_registration_with_status(self, email, course_mode, *course_ids, **kwargs):
"""
Create pending enrollments for the user in any number of courses, which will take effect on registration.
Expand Down
1 change: 1 addition & 0 deletions enterprise/settings/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ def root(*args):
LMS_ENROLLMENT_API_PATH = "/api/enrollment/v1/"
ECOMMERCE_PUBLIC_URL_ROOT = "http://localhost:18130"
ENTERPRISE_CATALOG_INTERNAL_ROOT_URL = "http://localhost:18160"
LICENSE_MANAGER_URL = "http://localhost:18170"

ENTERPRISE_ENROLLMENT_API_URL = LMS_INTERNAL_ROOT_URL + LMS_ENROLLMENT_API_PATH

Expand Down
39 changes: 39 additions & 0 deletions enterprise/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
CourseModes,
)
from enterprise.logging import getEnterpriseLogger
# from enterprise.api_client.license_manager_client import LicenseManagerApiClient

try:
from openedx.features.enterprise_support.enrollments.utils import lms_update_or_create_enrollment
Expand Down Expand Up @@ -728,6 +729,13 @@ def enterprise_customer_invite_key_model():
return apps.get_model('enterprise', 'EnterpriseCustomerInviteKey')


def enterprise_customer_sso_configuration_model():
"""
Returns the ``EnterpriseCustomerSsoConfiguration`` class.
"""
return apps.get_model('enterprise', 'EnterpriseCustomerSsoConfiguration')


def get_enterprise_customer(uuid):
"""
Get the ``EnterpriseCustomer`` instance associated with ``uuid``.
Expand Down Expand Up @@ -2466,9 +2474,40 @@ def get_integrations_for_customers(customer_uuid):
Returns:
list: a list of integration channel codes.
"""

'''
Currently, we are only returning an array of the integrated channel code. We need to
update this to also return the an array of objects that includes a created and last modified date
'''
unique_integrations = []
integrated_channel_choices = get_integrated_channel_choices()
for code, choice in integrated_channel_choices.items():
if choice.objects.filter(enterprise_customer__uuid=customer_uuid, active=True):
unique_integrations.append(code)
return unique_integrations


def get_active_sso_configurations_for_customer(customer_uuid):
"""
Helper method to get active sso configurations for each enterprise customer

Arguments:
customer_uuid (UUID): uuid of an enterprise customer
Returns:
list: a list of active sso configurations
"""
SsoConfigurations = enterprise_customer_sso_configuration_model()
sso_configurations = SsoConfigurations.objects.filter(enterprise_customer__uuid=customer_uuid,
active=True).values()
active_configurations = []
if sso_configurations:
for sso_configuration in sso_configurations:
active_configurations.append({
'created': sso_configuration.get('created'),
'modified': sso_configuration.get('modified'),
'is_removed': sso_configuration.get('is_removed'),
'display_name': sso_configuration.get('display_name'),
'uuid': sso_configuration.get('uuid'),
'enterprise_customer_id': sso_configuration.get('enterprise_customer_id'),
})
return active_configurations
Loading