feat: add support for fallback recs in case of error (#764)
* feat: add support for fallback recs in case of error * feat: update segment event
This commit is contained in:
@@ -20,7 +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 || [],
|
||||
GENERAL_RECOMMENDATIONS: process.env.GENERAL_RECOMMENDATIONS || '[]',
|
||||
INFO_EMAIL: process.env.INFO_EMAIL || '',
|
||||
};
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
Hyperlink, Image, Spinner, StatefulButton,
|
||||
} from '@edx/paragon';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Helmet } from 'react-helmet';
|
||||
|
||||
import { DEFAULT_REDIRECT_URL } from '../data/constants';
|
||||
import { EDUCATION_LEVEL_MAPPING, RECOMMENDATIONS_COUNT } from './data/constants';
|
||||
@@ -27,6 +28,7 @@ const RecommendationsPage = (props) => {
|
||||
|
||||
useEffect(() => {
|
||||
if (registrationResponse) {
|
||||
const generalRecommendations = JSON.parse(getConfig().GENERAL_RECOMMENDATIONS);
|
||||
let coursesWithKeys = [];
|
||||
getPersonalizedRecommendations(educationLevel).then((response) => {
|
||||
coursesWithKeys = response.map(course => ({
|
||||
@@ -37,7 +39,7 @@ const RecommendationsPage = (props) => {
|
||||
if (coursesWithKeys.length >= RECOMMENDATIONS_COUNT) {
|
||||
setRecommendations(coursesWithKeys.slice(0, RECOMMENDATIONS_COUNT));
|
||||
} else {
|
||||
const courseRecommendations = coursesWithKeys.concat(getConfig().GENERAL_RECOMMENDATIONS);
|
||||
const courseRecommendations = coursesWithKeys.concat(generalRecommendations);
|
||||
// Remove duplicate recommendations
|
||||
const uniqueRecommendations = courseRecommendations.filter(
|
||||
(recommendation, index, self) => index === self.findIndex((existingRecommendation) => (
|
||||
@@ -48,13 +50,14 @@ const RecommendationsPage = (props) => {
|
||||
}
|
||||
|
||||
setIsLoading(false);
|
||||
// We only want to track the recommendations returned by Algolia
|
||||
const courseKeys = coursesWithKeys.map(course => course.courseKey);
|
||||
trackRecommendationsViewed(courseKeys, false, userId);
|
||||
})
|
||||
.catch(() => {
|
||||
setRecommendations(generalRecommendations.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.slice(0, RECOMMENDATIONS_COUNT), false, userId);
|
||||
}
|
||||
}, [registrationResponse, DASHBOARD_URL, educationLevel, userId]);
|
||||
|
||||
@@ -82,37 +85,44 @@ const RecommendationsPage = (props) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="d-flex flex-column vh-100">
|
||||
<div className="mb-2">
|
||||
<div className="col-md-12 small-screen-top-stripe medium-screen-top-stripe extra-large-screen-top-stripe" />
|
||||
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
|
||||
<Image className="logo" alt={getConfig().SITE_NAME} src={getConfig().LOGO_URL} />
|
||||
</Hyperlink>
|
||||
</div>
|
||||
{(!isLoading && recommendations.length === RECOMMENDATIONS_COUNT) ? (
|
||||
<div className="d-flex flex-column align-items-center justify-content-center flex-grow-1 p-1">
|
||||
<RecommendationsList
|
||||
title={intl.formatMessage(messages['recommendation.page.heading'])}
|
||||
recommendations={recommendations}
|
||||
userId={userId}
|
||||
/>
|
||||
<div className="text-center">
|
||||
<StatefulButton
|
||||
className="font-weight-500"
|
||||
type="submit"
|
||||
variant="brand"
|
||||
labels={{
|
||||
default: intl.formatMessage(messages['recommendation.skip.button']),
|
||||
}}
|
||||
onClick={handleSkip}
|
||||
/>
|
||||
</div>
|
||||
<>
|
||||
<Helmet>
|
||||
<title>{intl.formatMessage(messages['recommendation.page.title'],
|
||||
{ siteName: getConfig().SITE_NAME })}
|
||||
</title>
|
||||
</Helmet>
|
||||
<div className="d-flex flex-column vh-100">
|
||||
<div className="mb-2">
|
||||
<div className="col-md-12 small-screen-top-stripe medium-screen-top-stripe extra-large-screen-top-stripe" />
|
||||
<Hyperlink destination={getConfig().MARKETING_SITE_BASE_URL}>
|
||||
<Image className="logo" alt={getConfig().SITE_NAME} src={getConfig().LOGO_URL} />
|
||||
</Hyperlink>
|
||||
</div>
|
||||
)
|
||||
: (
|
||||
<Spinner animation="border" variant="primary" className="centered-align-spinner" />
|
||||
)}
|
||||
</div>
|
||||
{(!isLoading && recommendations.length === RECOMMENDATIONS_COUNT) ? (
|
||||
<div className="d-flex flex-column align-items-center justify-content-center flex-grow-1 p-1">
|
||||
<RecommendationsList
|
||||
title={intl.formatMessage(messages['recommendation.page.heading'])}
|
||||
recommendations={recommendations}
|
||||
userId={userId}
|
||||
/>
|
||||
<div className="text-center">
|
||||
<StatefulButton
|
||||
className="font-weight-500"
|
||||
type="submit"
|
||||
variant="brand"
|
||||
labels={{
|
||||
default: intl.formatMessage(messages['recommendation.skip.button']),
|
||||
}}
|
||||
onClick={handleSkip}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
: (
|
||||
<Spinner animation="border" variant="primary" className="centered-align-spinner" />
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
const messages = defineMessages({
|
||||
'recommendation.page.title': {
|
||||
id: 'recommendation.page.title',
|
||||
defaultMessage: 'Recommendations| {siteName}',
|
||||
defaultMessage: 'Recommendations | {siteName}',
|
||||
description: 'recommendation page title',
|
||||
},
|
||||
'recommendation.page.heading': {
|
||||
|
||||
@@ -2,6 +2,7 @@ import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import { getConfig, mergeConfig } from '@edx/frontend-platform';
|
||||
import * as analytics from '@edx/frontend-platform/analytics';
|
||||
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { mount } from 'enzyme';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
@@ -15,9 +16,16 @@ import { mockedGeneralRecommendations, mockedResponse } from './mockedData';
|
||||
const IntlRecommendationsPage = injectIntl(RecommendationsPage);
|
||||
const mockStore = configureStore();
|
||||
|
||||
jest.mock('@edx/frontend-platform/analytics');
|
||||
jest.mock('../data/service');
|
||||
|
||||
analytics.sendTrackEvent = jest.fn();
|
||||
|
||||
describe('RecommendationsPageTests', () => {
|
||||
mergeConfig({
|
||||
GENERAL_RECOMMENDATIONS: '[]',
|
||||
});
|
||||
|
||||
let defaultProps = {};
|
||||
let store = {};
|
||||
|
||||
@@ -47,6 +55,7 @@ describe('RecommendationsPageTests', () => {
|
||||
location: {
|
||||
state: {
|
||||
registrationResult,
|
||||
userId: 111,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -81,6 +90,16 @@ describe('RecommendationsPageTests', () => {
|
||||
await getRecommendationsPage();
|
||||
|
||||
expect(getPersonalizedRecommendations.default).toHaveBeenCalledTimes(1);
|
||||
expect(analytics.sendTrackEvent).toHaveBeenCalledWith(
|
||||
'edx.bi.user.recommendations.viewed',
|
||||
{
|
||||
page: 'authn_recommendations',
|
||||
course_key_array: [],
|
||||
amplitude_recommendations: false,
|
||||
is_control: false,
|
||||
user_id: 111,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('should display recommendations returned by Algolia', async () => {
|
||||
|
||||
@@ -76,85 +76,4 @@ export const 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',
|
||||
},
|
||||
];
|
||||
export const mockedGeneralRecommendations = '[{"courseKey":"test+text1","activeRunKey":"course-v1:test+test1+2018","cardImageUrl":"https://test-recommendations.com/text-1.jpg","marketingUrl":"https://test-recommendations.com/test-1","objectId":"test-1","owners":[{"key":"Testx","logoImageUrl":"https://test-recommendations.com/organization/test-1.png","name":"General recommendation org 1"}],"title":"General recommendation 1","recommendationType":"general"},{"courseKey":"test+text2","activeRunKey":"course-v1:test+test2+2018","cardImageUrl":"https://test-recommendations.com/text-2.jpg","marketingUrl":"https://test-recommendations.com/test-2","objectId":"test-2","owners":[{"key":"Testx","logoImageUrl":"https://test-recommendations.com/organization/test-2.png","name":"General recommendation org 2"}],"title":"General recommendation 2","recommendationType":"general"},{"courseKey":"test+text3","activeRunKey":"course-v1:test+test3+2018","cardImageUrl":"https://test-recommendations.com/text-3.jpg","marketingUrl":"https://test-recommendations.com/test-3","objectId":"test-3","owners":[{"key":"Testx","logoImageUrl":"https://test-recommendations.com/organization/test-3.png","name":"General recommendation org 3"}],"title":"General recommendation 3","recommendationType":"general"},{"courseKey":"test+text4","activeRunKey":"course-v1:test+test4+2018","cardImageUrl":"https://test-recommendations.com/text-4.jpg","marketingUrl":"https://test-recommendations.com/test-4","objectId":"test-4","owners":[{"key":"Testx","logoImageUrl":"https://test-recommendations.com/organization/test-4.png","name":"General recommendation org 4"}],"title":"General recommendation 4","recommendationType":"general"},{"courseKey":"test+text5","activeRunKey":"course-v1:test+test5+2018","cardImageUrl":"https://test-recommendations.com/text-5.jpg","marketingUrl":"https://test-recommendations.com/test-5","objectId":"test-5","owners":[{"key":"Testx","logoImageUrl":"https://test-recommendations.com/organization/test-5.png","name":"General recommendation org 5"}],"title":"General recommendation 5","recommendationType":"general"}]';
|
||||
|
||||
Reference in New Issue
Block a user