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:
Zainab Amir
2023-03-01 17:49:38 +05:00
committed by GitHub
parent 3d10cea137
commit 1a61ba3cc7
5 changed files with 66 additions and 118 deletions

View File

@@ -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 || '',
};

View File

@@ -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>
</>
);
};

View File

@@ -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': {

View File

@@ -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 () => {

View File

@@ -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"}]';