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