From 4cc31ca4524a1e89bfd65b02e603b65674a260d5 Mon Sep 17 00:00:00 2001 From: mubbsharanwar Date: Wed, 31 Jan 2024 19:08:52 +0500 Subject: [PATCH] feat: set username behind flag username would be show in header bt enabling ENABLE_HEADER_WITHOUT_USERNAME flag. VAN-1804 --- README.rst | 1 + example/index.js | 1 + src/DesktopHeader.jsx | 123 +++++++++++------- src/Header.jsx | 2 + src/Header.messages.jsx | 9 +- src/Header.test.jsx | 28 +++- src/__snapshots__/Header.test.jsx.snap | 110 +++++++++++++++- .../AuthenticatedUserDropdown.jsx | 30 ++++- src/learning-header/LearningHeader.jsx | 1 + src/learning-header/LearningHeader.test.jsx | 12 ++ src/studio-header/HeaderBody.jsx | 4 + src/studio-header/StudioHeader.jsx | 1 + src/studio-header/StudioHeader.test.jsx | 7 + src/studio-header/UserMenu.jsx | 12 +- 14 files changed, 282 insertions(+), 59 deletions(-) diff --git a/README.rst b/README.rst index ab9e4db72f..eba016a348 100644 --- a/README.rst +++ b/README.rst @@ -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 +* ``ENABLE_HEADER_WITHOUT_USERNAME`` - 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. diff --git a/example/index.js b/example/index.js index 2559c03d48..ac41c49600 100644 --- a/example/index.js +++ b/example/index.js @@ -26,6 +26,7 @@ subscribe(APP_READY, () => { authenticatedUser: { userId: '123abc', username: 'testuser', + name: 'test user', roles: [], administrator: false, }, diff --git a/src/DesktopHeader.jsx b/src/DesktopHeader.jsx index 8bd142fb9e..055afd82d5 100644 --- a/src/DesktopHeader.jsx +++ b/src/DesktopHeader.jsx @@ -19,52 +19,26 @@ class DesktopHeader extends React.Component { super(props); } - renderMainMenu() { - const { mainMenu } = this.props; - - // Nodes are accepted as a prop - if (!Array.isArray(mainMenu)) { - return mainMenu; - } - - return mainMenu.map((menuItem) => { - const { - type, - href, - content, - submenuContent, - } = menuItem; - - if (type === 'item') { - return ( - {content} - ); - } - - return ( - - - {content} - - - {submenuContent} - - - ); - }); - } + userMenuWithUsername() { + const { + userMenu, + avatar, + username, + intl, + } = this.props; - // Renders an optional App Menu for - renderAppMenu() { - const { appMenu } = this.props; - const { content: appMenuContent, menuItems } = appMenu; return ( - - {appMenuContent} + + + {username} - {menuItems.map(({ type, href, content }) => ( + {userMenu.map(({ type, href, content }) => ( {content} ))} @@ -72,11 +46,11 @@ class DesktopHeader extends React.Component { ); } - renderUserMenu() { + userMenuWithoutUsername() { const { userMenu, avatar, - username, + name, intl, } = this.props; @@ -84,11 +58,11 @@ class DesktopHeader extends React.Component { - {username} + {userMenu.map(({ type, href, content }) => ( @@ -99,6 +73,63 @@ class DesktopHeader extends React.Component { ); } + renderUserMenu() { + return (getConfig().ENABLE_HEADER_WITHOUT_USERNAME ? this.userMenuWithoutUsername() : this.userMenuWithUsername()); + } + + // Renders an optional App Menu for + renderAppMenu() { + const { appMenu } = this.props; + const { content: appMenuContent, menuItems } = appMenu; + return ( + + + {appMenuContent} + + + {menuItems.map(({ type, href, content }) => ( + {content} + ))} + + + ); + } + + renderMainMenu() { + const { mainMenu } = this.props; + + // Nodes are accepted as a prop + if (!Array.isArray(mainMenu)) { + return mainMenu; + } + + return mainMenu.map((menuItem) => { + const { + type, + href, + content, + submenuContent, + } = menuItem; + + if (type === 'item') { + return ( + {content} + ); + } + + return ( + + + {content} + + + {submenuContent} + + + ); + }); + } + renderLoggedOutItems() { const { loggedOutItems } = this.props; @@ -178,6 +209,7 @@ DesktopHeader.propTypes = { logoDestination: PropTypes.string, avatar: PropTypes.string, username: PropTypes.string, + name: PropTypes.string, loggedIn: PropTypes.bool, // i18n @@ -207,6 +239,7 @@ DesktopHeader.defaultProps = { logoDestination: null, avatar: null, username: null, + name: null, loggedIn: false, appMenu: null, }; diff --git a/src/Header.jsx b/src/Header.jsx index c0db257c72..efc981d12f 100644 --- a/src/Header.jsx +++ b/src/Header.jsx @@ -27,6 +27,7 @@ ensureConfig([ subscribe(APP_CONFIG_INITIALIZED, () => { mergeConfig({ AUTHN_MINIMAL_HEADER: !!process.env.AUTHN_MINIMAL_HEADER, + ENABLE_HEADER_WITHOUT_USERNAME: !!process.env.ENABLE_HEADER_WITHOUT_USERNAME, }, 'Header additional config'); }); @@ -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, diff --git a/src/Header.messages.jsx b/src/Header.messages.jsx index a5ff3a5896..6db83e5c73 100644 --- a/src/Header.messages.jsx +++ b/src/Header.messages.jsx @@ -76,11 +76,16 @@ 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', + 'header.label.account.menu.with.username': { + id: 'header.label.account.menu.with.username', 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.without.username': { + id: 'header.label.account.without.username', + defaultMessage: 'Account menu for {name}', + description: 'The aria label for the account menu trigger when ENABLE_HEADER_WITHOUT_USERNAME is enabled', + }, 'header.label.main.nav': { id: 'header.label.main.nav', defaultMessage: 'Main', diff --git a/src/Header.test.jsx b/src/Header.test.jsx index 51fef2089c..ff0e33c307 100644 --- a/src/Header.test.jsx +++ b/src/Header.test.jsx @@ -38,11 +38,12 @@ describe('
', () => { 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, }, @@ -61,6 +62,30 @@ describe('
', () => { 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, + ENABLE_HEADER_WITHOUT_USERNAME: true, + }, + }; + const component = ; + + const wrapper = TestRenderer.create(component); + + expect(wrapper.toJSON()).toMatchSnapshot(); + }); + it('renders correctly for anonymous mobile', () => { const contextValue = { authenticatedUser: null, @@ -84,6 +109,7 @@ describe('
', () => { authenticatedUser: { userId: 'abc123', username: 'edX', + name: 'edX', roles: [], administrator: false, }, diff --git a/src/__snapshots__/Header.test.jsx.snap b/src/__snapshots__/Header.test.jsx.snap index f83161b154..be5cb8b467 100644 --- a/src/__snapshots__/Header.test.jsx.snap +++ b/src/__snapshots__/Header.test.jsx.snap @@ -196,7 +196,7 @@ exports[`
renders correctly for anonymous mobile 1`] = `
`; -exports[`
renders correctly for authenticated desktop 1`] = ` +exports[`
renders correctly for authenticated desktop with username 1`] = `
@@ -305,6 +305,114 @@ exports[`
renders correctly for authenticated desktop 1`] = `
`; +exports[`
renders correctly for authenticated desktop without username 1`] = ` +
+ + Skip to main content + +
+
+ + edX + + + +
+
+
+`; + exports[`
renders correctly for authenticated mobile 1`] = `
{ +const AuthenticatedUserDropdown = ({ + intl, username, avatar, name +}) => { const dashboardMenuItem = ( {intl.formatMessage(messages.dashboard)} ); + const showDropdownToggle = ( + + + {!getConfig().ENABLE_HEADER_WITHOUT_USERNAME ? ( + + {username} + + ) : } + + ); + return ( <> {intl.formatMessage(messages.help)} - - - - {username} - - + {showDropdownToggle} {dashboardMenuItem} @@ -51,6 +60,13 @@ const AuthenticatedUserDropdown = ({ intl, username }) => { AuthenticatedUserDropdown.propTypes = { intl: intlShape.isRequired, username: PropTypes.string.isRequired, + name: PropTypes.string, + avatar: PropTypes.string, +}; + +AuthenticatedUserDropdown.defaultProps = { + avatar: null, + name: null, }; export default injectIntl(AuthenticatedUserDropdown); diff --git a/src/learning-header/LearningHeader.jsx b/src/learning-header/LearningHeader.jsx index 373001d190..88dce7d7ce 100644 --- a/src/learning-header/LearningHeader.jsx +++ b/src/learning-header/LearningHeader.jsx @@ -51,6 +51,7 @@ const LearningHeader = ({ {showUserDropdown && authenticatedUser && ( )} {showUserDropdown && !authenticatedUser && ( diff --git a/src/learning-header/LearningHeader.test.jsx b/src/learning-header/LearningHeader.test.jsx index 3d80888efe..f004124ad7 100644 --- a/src/learning-header/LearningHeader.test.jsx +++ b/src/learning-header/LearningHeader.test.jsx @@ -1,4 +1,5 @@ import React from 'react'; +import { getConfig, mergeConfig } from '@edx/frontend-platform'; import { authenticatedUser, initializeMockApp, render, screen, } from '../setupTest'; @@ -15,6 +16,17 @@ describe('Header', () => { expect(screen.getByText(authenticatedUser.username)).toBeInTheDocument(); }); + it('displays user button without username', () => { + const config = getConfig(); + mergeConfig({ + ...config, + ENABLE_HEADER_WITHOUT_USERNAME: true, + }); + const { queryByText } = render(
); + const userName = queryByText(config.authenticatedUser.username); + expect(userName).not.toBeInTheDocument(); + }); + it('displays course data', () => { const courseData = { courseOrg: 'course-org', diff --git a/src/studio-header/HeaderBody.jsx b/src/studio-header/HeaderBody.jsx index af36906102..2ca70e1256 100644 --- a/src/studio-header/HeaderBody.jsx +++ b/src/studio-header/HeaderBody.jsx @@ -20,6 +20,7 @@ const HeaderBody = ({ number, org, title, + name, username, isAdmin, studioBaseUrl, @@ -99,6 +100,7 @@ const HeaderBody = ({