Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add bottom widget container #93

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,4 @@ ZENDESK_KEY=''
HOTJAR_APP_ID=''
HOTJAR_VERSION='6'
HOTJAR_DEBUG=''
MERCHANDISE_2U_LOB_EXP_ID=''
1 change: 1 addition & 0 deletions .env.development
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,4 @@ ZENDESK_KEY=''
HOTJAR_APP_ID=''
HOTJAR_VERSION='6'
HOTJAR_DEBUG=''
MERCHANDISE_2U_LOB_EXP_ID=''
2 changes: 2 additions & 0 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import track from 'tracking';
import fakeData from 'data/services/lms/fakeData/courses';

import messages from './messages';
import getOptimizely from './optimizely';

import './App.scss';

Expand Down Expand Up @@ -78,6 +79,7 @@ export const App = () => {
<Router>
<Helmet>
<title>{formatMessage(messages.pageTitle)}</title>
<script {...getOptimizely()} />
</Helmet>
<div>
<LearnerDashboardHeader />
Expand Down
8 changes: 7 additions & 1 deletion src/containers/Dashboard/DashboardLayout.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const columnConfig = {
},
};

export const DashboardLayout = ({ children, sidebar }) => {
export const DashboardLayout = ({ bottomWidgetContainer, children, sidebar }) => {
const isCollapsed = hooks.useIsDashboardCollapsed();

return (
Expand All @@ -30,10 +30,16 @@ export const DashboardLayout = ({ children, sidebar }) => {
{sidebar}
</Col>
</Row>
<Row>
<Col className="my-5.5">
{bottomWidgetContainer}
</Col>
</Row>
</Container>
);
};
DashboardLayout.propTypes = {
bottomWidgetContainer: PropTypes.node.isRequired,
children: PropTypes.node.isRequired,
sidebar: PropTypes.node.isRequired,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ exports[`DashboardLayout collapsed snapshot 1`] = `
test-sidebar-content
</Col>
</Row>
<Row>
<Col
className="my-5.5"
/>
</Row>
</Container>
`;

Expand Down Expand Up @@ -90,5 +95,10 @@ exports[`DashboardLayout not collapsed snapshot 1`] = `
test-sidebar-content
</Col>
</Row>
<Row>
<Col
className="my-5.5"
/>
</Row>
</Container>
`;
2 changes: 2 additions & 0 deletions src/containers/Dashboard/__snapshots__/index.test.jsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ exports[`Dashboard snapshots courses loaded, show select session modal, no avail
id="dashboard-content"
>
<DashboardLayout
bottomWidgetContainer={<BottomWidgetContainer />}
sidebar={<LoadedWidgetSidebar />}
>
<CourseList />
Expand Down Expand Up @@ -56,6 +57,7 @@ exports[`Dashboard snapshots there are no courses, there ARE available dashboard
id="dashboard-content"
>
<DashboardLayout
bottomWidgetContainer={<BottomWidgetContainer />}
sidebar={<NoCoursesWidgetSidebar />}
>
<CourseList />
Expand Down
6 changes: 5 additions & 1 deletion src/containers/Dashboard/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import EnterpriseDashboardModal from 'containers/EnterpriseDashboardModal';
import SelectSessionModal from 'containers/SelectSessionModal';
import CourseList from 'containers/CourseList';

import BottomWidgetContainer from 'containers/WidgetContainers/BottomWidgetContainer';
import LoadedSidebar from 'containers/WidgetContainers/LoadedSidebar';
import NoCoursesSidebar from 'containers/WidgetContainers/NoCoursesSidebar';

Expand Down Expand Up @@ -34,7 +35,10 @@ export const Dashboard = () => {
{initIsPending
? (<LoadingView />)
: (
<DashboardLayout sidebar={hasCourses ? <LoadedSidebar /> : <NoCoursesSidebar />}>
<DashboardLayout
sidebar={hasCourses ? <LoadedSidebar /> : <NoCoursesSidebar />}
bottomWidgetContainer={<BottomWidgetContainer />}
>
<CourseList />
</DashboardLayout>
)}
Expand Down
15 changes: 13 additions & 2 deletions src/containers/Dashboard/index.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import EnterpriseDashboardModal from 'containers/EnterpriseDashboardModal';
import SelectSessionModal from 'containers/SelectSessionModal';
import CourseList from 'containers/CourseList';

import BottomWidgetContainer from 'containers/WidgetContainers/BottomWidgetContainer';
import LoadedWidgetSidebar from 'containers/WidgetContainers/LoadedSidebar';
import NoCoursesWidgetSidebar from 'containers/WidgetContainers/NoCoursesSidebar';

Expand Down Expand Up @@ -121,7 +122,12 @@ describe('Dashboard', () => {
showSelectSessionModal: true,
},
content: ['LoadedView', (
<DashboardLayout sidebar={<LoadedWidgetSidebar />}><CourseList /></DashboardLayout>
<DashboardLayout
sidebar={<LoadedWidgetSidebar />}
bottomWidgetContainer={<BottomWidgetContainer />}
>
<CourseList />
</DashboardLayout>
)],
showEnterpriseModal: false,
showSelectSessionModal: true,
Expand All @@ -137,7 +143,12 @@ describe('Dashboard', () => {
showSelectSessionModal: false,
},
content: ['Dashboard layout with no courses sidebar and content', (
<DashboardLayout sidebar={<NoCoursesWidgetSidebar />}><CourseList /></DashboardLayout>
<DashboardLayout
sidebar={<NoCoursesWidgetSidebar />}
bottomWidgetContainer={<BottomWidgetContainer />}
>
<CourseList />
</DashboardLayout>
)],
showEnterpriseModal: true,
showSelectSessionModal: false,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`BottomWidgetContainer snapshots default 1`] = `<StaticCallouts />`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from 'react';

import TwoUWidget from 'widgets/TwoUWidget';

export const BottomWidgetContainer = () => (
<TwoUWidget />
);

export default BottomWidgetContainer;
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { shallow } from 'enzyme';

import BottomWidgetContainer from '.';

describe('BottomWidgetContainer', () => {
describe('snapshots', () => {
test('default', () => {
const wrapper = shallow(<BottomWidgetContainer />);
expect(wrapper).toMatchSnapshot();
});
});
});
5 changes: 4 additions & 1 deletion src/data/services/segment/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@ export const createEventTracker = (name, options = {}) => () => sendTrackEvent(
{ ...options, app_name: appName },
);

export const createLinkTracker = (tracker, href) => (e) => {
export const createLinkTracker = (tracker, href, openInNewTab = false) => (e) => {
e.preventDefault();
tracker();
if (openInNewTab) {
return setTimeout(() => { global.open(href, '_blank'); }, LINK_TIMEOUT);
}
return setTimeout(() => { global.location.href = href; }, LINK_TIMEOUT);
};
12 changes: 12 additions & 0 deletions src/optimizely.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const getOptimizely = () => {
const prodSrc = `${process.env.MARKETING_SITE_BASE_URL}/optimizelyjs/${process.env.OPTIMIZELY_PROJECT_ID}.js`;
const devSrc = `https://cdn.optimizely.com/js/${process.env.OPTIMIZELY_PROJECT_ID}.js`;
const src = process.env.NODE_ENV === 'development' ? devSrc : prodSrc;

return {
src,
async: true,
};
};

export default getOptimizely;
73 changes: 73 additions & 0 deletions src/widgets/TwoUWidget/LoadedView.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';

import { useIntl } from '@edx/frontend-platform/i18n';
import { Badge, Hyperlink, Icon } from '@edx/paragon';
import { ArrowForward } from '@edx/paragon/icons';

import { useIsMediumScreen } from './hooks';
import { getMerchandisingItems, countryCodeUS } from './constants';
import messages from './messages';
import track from './track';

export const LoadedView = ({ countryCode, show2ULobs }) => {
const { formatMessage } = useIntl();
const merchandisingItems = getMerchandisingItems(countryCode);
const isMediumScreen = useIsMediumScreen();
const markup = { markup: children => <span className="d-inline-flex mb-0 text-brand-500">{children}</span> };

const onClickHandler = (program, href) => {
track.twoUWidgetCardClicked(program, href);
};

const items = merchandisingItems.map(item => (
<Hyperlink
destination={item.itemURL}
key={item.key}
onClick={() => onClickHandler(item.key, item.itemURL)}
target="_blank"
showLaunchIcon={false}
className={classNames('static-callouts-item', {
'static-callouts-item-md': isMediumScreen && countryCode === countryCodeUS,
})}
>
<Badge variant="warning" className="item-badge mb-2">{formatMessage(messages.badgeTitle)}</Badge>
<h3>
{formatMessage(item.title)}
</h3>
<div className="card-description">
{formatMessage(item.description)}
<Icon className="d-inline-block align-middle mx-1" src={ArrowForward} />
</div>
</Hyperlink>
));

return (
show2ULobs && (
<div className="callouts-container">
<div>
<h2>
{formatMessage(messages.heading, markup)}
</h2>
<p className="mb-0 text-gray-700">
{formatMessage(messages.subheading)}
</p>
</div>
<div className={classNames('static-callouts-container', {
'static-callouts-container-md': isMediumScreen && countryCode === countryCodeUS,
})}
>
{items}
</div>
</div>
)
);
};

LoadedView.propTypes = {
countryCode: PropTypes.string.isRequired,
show2ULobs: PropTypes.bool.isRequired,
};

export default LoadedView;
25 changes: 25 additions & 0 deletions src/widgets/TwoUWidget/LoadedView.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from 'react';
import { shallow } from 'enzyme';

import LoadedView from './LoadedView';

jest.mock('./hooks', () => ({
useIsMediumScreen: jest.fn(() => false),
useOptimizelyExperiment: jest.fn(),
useTwoUWidgetData: jest.fn(),
}));

describe('LoadedView tests', () => {
it('Do not display static LOB cards if the experiment is not active', () => {
const wrapper = shallow(<LoadedView countryCode="PK" show2ULobs={false} />);
expect(wrapper.isEmptyRender()).toBe(true);
});
it('Display 4 static LOB cards for US users', () => {
const wrapper = shallow(<LoadedView countryCode="US" show2ULobs />);
expect(wrapper.find('div.static-callouts-container').children()).toHaveLength(4);
});
it('Display 3 static LOB cards for non-US users', () => {
const wrapper = shallow(<LoadedView countryCode="PK" show2ULobs />);
expect(wrapper.find('div.static-callouts-container').children()).toHaveLength(3);
});
});
11 changes: 11 additions & 0 deletions src/widgets/TwoUWidget/LoadingView.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react';
import { Skeleton } from '@edx/paragon';

export const LoadingView = () => (
<div className="loading-callouts-container">
<h2 className="skeleton-title-width"><Skeleton /></h2>
<p><Skeleton /></p>
</div>
);

export default LoadingView;
10 changes: 10 additions & 0 deletions src/widgets/TwoUWidget/LoadingView.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from 'react';
import { shallow } from 'enzyme';

import LoadingView from './LoadingView';

describe('TwoUWidget LoadingView', () => {
test('snapshot', () => {
expect(shallow(<LoadingView />)).toMatchSnapshot();
});
});
16 changes: 16 additions & 0 deletions src/widgets/TwoUWidget/__snapshots__/LoadingView.test.jsx.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`TwoUWidget LoadingView snapshot 1`] = `
<div
className="loading-callouts-container"
>
<h2
className="skeleton-title-width"
>
<Component />
</h2>
<p>
<Component />
</p>
</div>
`;
3 changes: 3 additions & 0 deletions src/widgets/TwoUWidget/__snapshots__/index.test.jsx.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`TwoUWidget tests TwoUWidget snapshot 1`] = `""`;
10 changes: 10 additions & 0 deletions src/widgets/TwoUWidget/api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { get, stringifyUrl } from 'data/services/lms/utils';
import urls from 'data/services/lms/urls';

export const fetchUrl = `${urls.api}/learner_home/twou_widget_context/`;

const fetchTwoUWidgetContext = () => get(stringifyUrl(fetchUrl));

export default {
fetchTwoUWidgetContext,
};
17 changes: 17 additions & 0 deletions src/widgets/TwoUWidget/api.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { get, stringifyUrl } from 'data/services/lms/utils';
import api, { fetchUrl } from './api';

jest.mock('data/services/lms/utils', () => ({
stringifyUrl: (...args) => ({ stringifyUrl: args }),
get: (...args) => ({ get: args }),
}));

describe('twoUWidgetContext api', () => {
describe('fetchTwoUWidgetContext', () => {
it('call gets the country code of the user', () => {
expect(api.fetchTwoUWidgetContext()).toEqual(
get(stringifyUrl(fetchUrl)),
);
});
});
});
Loading