feat: add explore course link on the header (#94)
This commit is contained in:
@@ -9,7 +9,7 @@ import { AvatarButton, Dropdown } from '@edx/paragon';
|
||||
import { hooks as appHooks } from 'data/redux';
|
||||
import urls from 'data/services/lms/urls';
|
||||
|
||||
import { useIsCollapsed } from './hooks';
|
||||
import { useIsCollapsed, findCoursesNavDropdownClicked } from './hooks';
|
||||
import messages from './messages';
|
||||
|
||||
export const AuthenticatedUserDropdown = ({ username }) => {
|
||||
@@ -17,6 +17,7 @@ export const AuthenticatedUserDropdown = ({ username }) => {
|
||||
const { authenticatedUser } = React.useContext(AppContext);
|
||||
const { profileImage } = authenticatedUser;
|
||||
const dashboard = appHooks.useEnterpriseDashboardData();
|
||||
const { courseSearchUrl } = appHooks.usePlatformSettingsData();
|
||||
const isCollapsed = useIsCollapsed();
|
||||
|
||||
return (
|
||||
@@ -48,9 +49,14 @@ export const AuthenticatedUserDropdown = ({ username }) => {
|
||||
{formatMessage(messages.profile)}
|
||||
</Dropdown.Item>
|
||||
{isCollapsed && (
|
||||
<Dropdown.Item href={urls.programsUrl}>
|
||||
{formatMessage(messages.viewPrograms)}
|
||||
</Dropdown.Item>
|
||||
<>
|
||||
<Dropdown.Item href={urls.programsUrl}>
|
||||
{formatMessage(messages.viewPrograms)}
|
||||
</Dropdown.Item>
|
||||
<Dropdown.Item href={courseSearchUrl} onClick={findCoursesNavDropdownClicked(courseSearchUrl)}>
|
||||
{formatMessage(messages.exploreCourses)}
|
||||
</Dropdown.Item>
|
||||
</>
|
||||
)}
|
||||
<Dropdown.Item href={`${getConfig().LMS_BASE_URL}/account/settings`}>
|
||||
{formatMessage(messages.account)}
|
||||
|
||||
@@ -14,10 +14,14 @@ jest.mock('@edx/frontend-platform/react', () => ({
|
||||
jest.mock('data/redux', () => ({
|
||||
hooks: {
|
||||
useEnterpriseDashboardData: jest.fn(),
|
||||
usePlatformSettingsData: jest.fn(() => ({
|
||||
courseSearchUrl: 'test-course-search-url',
|
||||
})),
|
||||
},
|
||||
}));
|
||||
jest.mock('containers/LearnerDashboardHeader/hooks', () => ({
|
||||
useIsCollapsed: jest.fn(),
|
||||
findCoursesNavDropdownClicked: (href) => jest.fn().mockName(`findCoursesNavDropdownClicked('${href}')`),
|
||||
}));
|
||||
|
||||
describe('AuthenticatedUserDropdown', () => {
|
||||
|
||||
@@ -50,6 +50,12 @@ exports[`AuthenticatedUserDropdown snapshots with enterprise dashboard 1`] = `
|
||||
>
|
||||
View Programs
|
||||
</Dropdown.Item>
|
||||
<Dropdown.Item
|
||||
href="test-course-search-url"
|
||||
onClick={[MockFunction findCoursesNavDropdownClicked('test-course-search-url')]}
|
||||
>
|
||||
Explore courses
|
||||
</Dropdown.Item>
|
||||
<Dropdown.Item
|
||||
href="http://localhost:18000/account/settings"
|
||||
>
|
||||
|
||||
@@ -28,8 +28,16 @@ exports[`LearnerDashboardHeader snapshots with collapsed 1`] = `
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="my-auto ml-1"
|
||||
className="my-auto ml-1 d-flex"
|
||||
>
|
||||
<IconButton
|
||||
as="a"
|
||||
href="test-course-search-url"
|
||||
iconAs="Icon"
|
||||
invertColors={true}
|
||||
onClick={[MockFunction findCoursesNavClicked('test-course-search-url')]}
|
||||
variant="primary"
|
||||
/>
|
||||
<UserMenu />
|
||||
</div>
|
||||
</div>
|
||||
@@ -66,6 +74,14 @@ exports[`LearnerDashboardHeader snapshots without collapsed 1`] = `
|
||||
<div
|
||||
className="flex-grow-1"
|
||||
/>
|
||||
<Button
|
||||
as="a"
|
||||
href="test-course-search-url"
|
||||
onClick={[MockFunction findCoursesNavClicked('test-course-search-url')]}
|
||||
variant="inverse-tertiary"
|
||||
>
|
||||
Explore courses
|
||||
</Button>
|
||||
<UserMenu />
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import React from 'react';
|
||||
import { useWindowSize, breakpoints } from '@edx/paragon';
|
||||
import track from 'tracking';
|
||||
import { linkNames } from 'tracking/constants';
|
||||
|
||||
export const useIsCollapsed = () => {
|
||||
const { width } = useWindowSize();
|
||||
@@ -7,4 +9,16 @@ export const useIsCollapsed = () => {
|
||||
return isCollapsed;
|
||||
};
|
||||
|
||||
export default { useIsCollapsed };
|
||||
export const findCoursesNavClicked = (href) => track.findCourses.findCoursesClicked(href, {
|
||||
linkName: linkNames.learnerHomeNavExplore,
|
||||
});
|
||||
|
||||
export const findCoursesNavDropdownClicked = (href) => track.findCourses.findCoursesClicked(href, {
|
||||
linkName: linkNames.learnerHomeNavDropdownExplore,
|
||||
});
|
||||
|
||||
export default {
|
||||
useIsCollapsed,
|
||||
findCoursesNavClicked,
|
||||
findCoursesNavDropdownClicked,
|
||||
};
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
import { useWindowSize, breakpoints } from '@edx/paragon';
|
||||
import { useIsCollapsed } from './hooks';
|
||||
import track from 'tracking';
|
||||
import { linkNames } from 'tracking/constants';
|
||||
import { useIsCollapsed, findCoursesNavClicked, findCoursesNavDropdownClicked } from './hooks';
|
||||
|
||||
jest.mock('tracking', () => ({
|
||||
findCourses: {
|
||||
findCoursesClicked: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
const url = 'http://example.com';
|
||||
|
||||
describe('LearnerDashboardHeader hooks', () => {
|
||||
describe('useIsCollapsed', () => {
|
||||
@@ -12,4 +22,22 @@ describe('LearnerDashboardHeader hooks', () => {
|
||||
expect(useIsCollapsed()).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findCoursesNavClicked', () => {
|
||||
test('calls tracking with nav link name', () => {
|
||||
findCoursesNavClicked(url);
|
||||
expect(track.findCourses.findCoursesClicked).toHaveBeenCalledWith(url, {
|
||||
linkName: linkNames.learnerHomeNavExplore,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('findCoursesNavDropdownClicked', () => {
|
||||
test('calls tracking with dropdown link name', () => {
|
||||
findCoursesNavDropdownClicked(url);
|
||||
expect(track.findCourses.findCoursesClicked).toHaveBeenCalledWith(url, {
|
||||
linkName: linkNames.learnerHomeNavDropdownExplore,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,18 +2,21 @@ import React, { useContext } from 'react';
|
||||
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { AppContext } from '@edx/frontend-platform/react';
|
||||
import { Program } from '@edx/paragon/icons';
|
||||
import { Button, Image } from '@edx/paragon';
|
||||
import { Program, Search } from '@edx/paragon/icons';
|
||||
import {
|
||||
Button, Image, IconButton, Icon,
|
||||
} from '@edx/paragon';
|
||||
|
||||
import topBanner from 'assets/top_stripe.svg';
|
||||
import MasqueradeBar from 'containers/MasqueradeBar';
|
||||
import urls from 'data/services/lms/urls';
|
||||
import { hooks as appHooks } from 'data/redux';
|
||||
|
||||
import AuthenticatedUserDropdown from './AuthenticatedUserDropdown';
|
||||
import GreetingBanner from './GreetingBanner';
|
||||
import ConfirmEmailBanner from './ConfirmEmailBanner';
|
||||
|
||||
import { useIsCollapsed } from './hooks';
|
||||
import { useIsCollapsed, findCoursesNavClicked } from './hooks';
|
||||
import messages from './messages';
|
||||
import './index.scss';
|
||||
|
||||
@@ -25,6 +28,9 @@ export const UserMenu = () => {
|
||||
export const LearnerDashboardHeader = () => {
|
||||
const { formatMessage } = useIntl();
|
||||
const isCollapsed = useIsCollapsed();
|
||||
const { courseSearchUrl } = appHooks.usePlatformSettingsData();
|
||||
|
||||
const exploreCoursesClick = findCoursesNavClicked(courseSearchUrl);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -43,9 +49,33 @@ export const LearnerDashboardHeader = () => {
|
||||
<div className="flex-grow-1">
|
||||
{isCollapsed && <GreetingBanner size="small" />}
|
||||
</div>
|
||||
{isCollapsed
|
||||
? (<div className="my-auto ml-1"><UserMenu /></div>)
|
||||
: (<UserMenu />)}
|
||||
{isCollapsed ? (
|
||||
<div className="my-auto ml-1 d-flex">
|
||||
<IconButton
|
||||
as="a"
|
||||
href={courseSearchUrl}
|
||||
variant="primary"
|
||||
invertColors
|
||||
src={Search}
|
||||
iconAs={Icon}
|
||||
onClick={exploreCoursesClick}
|
||||
/>
|
||||
<UserMenu />
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<Button
|
||||
as="a"
|
||||
href={courseSearchUrl}
|
||||
variant="inverse-tertiary"
|
||||
iconBefore={Search}
|
||||
onClick={exploreCoursesClick}
|
||||
>
|
||||
{formatMessage(messages.exploreCourses)}
|
||||
</Button>
|
||||
<UserMenu />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</header>
|
||||
{!isCollapsed && <GreetingBanner size="large" />}
|
||||
|
||||
@@ -14,6 +14,14 @@ jest.mock('@edx/frontend-platform/react', () => ({
|
||||
}));
|
||||
jest.mock('./hooks', () => ({
|
||||
useIsCollapsed: jest.fn(),
|
||||
findCoursesNavClicked: (href) => jest.fn().mockName(`findCoursesNavClicked('${href}')`),
|
||||
}));
|
||||
jest.mock('data/redux', () => ({
|
||||
hooks: {
|
||||
usePlatformSettingsData: jest.fn(() => ({
|
||||
courseSearchUrl: 'test-course-search-url',
|
||||
})),
|
||||
},
|
||||
}));
|
||||
jest.mock('containers/MasqueradeBar', () => 'MasqueradeBar');
|
||||
|
||||
|
||||
@@ -57,6 +57,11 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Switch to Programs',
|
||||
description: 'Header link for switching to program page.',
|
||||
},
|
||||
exploreCourses: {
|
||||
id: 'leanerDashboard.exploreCourses',
|
||||
defaultMessage: 'Explore courses',
|
||||
description: 'Header link for switching to course page.',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
|
||||
@@ -4,6 +4,7 @@ export const categories = StrictDict({
|
||||
dashboard: 'dashboard',
|
||||
upgrade: 'upgrade',
|
||||
userEngagement: 'user-engagement',
|
||||
searchButton: 'search_button',
|
||||
});
|
||||
|
||||
export const events = StrictDict({
|
||||
@@ -46,6 +47,12 @@ export const eventNames = StrictDict({
|
||||
enterpriseDashboardModalOpened: `${learnerPortal}.opened`,
|
||||
enterpriseDashboardModalCTAClicked: `${learnerPortal}.dashboard_cta.clicked`,
|
||||
enterpriseDashboardModalClosed: `${learnerPortal}.closed`,
|
||||
findCoursesClicked: 'edx.bi.dashboard.find_courses_button.clicked',
|
||||
});
|
||||
|
||||
export const linkNames = StrictDict({
|
||||
learnerHomeNavExplore: 'learner_home_nav_explore',
|
||||
learnerHomeNavDropdownExplore: 'learner_home_nav_dropdown_explore',
|
||||
});
|
||||
|
||||
export const appName = 'learner-home';
|
||||
|
||||
@@ -3,6 +3,7 @@ import engagement from './trackers/engagement';
|
||||
import enterpriseDashboard from './trackers/enterpriseDashboard';
|
||||
import entitlements from './trackers/entitlements';
|
||||
import socialShare from './trackers/socialShare';
|
||||
import findCourses from './trackers/findCourses';
|
||||
|
||||
export default {
|
||||
course,
|
||||
@@ -10,4 +11,5 @@ export default {
|
||||
enterpriseDashboard,
|
||||
entitlements,
|
||||
socialShare,
|
||||
findCourses,
|
||||
};
|
||||
|
||||
16
src/tracking/trackers/findCourses.js
Normal file
16
src/tracking/trackers/findCourses.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import { createLinkTracker, createEventTracker } from 'data/services/segment/utils';
|
||||
import { categories, eventNames } from '../constants';
|
||||
|
||||
export const findCoursesClicked = (href, args = {}) => createLinkTracker(
|
||||
createEventTracker(eventNames.findCoursesClicked, {
|
||||
pageName: 'learner_home',
|
||||
linkType: 'button',
|
||||
linkCategory: categories.searchButton,
|
||||
...args,
|
||||
}),
|
||||
href,
|
||||
);
|
||||
|
||||
export default {
|
||||
findCoursesClicked,
|
||||
};
|
||||
44
src/tracking/trackers/findCourses.test.js
Normal file
44
src/tracking/trackers/findCourses.test.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import { createLinkTracker, createEventTracker } from 'data/services/segment/utils';
|
||||
import { findCoursesClicked } from './findCourses';
|
||||
import { categories, eventNames } from '../constants';
|
||||
|
||||
jest.mock('data/services/segment/utils', () => ({
|
||||
createEventTracker: jest.fn((args) => ({ createEventTracker: args })),
|
||||
createLinkTracker: jest.fn((args) => ({ createLinkTracker: args })),
|
||||
}));
|
||||
|
||||
describe('find courses trackers', () => {
|
||||
const url = 'http://example.com';
|
||||
const defaultProps = {
|
||||
pageName: 'learner_home',
|
||||
linkType: 'button',
|
||||
linkCategory: categories.searchButton,
|
||||
};
|
||||
beforeEach(() => {
|
||||
createEventTracker.mockClear();
|
||||
createLinkTracker.mockClear();
|
||||
});
|
||||
test('no args', () => {
|
||||
findCoursesClicked(url);
|
||||
expect(createEventTracker).toHaveBeenCalledWith(eventNames.findCoursesClicked, defaultProps);
|
||||
expect(createLinkTracker).toHaveBeenCalledWith(
|
||||
createEventTracker(eventNames.findCoursesClicked, defaultProps), url,
|
||||
);
|
||||
});
|
||||
|
||||
test('with args', () => {
|
||||
const args = { linkName: 'foo' };
|
||||
findCoursesClicked(url, args);
|
||||
expect(createEventTracker).toHaveBeenCalledWith(eventNames.findCoursesClicked, {
|
||||
...defaultProps,
|
||||
...args,
|
||||
});
|
||||
expect(createLinkTracker).toHaveBeenCalledWith(
|
||||
createEventTracker(eventNames.findCoursesClicked, {
|
||||
...defaultProps,
|
||||
...args,
|
||||
}),
|
||||
url,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -19,6 +19,7 @@ exports[`LookingForChallengeWidget snapshots default 1`] = `
|
||||
<Hyperlink
|
||||
className="d-flex align-items-center"
|
||||
destination="course-search-url"
|
||||
onClick={[MockFunction track.findCoursesWidgetClicked('course-search-url')]}
|
||||
variant="brand"
|
||||
>
|
||||
<format-message-function
|
||||
|
||||
@@ -30,7 +30,7 @@ export const LookingForChallengeWidget = () => {
|
||||
<Hyperlink
|
||||
variant="brand"
|
||||
destination={courseSearchUrl}
|
||||
onClick={track.findCoursesClicked(courseSearchUrl)}
|
||||
onClick={track.findCoursesWidgetClicked(courseSearchUrl)}
|
||||
className="d-flex align-items-center"
|
||||
>
|
||||
{formatMessage(messages.findCoursesButton, { arrow: arrowIcon })}
|
||||
|
||||
@@ -11,7 +11,7 @@ jest.mock('data/redux', () => ({
|
||||
}));
|
||||
|
||||
jest.mock('../RecommendationsPanel/track', () => ({
|
||||
findCoursesClicked: jest.fn().mockName('track.findCoursesClicked'),
|
||||
findCoursesWidgetClicked: (href) => jest.fn().mockName(`track.findCoursesWidgetClicked('${href}')`),
|
||||
}));
|
||||
|
||||
describe('LookingForChallengeWidget', () => {
|
||||
|
||||
@@ -39,7 +39,7 @@ export const LoadedView = ({
|
||||
iconBefore={Search}
|
||||
as="a"
|
||||
href={courseSearchUrl}
|
||||
onClick={track.findCoursesClicked(courseSearchUrl)}
|
||||
onClick={track.findCoursesWidgetClicked(courseSearchUrl)}
|
||||
>
|
||||
{formatMessage(messages.exploreCoursesButton)}
|
||||
</Button>
|
||||
|
||||
@@ -14,7 +14,7 @@ jest.mock('data/redux', () => ({
|
||||
},
|
||||
}));
|
||||
jest.mock('./track', () => ({
|
||||
findCoursesClicked: () => 'find-courses-clicked',
|
||||
findCoursesWidgetClicked: (href) => jest.fn().mockName(`track.findCoursesWidgetClicked('${href}')`),
|
||||
}));
|
||||
|
||||
describe('RecommendationsPanel LoadedView', () => {
|
||||
|
||||
@@ -65,7 +65,7 @@ exports[`RecommendationsPanel LoadedView snapshot with personalize recommendatio
|
||||
<Button
|
||||
as="a"
|
||||
href="course-search-url"
|
||||
onClick="find-courses-clicked"
|
||||
onClick={[MockFunction track.findCoursesWidgetClicked('course-search-url')]}
|
||||
variant="tertiary"
|
||||
>
|
||||
Explore courses
|
||||
@@ -139,7 +139,7 @@ exports[`RecommendationsPanel LoadedView snapshot without personalize recommenda
|
||||
<Button
|
||||
as="a"
|
||||
href="course-search-url"
|
||||
onClick="find-courses-clicked"
|
||||
onClick={[MockFunction track.findCoursesWidgetClicked('course-search-url')]}
|
||||
variant="tertiary"
|
||||
>
|
||||
Explore courses
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
import { StrictDict } from 'utils';
|
||||
import { createLinkTracker, createEventTracker } from 'data/services/segment/utils';
|
||||
import track from 'tracking';
|
||||
|
||||
export const eventNames = StrictDict({
|
||||
findCoursesClicked: 'edx.bi.dashboard.find_courses_button.clicked',
|
||||
recommendedCourseClicked: 'edx.bi.user.recommended.course.click',
|
||||
});
|
||||
|
||||
export const findCoursesClicked = (href) => createLinkTracker(
|
||||
createEventTracker(eventNames.findCoursesClicked, {
|
||||
pageName: 'learner_home',
|
||||
linkType: 'button',
|
||||
linkCategory: 'search_button',
|
||||
}),
|
||||
href,
|
||||
);
|
||||
export const linkNames = StrictDict({
|
||||
findCoursesWidget: 'learner_home_widget_explore',
|
||||
});
|
||||
|
||||
export const findCoursesWidgetClicked = (href) => track.findCourses.findCoursesClicked(href, {
|
||||
linkName: linkNames.findCoursesWidget,
|
||||
});
|
||||
|
||||
export const recommendedCourseClicked = (courseKey, isPersonalized, href) => createLinkTracker(
|
||||
createEventTracker(eventNames.recommendedCourseClicked, {
|
||||
@@ -24,6 +23,6 @@ export const recommendedCourseClicked = (courseKey, isPersonalized, href) => cre
|
||||
);
|
||||
|
||||
export default {
|
||||
findCoursesClicked,
|
||||
findCoursesWidgetClicked,
|
||||
recommendedCourseClicked,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user