From 284e64d1f1ec7d91144200a7336a28320c353a05 Mon Sep 17 00:00:00 2001 From: Syed Sajjad Hussain Shah <52817156+syedsajjadkazmii@users.noreply.github.com> Date: Fri, 3 Feb 2023 16:40:22 +0500 Subject: [PATCH] feat: add eventing to personalized recommendations (#31703) VAN-1261 --- .../api/v0/tests/test_views.py | 2 ++ .../learner_dashboard/api/v0/views.py | 1 + .../learner_home/recommendations/views.py | 1 + .../learner_recommendations/serializers.py | 2 +- .../tests/test_views.py | 8 +++++- .../learner_recommendations/views.py | 28 ++++++++++++++++++- .../RecommendationsPanel.jsx | 1 + 7 files changed, 40 insertions(+), 3 deletions(-) diff --git a/lms/djangoapps/learner_dashboard/api/v0/tests/test_views.py b/lms/djangoapps/learner_dashboard/api/v0/tests/test_views.py index 883407f32f..f15593e0f2 100644 --- a/lms/djangoapps/learner_dashboard/api/v0/tests/test_views.py +++ b/lms/djangoapps/learner_dashboard/api/v0/tests/test_views.py @@ -361,6 +361,7 @@ class TestCourseRecommendationApiView(TestCase): "is_control": False, "amplitude_recommendations": False, "course_key_array": self.general_recommendation_courses, + "page": "dashboard", }, ) @@ -400,6 +401,7 @@ class TestCourseRecommendationApiView(TestCase): "amplitude_recommendations": True, "course_key_array": [course.get("key") for course in self._get_filtered_courses()[:expected_recommendations]], + "page": "dashboard", }, ) diff --git a/lms/djangoapps/learner_dashboard/api/v0/views.py b/lms/djangoapps/learner_dashboard/api/v0/views.py index b1eedf1214..171398ed35 100644 --- a/lms/djangoapps/learner_dashboard/api/v0/views.py +++ b/lms/djangoapps/learner_dashboard/api/v0/views.py @@ -385,6 +385,7 @@ class CourseRecommendationApiView(APIView): "course_key_array": [ course["course_key"] for course in recommended_courses ], + "page": "dashboard", }, ) diff --git a/lms/djangoapps/learner_home/recommendations/views.py b/lms/djangoapps/learner_home/recommendations/views.py index ef98618063..e32fd4bbbc 100644 --- a/lms/djangoapps/learner_home/recommendations/views.py +++ b/lms/djangoapps/learner_home/recommendations/views.py @@ -97,6 +97,7 @@ class CourseRecommendationApiView(APIView): "is_control": is_control, "amplitude_recommendations": amplitude_recommendations, "course_key_array": [course["course_key"] for course in recommended_courses], + "page": "dashboard", }, ) diff --git a/lms/djangoapps/learner_recommendations/serializers.py b/lms/djangoapps/learner_recommendations/serializers.py index c257ceae97..14b21de6cb 100644 --- a/lms/djangoapps/learner_recommendations/serializers.py +++ b/lms/djangoapps/learner_recommendations/serializers.py @@ -24,7 +24,7 @@ class CourseImageSerializer(serializers.Serializer): class RecommendedCourseSerializer(serializers.Serializer): """Serializer for a recommended course from the recommendation engine""" - + key = serializers.CharField() uuid = serializers.UUIDField() title = serializers.CharField() image = CourseImageSerializer() diff --git a/lms/djangoapps/learner_recommendations/tests/test_views.py b/lms/djangoapps/learner_recommendations/tests/test_views.py index a6fb946b7c..b8f75212d2 100644 --- a/lms/djangoapps/learner_recommendations/tests/test_views.py +++ b/lms/djangoapps/learner_recommendations/tests/test_views.py @@ -235,12 +235,13 @@ class TestAmplitudeRecommendationsView(APITestCase): self.assertEqual(response.status_code, 404) self.assertEqual(response.data, None) + @mock.patch("lms.djangoapps.learner_dashboard.api.v0.views.segment.track") @mock.patch( "lms.djangoapps.learner_recommendations.views.get_amplitude_course_recommendations" ) @mock.patch("lms.djangoapps.learner_recommendations.views.filter_recommended_courses") def test_successful_response( - self, filter_recommended_courses_mock, get_amplitude_course_recommendations_mock + self, filter_recommended_courses_mock, get_amplitude_course_recommendations_mock, segment_mock, ): """ Verify API returns course recommendations. @@ -252,6 +253,7 @@ class TestAmplitudeRecommendationsView(APITestCase): True, self.recommended_courses, ] + segment_mock.return_value = None response = self.client.get(self.url) response_content = json.loads(response.content) @@ -261,3 +263,7 @@ class TestAmplitudeRecommendationsView(APITestCase): self.assertEqual( len(response_content.get("courses")), expected_recommendations_length ) + + # Verify that the segment event was fired + assert segment_mock.call_count == 1 + assert segment_mock.call_args[0][1] == "edx.bi.user.recommendations.viewed" diff --git a/lms/djangoapps/learner_recommendations/views.py b/lms/djangoapps/learner_recommendations/views.py index cd5ea52bb6..4ad01d0ff4 100644 --- a/lms/djangoapps/learner_recommendations/views.py +++ b/lms/djangoapps/learner_recommendations/views.py @@ -15,6 +15,7 @@ from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework.views import APIView +from common.djangoapps.track import segment from openedx.core.djangoapps.catalog.utils import ( get_course_data, get_course_run_details, @@ -75,6 +76,27 @@ class AmplitudeRecommendationsView(APIView): recommendations_count = 4 + def _emit_recommendations_viewed_event( + self, + user_id, + is_control, + recommended_courses, + amplitude_recommendations=True, + ): + """Emits an event to track recommendation experiment views.""" + segment.track( + user_id, + "edx.bi.user.recommendations.viewed", + { + "is_control": is_control, + "amplitude_recommendations": amplitude_recommendations, + "course_key_array": [ + course["key"] for course in recommended_courses + ], + "page": "course_about_page", + }, + ) + def get(self, request, course_id): """ Returns @@ -105,7 +127,7 @@ class AmplitudeRecommendationsView(APIView): ip_address = get_client_ip(request)[0] user_country_code = country_code_from_ip(ip_address).upper() filtered_courses = filter_recommended_courses( - request.user, course_keys, user_country_code=user_country_code, request_course=course_key, + user, course_keys, user_country_code=user_country_code, request_course=course_key, ) for course in filtered_courses: @@ -119,6 +141,10 @@ class AmplitudeRecommendationsView(APIView): if len(recommended_courses) == self.recommendations_count: break + self._emit_recommendations_viewed_event( + user.id, is_control, recommended_courses + ) + return Response( RecommendationsSerializer( { diff --git a/lms/static/js/learner_dashboard/RecommendationsPanel.jsx b/lms/static/js/learner_dashboard/RecommendationsPanel.jsx index 7080324168..2d48c1fb8b 100644 --- a/lms/static/js/learner_dashboard/RecommendationsPanel.jsx +++ b/lms/static/js/learner_dashboard/RecommendationsPanel.jsx @@ -20,6 +20,7 @@ class RecommendationsPanel extends React.Component { window.analytics.track('edx.bi.user.recommended.course.click', { course_key: courseKey, is_control: this.state.isControl, + page: 'dashboard', }); let recommendedCourses = Cookies.get(this.cookieName);