Leangseu edx/enterprise portal (#5)

* feat: implement enterprise dashboard

* chore: update test
This commit is contained in:
leangseu-edx
2022-08-04 13:21:11 -04:00
committed by GitHub
parent 776c6989bd
commit 6d34853d09
10 changed files with 422 additions and 45 deletions

View File

@@ -2,50 +2,51 @@ import React from 'react';
import PropTypes from 'prop-types';
import { getConfig } from '@edx/frontend-platform';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Dropdown, Icon } from '@edx/paragon';
import { Person } from '@edx/paragon/icons';
import messages from './messages';
import EnterpriseDashboard from './EnterpriseDashboard';
export const AuthenticatedUserDropdown = ({ intl, username }) => (
<>
<Dropdown className="user-dropdown">
<Dropdown.Toggle id="user" variant="primary">
<Icon src={Person} />
<span data-hj-suppress className="d-none d-md-inline">
{username}
</span>
</Dropdown.Toggle>
<Dropdown.Menu className="dropdown-menu-right">
<Dropdown.Item href={`${getConfig().LMS_BASE_URL}/dashboard`}>
{intl.formatMessage(messages.dashboard)}
</Dropdown.Item>{' '}
<Dropdown.Item href={`${getConfig().LMS_BASE_URL}/u/${username}`}>
{intl.formatMessage(messages.profile)}
</Dropdown.Item>
<Dropdown.Item href={`${getConfig().LMS_BASE_URL}/account/settings`}>
{intl.formatMessage(messages.account)}
</Dropdown.Item>
{getConfig().ORDER_HISTORY_URL && (
<Dropdown.Item href={getConfig().ORDER_HISTORY_URL}>
{intl.formatMessage(messages.orderHistory)}
export const AuthenticatedUserDropdown = ({ username }) => {
const { formatMessage } = useIntl();
return (
<>
<Dropdown className="user-dropdown">
<Dropdown.Toggle id="user" variant="primary">
<Icon src={Person} />
<span data-hj-suppress className="d-none d-md-inline">
{username}
</span>
</Dropdown.Toggle>
<Dropdown.Menu className="dropdown-menu-right">
<EnterpriseDashboard />
<Dropdown.Item href={`${getConfig().LMS_BASE_URL}/u/${username}`}>
{formatMessage(messages.profile)}
</Dropdown.Item>
)}
<Dropdown.Item href={getConfig().SUPPORT_URL}>
{intl.formatMessage(messages.help)}
</Dropdown.Item>
<Dropdown.Item href={getConfig().LOGOUT_URL}>
{intl.formatMessage(messages.signOut)}
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
</>
);
<Dropdown.Item href={`${getConfig().LMS_BASE_URL}/account/settings`}>
{formatMessage(messages.account)}
</Dropdown.Item>
{getConfig().ORDER_HISTORY_URL && (
<Dropdown.Item href={getConfig().ORDER_HISTORY_URL}>
{formatMessage(messages.orderHistory)}
</Dropdown.Item>
)}
<Dropdown.Item href={getConfig().SUPPORT_URL}>
{formatMessage(messages.help)}
</Dropdown.Item>
<Dropdown.Item href={getConfig().LOGOUT_URL}>
{formatMessage(messages.signOut)}
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
</>
);
};
AuthenticatedUserDropdown.propTypes = {
intl: intlShape.isRequired,
username: PropTypes.string.isRequired,
};
export default injectIntl(AuthenticatedUserDropdown);
export default AuthenticatedUserDropdown;

View File

@@ -0,0 +1,130 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`EnterpriseDashboard snapshot initilized 1`] = `
<Fragment>
<Dropdown.Item
active={false}
key="Personal"
>
Personal
Dashboard
</Dropdown.Item>
<Dropdown.Item
active={true}
key="edX, Inc."
>
edX, Inc.
Dashboard
</Dropdown.Item>
<Dropdown.Item
active={false}
key="Harvard"
>
Harvard
Dashboard
</Dropdown.Item>
<ModalDialog
hasCloseButton={false}
isOpen={false}
onClose={[MockFunction hooks.nullMethod]}
title=""
>
<div
className="bg-white p-3 rounded shadow"
style={
Object {
"textAlign": "start",
}
}
>
<h4>
You have access to the undefined dashboard
</h4>
<p>
To access the coureses available to you through undefined, visit the undefined dashboard now.
</p>
<ActionRow>
<Button
onClick={[MockFunction cancelSelectDashboardItem]}
variant="tertiary"
>
Dismiss
</Button>
<Button
type="a"
>
Go To Dashboard
</Button>
</ActionRow>
</div>
</ModalDialog>
</Fragment>
`;
exports[`EnterpriseDashboard snapshot select item and open modal 1`] = `
<Fragment>
<Dropdown.Item
active={false}
key="Personal"
>
Personal
Dashboard
</Dropdown.Item>
<Dropdown.Item
active={true}
key="edX, Inc."
>
edX, Inc.
Dashboard
</Dropdown.Item>
<Dropdown.Item
active={false}
key="Harvard"
>
Harvard
Dashboard
</Dropdown.Item>
<ModalDialog
hasCloseButton={false}
isOpen={true}
onClose={[MockFunction hooks.nullMethod]}
title=""
>
<div
className="bg-white p-3 rounded shadow"
style={
Object {
"textAlign": "start",
}
}
>
<h4>
You have access to the Personal dashboard
</h4>
<p>
To access the coureses available to you through Personal, visit the Personal dashboard now.
</p>
<ActionRow>
<Button
onClick={[MockFunction cancelSelectDashboardItem]}
variant="tertiary"
>
Dismiss
</Button>
<Button
href="/dashboard"
type="a"
>
Go To Dashboard
</Button>
</ActionRow>
</div>
</ModalDialog>
</Fragment>
`;

