From e0efd9bbf23538d97a3f0237a317610a47b27c1f Mon Sep 17 00:00:00 2001
From: RehanAziz <rehanaziz@A006-00394.local>
Date: Thu, 8 Jul 2021 10:58:24 +0500
Subject: [PATCH] feat: Added enterprise uuid in event context for enterprise
 enrolment events

---
 common/djangoapps/student/models.py             | 17 ++++++++++++-----
 common/djangoapps/track/contexts.py             |  9 +++++++--
 common/djangoapps/track/tests/test_contexts.py  |  4 ++--
 .../djangoapps/track/tests/test_middleware.py   |  8 +++++---
 .../djangoapps/track/views/tests/test_views.py  |  2 ++
 .../grades/tests/integration/test_events.py     |  6 +++++-
 openedx/core/djangoapps/enrollments/api.py      |  5 +++--
 openedx/core/djangoapps/enrollments/data.py     |  5 +++--
 .../enrollments/tests/fake_data_api.py          |  9 +++++----
 openedx/core/djangoapps/enrollments/views.py    |  3 ++-
 10 files changed, 46 insertions(+), 22 deletions(-)

diff --git a/common/djangoapps/student/models.py b/common/djangoapps/student/models.py
index c529df7c1d7..687a22a382c 100644
--- a/common/djangoapps/student/models.py
+++ b/common/djangoapps/student/models.py
@@ -1377,7 +1377,7 @@ class CourseEnrollment(models.Model):
         from openedx.core.djangoapps.enrollments.permissions import ENROLL_IN_COURSE
         return not user.has_perm(ENROLL_IN_COURSE, course)
 
-    def update_enrollment(self, mode=None, is_active=None, skip_refund=False):
+    def update_enrollment(self, mode=None, is_active=None, skip_refund=False, enterprise_uuid=None):
         """
         Updates an enrollment for a user in a class.  This includes options
         like changing the mode, toggling is_active True/False, etc.
@@ -1413,7 +1413,7 @@ class CourseEnrollment(models.Model):
 
         if activation_changed:
             if self.is_active:
-                self.emit_event(EVENT_NAME_ENROLLMENT_ACTIVATED)
+                self.emit_event(EVENT_NAME_ENROLLMENT_ACTIVATED, enterprise_uuid=enterprise_uuid)
             else:
                 UNENROLL_DONE.send(sender=None, course_enrollment=self, skip_refund=skip_refund)
                 self.emit_event(EVENT_NAME_ENROLLMENT_DEACTIVATED)
@@ -1457,7 +1457,7 @@ class CourseEnrollment(models.Model):
                                   mode=mode, course_id=course_id,
                                   cost=cost, currency=currency)
 
-    def emit_event(self, event_name):
+    def emit_event(self, event_name, enterprise_uuid=None):
         """
         Emits an event to explicitly track course enrollment and unenrollment.
         """
@@ -1465,12 +1465,17 @@ class CourseEnrollment(models.Model):
 
         try:
             context = contexts.course_context_from_course_id(self.course_id)
+            if enterprise_uuid:
+                context["enterprise_uuid"] = enterprise_uuid
             assert isinstance(self.course_id, CourseKey)
             data = {
                 'user_id': self.user.id,
                 'course_id': str(self.course_id),
                 'mode': self.mode,
             }
+            if enterprise_uuid and 'username' not in context:
+                data['username'] = self.user.username
+
             segment_properties = {
                 'category': 'conversion',
                 'label': str(self.course_id),
@@ -1511,7 +1516,7 @@ class CourseEnrollment(models.Model):
                 )
 
     @classmethod
-    def enroll(cls, user, course_key, mode=None, check_access=False, can_upgrade=False):
+    def enroll(cls, user, course_key, mode=None, check_access=False, can_upgrade=False, enterprise_uuid=None):
         """
         Enroll a user in a course. This saves immediately.
 
@@ -1540,6 +1545,8 @@ class CourseEnrollment(models.Model):
                 while selecting a session. The default is set to False to avoid
                 breaking the orignal course enroll code.
 
+        enterprise_uuid (str): Add course enterprise uuid
+
         Exceptions that can be raised: NonExistentCourseError,
         EnrollmentClosedError, CourseFullError, AlreadyEnrolledError.  All these
         are subclasses of CourseEnrollmentException if you want to catch all of
