diff --git a/lms/djangoapps/program_enrollments/management/commands/send_program_course_nudge_email.py b/lms/djangoapps/program_enrollments/management/commands/send_program_course_nudge_email.py index af2bfdca752d..d8d1c51b1446 100644 --- a/lms/djangoapps/program_enrollments/management/commands/send_program_course_nudge_email.py +++ b/lms/djangoapps/program_enrollments/management/commands/send_program_course_nudge_email.py @@ -12,9 +12,11 @@ from django.conf import settings from django.contrib.auth import get_user_model +from django.contrib.auth.models import AbstractBaseUser from django.contrib.sites.models import Site from django.core.management import BaseCommand from django.utils import timezone +from edx_django_utils.plugins import pluggable_override from opaque_keys.edx.keys import CourseKey from opaque_keys.edx.locator import CourseLocator @@ -23,13 +25,36 @@ from openedx.core.constants import COURSE_PUBLISHED from openedx.core.djangoapps.catalog.utils import get_programs from openedx.core.djangoapps.programs.utils import ProgramProgressMeter -from openedx.features.enterprise_support.api import get_enterprise_learner_data_from_db User = get_user_model() LOGGER = logging.getLogger(__name__) +@pluggable_override('OVERRIDE_PROGRAM_NUDGE_SUGGESTED_COURSE_URL') +def get_suggested_course_url( + user: AbstractBaseUser, + suggested_course: dict, + suggested_course_run: dict, +) -> str: + """ + Return the URL that the nudge email's suggested course link should point at. + + Arguments: + user: The learner the nudge email is being sent to. + suggested_course: The catalog course being suggested. + suggested_course_run: The catalog course run being suggested. + + Returns: + str: The absolute URL of the suggested course's about page. + + This function can be overridden by an installed plugin via the + OVERRIDE_PROGRAM_NUDGE_SUGGESTED_COURSE_URL setting, for deployments that + route some learners to a different course landing page. + """ + return urljoin(settings.MKTG_URLS.get('ROOT'), suggested_course_run['marketing_url']) + + class Command(BaseCommand): """ Django management command for sending nudge emails to learners @@ -185,16 +210,11 @@ def emit_event(self, user, program, suggested_course_run, suggested_course, comp """ Emit the Segment event which will be used by Braze to send the email """ - learner_data = get_enterprise_learner_data_from_db(user) - enterprise_customer = learner_data[0]['enterprise_customer'] if learner_data else None - if enterprise_customer and enterprise_customer['enable_learner_portal']: - # If user is an enterprise learner then we want to redirect to B2B course landing page on learner portal. - recommended_course_url = urljoin( - settings.ENTERPRISE_LEARNER_PORTAL_BASE_URL, - '/'.join([enterprise_customer['slug'], 'course', suggested_course['key']]), - ) - else: - recommended_course_url = urljoin(settings.MKTG_URLS.get('ROOT'), suggested_course_run['marketing_url']) + recommended_course_url = get_suggested_course_url( + user=user, + suggested_course=suggested_course, + suggested_course_run=suggested_course_run, + ) event_properties = { 'COURSE_ONE_NAME': completed_course_run['title'], diff --git a/lms/djangoapps/program_enrollments/management/commands/tests/test_send_program_course_nudge_email.py b/lms/djangoapps/program_enrollments/management/commands/tests/test_send_program_course_nudge_email.py index f5a6dcebb846..65cb7ff3e32c 100644 --- a/lms/djangoapps/program_enrollments/management/commands/tests/test_send_program_course_nudge_email.py +++ b/lms/djangoapps/program_enrollments/management/commands/tests/test_send_program_course_nudge_email.py @@ -3,8 +3,10 @@ """ from datetime import timedelta from unittest.mock import patch +from urllib.parse import urljoin import ddt +from django.conf import settings from django.core.management import call_command from django.test.utils import override_settings from django.utils import timezone @@ -17,11 +19,18 @@ from lms.djangoapps.grades.models import PersistentCourseGrade from openedx.core.djangoapps.catalog.tests.factories import CourseFactory as CatalogCourseFactory from openedx.core.djangoapps.catalog.tests.factories import CourseRunFactory, ProgramFactory -from openedx.features.enterprise_support.tests.factories import EnterpriseCustomerUserFactory from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory LOG_PATH = 'lms.djangoapps.program_enrollments.management.commands.send_program_course_nudge_email' +TEST_MODULE_PATH = __name__ + + +def replacement_suggested_course_url(prev_fn, user, suggested_course, suggested_course_run): + """ + Test override that builds its own URL instead of delegating to ``prev_fn``. + """ + return f'https://example.com/{user.username}/{suggested_course["key"]}' @ddt.ddt @@ -40,10 +49,6 @@ def setUp(self): self.user_1 = UserFactory() self.user_2 = UserFactory() - self.enterprise_customer_user = EnterpriseCustomerUserFactory.create( - user_id=self.user_1.id, enterprise_customer__enable_learner_portal=True - ) - completed_course = CourseFactory.create() self.completed_course_run = CourseRunFactory(key=str(completed_course.id)) user_1_in_progress_course = CourseFactory.create() @@ -108,7 +113,6 @@ def enroll_user(self, user, course, create_grade=False): @patch('common.djangoapps.student.models.course_enrollment.segment.track') @patch('lms.djangoapps.program_enrollments.management.commands.send_program_course_nudge_email.get_programs') @patch('lms.djangoapps.certificates.api.certificates_viewable_for_course', return_value=True) - @override_settings(FEATURES=dict(ENABLE_ENTERPRISE_INTEGRATION=True)) def test_email_send(self, add_no_commit, __, get_programs_mock, mock_track): """ Test Segment fired as expected. @@ -145,7 +149,6 @@ def test_email_send(self, add_no_commit, __, get_programs_mock, mock_track): ) @patch('common.djangoapps.student.models.course_enrollment.segment.track') @patch('lms.djangoapps.program_enrollments.management.commands.send_program_course_nudge_email.get_programs') - @override_settings(FEATURES=dict(ENABLE_ENTERPRISE_INTEGRATION=True)) def test_email_no_course_recommendation(self, add_no_commit, get_programs_mock, mock_track): """ Test Segment fired as expected. @@ -164,3 +167,37 @@ def test_email_no_course_recommendation(self, add_no_commit, get_programs_mock, ) ) assert mock_track.call_count == 0 + + @ddt.data( + { + 'override_setting': (), + 'expected_url_template': '{default_url}', + }, + { + 'override_setting': f'{TEST_MODULE_PATH}.replacement_suggested_course_url', + 'expected_url_template': 'https://example.com/{username}/{course_key}', + }, + ) + @ddt.unpack + @patch('common.djangoapps.student.models.course_enrollment.segment.track') + @patch('lms.djangoapps.program_enrollments.management.commands.send_program_course_nudge_email.get_programs') + @patch('lms.djangoapps.certificates.api.certificates_viewable_for_course', return_value=True) + def test_suggested_course_url(self, __, get_programs_mock, mock_track, override_setting, expected_url_template): + """ + Test the suggested course link, with no override configured and with one that replaces it. + """ + get_programs_mock.return_value = [self.partially_completed_program_1] + with override_settings(OVERRIDE_PROGRAM_NUDGE_SUGGESTED_COURSE_URL=override_setting): + call_command(self.command) + + default_url = urljoin(settings.MKTG_URLS.get('ROOT'), self.not_started_course_run_1['marketing_url']) + course_key = self.catalog_not_started_course_1['key'] + tracked_urls = {call.args[0]: call.args[2]['COURSE_TWO_LINK'] for call in mock_track.call_args_list} + assert tracked_urls == { + user.id: expected_url_template.format( + default_url=default_url, + username=user.username, + course_key=course_key, + ) + for user in (self.user_1, self.user_2) + }