diff --git a/enterprise/constants.py b/enterprise/constants.py index 97b48dec7b..91aa7b6c87 100644 --- a/enterprise/constants.py +++ b/enterprise/constants.py @@ -219,3 +219,6 @@ class FulfillmentTypes: SSO_BRAZE_CAMPAIGN_ID = 'a5f10d46-8093-4ce1-bab7-6df018d03660' + +# The maximum length of a text field in the database. +MAX_ALLOWED_TEXT_LENGTH = 16_000_000 diff --git a/enterprise/utils.py b/enterprise/utils.py index 28725480c9..291f7d139b 100644 --- a/enterprise/utils.py +++ b/enterprise/utils.py @@ -39,6 +39,7 @@ DEFAULT_CATALOG_CONTENT_FILTER, LMS_API_DATETIME_FORMAT, LMS_API_DATETIME_FORMAT_WITHOUT_TIMEZONE, + MAX_ALLOWED_TEXT_LENGTH, PATHWAY_CUSTOMER_ADMIN_ENROLLMENT, PROGRAM_TYPE_DESCRIPTION, CourseModes, @@ -2365,3 +2366,19 @@ def camelCase(string): """ output = ''.join(x for x in string.title() if x.isalnum()) return output[0].lower() + output[1:] + + +def truncate_string(string, max_length=MAX_ALLOWED_TEXT_LENGTH): + """ + Truncate a string to the specified max length. + If max length is not specified, it will be set to MAX_ALLOWED_TEXT_LENGTH. + + Returns: + (tuple): (truncated_string, was_truncated) + """ + was_truncated = False + if len(string) > max_length: + truncated_string = string[:max_length] + was_truncated = True + return (truncated_string, was_truncated) + return (string, was_truncated) diff --git a/integrated_channels/integrated_channel/transmitters/content_metadata.py b/integrated_channels/integrated_channel/transmitters/content_metadata.py index dc87a33a40..b53fcbfa5f 100644 --- a/integrated_channels/integrated_channel/transmitters/content_metadata.py +++ b/integrated_channels/integrated_channel/transmitters/content_metadata.py @@ -12,7 +12,7 @@ from django.apps import apps from django.conf import settings -from enterprise.utils import localized_utcnow +from enterprise.utils import localized_utcnow, truncate_string from integrated_channels.exceptions import ClientError from integrated_channels.integrated_channel.client import IntegratedChannelApiClient from integrated_channels.integrated_channel.transmitters import Transmitter @@ -230,6 +230,13 @@ def _transmit_action(self, content_metadata_item_map, client_method, action_name api_content_response = response_body if was_successful: api_content_response = self._filter_api_response(api_content_response, content_id) + (api_content_response, was_truncated) = truncate_string(api_content_response) + if was_truncated: + self._log_info( + f'integrated_channel_content_transmission_id={transmission.id}, ' + f'api response truncated', + course_or_course_run_key=content_id + ) if transmission.api_record: transmission.api_record.body = api_content_response transmission.api_record.status_code = response_status_code diff --git a/tests/test_enterprise/test_utils.py b/tests/test_enterprise/test_utils.py index d98921e6c0..9dc3cac117 100644 --- a/tests/test_enterprise/test_utils.py +++ b/tests/test_enterprise/test_utils.py @@ -12,6 +12,7 @@ from django.conf import settings from django.forms.models import model_to_dict +from enterprise.constants import MAX_ALLOWED_TEXT_LENGTH from enterprise.models import EnterpriseCourseEnrollment, LicensedEnterpriseCourseEnrollment from enterprise.utils import ( enroll_subsidy_users_in_courses, @@ -22,6 +23,7 @@ localized_utcnow, parse_lms_api_datetime, serialize_notification_content, + truncate_string, ) from test_utils import FAKE_UUIDS, TEST_PASSWORD, TEST_USERNAME, factories @@ -516,3 +518,21 @@ def test_get_default_invite_key_expiration_date(self): expiration_date = get_default_invite_key_expiration_date() expected_expiration_date = current_time + timedelta(days=365) self.assertEqual(expiration_date.date(), expected_expiration_date.date()) + + def test_truncate_string(self): + """ + Test that `truncate_string` returns the expected string. + """ + test_string_1 = 'This is a test string' + (truncated_string_1, was_truncated_1) = truncate_string(test_string_1, 10) + self.assertTrue(was_truncated_1) + self.assertEqual('This is a ', truncated_string_1) + + (truncated_string_2, was_truncated_2) = truncate_string(test_string_1, 100) + self.assertFalse(was_truncated_2) + self.assertEqual('This is a test string', truncated_string_2) + + test_string_2 = ''.rjust(MAX_ALLOWED_TEXT_LENGTH + 10, 'x') + (truncated_string, was_truncated) = truncate_string(test_string_2) + self.assertTrue(was_truncated) + self.assertEqual(len(truncated_string), MAX_ALLOWED_TEXT_LENGTH)