Skip to content

Commit e8886c9

Browse files
authored
fix: display programs only if the url is configured (#479)
Removes the link of programs from the Header if the service is not configured.
1 parent a074459 commit e8886c9

File tree

9 files changed

+84
-10
lines changed

9 files changed

+84
-10
lines changed

src/containers/LearnerDashboardHeader/LearnerDashboardMenu.jsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const getLearnerHeaderMenu = (
99
courseSearchUrl,
1010
authenticatedUser,
1111
exploreCoursesClick,
12+
programsEnabled = false,
1213
) => ({
1314
mainMenu: [
1415
{
@@ -17,11 +18,11 @@ const getLearnerHeaderMenu = (
1718
content: formatMessage(messages.course),
1819
isActive: true,
1920
},
20-
{
21+
...(programsEnabled ? [{
2122
type: 'item',
2223
href: `${urls.programsUrl()}`,
2324
content: formatMessage(messages.program),
24-
},
25+
}] : []),
2526
{
2627
type: 'item',
2728
href: `${urls.baseAppUrl(courseSearchUrl)}`,

src/containers/LearnerDashboardHeader/__snapshots__/index.test.jsx.snap

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,6 @@ exports[`LearnerDashboardHeader render 1`] = `
1212
"isActive": true,
1313
"type": "item",
1414
},
15-
{
16-
"content": "Programs",
17-
"href": "http://localhost:18000/dashboard/programs",
18-
"type": "item",
19-
},
2015
{
2116
"content": "Discover New",
2217
"href": "http://localhost:18000/course-search-url",

src/containers/LearnerDashboardHeader/hooks.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import track from 'tracking';
55
import { StrictDict } from 'utils';
66
import { linkNames } from 'tracking/constants';
77

8+
import { apiHooks } from 'hooks';
89
import getLearnerHeaderMenu from './LearnerDashboardMenu';
910

1011
import * as module from './hooks';
@@ -30,8 +31,9 @@ export const findCoursesNavDropdownClicked = (href) => track.findCourses.findCou
3031
export const useLearnerDashboardHeaderMenu = ({
3132
courseSearchUrl, authenticatedUser, exploreCoursesClick,
3233
}) => {
34+
const { enabled: programsEnabled } = apiHooks.useProgramsConfig();
3335
const { formatMessage } = useIntl();
34-
return getLearnerHeaderMenu(formatMessage, courseSearchUrl, authenticatedUser, exploreCoursesClick);
36+
return getLearnerHeaderMenu(formatMessage, courseSearchUrl, authenticatedUser, exploreCoursesClick, programsEnabled);
3537
};
3638

3739
export const useLearnerDashboardHeaderData = () => {

src/containers/LearnerDashboardHeader/hooks.test.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ jest.mock('tracking', () => ({
2121
findCoursesClicked: jest.fn(),
2222
},
2323
}));
24+
jest.mock('hooks', () => ({
25+
apiHooks: {
26+
useProgramsConfig: jest.fn(() => ({})),
27+
},
28+
}));
2429

2530
const url = 'http://example.com';
2631

@@ -56,7 +61,7 @@ describe('LearnerDashboardHeader hooks', () => {
5661
username: 'test',
5762
};
5863
const learnerHomeHeaderMenu = useLearnerDashboardHeaderMenu({ courseSearchUrl, authenticatedUser });
59-
expect(learnerHomeHeaderMenu.mainMenu.length).toBe(3);
64+
expect(learnerHomeHeaderMenu.mainMenu.length).toBe(2);
6065
});
6166
});
6267

src/containers/LearnerDashboardHeader/index.test.jsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { shallow } from '@edx/react-unit-test-utils';
33
import Header from '@edx/frontend-component-header';
44

55
import urls from 'data/services/lms/urls';
6+
import { apiHooks } from 'hooks';
67
import LearnerDashboardHeader from '.';
78
import { findCoursesNavClicked } from './hooks';
89

@@ -12,6 +13,9 @@ jest.mock('hooks', () => ({
1213
courseSearchUrl: '/course-search-url',
1314
})),
1415
},
16+
apiHooks: {
17+
useProgramsConfig: jest.fn(() => ({})),
18+
},
1519
}));
1620
jest.mock('./hooks', () => ({
1721
...jest.requireActual('./hooks'),
@@ -29,7 +33,7 @@ describe('LearnerDashboardHeader', () => {
2933
expect(wrapper.instance.findByType('ConfirmEmailBanner')).toHaveLength(1);
3034
expect(wrapper.instance.findByType('MasqueradeBar')).toHaveLength(1);
3135
expect(wrapper.instance.findByType(Header)).toHaveLength(1);
32-
wrapper.instance.findByType(Header)[0].props.mainMenuItems[2].onClick();
36+
wrapper.instance.findByType(Header)[0].props.mainMenuItems[1].onClick();
3337
expect(findCoursesNavClicked).toHaveBeenCalledWith(urls.baseAppUrl('/course-search-url'));
3438
expect(wrapper.instance.findByType(Header)[0].props.secondaryMenuItems.length).toBe(0);
3539
});
@@ -38,5 +42,11 @@ describe('LearnerDashboardHeader', () => {
3842
mergeConfig({ SUPPORT_URL: 'http://localhost:18000/support' });
3943
const wrapper = shallow(<LearnerDashboardHeader />);
4044
expect(wrapper.instance.findByType(Header)[0].props.secondaryMenuItems.length).toBe(1);
45+
expect(wrapper.instance.findByType(Header)[0].props.mainMenuItems.length).toBe(2);
46+
});
47+
test('should display Programs link if the service is configured in the backend', () => {
48+
apiHooks.useProgramsConfig.mockReturnValue({ enabled: true });
49+
const wrapper = shallow(<LearnerDashboardHeader />);
50+
expect(wrapper.instance.findByType(Header)[0].props.mainMenuItems.length).toBe(3);
4151
});
4252
});

src/data/services/lms/api.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ export const initializeList = ({ user } = {}) => get(
2020
stringifyUrl(urls.getInitApiUrl(), { [apiKeys.user]: user }),
2121
);
2222

23+
export const getProgramsConfig = () => get(urls.programsConfigUrl());
24+
2325
export const updateEntitlementEnrollment = ({ uuid, courseId }) => post(
2426
urls.entitlementEnrollment(uuid),
2527
{ [apiKeys.courseRunId]: courseId },
@@ -73,6 +75,7 @@ export const createCreditRequest = ({ providerId, courseId, username }) => post(
7375

7476
export default {
7577
initializeList,
78+
getProgramsConfig,
7679
unenrollFromCourse,
7780
updateEmailSettings,
7881
updateEntitlementEnrollment,

src/data/services/lms/urls.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export const baseAppUrl = (url) => updateUrl(getBaseUrl(), url);
2222
export const learningMfeUrl = (url) => updateUrl(getConfig().LEARNING_BASE_URL, url);
2323

2424
// static view url
25+
const programsConfigUrl = () => baseAppUrl('/config/programs');
2526
const programsUrl = () => baseAppUrl('/dashboard/programs');
2627

2728
export const creditPurchaseUrl = (courseId) => `${getEcommerceUrl()}/credit/checkout/${courseId}/`;
@@ -37,6 +38,7 @@ export default StrictDict({
3738
event,
3839
getInitApiUrl,
3940
learningMfeUrl,
41+
programsConfigUrl,
4042
programsUrl,
4143
updateEmailSettings,
4244
});

src/hooks/api.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React from 'react';
22

3+
import { logError } from '@edx/frontend-platform/logging';
34
import { AppContext } from '@edx/frontend-platform/react';
45

56
import { RequestKeys } from 'data/constants/requests';
@@ -31,6 +32,25 @@ export const useInitializeApp = () => {
3132
});
3233
};
3334

35+
export const useProgramsConfig = () => {
36+
const [config, setConfig] = React.useState({});
37+
38+
React.useEffect(() => {
39+
const fetchProgramsConfig = async () => {
40+
try {
41+
const { data } = await api.getProgramsConfig();
42+
setConfig(data);
43+
} catch (error) {
44+
logError(`Error accessing programs configuration ${error}`);
45+
}
46+
};
47+
48+
fetchProgramsConfig();
49+
}, []);
50+
51+
return config;
52+
};
53+
3454
export const useNewEntitlementEnrollment = (cardId) => {
3555
const { uuid } = reduxHooks.useCardEntitlementData(cardId);
3656
const onSuccess = module.useInitializeApp();

src/hooks/api.test.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React from 'react';
2+
import { logError } from '@edx/frontend-platform/logging';
23
import { AppContext } from '@edx/frontend-platform/react';
34
import { keyStore } from 'utils';
45
import { RequestKeys } from 'data/constants/requests';
@@ -10,17 +11,24 @@ import * as apiHooks from './api';
1011

1112
const reduxKeys = keyStore(reduxHooks);
1213

14+
jest.mock('@edx/frontend-platform/logging', () => ({
15+
logError: jest.fn(),
16+
}));
17+
1318
jest.mock('data/services/lms/utils', () => ({
1419
post: jest.fn((...args) => ({ post: args })),
1520
}));
21+
1622
jest.mock('data/services/lms/api', () => ({
1723
initializeList: jest.fn(),
1824
updateEntitlementEnrollment: jest.fn(),
1925
unenrollFromCourse: jest.fn(),
2026
deleteEntitlementEnrollment: jest.fn(),
2127
updateEmailSettings: jest.fn(),
2228
createCreditRequest: jest.fn(),
29+
getProgramsConfig: jest.fn(),
2330
}));
31+
2432
jest.mock('data/redux/hooks', () => ({
2533
useCardCourseRunData: jest.fn(),
2634
useCardCreditData: jest.fn(),
@@ -110,6 +118,34 @@ describe('api hooks', () => {
110118
});
111119
});
112120

121+
describe('useProgramsConfig', () => {
122+
let mockState;
123+
const setState = jest.fn((newState) => { Object.assign(mockState, newState); });
124+
beforeEach(() => {
125+
mockState = {};
126+
React.useState.mockReturnValue([mockState, setState]);
127+
});
128+
129+
it('should return the programs configuration when the API call is successful', async () => {
130+
api.getProgramsConfig.mockResolvedValue({ data: { enabled: true } });
131+
const config = apiHooks.useProgramsConfig();
132+
const [cb] = React.useEffect.mock.calls[0];
133+
await cb();
134+
expect(setState).toHaveBeenCalled();
135+
expect(config).toEqual({ enabled: true });
136+
});
137+
138+
it('should return an empty object if the api call fails', async () => {
139+
mockState = {};
140+
api.getProgramsConfig.mockRejectedValue(new Error('error test'));
141+
const config = apiHooks.useProgramsConfig();
142+
const [cb] = React.useEffect.mock.calls[0];
143+
await cb();
144+
expect(config).toEqual({});
145+
expect(logError).toHaveBeenCalled();
146+
});
147+
});
148+
113149
describe('entitlement enrollment hooks', () => {
114150
beforeEach(() => {
115151
jest.spyOn(apiHooks, moduleKeys.useInitializeApp).mockReturnValue(initializeApp);

0 commit comments

Comments
 (0)