feat: implement fallback recommendations (#758)
This commit is contained in:
@@ -20,6 +20,7 @@ const configuration = {
|
||||
TOS_AND_HONOR_CODE: process.env.TOS_AND_HONOR_CODE || null,
|
||||
TOS_LINK: process.env.TOS_LINK || null,
|
||||
// Miscellaneous
|
||||
GENERAL_RECOMMENDATIONS: process.env.GENERAL_RECOMMENDATIONS || [],
|
||||
INFO_EMAIL: process.env.INFO_EMAIL || '',
|
||||
};
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ const RecommendationCard = (props) => {
|
||||
position + 1,
|
||||
userId,
|
||||
recommendation.marketingUrl,
|
||||
recommendation.recommendationType || 'algolia',
|
||||
);
|
||||
};
|
||||
|
||||
@@ -71,6 +72,7 @@ RecommendationCard.propTypes = {
|
||||
logoImageUrl: PropTypes.string.isRequired,
|
||||
})),
|
||||
marketingUrl: PropTypes.string.isRequired,
|
||||
recommendationType: PropTypes.string,
|
||||
}).isRequired,
|
||||
position: PropTypes.number.isRequired,
|
||||
userId: PropTypes.number,
|
||||
|
||||
@@ -43,6 +43,7 @@ RecommendationsList.propTypes = {
|
||||
logoImageUrl: PropTypes.string.isRequired,
|
||||
})),
|
||||
marketingUrl: PropTypes.string.isRequired,
|
||||
recommendationType: PropTypes.string,
|
||||
})),
|
||||
userId: PropTypes.number,
|
||||
};
|
||||
|
||||
@@ -29,14 +29,26 @@ const RecommendationsPage = (props) => {
|
||||
if (registrationResponse) {
|
||||
let coursesWithKeys = [];
|
||||
getPersonalizedRecommendations(educationLevel).then((response) => {
|
||||
if (response.length) {
|
||||
coursesWithKeys = response.map(course => ({
|
||||
...course,
|
||||
courseKey: convertCourseRunKeytoCourseKey(course.activeRunKey),
|
||||
}));
|
||||
coursesWithKeys = response.map(course => ({
|
||||
...course,
|
||||
courseKey: convertCourseRunKeytoCourseKey(course.activeRunKey),
|
||||
}));
|
||||
|
||||
if (coursesWithKeys.length >= RECOMMENDATIONS_COUNT) {
|
||||
setRecommendations(coursesWithKeys.slice(0, RECOMMENDATIONS_COUNT));
|
||||
} else {
|
||||
const courseRecommendations = coursesWithKeys.concat(getConfig().GENERAL_RECOMMENDATIONS);
|
||||
// Remove duplicate recommendations
|
||||
const uniqueRecommendations = courseRecommendations.filter(
|
||||
(recommendation, index, self) => index === self.findIndex((existingRecommendation) => (
|
||||
existingRecommendation.courseKey === recommendation.courseKey
|
||||
)),
|
||||
);
|
||||
setRecommendations(uniqueRecommendations.slice(0, RECOMMENDATIONS_COUNT));
|
||||
}
|
||||
|
||||
setIsLoading(false);
|
||||
// We only want to track the recommendations returned by Algolia
|
||||
const courseKeys = coursesWithKeys.map(course => course.courseKey);
|
||||
trackRecommendationsViewed(courseKeys, false, userId);
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { getConfig, mergeConfig } from '@edx/frontend-platform';
|
||||
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { mount } from 'enzyme';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
@@ -10,7 +10,7 @@ import configureStore from 'redux-mock-store';
|
||||
import { DEFAULT_REDIRECT_URL } from '../../data/constants';
|
||||
import * as getPersonalizedRecommendations from '../data/service';
|
||||
import RecommendationsPage from '../RecommendationsPage';
|
||||
import mockedResponse from './mockedData';
|
||||
import { mockedGeneralRecommendations, mockedResponse } from './mockedData';
|
||||
|
||||
const IntlRecommendationsPage = injectIntl(RecommendationsPage);
|
||||
const mockStore = configureStore();
|
||||
@@ -100,6 +100,16 @@ describe('RecommendationsPageTests', () => {
|
||||
expect(window.location.href).toEqual(registrationResult.redirectUrl);
|
||||
});
|
||||
|
||||
it('should not redirect if fallback recommendations are enabled', async () => {
|
||||
mergeConfig({
|
||||
GENERAL_RECOMMENDATIONS: mockedGeneralRecommendations,
|
||||
});
|
||||
getPersonalizedRecommendations.default = jest.fn().mockImplementation(() => Promise.resolve([]));
|
||||
const recommendationsPage = await getRecommendationsPage();
|
||||
|
||||
expect(recommendationsPage.find('#course-recommendations').exists()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should display all owners for a course', async () => {
|
||||
getPersonalizedRecommendations.default = jest.fn().mockImplementation(() => Promise.resolve(mockedResponse));
|
||||
const recommendationsPage = await getRecommendationsPage();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const mockedResponse = [
|
||||
export const mockedResponse = [
|
||||
{
|
||||
title: 'How to Learn Online 1',
|
||||
marketingUrl: 'https://test-recommendations.com/course/how-to-learn-online-1',
|
||||
@@ -76,4 +76,85 @@ const mockedResponse = [
|
||||
},
|
||||
];
|
||||
|
||||
export default mockedResponse;
|
||||
export const mockedGeneralRecommendations = [
|
||||
{
|
||||
courseKey: 'MITx+6.00.1x',
|
||||
activeRunKey: 'course-v1:MITx+6.00.1x+1T2023',
|
||||
cardImageUrl: 'https://prod-discovery.edx-cdn.org/media/course/image/956319ec-8665-4039-8bc6-32c9a9aea5e9-885268c71902.jpg',
|
||||
marketingUrl: 'https://www.edx.org/course/introduction-to-computer-science-and-programming-7',
|
||||
objectId: 'course-956319ec-8665-4039-8bc6-32c9a9aea5e9',
|
||||
owners: [
|
||||
{
|
||||
key: 'MITx',
|
||||
logoImageUrl: 'https://prod-discovery.edx-cdn.org/organization/logos/2a73d2ce-c34a-4e08-8223-83bca9d2f01d-2cc8854c6fee.png',
|
||||
name: 'Massachusetts Institute of Technology',
|
||||
},
|
||||
],
|
||||
title: 'Introduction to Computer Science and Programming Using Python',
|
||||
recommendationType: 'general',
|
||||
},
|
||||
{
|
||||
courseKey: 'IBM+PY0101EN',
|
||||
activeRunKey: 'course-v1:IBM+PY0101EN+2T2021',
|
||||
cardImageUrl: 'https://prod-discovery.edx-cdn.org/media/course/image/381a0046-5d78-4790-8776-74620d59f48e-e2e7f4677ce2.jpeg',
|
||||
marketingUrl: 'https://www.edx.org/course/python-basics-for-data-science',
|
||||
objectID: 'course-381a0046-5d78-4790-8776-74620d59f48e',
|
||||
owners: [
|
||||
{
|
||||
key: 'IBM',
|
||||
logoImageUrl: 'https://prod-discovery.edx-cdn.org/organization/logos/87b07564-d569-4cfd-bee6-8b0a407acb73-dc33e4b5f353.png',
|
||||
name: 'IBM',
|
||||
},
|
||||
],
|
||||
title: 'Python Basics for Data Science',
|
||||
recommendationType: 'general',
|
||||
},
|
||||
{
|
||||
courseKey: 'HarvardX+CS50P',
|
||||
activeRunKey: 'course-v1:HarvardX+CS50P+Python',
|
||||
cardImageUrl: 'https://prod-discovery.edx-cdn.org/media/course/image/2cc794d0-316d-42f7-bbfd-25c34e4cd5df-033e46d516c0.png',
|
||||
marketingUrl: 'https://www.edx.org/course/cs50s-introduction-to-programming-with-python',
|
||||
objectID: 'course-2cc794d0-316d-42f7-bbfd-25c34e4cd5df',
|
||||
owners: [
|
||||
{
|
||||
key: 'HarvardX',
|
||||
logoImageUrl: 'https://prod-discovery.edx-cdn.org/organization/logos/44022f13-20df-4666-9111-cede3e5dc5b6-2cc39992c67a.png',
|
||||
name: 'Harvard University',
|
||||
},
|
||||
],
|
||||
title: 'CS50\'s Introduction to Programming with Python',
|
||||
recommendationType: 'general',
|
||||
},
|
||||
{
|
||||
courseKey: 'UQx+IELTSx',
|
||||
activeRunKey: 'course-v1:UQx+IELTSx+1T2022',
|
||||
cardImageUrl: 'https://prod-discovery.edx-cdn.org/media/course/image/d61d7a1f-3333-4169-a786-92e2bf690c6f-fa8a6909baec.jpg',
|
||||
marketingUrl: 'https://www.edx.org/course/ielts-academic-test-preparation',
|
||||
objectID: 'course-d61d7a1f-3333-4169-a786-92e2bf690c6f',
|
||||
owners: [
|
||||
{
|
||||
key: 'UQx',
|
||||
logoImageUrl: 'https://prod-discovery.edx-cdn.org/organization/logos/8554749f-b920-4d7f-8986-af6bb95290aa-f336c6a2ca11.png',
|
||||
name: 'The University of Queensland',
|
||||
},
|
||||
],
|
||||
title: 'IELTS Academic Test Preparation',
|
||||
recommendationType: 'general',
|
||||
},
|
||||
{
|
||||
courseKey: 'HarvardX+CS50x',
|
||||
activeRunKey: 'course-v1:HarvardX+CS50+X',
|
||||
cardImageUrl: 'https://prod-discovery.edx-cdn.org/media/course/image/da1b2400-322b-459b-97b0-0c557f05d017-a3d1899c3344.png',
|
||||
marketingUrl: 'https://www.edx.org/course/introduction-computer-science-harvardx-cs50x',
|
||||
objectID: 'course-da1b2400-322b-459b-97b0-0c557f05d017',
|
||||
owners: [
|
||||
{
|
||||
key: 'HarvardX',
|
||||
logoImageUrl: 'https://prod-discovery.edx-cdn.org/organization/logos/44022f13-20df-4666-9111-cede3e5dc5b6-2cc39992c67a.png',
|
||||
name: 'Harvard University',
|
||||
},
|
||||
],
|
||||
title: 'CS50\'s Introduction to Computer Science',
|
||||
recommendationType: 'general',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -16,11 +16,12 @@ export const createLinkTracker = (tracker, href, openInNewTab = false) => (e) =>
|
||||
return setTimeout(() => { global.location.href = href; }, LINK_TIMEOUT);
|
||||
};
|
||||
|
||||
export const trackRecommendationsClicked = (courseKey, isControl, position, userId, href) => {
|
||||
export const trackRecommendationsClicked = (courseKey, isControl, position, userId, href, recommendationType) => {
|
||||
createLinkTracker(
|
||||
sendTrackEvent(eventNames.recommendedCourseClicked, {
|
||||
page: 'authn_recommendations',
|
||||
position,
|
||||
recommendation_type: recommendationType,
|
||||
course_key: courseKey,
|
||||
is_control: isControl,
|
||||
user_id: userId,
|
||||
|
||||
Reference in New Issue
Block a user