Skip to content

Commit

Permalink
feat: set username behind flag
Browse files Browse the repository at this point in the history
username would be show in header bt enabling ENABLE_HEADER_WITHOUT_USERNAME flag.

VAN-1804

fix: address reviewer comments
  • Loading branch information
mubbsharanwar committed Feb 12, 2024
1 parent 3b2a2bf commit fcaa2b6
Show file tree
Hide file tree
Showing 14 changed files with 215 additions and 21 deletions.
1 change: 1 addition & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ Environment Variables
* ``ACCOUNT_PROFILE_URL`` - The URL of the account profile page.
* ``ACCOUNT_SETTINGS_URL`` - The URL of the account settings page.
* ``AUTHN_MINIMAL_HEADER`` - A boolean flag which hides the main menu, user menu, and logged-out
* ``HIDE_USERNAME_FROM_HEADER`` - A boolean flag which hides the username from the header
menu items when truthy. This is intended to be used in micro-frontends like
frontend-app-authentication in which these menus are considered distractions from the user's task.

Expand Down
1 change: 1 addition & 0 deletions example/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ subscribe(APP_READY, () => {
authenticatedUser: {
userId: '123abc',
username: 'testuser',
name: 'test user',
roles: [],
administrator: false,
},
Expand Down
12 changes: 9 additions & 3 deletions src/DesktopHeader.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,21 +74,25 @@ class DesktopHeader extends React.Component {

renderUserMenu() {
const {
intl,
userMenu,
avatar,
name,
username,
intl,
} = this.props;
const hideUsername = getConfig().HIDE_USERNAME_FROM_HEADER;
const usernameOrName = hideUsername ? name : username;

return (
<Menu transitionClassName="menu-dropdown" transitionTimeout={250}>
<MenuTrigger
tag="button"
aria-label={intl.formatMessage(messages['header.label.account.menu.for'], { username })}
aria-label={intl.formatMessage(messages['header.label.account.menu.using.name.or.username'], { usernameOrName })}
className="btn btn-outline-primary d-inline-flex align-items-center pl-2 pr-3"
>
<Avatar size="1.5em" src={avatar} alt="" className="mr-2" />
{username} <CaretIcon role="img" aria-hidden focusable="false" />
{!hideUsername && username}
<CaretIcon role="img" aria-hidden focusable="false" />
</MenuTrigger>
<MenuContent className="mb-0 dropdown-menu show dropdown-menu-right pin-right shadow py-2">
{userMenu.map(({ type, href, content }) => (
Expand Down Expand Up @@ -178,6 +182,7 @@ DesktopHeader.propTypes = {
logoDestination: PropTypes.string,
avatar: PropTypes.string,
username: PropTypes.string,
name: PropTypes.string,
loggedIn: PropTypes.bool,

// i18n
Expand Down Expand Up @@ -207,6 +212,7 @@ DesktopHeader.defaultProps = {
logoDestination: null,
avatar: null,
username: null,
name: null,
loggedIn: false,
appMenu: null,
};
Expand Down
2 changes: 2 additions & 0 deletions src/Header.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ ensureConfig([
subscribe(APP_CONFIG_INITIALIZED, () => {
mergeConfig({
AUTHN_MINIMAL_HEADER: !!process.env.AUTHN_MINIMAL_HEADER,
HIDE_USERNAME_FROM_HEADER: !!process.env.HIDE_USERNAME_FROM_HEADER,
}, 'Header additional config');
});

Expand Down Expand Up @@ -94,6 +95,7 @@ const Header = ({ intl }) => {
logoDestination: `${config.LMS_BASE_URL}/dashboard`,
loggedIn: authenticatedUser !== null,
username: authenticatedUser !== null ? authenticatedUser.username : null,
name: authenticatedUser !== null ? authenticatedUser.name : null,
avatar: authenticatedUser !== null ? authenticatedUser.avatar : null,
mainMenu: getConfig().AUTHN_MINIMAL_HEADER ? [] : mainMenu,
userMenu: getConfig().AUTHN_MINIMAL_HEADER ? [] : userMenu,
Expand Down
8 changes: 4 additions & 4 deletions src/Header.messages.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,10 @@ const messages = defineMessages({
defaultMessage: 'Account Menu',
description: 'The aria label for the account menu trigger',
},
'header.label.account.menu.for': {
id: 'header.label.account.menu.for',
defaultMessage: 'Account menu for {username}',
description: 'The aria label for the account menu trigger when the username is displayed in it',
'header.label.account.menu.using.name.or.username': {
id: 'header.label.account.menu.using.name.or.username',
defaultMessage: 'Account menu for {usernameOrName}',
description: 'The aria label for the account menu trigger when the username is displayed or hide in it',
},
'header.label.main.nav': {
id: 'header.label.main.nav',
Expand Down
28 changes: 27 additions & 1 deletion src/Header.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,12 @@ describe('<Header />', () => {
expect(wrapper.toJSON()).toMatchSnapshot();
});

it('renders correctly for authenticated desktop', () => {
it('renders correctly for authenticated desktop with username', () => {
const contextValue = {
authenticatedUser: {
userId: 'abc123',
username: 'edX',
name: 'edX',
roles: [],
administrator: false,
},
Expand All @@ -61,6 +62,30 @@ describe('<Header />', () => {
expect(wrapper.toJSON()).toMatchSnapshot();
});

it('renders correctly for authenticated desktop without username', () => {
const contextValue = {
authenticatedUser: {
userId: 'abc123',
name: 'edX',
roles: [],
administrator: false,
},
config: {
LMS_BASE_URL: process.env.LMS_BASE_URL,
SITE_NAME: process.env.SITE_NAME,
LOGIN_URL: process.env.LOGIN_URL,
LOGOUT_URL: process.env.LOGOUT_URL,
LOGO_URL: process.env.LOGO_URL,
HIDE_USERNAME_FROM_HEADER: true,
},
};
const component = <HeaderComponent width={{ width: 1280 }} contextValue={contextValue} />;

const wrapper = TestRenderer.create(component);

expect(wrapper.toJSON()).toMatchSnapshot();
});

it('renders correctly for anonymous mobile', () => {
const contextValue = {
authenticatedUser: null,
Expand All @@ -84,6 +109,7 @@ describe('<Header />', () => {
authenticatedUser: {
userId: 'abc123',
username: 'edX',
name: 'edX',
roles: [],
administrator: false,
},
Expand Down
110 changes: 108 additions & 2 deletions src/__snapshots__/Header.test.jsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ exports[`<Header /> renders correctly for anonymous mobile 1`] = `
</header>
`;

exports[`<Header /> renders correctly for authenticated desktop 1`] = `
exports[`<Header /> renders correctly for authenticated desktop with username 1`] = `
<header
className="site-header-desktop"
>
Expand Down Expand Up @@ -281,7 +281,113 @@ exports[`<Header /> renders correctly for authenticated desktop 1`] = `
</svg>
</span>
edX
<svg
aria-hidden={true}
focusable="false"
height="16px"
role="img"
version="1.1"
viewBox="0 0 16 16"
width="16px"
>
<path
d="M7,4 L7,8 L11,8 L11,10 L5,10 L5,4 L7,4 Z"
fill="currentColor"
transform="translate(8.000000, 7.000000) rotate(-45.000000) translate(-8.000000, -7.000000) "
/>
</svg>
</button>
</div>
</nav>
</div>
</div>
</header>
`;

exports[`<Header /> renders correctly for authenticated desktop without username 1`] = `
<header
className="site-header-desktop"
>
<a
className="nav-skip sr-only sr-only-focusable"
href="#main"
>
Skip to main content
</a>
<div
className="container-fluid null"
>
<div
className="nav-container position-relative d-flex align-items-center"
>
<a
className="logo"
href="http://localhost:18000/dashboard"
>
<img
alt="edX"
className="d-block"
src="https://edx-cdn.org/v3/default/logo.svg"
/>
</a>
<nav
aria-label="Main"
className="nav main-nav"
>
<a
className="nav-link"
href="http://localhost:18000/dashboard"
>
Courses
</a>
</nav>
<nav
aria-label="Secondary"
className="nav secondary-menu-container align-items-center ml-auto"
>
<div
className="menu null"
onKeyDown={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
<button
aria-expanded={false}
aria-haspopup="menu"
aria-label="Account menu for "
className="menu-trigger btn btn-outline-primary d-inline-flex align-items-center pl-2 pr-3"
onClick={[Function]}
>
<span
className="avatar overflow-hidden d-inline-flex rounded-circle mr-2"
style={
Object {
"height": "1.5em",
"width": "1.5em",
}
}
>
<svg
aria-hidden={true}
focusable="false"
height="24px"
role="img"
style={
Object {
"height": "1.5em",
"width": "1.5em",
}
}
version="1.1"
viewBox="0 0 24 24"
width="24px"
>
<path
d="M4.10255106,18.1351061 C4.7170266,16.0581859 8.01891846,14.4720277 12,14.4720277 C15.9810815,14.4720277 19.2829734,16.0581859 19.8974489,18.1351061 C21.215206,16.4412566 22,14.3122775 22,12 C22,6.4771525 17.5228475,2 12,2 C6.4771525,2 2,6.4771525 2,12 C2,14.3122775 2.78479405,16.4412566 4.10255106,18.1351061 Z M12,24 C5.372583,24 0,18.627417 0,12 C0,5.372583 5.372583,0 12,0 C18.627417,0 24,5.372583 24,12 C24,18.627417 18.627417,24 12,24 Z M12,13 C9.790861,13 8,11.209139 8,9 C8,6.790861 9.790861,5 12,5 C14.209139,5 16,6.790861 16,9 C16,11.209139 14.209139,13 12,13 Z"
fill="currentColor"
/>
</svg>
</span>
<svg
aria-hidden={true}
focusable="false"
Expand Down
32 changes: 24 additions & 8 deletions src/learning-header/AuthenticatedUserDropdown.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,38 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faUserCircle } from '@fortawesome/free-solid-svg-icons';
import { getConfig } from '@edx/frontend-platform';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Dropdown } from '@openedx/paragon';
import { Avatar, Dropdown } from '@openedx/paragon';

import messages from './messages';

const AuthenticatedUserDropdown = ({ intl, username }) => {
const AuthenticatedUserDropdown = ({
intl, username, name,
}) => {
const dashboardMenuItem = (
<Dropdown.Item href={`${getConfig().LMS_BASE_URL}/dashboard`}>
{intl.formatMessage(messages.dashboard)}
</Dropdown.Item>
);

// show avatar instead username if HIDE_USERNAME_FROM_HEADER flag is enabled
const dropdownToggle = (
<Dropdown.Toggle variant="outline-primary">
<FontAwesomeIcon icon={faUserCircle} className="d-md-none" size="lg" />
{getConfig().HIDE_USERNAME_FROM_HEADER ? (
<Avatar size="sm" alt={name} className="mr-2" />
) : (
<span data-hj-suppress className="d-none d-md-inline" data-testid="username">
{username}
</span>
)}
</Dropdown.Toggle>
);

return (
<>
<a className="text-gray-700" href={`${getConfig().SUPPORT_URL}`}>{intl.formatMessage(messages.help)}</a>
<Dropdown className="user-dropdown ml-3">
<Dropdown.Toggle variant="outline-primary">
<FontAwesomeIcon icon={faUserCircle} className="d-md-none" size="lg" />
<span data-hj-suppress className="d-none d-md-inline">
{username}
</span>
</Dropdown.Toggle>
{dropdownToggle}
<Dropdown.Menu className="dropdown-menu-right">
{dashboardMenuItem}
<Dropdown.Item href={`${getConfig().ACCOUNT_PROFILE_URL}/u/${username}`}>
Expand All @@ -51,6 +62,11 @@ const AuthenticatedUserDropdown = ({ intl, username }) => {
AuthenticatedUserDropdown.propTypes = {
intl: intlShape.isRequired,
username: PropTypes.string.isRequired,
name: PropTypes.string,
};

AuthenticatedUserDropdown.defaultProps = {
name: null,
};

export default injectIntl(AuthenticatedUserDropdown);
1 change: 1 addition & 0 deletions src/learning-header/LearningHeader.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ const LearningHeader = ({
{showUserDropdown && authenticatedUser && (
<AuthenticatedUserDropdown
username={authenticatedUser.username}
name={authenticatedUser.name}
/>
)}
{showUserDropdown && !authenticatedUser && (
Expand Down
12 changes: 12 additions & 0 deletions src/learning-header/LearningHeader.test.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react';
import { getConfig, mergeConfig } from '@edx/frontend-platform';
import {
authenticatedUser, initializeMockApp, render, screen,
} from '../setupTest';
Expand All @@ -15,6 +16,17 @@ describe('Header', () => {
expect(screen.getByText(authenticatedUser.username)).toBeInTheDocument();
});

it('displays user button without username', () => {
const config = getConfig();
mergeConfig({
...config,
HIDE_USERNAME_FROM_HEADER: true,
});
const { queryByTestId } = render(<Header />);
const userName = queryByTestId('username');
expect(userName).not.toBeInTheDocument();
});

it('displays course data', () => {
const courseData = {
courseOrg: 'course-org',
Expand Down
4 changes: 4 additions & 0 deletions src/studio-header/HeaderBody.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const HeaderBody = ({
number,
org,
title,
name,
username,
isAdmin,
studioBaseUrl,
Expand Down Expand Up @@ -99,6 +100,7 @@ const HeaderBody = ({
<Nav>
<UserMenu
{...{
name,
username,
studioBaseUrl,
logoutUrl,
Expand All @@ -124,6 +126,7 @@ HeaderBody.propTypes = {
logo: PropTypes.string,
logoAltText: PropTypes.string,
authenticatedUserAvatar: PropTypes.string,
name: PropTypes.string,
username: PropTypes.string,
isAdmin: PropTypes.bool,
isMobile: PropTypes.bool,
Expand All @@ -149,6 +152,7 @@ HeaderBody.defaultProps = {
org: '',
title: '',
authenticatedUserAvatar: null,
name: null,
username: null,
isAdmin: false,
isMobile: false,
Expand Down
1 change: 1 addition & 0 deletions src/studio-header/StudioHeader.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const StudioHeader = ({
number,
org,
title,
name: authenticatedUser?.name,
username: authenticatedUser?.username,
isAdmin: authenticatedUser?.administrator,
authenticatedUserAvatar: authenticatedUser?.avatar,
Expand Down
Loading

0 comments on commit fcaa2b6

Please sign in to comment.