Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/openedx/edx-enterprise in…
Browse files Browse the repository at this point in the history
…to hamza/ENT-7725
  • Loading branch information
hamzawaleed01 committed Oct 20, 2023
2 parents a539c17 + 2cf8b5d commit 7c8c5db
Show file tree
Hide file tree
Showing 8 changed files with 77 additions and 9 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ Change Log
Unreleased
----------
[4.6.7]
-------
feat: filter courses from API Response of SAPSF to store in the APIResponseRecord table

[4.6.6]
-------
chore: orchestrator exception handling and submission refinements

[4.6.5]
-------
feat: Added logs for Degreed2 client
Expand Down
2 changes: 1 addition & 1 deletion enterprise/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
Your project description goes here.
"""

__version__ = "4.6.5"
__version__ = "4.6.7"
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ def update(self, request, *args, **kwargs):
with transaction.atomic():
sso_configuration_record.update(**request_data)
sso_configuration_record.first().submit_for_configuration(updating_existing_record=True)
except (TypeError, FieldDoesNotExist, ValidationError) as e:
except (TypeError, FieldDoesNotExist, ValidationError, SsoOrchestratorClientError) as e:
LOGGER.error(f'{CONFIG_UPDATE_ERROR}{e}')
return Response({'error': f'{CONFIG_UPDATE_ERROR}{e}'}, status=HTTP_400_BAD_REQUEST)
serializer = self.serializer_class(sso_configuration_record.first())
Expand Down
7 changes: 4 additions & 3 deletions enterprise/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -4082,7 +4082,8 @@ def submit_for_configuration(self, updating_existing_record=False):
config_data = {}
if self.identity_provider == self.SAP_SUCCESS_FACTORS:
for field in self.sap_config_fields:
sap_data[utils.camelCase(field)] = getattr(self, field)
if field_value := getattr(self, field):
sap_data[utils.camelCase(field)] = field_value
is_sap = True
else:
for field in self.base_saml_config_fields:
Expand All @@ -4091,8 +4092,8 @@ def submit_for_configuration(self, updating_existing_record=False):
config_data['enable'] = True
else:
config_data['enable'] = getattr(self, field)
else:
config_data[utils.camelCase(field)] = getattr(self, field)
elif field_value := getattr(self, field):
config_data[utils.camelCase(field)] = field_value

EnterpriseSSOOrchestratorApiClient().configure_sso_orchestration_record(
config_data=config_data,
Expand Down
2 changes: 1 addition & 1 deletion integrated_channels/degreed2/exporters/learner_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,6 @@ def get_learner_data_records(
enterprise_enrollment.enterprise_customer_user.enterprise_customer.uuid,
enterprise_enrollment.enterprise_customer_user.user_id,
enterprise_enrollment.course_id,
'Degreed2 get_learner_data_records failed, possibly due to an invalid customer configuration. '
'[Degreed2Client] get_learner_data_records failed, possibly due to an invalid customer configuration. '
f'Error: {e}'
))
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,14 @@ def _serialize_items(self, channel_metadata_items):
sort_keys=True
).encode('utf-8')

def _filter_api_response(self, response, content_id): # pylint: disable=unused-argument
"""
Filter the response from the integrated channel API client.
This can be overridden by subclasses to parse the response
expected by the integrated channel.
"""
return response

def _transmit_action(self, content_metadata_item_map, client_method, action_name): # pylint: disable=too-many-statements
"""
Do the work of calling the appropriate client method, saving the results, and updating
Expand Down Expand Up @@ -219,9 +227,9 @@ def _transmit_action(self, content_metadata_item_map, client_method, action_name
)
transmission.api_response_status_code = response_status_code
was_successful = response_status_code < 300

api_content_response = self._filter_api_response(response_body, content_id)
if transmission.api_record:
transmission.api_record.body = response_body
transmission.api_record.body = api_content_response
transmission.api_record.status_code = response_status_code
transmission.api_record.save()
else:
Expand All @@ -230,7 +238,7 @@ def _transmit_action(self, content_metadata_item_map, client_method, action_name
'ApiResponseRecord'
)
transmission.api_record = ApiResponseRecord.objects.create(
body=response_body, status_code=response_status_code
body=api_content_response, status_code=response_status_code
)
if action_name == 'create':
transmission.remote_created_at = action_happened_at
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
"""
Class for transmitting content metadata to SuccessFactors.
"""
import json
import logging

from integrated_channels.integrated_channel.transmitters.content_metadata import ContentMetadataTransmitter
from integrated_channels.sap_success_factors.client import SAPSuccessFactorsAPIClient

LOGGER = logging.getLogger(__name__)


class SapSuccessFactorsContentMetadataTransmitter(ContentMetadataTransmitter):
"""
Expand All @@ -19,6 +24,21 @@ def __init__(self, enterprise_configuration, client=SAPSuccessFactorsAPIClient):
client=client
)

def _filter_api_response(self, response, content_id):
"""
Filter the response from SAPSF to only include the content
based on the content_id
"""
try:
parsed_response = json.loads(response)
parsed_response["ocnCourses"] = [item for item in parsed_response["ocnCourses"]
if item["courseID"] == content_id]
filtered_response = json.dumps(parsed_response)
return filtered_response
except Exception as exc: # pylint: disable=broad-except
LOGGER.exception("Error filtering response from SAPSF for Course: %s, %s", content_id, exc)
return response

def transmit(self, create_payload, update_payload, delete_payload):
"""
Transmit method overriding base transmissions. Due to rate limiting on SAP
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Tests for the SAP SuccessFactors content metadata transmitter.
"""

import json
import unittest
from datetime import datetime
from unittest import mock
Expand Down Expand Up @@ -326,3 +327,33 @@ def test_transmit_api_usage_limit_disabled(self, create_content_metadata_mock):
plugin_configuration_id=self.enterprise_config.id,
integrated_channel_code=self.enterprise_config.channel_code(),
).count() == 2

@mock.patch('integrated_channels.sap_success_factors.transmitters.content_metadata.LOGGER')
def test_filter_api_response_successful(self, logger_mock):
"""
Test that the api response is successfully filtered
"""
response = '{"ocnCourses": [{"courseID": "course:DemoX"}, {"courseID": "course:DemoX2"}]}'
content_id = 'course:DemoX'

transmitter = SapSuccessFactorsContentMetadataTransmitter(self.enterprise_config)
# pylint: disable=protected-access
filtered_response = transmitter._filter_api_response(response, content_id)

assert json.loads(filtered_response) == {"ocnCourses": [{"courseID": "course:DemoX"}]}
assert logger_mock.exception.call_count == 0

@mock.patch('integrated_channels.sap_success_factors.transmitters.content_metadata.LOGGER')
def test_filter_api_response_exception(self, logger_mock):
"""
Test that the api response is not filtered if an exception occurs
"""
response = 'Invalid JSON response'
content_id = 'course:DemoX'

transmitter = SapSuccessFactorsContentMetadataTransmitter(self.enterprise_config)
# pylint: disable=protected-access
filtered_response = transmitter._filter_api_response(response, content_id)

assert filtered_response == response
logger_mock.exception.assert_called_once()

0 comments on commit 7c8c5db

Please sign in to comment.