View File

@@ -0,0 +1,37 @@
import React from 'react';
import { hooks as appHooks } from 'data/redux';
import { StrictDict } from 'utils';
import * as module from './hooks';
export const state = StrictDict({
showDialog: (val) => React.useState(val), // eslint-disable-line
selectedItem: (val) => React.useState(val), // eslint-disable-line
});
export const useEnterpriseDashboardHook = () => {
const { availableDashboards, mostRecentDashboard } = appHooks.useEnterpriseDashboardData();
const [showDialog, setShowDialog] = module.state.showDialog(false);
const [selectedItem, setSelectedItem] = module.state.selectedItem({});
const beginSelectDashboardItem = (val) => () => {
setSelectedItem(val);
setShowDialog(true);
};
const cancelSelectDashboardItem = () => {
setSelectedItem({});
setShowDialog(false);
};
return {
availableDashboards,
mostRecentDashboard,
showDialog,
selectedItem,
beginSelectDashboardItem,
cancelSelectDashboardItem,
};
};
export default useEnterpriseDashboardHook;

View File

@@ -0,0 +1,60 @@
import { MockUseState } from 'testUtils';
import { hooks as appHooks } from 'data/redux';
import * as hooks from './hooks';
jest.mock('data/redux', () => ({
hooks: {
useEnterpriseDashboardData: jest.fn(),
},
}));
const state = new MockUseState(hooks);
const enterpriseDashboardData = {
availableDashboards: [
{ label: 'Personal', url: '/dashboard' },
{ label: 'edX, Inc.', url: '/edx-dashboard' },
{ label: 'Harvard', url: '/harvard-dashboard' },
],
mostRecentDashboard: { label: 'edX, Inc.', url: '/edx-dashboard' },
};
describe('EnterpriseDashboard hooks', () => {
appHooks.useEnterpriseDashboardData.mockReturnValue({ ...enterpriseDashboardData });
describe('state values', () => {
state.testGetter(state.keys.showDialog);
state.testGetter(state.keys.selectedItem);
});
describe('behavior', () => {
let out;
beforeEach(() => {
state.mock();
out = hooks.useEnterpriseDashboardHook();
});
afterEach(state.restore);
test('useEnterpriseDashboardHook to return dashboard data from redux hooks', () => {
expect(out.availableDashboards).toMatchObject(enterpriseDashboardData.availableDashboards);
expect(out.mostRecentDashboard).toMatchObject(enterpriseDashboardData.mostRecentDashboard);
});
test('modal is open on begin select dashboard item', () => {
state.expectInitializedWith('showDialog', false);
state.expectInitializedWith('selectedItem', {});
const selectedItem = { abitary: 'not so true' };
out.beginSelectDashboardItem(selectedItem)();
expect(state.values.showDialog).toEqual(true);
expect(state.values.selectedItem).toMatchObject(selectedItem);
});
test('modal is close on cancel select dashboard item', () => {
out.cancelSelectDashboardItem();
expect(state.values.selectedItem).toMatchObject({});
expect(state.values.showDialog).toEqual(false);
});
});
});

View File

