feat: add experiment and eventing to recommendations painted door exp
VAN-1604
This commit is contained in:
@@ -5,6 +5,15 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="shortcut icon" href="<%=htmlWebpackPlugin.options.FAVICON_URL%>" type="image/x-icon" />
|
||||
<% if (process.env.OPTIMIZELY_URL) { %>
|
||||
<script
|
||||
src="<%= process.env.OPTIMIZELY_URL %>"
|
||||
></script>
|
||||
<% } else if (process.env.OPTIMIZELY_PROJECT_ID) { %>
|
||||
<script
|
||||
src="<%= process.env.MARKETING_SITE_BASE_URL %>/optimizelyjs/<%= process.env.OPTIMIZELY_PROJECT_ID %>.js"
|
||||
></script>
|
||||
<% } %>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
33
src/App.jsx
33
src/App.jsx
@@ -29,6 +29,7 @@ import LearnerDashboardHeader from './containers/LearnerDashboardHeader';
|
||||
import messages from './messages';
|
||||
|
||||
import './App.scss';
|
||||
import { PaintedDoorExperimentProvider } from './RecsPaintedDoorExpContext';
|
||||
|
||||
export const App = () => {
|
||||
const { authenticatedUser } = React.useContext(AppContext);
|
||||
@@ -78,21 +79,23 @@ export const App = () => {
|
||||
<title>{formatMessage(messages.pageTitle)}</title>
|
||||
</Helmet>
|
||||
<div>
|
||||
<LearnerDashboardHeader />
|
||||
<main>
|
||||
{hasNetworkFailure
|
||||
? (
|
||||
<Alert variant="danger">
|
||||
<ErrorPage message={formatMessage(messages.errorMessage, { supportEmail })} />
|
||||
</Alert>
|
||||
) : (
|
||||
<ExperimentProvider>
|
||||
<Dashboard />
|
||||
</ExperimentProvider>
|
||||
)}
|
||||
</main>
|
||||
<Footer logo={process.env.LOGO_POWERED_BY_OPEN_EDX_URL_SVG} />
|
||||
<ZendeskFab />
|
||||
<PaintedDoorExperimentProvider>
|
||||
<LearnerDashboardHeader />
|
||||
<main>
|
||||
{hasNetworkFailure
|
||||
? (
|
||||
<Alert variant="danger">
|
||||
<ErrorPage message={formatMessage(messages.errorMessage, { supportEmail })} />
|
||||
</Alert>
|
||||
) : (
|
||||
<ExperimentProvider>
|
||||
<Dashboard />
|
||||
</ExperimentProvider>
|
||||
)}
|
||||
</main>
|
||||
<Footer logo={process.env.LOGO_POWERED_BY_OPEN_EDX_URL_SVG} />
|
||||
<ZendeskFab />
|
||||
</PaintedDoorExperimentProvider>
|
||||
</div>
|
||||
</Router>
|
||||
);
|
||||
|
||||
@@ -25,6 +25,9 @@ jest.mock('components/ZendeskFab', () => 'ZendeskFab');
|
||||
jest.mock('ExperimentContext', () => ({
|
||||
ExperimentProvider: 'ExperimentProvider',
|
||||
}));
|
||||
jest.mock('RecsPaintedDoorExpContext', () => ({
|
||||
PaintedDoorExperimentProvider: 'PaintedDoorExperimentProvider',
|
||||
}));
|
||||
jest.mock('data/redux', () => ({
|
||||
selectors: 'redux.selectors',
|
||||
actions: 'redux.actions',
|
||||
|
||||
120
src/RecsPaintedDoorExpContext.jsx
Normal file
120
src/RecsPaintedDoorExpContext.jsx
Normal file
@@ -0,0 +1,120 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { StrictDict } from 'utils';
|
||||
import * as module from './RecsPaintedDoorExpContext';
|
||||
import {
|
||||
useEmailConfirmationData,
|
||||
useHasAvailableDashboards,
|
||||
useRequestIsPending,
|
||||
} from './data/redux/hooks';
|
||||
import { RequestKeys } from './data/constants/requests';
|
||||
import { trackPaintedDoorVariationGroup } from './widgets/RecommendationsPanel/recsPaintedDoorExpTrack';
|
||||
|
||||
export const state = StrictDict({
|
||||
enterpriseUser: (val) => React.useState(val), // eslint-disable-line
|
||||
experimentData: (val) => React.useState(val), // eslint-disable-line
|
||||
});
|
||||
|
||||
const PAINTED_DOOR_RECOMMENDATIONS_EXP_ID = 25116810832;
|
||||
const PAINTED_DOOR_RECOMMENDATIONS_PAGE = 'url_targeting_for_van1604_recommendations_painted_door_exp';
|
||||
const PAINTED_DOOR_RECS_EXP_NAVBAR_BTN_VARIATION = 'btn_navbar';
|
||||
const PAINTED_DOOR_RECS_EXP_WIDGET_BTN_VARIATION = 'btn_widget';
|
||||
|
||||
export function getPaintedDoorRecommendationsExperimentVariation() {
|
||||
try {
|
||||
if (window.optimizely && window.optimizely.get('data').experiments[PAINTED_DOOR_RECOMMENDATIONS_EXP_ID]) {
|
||||
const selectedVariant = window.optimizely.get('state').getVariationMap()[PAINTED_DOOR_RECOMMENDATIONS_EXP_ID];
|
||||
return selectedVariant?.name;
|
||||
}
|
||||
} catch (e) { /* empty */ }
|
||||
return '';
|
||||
}
|
||||
|
||||
export function activatePaintedDoorRecommendationsExperiment() {
|
||||
window.optimizely = window.optimizely || [];
|
||||
window.optimizely.push({
|
||||
type: 'page',
|
||||
pageName: PAINTED_DOOR_RECOMMENDATIONS_PAGE,
|
||||
});
|
||||
}
|
||||
|
||||
export const PaintedDoorExperimentContext = React.createContext();
|
||||
|
||||
const useIsEnterpriseUser = () => {
|
||||
const [enterpriseUser, setEnterpriseUser] = module.state.enterpriseUser({
|
||||
isEnterpriseUser: false,
|
||||
isLoading: true,
|
||||
});
|
||||
|
||||
const initIsPending = useRequestIsPending(RequestKeys.initialize);
|
||||
const hasAvailableDashboards = useHasAvailableDashboards();
|
||||
const confirmationData = useEmailConfirmationData();
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!initIsPending && Object.keys(confirmationData).length && hasAvailableDashboards) {
|
||||
setEnterpriseUser(prev => ({
|
||||
...prev,
|
||||
isEnterpriseUser: true,
|
||||
isLoading: false,
|
||||
}));
|
||||
} else if (!initIsPending && Object.keys(confirmationData).length && !hasAvailableDashboards) {
|
||||
setEnterpriseUser(prev => ({
|
||||
...prev,
|
||||
isEnterpriseUser: false,
|
||||
isLoading: false,
|
||||
}));
|
||||
}
|
||||
}, [initIsPending]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
return enterpriseUser;
|
||||
};
|
||||
|
||||
export const PaintedDoorExperimentProvider = ({ children }) => {
|
||||
const [experimentData, setExperimentData] = module.state.experimentData({
|
||||
experimentVariation: '',
|
||||
isPaintedDoorNavbarBtnVariation: false,
|
||||
isPaintedDoorWidgetBtnVariation: false,
|
||||
experimentLoading: true,
|
||||
});
|
||||
const enterpriseUser = useIsEnterpriseUser();
|
||||
|
||||
const contextValue = React.useMemo(
|
||||
() => ({
|
||||
...experimentData,
|
||||
}),
|
||||
[experimentData],
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
let timer = null;
|
||||
if (!enterpriseUser.isLoading && !enterpriseUser.isEnterpriseUser) {
|
||||
activatePaintedDoorRecommendationsExperiment();
|
||||
timer = setTimeout(() => {
|
||||
const variation = getPaintedDoorRecommendationsExperimentVariation();
|
||||
setExperimentData(prev => ({
|
||||
...prev,
|
||||
experimentVariation: variation,
|
||||
isPaintedDoorNavbarBtnVariation: variation === PAINTED_DOOR_RECS_EXP_NAVBAR_BTN_VARIATION,
|
||||
isPaintedDoorWidgetBtnVariation: variation === PAINTED_DOOR_RECS_EXP_WIDGET_BTN_VARIATION,
|
||||
experimentLoading: false,
|
||||
}));
|
||||
trackPaintedDoorVariationGroup(variation);
|
||||
}, 500);
|
||||
}
|
||||
return () => clearTimeout(timer);
|
||||
}, [enterpriseUser]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
return (
|
||||
<PaintedDoorExperimentContext.Provider value={contextValue}>
|
||||
{children}
|
||||
</PaintedDoorExperimentContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const usePaintedDoorExperimentContext = () => React.useContext(PaintedDoorExperimentContext);
|
||||
|
||||
PaintedDoorExperimentProvider.propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
};
|
||||
|
||||
export default usePaintedDoorExperimentContext;
|
||||
@@ -11,20 +11,22 @@ exports[`App router component component initialize failure snapshot 1`] = `
|
||||
</title>
|
||||
</HelmetWrapper>
|
||||
<div>
|
||||
<LearnerDashboardHeader />
|
||||
<main>
|
||||
<Alert
|
||||
variant="danger"
|
||||
>
|
||||
<ErrorPage
|
||||
message="If you experience repeated failures, please email support at test-support-url"
|
||||
/>
|
||||
</Alert>
|
||||
</main>
|
||||
<Footer
|
||||
logo="fakeLogo.png"
|
||||
/>
|
||||
<ZendeskFab />
|
||||
<PaintedDoorExperimentProvider>
|
||||
<LearnerDashboardHeader />
|
||||
<main>
|
||||
<Alert
|
||||
variant="danger"
|
||||
>
|
||||
<ErrorPage
|
||||
message="If you experience repeated failures, please email support at test-support-url"
|
||||
/>
|
||||
</Alert>
|
||||
</main>
|
||||
<Footer
|
||||
logo="fakeLogo.png"
|
||||
/>
|
||||
<ZendeskFab />
|
||||
</PaintedDoorExperimentProvider>
|
||||
</div>
|
||||
</BrowserRouter>
|
||||
`;
|
||||
@@ -40,16 +42,18 @@ exports[`App router component component no network failure snapshot 1`] = `
|
||||
</title>
|
||||
</HelmetWrapper>
|
||||
<div>
|
||||
<LearnerDashboardHeader />
|
||||
<main>
|
||||
<ExperimentProvider>
|
||||
<Dashboard />
|
||||
</ExperimentProvider>
|
||||
</main>
|
||||
<Footer
|
||||
logo="fakeLogo.png"
|
||||
/>
|
||||
<ZendeskFab />
|
||||
<PaintedDoorExperimentProvider>
|
||||
<LearnerDashboardHeader />
|
||||
<main>
|
||||
<ExperimentProvider>
|
||||
<Dashboard />
|
||||
</ExperimentProvider>
|
||||
</main>
|
||||
<Footer
|
||||
logo="fakeLogo.png"
|
||||
/>
|
||||
<ZendeskFab />
|
||||
</PaintedDoorExperimentProvider>
|
||||
</div>
|
||||
</BrowserRouter>
|
||||
`;
|
||||
@@ -65,20 +69,22 @@ exports[`App router component component refresh failure snapshot 1`] = `
|
||||
</title>
|
||||
</HelmetWrapper>
|
||||
<div>
|
||||
<LearnerDashboardHeader />
|
||||
<main>
|
||||
<Alert
|
||||
variant="danger"
|
||||
>
|
||||
<ErrorPage
|
||||
message="If you experience repeated failures, please email support at test-support-url"
|
||||
/>
|
||||
</Alert>
|
||||
</main>
|
||||
<Footer
|
||||
logo="fakeLogo.png"
|
||||
/>
|
||||
<ZendeskFab />
|
||||
<PaintedDoorExperimentProvider>
|
||||
<LearnerDashboardHeader />
|
||||
<main>
|
||||
<Alert
|
||||
variant="danger"
|
||||
>
|
||||
<ErrorPage
|
||||
message="If you experience repeated failures, please email support at test-support-url"
|
||||
/>
|
||||
</Alert>
|
||||
</main>
|
||||
<Footer
|
||||
logo="fakeLogo.png"
|
||||
/>
|
||||
<ZendeskFab />
|
||||
</PaintedDoorExperimentProvider>
|
||||
</div>
|
||||
</BrowserRouter>
|
||||
`;
|
||||
|
||||
@@ -35,15 +35,17 @@ exports[`ModalView snapshot should renders default ModalView 1`] = `
|
||||
<Component>
|
||||
<ActionRow>
|
||||
<Component
|
||||
onClick={[Function]}
|
||||
variant="tertiary"
|
||||
>
|
||||
Skip for now
|
||||
</Component>
|
||||
<Button
|
||||
<Component
|
||||
onClick={[Function]}
|
||||
variant="primary"
|
||||
>
|
||||
Count me in!
|
||||
</Button>
|
||||
</Component>
|
||||
</ActionRow>
|
||||
</Component>
|
||||
</ModalDialog>
|
||||
|
||||
@@ -1,18 +1,26 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { ModalDialog, ActionRow, Button } from '@edx/paragon';
|
||||
import { ModalDialog, ActionRow } from '@edx/paragon';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import messages from './messages';
|
||||
|
||||
import './index.scss';
|
||||
import {
|
||||
trackPaintedDoorRecommendationHomeInterestBtnClicked,
|
||||
trackPaintedDoorRecommendationHomeSkipBtnClicked,
|
||||
} from '../../widgets/RecommendationsPanel/recsPaintedDoorExpTrack';
|
||||
|
||||
export const ModalView = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
variation,
|
||||
}) => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
const handleSkipBtnClick = () => trackPaintedDoorRecommendationHomeSkipBtnClicked(variation);
|
||||
const handleInterestBtnClick = () => trackPaintedDoorRecommendationHomeInterestBtnClicked(variation);
|
||||
|
||||
return (
|
||||
<div className="containers">
|
||||
<ModalDialog
|
||||
@@ -35,10 +43,12 @@ export const ModalView = ({
|
||||
</ModalDialog.Body>
|
||||
<ModalDialog.Footer>
|
||||
<ActionRow>
|
||||
<ModalDialog.CloseButton variant="tertiary">
|
||||
<ModalDialog.CloseButton variant="tertiary" onClick={handleSkipBtnClick}>
|
||||
{formatMessage(messages.modalSkipButton)}
|
||||
</ModalDialog.CloseButton>
|
||||
<Button variant="primary">{formatMessage(messages.modalCountMeButton)}</Button>
|
||||
<ModalDialog.CloseButton variant="primary" onClick={handleInterestBtnClick}>
|
||||
{formatMessage(messages.modalCountMeButton)}
|
||||
</ModalDialog.CloseButton>
|
||||
</ActionRow>
|
||||
</ModalDialog.Footer>
|
||||
</ModalDialog>
|
||||
@@ -53,6 +63,7 @@ ModalView.defaultProps = {
|
||||
ModalView.propTypes = {
|
||||
onClose: PropTypes.func.isRequired,
|
||||
isOpen: PropTypes.bool,
|
||||
variation: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default ModalView;
|
||||
|
||||
@@ -14,15 +14,31 @@ import { findCoursesNavDropdownClicked } from '../hooks';
|
||||
|
||||
import ModalView from '../../../components/ModalView';
|
||||
import messages from '../messages';
|
||||
import { useRecommendationsModal } from '../../../components/ModalView/hooks';
|
||||
import usePaintedDoorExperimentContext from '../../../RecsPaintedDoorExpContext';
|
||||
import {
|
||||
trackPaintedDoorRecommendationHomeBtnClicked,
|
||||
} from '../../../widgets/RecommendationsPanel/recsPaintedDoorExpTrack';
|
||||
|
||||
export const CollapseMenuBody = ({ isOpen, isRecommendationsModalOpen, setIsRecommendationsModalOpen }) => {
|
||||
export const CollapseMenuBody = ({ isOpen }) => {
|
||||
const { formatMessage } = useIntl();
|
||||
const { authenticatedUser } = React.useContext(AppContext);
|
||||
|
||||
const dashboard = reduxHooks.useEnterpriseDashboardData();
|
||||
const { courseSearchUrl } = reduxHooks.usePlatformSettingsData();
|
||||
const { isRecommendationsModalOpen, toggleRecommendationsModal } = useRecommendationsModal();
|
||||
|
||||
const exploreCoursesClick = findCoursesNavDropdownClicked(urls.baseAppUrl(courseSearchUrl));
|
||||
const {
|
||||
experimentVariation,
|
||||
isPaintedDoorNavbarBtnVariation,
|
||||
experimentLoading,
|
||||
} = usePaintedDoorExperimentContext();
|
||||
|
||||
const handleSeeAllRecommendationsClick = () => {
|
||||
toggleRecommendationsModal();
|
||||
trackPaintedDoorRecommendationHomeBtnClicked(experimentVariation);
|
||||
};
|
||||
|
||||
return (
|
||||
isOpen && (
|
||||
@@ -41,12 +57,14 @@ export const CollapseMenuBody = ({ isOpen, isRecommendationsModalOpen, setIsReco
|
||||
>
|
||||
{formatMessage(messages.discoverNew)}
|
||||
</Button>
|
||||
{(!experimentLoading && isPaintedDoorNavbarBtnVariation) && (
|
||||
<Button
|
||||
variant="inverse-primary"
|
||||
onClick={setIsRecommendationsModalOpen}
|
||||
onClick={handleSeeAllRecommendationsClick}
|
||||
>
|
||||
{formatMessage(messages.recommendedForYou)}
|
||||
</Button>
|
||||
)}
|
||||
<Button as="a" href={getConfig().SUPPORT_URL} variant="inverse-primary">
|
||||
{formatMessage(messages.help)}
|
||||
</Button>
|
||||
@@ -99,7 +117,11 @@ export const CollapseMenuBody = ({ isOpen, isRecommendationsModalOpen, setIsReco
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
<ModalView isOpen={isRecommendationsModalOpen} onClose={setIsRecommendationsModalOpen} />
|
||||
<ModalView
|
||||
isOpen={isRecommendationsModalOpen}
|
||||
onClose={toggleRecommendationsModal}
|
||||
variation={experimentVariation}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
@@ -107,13 +129,6 @@ export const CollapseMenuBody = ({ isOpen, isRecommendationsModalOpen, setIsReco
|
||||
|
||||
CollapseMenuBody.propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
isRecommendationsModalOpen: PropTypes.bool,
|
||||
setIsRecommendationsModalOpen: PropTypes.func,
|
||||
};
|
||||
|
||||
CollapseMenuBody.defaultProps = {
|
||||
isRecommendationsModalOpen: false,
|
||||
setIsRecommendationsModalOpen: () => {},
|
||||
};
|
||||
|
||||
export default CollapseMenuBody;
|
||||
|
||||
@@ -30,6 +30,13 @@ jest.mock('hooks', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('../../../components/ModalView/hooks', () => ({
|
||||
useRecommendationsModal: jest.fn(() => ({
|
||||
isRecommendationsModalOpen: false,
|
||||
toggleRecommendationsModal: jest.fn(),
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock('../hooks', () => ({
|
||||
findCoursesNavDropdownClicked: (url) => jest.fn().mockName(`findCoursesNavDropdownClicked("${url}")`),
|
||||
}));
|
||||
|
||||
@@ -26,12 +26,6 @@ exports[`CollapseMenuBody render 1`] = `
|
||||
>
|
||||
Discover New
|
||||
</Button>
|
||||
<Button
|
||||
onClick={[Function]}
|
||||
variant="inverse-primary"
|
||||
>
|
||||
Recommended for you
|
||||
</Button>
|
||||
<Button
|
||||
as="a"
|
||||
variant="inverse-primary"
|
||||
@@ -72,10 +66,6 @@ exports[`CollapseMenuBody render 1`] = `
|
||||
>
|
||||
Sign Out
|
||||
</Button>
|
||||
<ModalView
|
||||
isOpen={false}
|
||||
onClose={[Function]}
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -107,21 +97,11 @@ exports[`CollapseMenuBody render unauthenticated 1`] = `
|
||||
>
|
||||
Discover New
|
||||
</Button>
|
||||
<Button
|
||||
onClick={[Function]}
|
||||
variant="inverse-primary"
|
||||
>
|
||||
Recommended for you
|
||||
</Button>
|
||||
<Button
|
||||
as="a"
|
||||
variant="inverse-primary"
|
||||
>
|
||||
Help
|
||||
</Button>
|
||||
<ModalView
|
||||
isOpen={false}
|
||||
onClose={[Function]}
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -20,8 +20,6 @@ exports[`CollapsedHeader renders 1`] = `
|
||||
</header>
|
||||
<mockConstructor
|
||||
isOpen={false}
|
||||
isRecommendationsModalOpen={false}
|
||||
setIsRecommendationsModalOpen={[MockFunction]}
|
||||
/>
|
||||
</Fragment>
|
||||
`;
|
||||
@@ -45,8 +43,6 @@ exports[`CollapsedHeader renders with isOpen true 1`] = `
|
||||
</header>
|
||||
<mockConstructor
|
||||
isOpen={true}
|
||||
isRecommendationsModalOpen={false}
|
||||
setIsRecommendationsModalOpen={[MockFunction]}
|
||||
/>
|
||||
</Fragment>
|
||||
`;
|
||||
|
||||
@@ -5,7 +5,6 @@ import { Menu, Close } from '@edx/paragon/icons';
|
||||
import { IconButton, Icon } from '@edx/paragon';
|
||||
|
||||
import { useLearnerDashboardHeaderData, useIsCollapsed } from '../hooks';
|
||||
import { useRecommendationsModal } from '../../../components/ModalView/hooks';
|
||||
|
||||
import CollapseMenuBody from './CollapseMenuBody';
|
||||
import BrandLogo from '../BrandLogo';
|
||||
@@ -16,7 +15,6 @@ export const CollapsedHeader = () => {
|
||||
const { formatMessage } = useIntl();
|
||||
const isCollapsed = useIsCollapsed();
|
||||
const { isOpen, toggleIsOpen } = useLearnerDashboardHeaderData();
|
||||
const { isRecommendationsModalOpen, toggleRecommendationsModal } = useRecommendationsModal();
|
||||
|
||||
return (
|
||||
isCollapsed && (
|
||||
@@ -40,8 +38,6 @@ export const CollapsedHeader = () => {
|
||||
</header>
|
||||
<CollapseMenuBody
|
||||
isOpen={isOpen}
|
||||
setIsRecommendationsModalOpen={toggleRecommendationsModal}
|
||||
isRecommendationsModalOpen={isRecommendationsModalOpen}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -3,7 +3,6 @@ import { shallow } from 'enzyme';
|
||||
import CollapsedHeader from '.';
|
||||
|
||||
import { useLearnerDashboardHeaderData, useIsCollapsed } from '../hooks';
|
||||
import { useRecommendationsModal } from '../../../components/ModalView/hooks';
|
||||
|
||||
jest.mock('../BrandLogo', () => jest.fn(() => 'BrandLogo'));
|
||||
jest.mock('./CollapseMenuBody', () => jest.fn(() => 'CollapseMenuBody'));
|
||||
@@ -16,26 +15,14 @@ jest.mock('../hooks', () => ({
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock('../../../components/ModalView/hooks', () => ({
|
||||
useRecommendationsModal: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('CollapsedHeader', () => {
|
||||
it('renders', () => {
|
||||
useRecommendationsModal.mockReturnValueOnce({
|
||||
isRecommendationsModalOpen: false,
|
||||
toggleRecommendationsModal: jest.fn(),
|
||||
});
|
||||
const wrapper = shallow(<CollapsedHeader />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('render nothing if not collapsed', () => {
|
||||
useIsCollapsed.mockReturnValueOnce(false);
|
||||
useRecommendationsModal.mockReturnValueOnce({
|
||||
isRecommendationsModalOpen: false,
|
||||
toggleRecommendationsModal: jest.fn(),
|
||||
});
|
||||
const wrapper = shallow(<CollapsedHeader />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
@@ -45,10 +32,6 @@ describe('CollapsedHeader', () => {
|
||||
isOpen: true,
|
||||
toggleIsOpen: jest.fn().mockName('toggleIsOpen'),
|
||||
});
|
||||
useRecommendationsModal.mockReturnValueOnce({
|
||||
isRecommendationsModalOpen: false,
|
||||
toggleRecommendationsModal: jest.fn(),
|
||||
});
|
||||
const wrapper = shallow(<CollapsedHeader />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
@@ -33,13 +33,6 @@ exports[`ExpandedHeader render 1`] = `
|
||||
>
|
||||
Discover New
|
||||
</Button>
|
||||
<Button
|
||||
className="p-4"
|
||||
onClick={[MockFunction]}
|
||||
variant="inverse-primary"
|
||||
>
|
||||
Recommended for you
|
||||
</Button>
|
||||
<span
|
||||
className="flex-grow-1"
|
||||
/>
|
||||
@@ -53,10 +46,6 @@ exports[`ExpandedHeader render 1`] = `
|
||||
</Button>
|
||||
</div>
|
||||
<AuthenticatedUserDropdown />
|
||||
<ModalView
|
||||
isOpen={false}
|
||||
onClose={[MockFunction]}
|
||||
/>
|
||||
</header>
|
||||
`;
|
||||
|
||||
|
||||
@@ -14,6 +14,10 @@ import { useRecommendationsModal } from '../../../components/ModalView/hooks';
|
||||
import messages from '../messages';
|
||||
import ModalView from '../../../components/ModalView';
|
||||
import BrandLogo from '../BrandLogo';
|
||||
import usePaintedDoorExperimentContext from '../../../RecsPaintedDoorExpContext';
|
||||
import {
|
||||
trackPaintedDoorRecommendationHomeBtnClicked,
|
||||
} from '../../../widgets/RecommendationsPanel/recsPaintedDoorExpTrack';
|
||||
|
||||
export const ExpandedHeader = () => {
|
||||
const { formatMessage } = useIntl();
|
||||
@@ -22,6 +26,21 @@ export const ExpandedHeader = () => {
|
||||
const { isRecommendationsModalOpen, toggleRecommendationsModal } = useRecommendationsModal();
|
||||
|
||||
const exploreCoursesClick = findCoursesNavClicked(urls.baseAppUrl(courseSearchUrl));
|
||||
const {
|
||||
experimentVariation,
|
||||
isPaintedDoorNavbarBtnVariation,
|
||||
experimentLoading,
|
||||
} = usePaintedDoorExperimentContext();
|
||||
|
||||
console.log('test ', {
|
||||
experimentVariation,
|
||||
isPaintedDoorNavbarBtnVariation,
|
||||
experimentLoading,
|
||||
});
|
||||
const handleSeeAllRecommendationsClick = () => {
|
||||
toggleRecommendationsModal();
|
||||
trackPaintedDoorRecommendationHomeBtnClicked(experimentVariation);
|
||||
};
|
||||
|
||||
return (
|
||||
!isCollapsed && (
|
||||
@@ -54,13 +73,15 @@ export const ExpandedHeader = () => {
|
||||
>
|
||||
{formatMessage(messages.discoverNew)}
|
||||
</Button>
|
||||
{(!experimentLoading && isPaintedDoorNavbarBtnVariation) && (
|
||||
<Button
|
||||
variant="inverse-primary"
|
||||
className="p-4"
|
||||
onClick={toggleRecommendationsModal}
|
||||
onClick={handleSeeAllRecommendationsClick}
|
||||
>
|
||||
{formatMessage(messages.recommendedForYou)}
|
||||
</Button>
|
||||
)}
|
||||
<span className="flex-grow-1" />
|
||||
<Button
|
||||
as="a"
|
||||
@@ -73,7 +94,11 @@ export const ExpandedHeader = () => {
|
||||
</div>
|
||||
|
||||
<AuthenticatedUserDropdown />
|
||||
<ModalView isOpen={isRecommendationsModalOpen} onClose={toggleRecommendationsModal} />
|
||||
<ModalView
|
||||
isOpen={isRecommendationsModalOpen}
|
||||
onClose={toggleRecommendationsModal}
|
||||
variation={experimentVariation}
|
||||
/>
|
||||
</header>
|
||||
)
|
||||
);
|
||||
|
||||
@@ -8,7 +8,7 @@ import { reduxHooks } from 'hooks';
|
||||
import moreCoursesSVG from 'assets/more-courses-sidewidget.svg';
|
||||
import { baseAppUrl } from 'data/services/lms/urls';
|
||||
|
||||
import track from '../RecommendationsPanel/track';
|
||||
import track, { trackRecommendationUnavailable } from '../RecommendationsPanel/track';
|
||||
import messages from './messages';
|
||||
import './index.scss';
|
||||
|
||||
@@ -17,6 +17,11 @@ export const arrowIcon = (<Icon className="mx-1" src={ArrowForward} />);
|
||||
export const LookingForChallengeWidget = () => {
|
||||
const { formatMessage } = useIntl();
|
||||
const { courseSearchUrl } = reduxHooks.usePlatformSettingsData();
|
||||
|
||||
React.useEffect(() => {
|
||||
trackRecommendationUnavailable();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Card orientation="horizontal" id="looking-for-challenge-widget">
|
||||
<Card.ImageCap
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import React from 'react';
|
||||
import LookingForChallengeWidget from '.';
|
||||
import { trackRecommendationUnavailable } from '../RecommendationsPanel/track';
|
||||
|
||||
jest.mock('hooks', () => ({
|
||||
reduxHooks: {
|
||||
@@ -12,6 +14,7 @@ jest.mock('hooks', () => ({
|
||||
|
||||
jest.mock('../RecommendationsPanel/track', () => ({
|
||||
findCoursesWidgetClicked: (href) => jest.fn().mockName(`track.findCoursesWidgetClicked('${href}')`),
|
||||
trackRecommendationUnavailable: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('LookingForChallengeWidget', () => {
|
||||
@@ -21,4 +24,10 @@ describe('LookingForChallengeWidget', () => {
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
test('test recommendations unavailable event is fired', () => {
|
||||
shallow(<LookingForChallengeWidget />);
|
||||
const [cb] = React.useEffect.mock.calls[0];
|
||||
cb();
|
||||
expect(trackRecommendationUnavailable).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,22 +1,40 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { Button } from '@edx/paragon';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Button } from '@edx/paragon';
|
||||
import { Search } from '@edx/paragon/icons';
|
||||
import { baseAppUrl } from 'data/services/lms/urls';
|
||||
|
||||
import { reduxHooks } from 'hooks';
|
||||
import track from './track';
|
||||
import CourseCard from './components/CourseCard';
|
||||
import messages from './messages';
|
||||
import ModalView from '../../components/ModalView';
|
||||
|
||||
import './index.scss';
|
||||
import usePaintedDoorExperimentContext from '../../RecsPaintedDoorExpContext';
|
||||
import { useRecommendationsModal } from '../../components/ModalView/hooks';
|
||||
import { trackPaintedDoorRecommendationHomeBtnClicked } from './recsPaintedDoorExpTrack';
|
||||
|
||||
export const LoadedView = ({
|
||||
courses,
|
||||
isControl,
|
||||
setIsRecommendationsModalOpen,
|
||||
isRecommendationsModalOpen,
|
||||
}) => {
|
||||
const { formatMessage } = useIntl();
|
||||
const { courseSearchUrl } = reduxHooks.usePlatformSettingsData();
|
||||
const { isRecommendationsModalOpen, toggleRecommendationsModal } = useRecommendationsModal();
|
||||
|
||||
const {
|
||||
experimentVariation,
|
||||
isPaintedDoorWidgetBtnVariation,
|
||||
experimentLoading,
|
||||
} = usePaintedDoorExperimentContext();
|
||||
|
||||
const handleSeeAllRecommendationsClick = () => {
|
||||
toggleRecommendationsModal();
|
||||
trackPaintedDoorRecommendationHomeBtnClicked(experimentVariation);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -34,15 +52,31 @@ export const LoadedView = ({
|
||||
))}
|
||||
</div>
|
||||
<div className="text-center explore-courses-btn">
|
||||
<Button
|
||||
variant="brand"
|
||||
onClick={setIsRecommendationsModalOpen}
|
||||
>
|
||||
{formatMessage(messages.seeAllRecommendationsButton)}
|
||||
</Button>
|
||||
{!experimentLoading && isPaintedDoorWidgetBtnVariation ? (
|
||||
<Button
|
||||
variant="brand"
|
||||
onClick={handleSeeAllRecommendationsClick}
|
||||
>
|
||||
{formatMessage(messages.seeAllRecommendationsButton)}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
variant="tertiary"
|
||||
iconBefore={Search}
|
||||
as="a"
|
||||
href={baseAppUrl(courseSearchUrl)}
|
||||
onClick={track.findCoursesWidgetClicked(baseAppUrl(courseSearchUrl))}
|
||||
>
|
||||
{formatMessage(messages.exploreCoursesButton)}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<ModalView isOpen={isRecommendationsModalOpen} onClose={setIsRecommendationsModalOpen} />
|
||||
<ModalView
|
||||
isOpen={isRecommendationsModalOpen}
|
||||
onClose={toggleRecommendationsModal}
|
||||
variation={experimentVariation}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -59,8 +93,6 @@ LoadedView.propTypes = {
|
||||
marketingUrl: PropTypes.string,
|
||||
})).isRequired,
|
||||
isControl: PropTypes.oneOf([true, false, null]),
|
||||
setIsRecommendationsModalOpen: PropTypes.func.isRequired,
|
||||
isRecommendationsModalOpen: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default LoadedView;
|
||||
|
||||
@@ -19,6 +19,12 @@ jest.mock('./track', () => ({
|
||||
findCoursesWidgetClicked: (href) => jest.fn().mockName(`track.findCoursesWidgetClicked('${href}')`),
|
||||
}));
|
||||
jest.mock('./components/CourseCard', () => 'CourseCard');
|
||||
jest.mock('../../components/ModalView/hooks', () => ({
|
||||
useRecommendationsModal: jest.fn(() => ({
|
||||
isRecommendationsModalOpen: false,
|
||||
toggleRecommendationsModal: jest.fn(),
|
||||
})),
|
||||
}));
|
||||
|
||||
describe('RecommendationsPanel LoadedView', () => {
|
||||
const props = {
|
||||
|
||||
@@ -64,17 +64,16 @@ exports[`RecommendationsPanel LoadedView snapshot with personalize recommendatio
|
||||
className="text-center explore-courses-btn"
|
||||
>
|
||||
<Button
|
||||
onClick={[Function]}
|
||||
variant="brand"
|
||||
as="a"
|
||||
href="http://localhost:18000/course-search-url"
|
||||
iconBefore={[MockFunction icons.Search]}
|
||||
onClick={[MockFunction track.findCoursesWidgetClicked('http://localhost:18000/course-search-url')]}
|
||||
variant="tertiary"
|
||||
>
|
||||
See All Recommendations
|
||||
Explore courses
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<ModalView
|
||||
isOpen={false}
|
||||
onClose={[Function]}
|
||||
/>
|
||||
</Fragment>
|
||||
`;
|
||||
|
||||
@@ -142,16 +141,15 @@ exports[`RecommendationsPanel LoadedView snapshot without personalize recommenda
|
||||
className="text-center explore-courses-btn"
|
||||
>
|
||||
<Button
|
||||
onClick={[Function]}
|
||||
variant="brand"
|
||||
as="a"
|
||||
href="http://localhost:18000/course-search-url"
|
||||
iconBefore={[MockFunction icons.Search]}
|
||||
onClick={[MockFunction track.findCoursesWidgetClicked('http://localhost:18000/course-search-url')]}
|
||||
variant="tertiary"
|
||||
>
|
||||
See All Recommendations
|
||||
Explore courses
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<ModalView
|
||||
isOpen={false}
|
||||
onClose={[Function]}
|
||||
/>
|
||||
</Fragment>
|
||||
`;
|
||||
|
||||
@@ -4,7 +4,6 @@ import LookingForChallengeWidget from 'widgets/LookingForChallengeWidget';
|
||||
import LoadingView from './LoadingView';
|
||||
import LoadedView from './LoadedView';
|
||||
import hooks from './hooks';
|
||||
import { useRecommendationsModal } from '../../components/ModalView/hooks';
|
||||
|
||||
export const RecommendationsPanel = () => {
|
||||
const {
|
||||
@@ -15,8 +14,6 @@ export const RecommendationsPanel = () => {
|
||||
isLoading,
|
||||
} = hooks.useRecommendationPanelData();
|
||||
|
||||
const { isRecommendationsModalOpen, toggleRecommendationsModal } = useRecommendationsModal();
|
||||
|
||||
if (isLoading) {
|
||||
return (<LoadingView />);
|
||||
}
|
||||
@@ -25,8 +22,6 @@ export const RecommendationsPanel = () => {
|
||||
<LoadedView
|
||||
courses={courses}
|
||||
isControl={isControl}
|
||||
setIsRecommendationsModalOpen={toggleRecommendationsModal}
|
||||
isRecommendationsModalOpen={isRecommendationsModalOpen}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
35
src/widgets/RecommendationsPanel/recsPaintedDoorExpTrack.js
Normal file
35
src/widgets/RecommendationsPanel/recsPaintedDoorExpTrack.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import { StrictDict } from 'utils';
|
||||
import { createEventTracker } from 'data/services/segment/utils';
|
||||
|
||||
export const eventNames = StrictDict({
|
||||
variationGroup: 'edx.bi.user.recommendation_home.variation.group',
|
||||
recommendationHomeBtnClicked: 'edx.bi.user.recommendation_home.btn.clicked',
|
||||
recommendationHomeModalInterestBtnClicked: 'edx.bi.user.recommendation_home.modal.interest_btn.clicked',
|
||||
recommendationHomeModalSkipBtnClicked: 'edx.bi.user.recommendation_home.modal.skip_btn.clicked',
|
||||
});
|
||||
|
||||
export const trackPaintedDoorVariationGroup = (variation) => {
|
||||
createEventTracker(eventNames.variationGroup, {
|
||||
variation,
|
||||
page: 'dashboard',
|
||||
});
|
||||
};
|
||||
|
||||
export const trackPaintedDoorRecommendationHomeBtnClicked = (variation) => {
|
||||
createEventTracker(eventNames.recommendationHomeBtnClicked, {
|
||||
variation,
|
||||
page: 'dashboard',
|
||||
});
|
||||
};
|
||||
|
||||
export const trackPaintedDoorRecommendationHomeInterestBtnClicked = (variation) => {
|
||||
createEventTracker(eventNames.recommendationHomeModalInterestBtnClicked, {
|
||||
variation,
|
||||
});
|
||||
};
|
||||
|
||||
export const trackPaintedDoorRecommendationHomeSkipBtnClicked = (variation) => {
|
||||
createEventTracker(eventNames.recommendationHomeModalSkipBtnClicked, {
|
||||
variation,
|
||||
});
|
||||
};
|
||||
@@ -4,6 +4,7 @@ import track from 'tracking';
|
||||
|
||||
export const eventNames = StrictDict({
|
||||
recommendedCourseClicked: 'edx.bi.user.recommended.course.click',
|
||||
recommendationsUnavailable: 'edx.bi.user.recommendations.unavailable',
|
||||
});
|
||||
|
||||
export const linkNames = StrictDict({
|
||||
@@ -23,7 +24,15 @@ export const recommendedCourseClicked = (courseKey, isControl, href) => createLi
|
||||
href,
|
||||
);
|
||||
|
||||
export const trackRecommendationUnavailable = () => {
|
||||
createEventTracker(eventNames.recommendationsUnavailable, {
|
||||
type: 'personalized',
|
||||
page: 'dashboard',
|
||||
});
|
||||
};
|
||||
|
||||
export default {
|
||||
findCoursesWidgetClicked,
|
||||
recommendedCourseClicked,
|
||||
trackRecommendationUnavailable,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user