@@ -1590,7 +1597,7 @@ class CourseEnrollment(models.Model):
 
         # User is allowed to enroll if they've reached this point.
         enrollment = cls.get_or_create_enrollment(user, course_key)
-        enrollment.update_enrollment(is_active=True, mode=mode)
+        enrollment.update_enrollment(is_active=True, mode=mode, enterprise_uuid=enterprise_uuid)
         enrollment.send_signal(EnrollStatusChange.enroll)
 
         return enrollment
diff --git a/common/djangoapps/track/contexts.py b/common/djangoapps/track/contexts.py
index 6d4e9da3caf..531b62b385a 100644
--- a/common/djangoapps/track/contexts.py
+++ b/common/djangoapps/track/contexts.py
@@ -68,7 +68,8 @@ def context_dict_for_learning_context(context_key):
         {
             'context_id': 'course-v1:org+course+run',
             'course_id': 'course-v1:org+course+run',
-            'org_id': 'org'
+            'org_id': 'org',
+            'enterprise_uuid': 'enterprise_customer_uuid'
         }
 
     Example 2::
@@ -76,7 +77,8 @@ def context_dict_for_learning_context(context_key):
         {
             'context_id': 'lib:edX:a-content-library',
             'course_id': '',
-            'org_id': 'edX'
+            'org_id': 'edX',
+            'enterprise_uuid': '1a0fbcbe-49e5-42f1-8e83-4cddfa592f22'
         }
 
     """
@@ -84,6 +86,7 @@ def context_dict_for_learning_context(context_key):
         'context_id': str(context_key) if context_key else '',
         'course_id': '',
         'org_id': '',
+        'enterprise_uuid': '',
     }
     if context_key is not None:
         assert isinstance(context_key, LearningContextKey)
@@ -91,4 +94,6 @@ def context_dict_for_learning_context(context_key):
             context_dict['course_id'] = str(context_key)
         if hasattr(context_key, 'org'):
             context_dict['org_id'] = context_key.org
+        if hasattr(context_key, 'enterprise_uuid'):
+            context_dict['enterprise_uuid'] = context_key.enterprise_uuid
     return context_dict
diff --git a/common/djangoapps/track/tests/test_contexts.py b/common/djangoapps/track/tests/test_contexts.py
index f20423adc9d..e216c60fc01 100644
--- a/common/djangoapps/track/tests/test_contexts.py
+++ b/common/djangoapps/track/tests/test_contexts.py
@@ -28,13 +28,13 @@ class TestContexts(TestCase):  # lint-amnesty, pylint: disable=missing-class-doc
 
     def assert_parses_course_id_from_url(self, format_string, course_id):
         assert contexts.course_context_from_url(format_string.format(course_id=course_id)) ==\
-               {'course_id': course_id, 'org_id': self.ORG_ID}
+               {'course_id': course_id, 'org_id': self.ORG_ID, 'enterprise_uuid': ''}
 
     def test_no_course_id_in_url(self):
         self.assert_empty_context_for_url('http://foo.bar.com/dashboard')
 
     def assert_empty_context_for_url(self, url):
-        assert contexts.course_context_from_url(url) == {'course_id': '', 'org_id': ''}
+        assert contexts.course_context_from_url(url) == {'course_id': '', 'org_id': '', 'enterprise_uuid': ''}
 
     @ddt.data('', '/', '/?', '?format=json')
     def test_malformed_course_id(self, postfix):
diff --git a/common/djangoapps/track/tests/test_middleware.py b/common/djangoapps/track/tests/test_middleware.py
index 4f4096a7fa3..55c9d350962 100644
--- a/common/djangoapps/track/tests/test_middleware.py
+++ b/common/djangoapps/track/tests/test_middleware.py
@@ -74,9 +74,11 @@ class TrackMiddlewareTestCase(TestCase):
 
     def test_default_request_context(self):
         context = self.get_context_for_path('/courses/')
-        assert context == {'accept_language': '', 'referer': '', 'user_id': '', 'session': '', 'username': '',
-                           'ip': '127.0.0.1', 'host': 'testserver', 'agent': '', 'path': '/courses/', 'org_id': '',
-                           'course_id': '', 'client_id': None}
+        assert context == {
+            'accept_language': '', 'referer': '', 'user_id': '', 'session': '', 'username': '', 'ip': '127.0.0.1',
+            'host': 'testserver', 'agent': '', 'path': '/courses/', 'org_id': '', 'course_id': '', 'client_id': None,
+            'enterprise_uuid': ''
+        }
 
     def test_no_forward_for_header_ip_context(self):
         request = self.request_factory.get('/courses/')
diff --git a/common/djangoapps/track/views/tests/test_views.py b/common/djangoapps/track/views/tests/test_views.py
index f8c7ef363c4..2ffc7549085 100644
--- a/common/djangoapps/track/views/tests/test_views.py
+++ b/common/djangoapps/track/views/tests/test_views.py
@@ -238,6 +238,7 @@ class TestTrackViews(EventTrackingTestCase):  # lint-amnesty, pylint: disable=mi
                     'referer': '',
                     'client_id': None,
                     'course_id': 'foo/bar/baz',
+                    'enterprise_uuid': '',
                     'path': self.path_with_course,
                     'page': None
                 }
@@ -279,6 +280,7 @@ class TestTrackViews(EventTrackingTestCase):  # lint-amnesty, pylint: disable=mi
                     'referer': '',
                     'client_id': '1033501218.1368477899',
                     'course_id': 'foo/bar/baz',
+                    'enterprise_uuid': '',
                     'path': self.path_with_course,
                     'page': None
                 }
diff --git a/lms/djangoapps/grades/tests/integration/test_events.py b/lms/djangoapps/grades/tests/integration/test_events.py
index 139778ad681..eb99fdeea82 100644
--- a/lms/djangoapps/grades/tests/integration/test_events.py
+++ b/lms/djangoapps/grades/tests/integration/test_events.py
@@ -174,7 +174,11 @@ class GradesEventIntegrationTest(ProblemSubmissionTestMixin, SharedModuleStoreTe
 
         # make sure the tracker's context is updated with course info
         for args in events_tracker.get_tracker().context.call_args_list:
-            assert args[0][1] == {'course_id': str(self.course.id), 'org_id': str(self.course.org)}
+            assert args[0][1] == {
+                'course_id': str(self.course.id),
+                'enterprise_uuid': '',
+                'org_id': str(self.course.org)
+            }
 
         event_transaction_id = events_tracker.emit.mock_calls[0][1][1]['event_transaction_id']
         events_tracker.emit.assert_has_calls(
diff --git a/openedx/core/djangoapps/enrollments/api.py b/openedx/core/djangoapps/enrollments/api.py
index d2e39982c46..c0144b66c6f 100644
--- a/openedx/core/djangoapps/enrollments/api.py
+++ b/openedx/core/djangoapps/enrollments/api.py
@@ -146,7 +146,7 @@ def get_enrollment(username, course_id):
     return _data_api().get_course_enrollment(username, course_id)
 
 
-def add_enrollment(username, course_id, mode=None, is_active=True, enrollment_attributes=None):
+def add_enrollment(username, course_id, mode=None, is_active=True, enrollment_attributes=None, enterprise_uuid=None):
     """Enrolls a user in a course.
 
     Enrolls a user in a course. If the mode is not specified, this will default to `CourseMode.DEFAULT_MODE_SLUG`.
