revert: remove algolia API endpoint and unit tests from lms (#31868)
This commit is contained in:
@@ -14,129 +14,6 @@ from lms.djangoapps.learner_recommendations.toggles import (
|
||||
)
|
||||
|
||||
|
||||
class TestAlgoliaCoursesSearchView(APITestCase):
|
||||
"""Unit tests for the Algolia courses recommendation."""
|
||||
|
||||
password = "test"
|
||||
view_url = reverse_lazy(
|
||||
"learner_recommendations:algolia_courses",
|
||||
kwargs={'course_id': 'course-v1:test+TestX+Test_Course'}
|
||||
)
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.user = UserFactory()
|
||||
self.expected_courses_recommendation = {
|
||||
"hits": [
|
||||
{
|
||||
"availability": ["Available now"],
|
||||
"level": ["Introductory"],
|
||||
"marketing_url": "https://marketing-site.com/course/monsters-anatomy-101",
|
||||
"card_image_url": "https://card-site.com/course/monsters-anatomy-101",
|
||||
"active_run_key": "course-v1:test+TestX+Test_Course_1",
|
||||
"skills": [{"skill": "skill_1"}, {"skill": "skill_2"}],
|
||||
},
|
||||
{
|
||||
"availability": ["Available now"],
|
||||
"level": ["Intermediate"],
|
||||
"marketing_url": "https://marketing-site.com/course/monsters-anatomy-101",
|
||||
"card_image_url": "https://card-site.com/course/monsters-anatomy-101",
|
||||
"active_run_key": "course-v1:test+TestX+Test_Course_2",
|
||||
"skills": [{"skill": "skill_1"}, {"skill": "skill_2"}],
|
||||
}
|
||||
],
|
||||
"nbHits": 2
|
||||
}
|
||||
|
||||
def test_unauthenticated_request(self):
|
||||
"""
|
||||
Test unauthenticated request to Algolia courses recommendation API view.
|
||||
"""
|
||||
|
||||
response = self.client.get(self.view_url)
|
||||
self.assertEqual(response.status_code, 401)
|
||||
|
||||
@mock.patch(
|
||||
"lms.djangoapps.learner_recommendations.views.get_course_data"
|
||||
)
|
||||
@mock.patch(
|
||||
"lms.djangoapps.learner_recommendations.views.get_course_run_details"
|
||||
)
|
||||
def test_no_course_data(
|
||||
self,
|
||||
mocked_get_course_run_details,
|
||||
mocked_get_course_data
|
||||
):
|
||||
"""
|
||||
Verify API returns empty response if no course data found.
|
||||
"""
|
||||
mocked_get_course_run_details.return_value = {"course": "edX+DemoX"}
|
||||
mocked_get_course_data.return_value = None
|
||||
|
||||
self.client.login(username=self.user.username, password=self.password)
|
||||
response = self.client.get(self.view_url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
response_content = json.loads(response.content)
|
||||
self.assertEqual(response_content.get("courses"), [])
|
||||
self.assertEqual(response_content.get("count"), 0)
|
||||
|
||||
@mock.patch(
|
||||
"lms.djangoapps.learner_recommendations.views.get_course_data"
|
||||
)
|
||||
@mock.patch(
|
||||
"lms.djangoapps.learner_recommendations.views.get_course_run_details"
|
||||
)
|
||||
def test_no_course_skill_names(
|
||||
self,
|
||||
mocked_get_course_run_details,
|
||||
mocked_get_course_data
|
||||
):
|
||||
"""
|
||||
Verify API returns empty response if no course skill_names found.
|
||||
"""
|
||||
mocked_get_course_run_details.return_value = {"course": "edX+DemoX"}
|
||||
mocked_get_course_data.return_value = {"level_type": "Advanced", "skill_names": []}
|
||||
|
||||
self.client.login(username=self.user.username, password=self.password)
|
||||
response = self.client.get(self.view_url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
response_content = json.loads(response.content)
|
||||
self.assertEqual(response_content.get("courses"), [])
|
||||
self.assertEqual(response_content.get("count"), 0)
|
||||
|
||||
@mock.patch(
|
||||
"lms.djangoapps.learner_recommendations.views.get_algolia_courses_recommendation"
|
||||
)
|
||||
@mock.patch(
|
||||
"lms.djangoapps.learner_recommendations.views.get_course_run_details"
|
||||
)
|
||||
@mock.patch(
|
||||
"lms.djangoapps.learner_recommendations.views.get_course_data"
|
||||
)
|
||||
def test_recommendations(
|
||||
self,
|
||||
mocked_get_course_data,
|
||||
mocked_get_course_run_details,
|
||||
mocked_get_algolia_courses_recommendation
|
||||
):
|
||||
"""
|
||||
Verify API response structure.
|
||||
"""
|
||||
mocked_get_course_run_details.return_value = {"course": "edX+DemoX"}
|
||||
mocked_get_course_data.return_value = {"level_type": "Advanced", "skill_names": ["skill_1", "skill_2"]}
|
||||
mocked_get_algolia_courses_recommendation.return_value = self.expected_courses_recommendation
|
||||
|
||||
self.client.login(username=self.user.username, password=self.password)
|
||||
response = self.client.get(self.view_url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
response_content = json.loads(response.content)
|
||||
self.assertEqual(response_content.get("courses"), self.expected_courses_recommendation["hits"])
|
||||
self.assertEqual(response_content.get("count"), self.expected_courses_recommendation["nbHits"])
|
||||
|
||||
|
||||
@override_waffle_flag(ENABLE_COURSE_ABOUT_PAGE_RECOMMENDATIONS, active=True)
|
||||
class TestAmplitudeRecommendationsView(APITestCase):
|
||||
"""Unit tests for the Amplitude recommendations API"""
|
||||
|
||||
@@ -10,9 +10,6 @@ from lms.djangoapps.learner_recommendations import views
|
||||
app_name = "learner_recommendations"
|
||||
|
||||
urlpatterns = [
|
||||
re_path(fr'^algolia/courses/{settings.COURSE_ID_PATTERN}/$',
|
||||
views.AlgoliaCoursesSearchView.as_view(),
|
||||
name='algolia_courses'),
|
||||
re_path(fr'^amplitude/{settings.COURSE_ID_PATTERN}/$',
|
||||
views.AmplitudeRecommendationsView.as_view(),
|
||||
name='amplitude_recommendations'),
|
||||
|
||||
@@ -4,7 +4,6 @@ Additional utilities for Learner Recommendations.
|
||||
import logging
|
||||
import requests
|
||||
|
||||
from algoliasearch.exceptions import RequestException, AlgoliaUnreachableHostException
|
||||
from algoliasearch.search_client import SearchClient
|
||||
from django.conf import settings
|
||||
|
||||
@@ -114,48 +113,6 @@ def _get_program_duration(weeks):
|
||||
return f'{total_years} years {total_remainder_months} months'
|
||||
|
||||
|
||||
def get_algolia_courses_recommendation(course_data):
|
||||
"""
|
||||
Get courses recommendation from Algolia search.
|
||||
|
||||
Args:
|
||||
course_data (dict): Course data to create the search query.
|
||||
|
||||
Returns:
|
||||
Response object with courses recommendation from Algolia search.
|
||||
"""
|
||||
algolia_client = AlgoliaClient.get_algolia_client()
|
||||
|
||||
search_query = " ".join(course_data["skill_names"])
|
||||
searchable_course_levels = [
|
||||
f"level:{course_level}"
|
||||
for course_level in COURSE_LEVELS
|
||||
if course_level != course_data["level_type"]
|
||||
]
|
||||
if algolia_client and search_query:
|
||||
algolia_index = algolia_client.init_index(settings.ALGOLIA_COURSES_RECOMMENDATION_INDEX_NAME)
|
||||
try:
|
||||
# Algolia search filter criteria:
|
||||
# - Product type: Course
|
||||
# - Courses are available (enrollable)
|
||||
# - Courses should not have the same course level as the current course
|
||||
# - Exclude current course from the results
|
||||
results = algolia_index.search(
|
||||
search_query,
|
||||
{
|
||||
"filters": f"NOT active_run_key:'{course_data['key']}'",
|
||||
"facetFilters": ["availability:Available now", "product:Course", searchable_course_levels],
|
||||
"optionalWords": f"{search_query}",
|
||||
}
|
||||
)
|
||||
|
||||
return results
|
||||
except (AlgoliaUnreachableHostException, RequestException) as ex:
|
||||
log.warning(f"Unexpected exception while attempting to fetch courses data from Algolia: {str(ex)}")
|
||||
|
||||
return {}
|
||||
|
||||
|
||||
def get_amplitude_course_recommendations(user_id, recommendation_id):
|
||||
"""
|
||||
Get personalized recommendations from Amplitude.
|
||||
|
||||
@@ -16,15 +16,10 @@ 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,
|
||||
)
|
||||
from openedx.core.djangoapps.geoinfo.api import country_code_from_ip
|
||||
from openedx.features.enterprise_support.utils import is_enterprise_learner
|
||||
from lms.djangoapps.learner_recommendations.toggles import enable_course_about_page_recommendations
|
||||
from lms.djangoapps.learner_recommendations.utils import (
|
||||
get_algolia_courses_recommendation,
|
||||
get_amplitude_course_recommendations,
|
||||
filter_recommended_courses,
|
||||
)
|
||||
@@ -34,35 +29,6 @@ from lms.djangoapps.learner_recommendations.serializers import RecommendationsSe
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AlgoliaCoursesSearchView(APIView):
|
||||
"""
|
||||
**Example Request**
|
||||
|
||||
GET api/learner_recommendations/algolia/courses/{course_id}/
|
||||
"""
|
||||
|
||||
authentication_classes = (JwtAuthentication, SessionAuthenticationAllowInactiveUser,)
|
||||
permission_classes = (IsAuthenticated,)
|
||||
|
||||
def get(self, request, course_id):
|
||||
""" Retrieves course recommendations from Algolia based on course skills. """
|
||||
|
||||
course_run_data = get_course_run_details(course_id, ["course"])
|
||||
course_key_str = course_run_data.get("course", None)
|
||||
|
||||
# Fetching course level type and skills from discovery service.
|
||||
course_data = get_course_data(course_key_str, ["level_type", "skill_names"])
|
||||
|
||||
# If discovery service fails to fetch data, we will not run recommendations engine.
|
||||
if not course_data:
|
||||
return Response({"courses": [], "count": 0}, status=200)
|
||||
|
||||
course_data["key"] = course_id
|
||||
response = get_algolia_courses_recommendation(course_data)
|
||||
|
||||
return Response({"courses": response.get("hits", []), "count": response.get("nbHits", 0)}, status=200)
|
||||
|
||||
|
||||
class AmplitudeRecommendationsView(APIView):
|
||||
"""
|
||||
**Example Request**
|
||||
|
||||
Reference in New Issue
Block a user