@@ -0,0 +1,73 @@
import React from 'react';
// import PropTypes from 'prop-types';
import { useIntl } from '@edx/frontend-platform/i18n';
import {
Dropdown, ModalDialog, ActionRow, Button,
} from '@edx/paragon';
import { nullMethod } from 'hooks';
import messages from './messages';
import useEnterpriseDashboardHook from './hooks';
export const EnterpriseDashboard = () => {
const { formatMessage } = useIntl();
const {
availableDashboards,
mostRecentDashboard,
showDialog,
selectedItem,
beginSelectDashboardItem,
cancelSelectDashboardItem,
} = useEnterpriseDashboardHook();
return (
<>
{availableDashboards.map((dashboard) => (
<Dropdown.Item
onClick={beginSelectDashboardItem(dashboard)}
active={dashboard.label === mostRecentDashboard.label}
key={dashboard.label}
>
{dashboard.label} {formatMessage(messages.dashboard)}
</Dropdown.Item>
))}
<ModalDialog
isOpen={showDialog}
onClose={nullMethod}
hasCloseButton={false}
title=""
>
<div
className="bg-white p-3 rounded shadow"
style={{ textAlign: 'start' }}
>
<h4>
{formatMessage(messages.enterpriseDialogHeader, {
label: selectedItem.label,
})}
</h4>
<p>
{formatMessage(messages.enterpriseDialogBody, {
label: selectedItem.label,
})}
</p>
<ActionRow>
<Button variant="tertiary" onClick={cancelSelectDashboardItem}>
{formatMessage(messages.enterpriseDialogDismissButton)}
</Button>
<Button type="a" href={selectedItem.url}>
{formatMessage(messages.enterpriseDialogConfirmButton)}
</Button>
</ActionRow>
</div>
</ModalDialog>
</>
);
};
EnterpriseDashboard.propTypes = {};
export default EnterpriseDashboard;

View File

@@ -0,0 +1,49 @@
import { shallow } from 'enzyme';
import EnterpriseDashboard from '.';
import useEnterpriseDashboardHook from './hooks';
jest.mock('./hooks', () => ({
__esModule: true,
default: jest.fn(),
}));
const enterpriseDashboardData = {
availableDashboards: [
{ label: 'Personal', url: '/dashboard' },
{ label: 'edX, Inc.', url: '/edx-dashboard' },
{ label: 'Harvard', url: '/harvard-dashboard' },
],
mostRecentDashboard: { label: 'edX, Inc.', url: '/edx-dashboard' },
};
describe('EnterpriseDashboard', () => {
describe('snapshot', () => {
const hookReturn = {
...enterpriseDashboardData,
showDialog: false,
selectedItem: {},
beginSelectDashboardItem: jest.fn().mockName('beginSelectDashboardItem'),
cancelSelectDashboardItem: jest
.fn()
.mockName('cancelSelectDashboardItem'),
};
test('initilized', () => {
useEnterpriseDashboardHook.mockReturnValueOnce({ ...hookReturn });
const el = shallow(<EnterpriseDashboard />);
expect(el).toMatchSnapshot();
});
test('select item and open modal', () => {
useEnterpriseDashboardHook.mockReturnValueOnce({
...hookReturn,
selectedItem: enterpriseDashboardData.availableDashboards[0],
showDialog: true,
});
const el = shallow(<EnterpriseDashboard />);
expect(el).toMatchSnapshot();
});
});
});

View File

@@ -0,0 +1,31 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
dashboard: {
id: 'leanerDashboard.menu.dashboard.label',
defaultMessage: 'Dashboard',
description: 'The text for the user menu Dashboard navigation link.',
},
enterpriseDialogHeader: {
id: 'leanerDashboard.enterpriseDialogHeader',
defaultMessage: 'You have access to the {label} dashboard',
description: 'title for enterpise dashboard dialog',
},
enterpriseDialogBody: {
id: 'leanerDashboard.enterpriseDialogBody',
defaultMessage: 'To access the coureses available to you through {label}, visit the {label} dashboard now.',
description: 'Body text for enterpise dashboard dialog',
},
enterpriseDialogDismissButton: {
id: 'leanerDashboard.enterpriseDialogDismissButton',
defaultMessage: 'Dismiss',
description: 'Dismiss button to cancel visiting dashboard',
},
enterpriseDialogConfirmButton: {
id: 'leanerDashboard.enterpriseDialogConfirmButton',
defaultMessage: 'Go To Dashboard',
description: 'Confirm button to go to the dashboard url',
},
});
export default messages;

View File

@@ -1,11 +1,6 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
dashboard: {
id: 'leanerDashboard.menu.dashboard.label',
defaultMessage: 'Dashboard',
description: 'The text for the user menu Dashboard navigation link.',
},
help: {
id: 'leanerDashboard.help.label',
defaultMessage: 'Help',

View File

@@ -59,10 +59,11 @@ const globalData = {
},
enterpriseDashboards: {
availableDashboards: [
{ label: 'edX', url: 'edx.org/edx-dashboard' },
{ label: 'harvard', url: 'edx.org/harvard-dashboard' },
{ label: 'Personal', url: '/dashboard' },
{ label: 'edX, Inc.', url: '/edx-dashboard' },
{ label: 'Harvard', url: '/harvard-dashboard' },
],
mostRecentDashboard: { label: 'edX', url: 'edx.org/edx-dashboard' },
mostRecentDashboard: { label: 'edX, Inc.', url: '/edx-dashboard' },
},
platformSettings: {
supportEmail: 'support@example.com',

View File

@@ -17,7 +17,7 @@ export const formatMessage = (msg, values) => {
}
Object.keys(values).forEach((key) => {
// eslint-disable-next-line
message = message.replace(`{${key}}`, values[key]);
message = message.replaceAll(`{${key}}`, values[key]);
});
return message;
};