@@ -159,6 +159,7 @@ def add_enrollment(username, course_id, mode=None, is_active=True, enrollment_at
         is_active (boolean): Optional argument for making the new enrollment inactive. If not specified, is_active
             defaults to True.
         enrollment_attributes (list): Attributes to be set the enrollment.
+        enterprise_uuid (str): Add course enterprise uuid
 
     Returns:
         A serializable dictionary of the new course enrollment.
@@ -197,7 +198,7 @@ def add_enrollment(username, course_id, mode=None, is_active=True, enrollment_at
     if mode is None:
         mode = _default_course_mode(course_id)
     validate_course_mode(course_id, mode, is_active=is_active)
-    enrollment = _data_api().create_course_enrollment(username, course_id, mode, is_active)
+    enrollment = _data_api().create_course_enrollment(username, course_id, mode, is_active, enterprise_uuid)
 
     if enrollment_attributes is not None:
         set_enrollment_attributes(username, course_id, enrollment_attributes)
diff --git a/openedx/core/djangoapps/enrollments/data.py b/openedx/core/djangoapps/enrollments/data.py
index 457a03bdcb2..fbea06edffa 100644
--- a/openedx/core/djangoapps/enrollments/data.py
+++ b/openedx/core/djangoapps/enrollments/data.py
@@ -115,7 +115,7 @@ def get_user_enrollments(course_key):
     ).order_by('created')
 
 
-def create_course_enrollment(username, course_id, mode, is_active):
+def create_course_enrollment(username, course_id, mode, is_active, enterprise_uuid=None):
     """Create a new course enrollment for the given user.
 
     Creates a new course enrollment for the specified user username.
@@ -125,6 +125,7 @@ def create_course_enrollment(username, course_id, mode, is_active):
         course_id (str): The course to create the course enrollment for.
         mode (str): (Optional) The mode for the new enrollment.
         is_active (boolean): (Optional) Determines if the enrollment is active.
+        enterprise_uuid (str): Add course enterprise uuid
 
     Returns:
         A serializable dictionary representing the new course enrollment.
@@ -146,7 +147,7 @@ def create_course_enrollment(username, course_id, mode, is_active):
         raise UserNotFoundError(msg)  # lint-amnesty, pylint: disable=raise-missing-from
 
     try:
-        enrollment = CourseEnrollment.enroll(user, course_key, check_access=True)
+        enrollment = CourseEnrollment.enroll(user, course_key, check_access=True, enterprise_uuid=enterprise_uuid)
         return _update_enrollment(enrollment, is_active=is_active, mode=mode)
     except NonExistentCourseError as err:
         raise CourseNotFoundError(str(err))  # lint-amnesty, pylint: disable=raise-missing-from
diff --git a/openedx/core/djangoapps/enrollments/tests/fake_data_api.py b/openedx/core/djangoapps/enrollments/tests/fake_data_api.py
index 47ff051d836..afe972cb45c 100644
--- a/openedx/core/djangoapps/enrollments/tests/fake_data_api.py
+++ b/openedx/core/djangoapps/enrollments/tests/fake_data_api.py
@@ -36,9 +36,9 @@ def get_course_enrollment(student_id, course_id):
     return _get_fake_enrollment(student_id, course_id)
 
 
-def create_course_enrollment(student_id, course_id, mode='honor', is_active=True):
+def create_course_enrollment(student_id, course_id, mode='honor', is_active=True, enterprise_uuid=None):
     """Stubbed out Enrollment creation request. """
-    return add_enrollment(student_id, course_id, mode=mode, is_active=is_active)
+    return add_enrollment(student_id, course_id, mode=mode, is_active=is_active, enterprise_uuid=enterprise_uuid)
 
 
 def update_course_enrollment(student_id, course_id, mode=None, is_active=None):
@@ -74,14 +74,15 @@ def _get_fake_course_info(course_id, include_expired=False):
             return course
 
 
-def add_enrollment(student_id, course_id, is_active=True, mode='honor'):
+def add_enrollment(student_id, course_id, is_active=True, mode='honor', enterprise_uuid=None):
     """Append an enrollment to the enrollments array."""
     enrollment = {
         "created": datetime.datetime.now(),
         "mode": mode,
         "is_active": is_active,
         "course": _get_fake_course_info(course_id),
-        "student": student_id
+        "student": student_id,
+        "enterprise_uuid": enterprise_uuid
     }
     _ENROLLMENTS.append(enrollment)
     return enrollment
diff --git a/openedx/core/djangoapps/enrollments/views.py b/openedx/core/djangoapps/enrollments/views.py
index af48f244268..e1616e23e2a 100644
--- a/openedx/core/djangoapps/enrollments/views.py
+++ b/openedx/core/djangoapps/enrollments/views.py
@@ -800,7 +800,8 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn):
                     str(course_id),
                     mode=mode,
                     is_active=is_active,
-                    enrollment_attributes=enrollment_attributes
+                    enrollment_attributes=enrollment_attributes,
+                    enterprise_uuid=request.data.get('enterprise_uuid')
                 )
 
             cohort_name = request.data.get('cohort')
-- 
GitLab