From d6e4b37b13be9f2aed58c2fcb035eff217fcc79c Mon Sep 17 00:00:00 2001 From: Hamzah Ullah Date: Thu, 3 Sep 2026 12:10:13 -0400 Subject: [PATCH] feat: add SupportContactEnterpriseTagStep pipeline step MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds SupportContactEnterpriseTagStep, registered in ENTERPRISE_FILTERS_CONFIG against the new SupportContactContextRequested openedx-filter. Per pwnage101's review feedback, the current request is fetched internally via crum.get_current_request() rather than threaded through the filter/pipeline signature (matching courseware.py, dashboard.py, enrollment.py, logistration.py, course_modes.py). Also per review feedback, doesn't add a defensive None-check around the guarded enterprise_support import — the top-level try/except ImportError guard is sufficient, matching discounts.py/courseware.py, and the extra check would just need to be removed again once ENT-11576 drops the guarded import entirely. Follows the established pattern used by the other edx-enterprise filter pipeline steps: top-level imports guarded by try/except ImportError, no broad exception swallowing, and tests built on real Django model factories. ENT-11574 --- CHANGELOG.rst | 4 ++ enterprise/__init__.py | 2 +- enterprise/filters/support.py | 32 +++++++++++++++ enterprise/settings/common.py | 4 ++ tests/filters/test_support.py | 75 +++++++++++++++++++++++++++++++++++ 5 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 enterprise/filters/support.py create mode 100644 tests/filters/test_support.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 453bf349a..5f572eb90 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -17,6 +17,10 @@ Unreleased ---------- * nothing unreleased +[8.10.0] - 2026-09-08 +---------------------- +* feat: add SupportContactEnterpriseTagStep pipeline step (ENT-11574) + [8.9.4] - 2026-08-27 --------------------- * fix: add Django admin helper text for ``enable_demo_data_for_analytics_and_lpr`` explaining that diff --git a/enterprise/__init__.py b/enterprise/__init__.py index 3732f4830..b64484524 100644 --- a/enterprise/__init__.py +++ b/enterprise/__init__.py @@ -2,4 +2,4 @@ Your project description goes here. """ -__version__ = "8.9.4" +__version__ = "8.10.0" diff --git a/enterprise/filters/support.py b/enterprise/filters/support.py new file mode 100644 index 000000000..646d89a35 --- /dev/null +++ b/enterprise/filters/support.py @@ -0,0 +1,32 @@ +""" +Pipeline steps for the support views filters. +""" +from crum import get_current_request +from openedx_filters.filters import PipelineStep + +# This import will be replaced with an internal path in ENT-11576 when +# enterprise_support is migrated into edx-enterprise. +try: + from openedx.features.enterprise_support.api import enterprise_customer_for_request +except ImportError: + enterprise_customer_for_request = None + + +class SupportContactEnterpriseTagStep(PipelineStep): + """ + Append a support-ticket tag for linked customer-account requests. + + This step is intended to be registered as a pipeline step for the + ``org.openedx.learning.support.contact.context.requested.v1`` filter. + """ + + def run_filter(self, tags, user): # pylint: disable=arguments-differ + """ + Append 'enterprise_learner' to tags if the requester is linked to a customer account. + """ + request = get_current_request() + customer = enterprise_customer_for_request(request) + if customer and 'enterprise_learner' not in tags: + tags = [*tags, 'enterprise_learner'] + + return {'tags': tags, 'user': user} diff --git a/enterprise/settings/common.py b/enterprise/settings/common.py index fc429efca..855aa5008 100644 --- a/enterprise/settings/common.py +++ b/enterprise/settings/common.py @@ -32,6 +32,10 @@ "fail_silently": False, "pipeline": ["enterprise.filters.course_modes.CalculateEnterpriseDiscountedPrice"], }, + "org.openedx.learning.support.contact.context.requested.v1": { + "fail_silently": False, + "pipeline": ["enterprise.filters.support.SupportContactEnterpriseTagStep"], + }, # NOTE: Pipeline ordering matters here. ActiveEnterpriseCheckStep must run before # consent's DataSharingConsentCourseAccessStep to match the original platform behavior # (the incorrect-enterprise redirect took priority over the DSC redirect). This ordering diff --git a/tests/filters/test_support.py b/tests/filters/test_support.py new file mode 100644 index 000000000..ee222c8c5 --- /dev/null +++ b/tests/filters/test_support.py @@ -0,0 +1,75 @@ +""" +Tests for enterprise.filters.support pipeline steps. +""" +from unittest.mock import patch + +from django.test import RequestFactory, TestCase + +from enterprise.filters.support import SupportContactEnterpriseTagStep +from test_utils.factories import UserFactory + +CONTACT_FILTER_TYPE = "org.openedx.learning.support.contact.context.requested.v1" + + +class TestSupportContactEnterpriseTagStep(TestCase): + """ + Tests for SupportContactEnterpriseTagStep pipeline step. + """ + + def _make_step(self): + return SupportContactEnterpriseTagStep(CONTACT_FILTER_TYPE, []) + + def _make_request(self): + return RequestFactory().get('/') + + @patch('enterprise.filters.support.enterprise_customer_for_request') + @patch('enterprise.filters.support.get_current_request') + def test_appends_tag_for_linked_customer_request(self, mock_get_current_request, mock_customer_for_request): + """ + When the request is associated with a linked customer account, 'enterprise_learner' + is appended to the tags list. + """ + mock_customer_for_request.return_value = {'uuid': 'some-uuid', 'name': 'Test Customer'} + request = self._make_request() + mock_get_current_request.return_value = request + user = UserFactory() + tags = ['some_tag'] + + step = self._make_step() + result = step.run_filter(tags=tags, user=user) + + assert result['tags'] == ['some_tag', 'enterprise_learner'] + assert result['user'] is user + mock_customer_for_request.assert_called_once_with(request) + + @patch('enterprise.filters.support.enterprise_customer_for_request') + @patch('enterprise.filters.support.get_current_request') + def test_does_not_duplicate_tag(self, mock_get_current_request, mock_customer_for_request): + """ + When 'enterprise_learner' is already in the tags list, it is not duplicated. + """ + mock_customer_for_request.return_value = {'uuid': 'some-uuid', 'name': 'Test Customer'} + mock_get_current_request.return_value = self._make_request() + user = UserFactory() + tags = ['enterprise_learner'] + + step = self._make_step() + result = step.run_filter(tags=tags, user=user) + + assert result['tags'].count('enterprise_learner') == 1 + + @patch('enterprise.filters.support.enterprise_customer_for_request') + @patch('enterprise.filters.support.get_current_request') + def test_does_not_append_tag_for_unlinked_request(self, mock_get_current_request, mock_customer_for_request): + """ + When the request is not associated with a linked customer account, tags are unchanged. + """ + mock_customer_for_request.return_value = None + mock_get_current_request.return_value = self._make_request() + user = UserFactory() + tags = ['some_tag'] + + step = self._make_step() + result = step.run_filter(tags=tags, user=user) + + assert result['tags'] == ['some_tag']