From 15f29aee2207dac663c33cf27d7ee69e5bd2e383 Mon Sep 17 00:00:00 2001 From: Ryan Keairns Date: Thu, 17 Dec 2020 15:57:12 -0600 Subject: [PATCH 1/9] Update text and icons to align with Cloud --- x-pack/plugins/cloud/public/user_menu_links.ts | 4 ++-- .../account_management/account_management_page.tsx | 10 +++++++++- .../public/nav_control/nav_control_component.tsx | 4 ++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/cloud/public/user_menu_links.ts b/x-pack/plugins/cloud/public/user_menu_links.ts index 15e2f14e885ba..b57f166e07cc5 100644 --- a/x-pack/plugins/cloud/public/user_menu_links.ts +++ b/x-pack/plugins/cloud/public/user_menu_links.ts @@ -15,9 +15,9 @@ export const createUserMenuLinks = (config: CloudConfigType): UserMenuLink[] => if (resetPasswordUrl) { userMenuLinks.push({ label: i18n.translate('xpack.cloud.userMenuLinks.profileLinkText', { - defaultMessage: 'Cloud profile', + defaultMessage: 'Profile', }), - iconType: 'logoCloud', + iconType: 'user', href: resetPasswordUrl, order: 100, }); diff --git a/x-pack/plugins/security/public/account_management/account_management_page.tsx b/x-pack/plugins/security/public/account_management/account_management_page.tsx index 2c870bf788ceb..067658b8cfa7b 100644 --- a/x-pack/plugins/security/public/account_management/account_management_page.tsx +++ b/x-pack/plugins/security/public/account_management/account_management_page.tsx @@ -3,6 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { FormattedMessage } from '@kbn/i18n/react'; import React, { useEffect, useState } from 'react'; import ReactDOM from 'react-dom'; import { EuiPage, EuiPageBody, EuiPanel, EuiSpacer, EuiText } from '@elastic/eui'; @@ -35,7 +36,14 @@ export const AccountManagementPage = ({ userAPIClient, authc, notifications }: P -

{getUserDisplayName(currentUser)}

+

+ +   + {getUserDisplayName(currentUser)} +

diff --git a/x-pack/plugins/security/public/nav_control/nav_control_component.tsx b/x-pack/plugins/security/public/nav_control/nav_control_component.tsx index e846539025452..a8431ba9a6d09 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_component.tsx +++ b/x-pack/plugins/security/public/nav_control/nav_control_component.tsx @@ -126,10 +126,10 @@ export class SecurityNavControl extends Component { name: ( ), - icon: , + icon: , href: editProfileUrl, 'data-test-subj': 'profileLink', }; From 87606d4062c2b163ef67db578e39a562e6695527 Mon Sep 17 00:00:00 2001 From: Ryan Keairns Date: Thu, 17 Dec 2020 18:41:38 -0600 Subject: [PATCH 2/9] Update test to reflect new page title prefix --- .../account_management_page.test.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/security/public/account_management/account_management_page.test.tsx b/x-pack/plugins/security/public/account_management/account_management_page.test.tsx index 19517b0b6f8f8..5335db760d590 100644 --- a/x-pack/plugins/security/public/account_management/account_management_page.test.tsx +++ b/x-pack/plugins/security/public/account_management/account_management_page.test.tsx @@ -57,9 +57,10 @@ describe('', () => { wrapper.update(); }); - expect(wrapper.find('EuiText[data-test-subj="userDisplayName"]').text()).toEqual( - user.full_name - ); + // Replace non breaking spaces ( ) with ordinary space + expect( + wrapper.find('EuiText[data-test-subj="userDisplayName"]').text().replace(/\s/g, ' ') + ).toEqual(`Settings for ${user.full_name}`); expect(wrapper.find('[data-test-subj="username"]').text()).toEqual(user.username); expect(wrapper.find('[data-test-subj="email"]').text()).toEqual(user.email); }); @@ -79,7 +80,10 @@ describe('', () => { wrapper.update(); }); - expect(wrapper.find('EuiText[data-test-subj="userDisplayName"]').text()).toEqual(user.username); + // Replace non breaking spaces ( ) with ordinary space + expect( + wrapper.find('EuiText[data-test-subj="userDisplayName"]').text().replace(/\s/g, ' ') + ).toEqual(`Settings for ${user.username}`); }); it(`displays a placeholder when no email address is provided`, async () => { From a3dad8907c33823e49f834452804ae429e686e3a Mon Sep 17 00:00:00 2001 From: Ryan Keairns Date: Fri, 18 Dec 2020 10:01:38 -0600 Subject: [PATCH 3/9] Change links conditionally --- .../nav_control/nav_control_component.tsx | 44 ++++++++++--------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/x-pack/plugins/security/public/nav_control/nav_control_component.tsx b/x-pack/plugins/security/public/nav_control/nav_control_component.tsx index a8431ba9a6d09..6d9a75b849e8e 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_component.tsx +++ b/x-pack/plugins/security/public/nav_control/nav_control_component.tsx @@ -20,6 +20,7 @@ import { EuiText, } from '@elastic/eui'; import { AuthenticatedUser } from '../../common/model'; +import { CloudSetup } from '../../../cloud/public'; import './nav_control_component.scss'; @@ -35,6 +36,7 @@ interface Props { editProfileUrl: string; logoutUrl: string; userMenuLinks$: Observable; + cloud?: CloudSetup; } interface State { @@ -91,8 +93,9 @@ export class SecurityNavControl extends Component { }; render() { - const { editProfileUrl, logoutUrl } = this.props; + const { editProfileUrl, logoutUrl, cloud } = this.props; const { authenticatedUser, userMenuLinks } = this.state; + const isCloudEnabled = Boolean(cloud?.isCloudEnabled); const username = (authenticatedUser && (authenticatedUser.full_name || authenticatedUser.username)) || ''; @@ -121,21 +124,6 @@ export class SecurityNavControl extends Component { const isAnonymousUser = authenticatedUser?.authentication_provider.type === 'anonymous'; const items: EuiContextMenuPanelItemDescriptor[] = []; - if (!isAnonymousUser) { - const profileMenuItem = { - name: ( - - ), - icon: , - href: editProfileUrl, - 'data-test-subj': 'profileLink', - }; - items.push(profileMenuItem); - } - if (userMenuLinks.length) { const userMenuLinkMenuItems = userMenuLinks .sort(({ order: orderA = Infinity }, { order: orderB = Infinity }) => orderA - orderB) @@ -145,11 +133,27 @@ export class SecurityNavControl extends Component { href, 'data-test-subj': `userMenuLink__${label}`, })); + items.push(...userMenuLinkMenuItems); + } - items.push(...userMenuLinkMenuItems, { - isSeparator: true, - key: 'securityNavControlComponent__userMenuLinksSeparator', - }); + if (!isAnonymousUser) { + const profileMenuItem = { + name: isCloudEnabled ? ( + + ) : ( + + ), + icon: , + href: editProfileUrl, + 'data-test-subj': 'profileLink', + }; + items.push(profileMenuItem); } const logoutMenuItem = { From 413d26c8e23946fd8ea4ecb5a8fb91b791d4afea Mon Sep 17 00:00:00 2001 From: Ryan Keairns Date: Fri, 18 Dec 2020 11:18:35 -0600 Subject: [PATCH 4/9] Simplify profile link logic --- .../public/nav_control/nav_control_component.tsx | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/security/public/nav_control/nav_control_component.tsx b/x-pack/plugins/security/public/nav_control/nav_control_component.tsx index 6d9a75b849e8e..8b045dc079b29 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_component.tsx +++ b/x-pack/plugins/security/public/nav_control/nav_control_component.tsx @@ -20,7 +20,6 @@ import { EuiText, } from '@elastic/eui'; import { AuthenticatedUser } from '../../common/model'; -import { CloudSetup } from '../../../cloud/public'; import './nav_control_component.scss'; @@ -36,7 +35,6 @@ interface Props { editProfileUrl: string; logoutUrl: string; userMenuLinks$: Observable; - cloud?: CloudSetup; } interface State { @@ -93,9 +91,8 @@ export class SecurityNavControl extends Component { }; render() { - const { editProfileUrl, logoutUrl, cloud } = this.props; + const { editProfileUrl, logoutUrl } = this.props; const { authenticatedUser, userMenuLinks } = this.state; - const isCloudEnabled = Boolean(cloud?.isCloudEnabled); const username = (authenticatedUser && (authenticatedUser.full_name || authenticatedUser.username)) || ''; @@ -138,9 +135,9 @@ export class SecurityNavControl extends Component { if (!isAnonymousUser) { const profileMenuItem = { - name: isCloudEnabled ? ( + name: userMenuLinks.length ? ( ) : ( @@ -149,7 +146,7 @@ export class SecurityNavControl extends Component { defaultMessage="Profile" /> ), - icon: , + icon: , href: editProfileUrl, 'data-test-subj': 'profileLink', }; From aa98142e2defee8b6b2b8124cf297dfd4b3e6c94 Mon Sep 17 00:00:00 2001 From: Ryan Keairns Date: Fri, 18 Dec 2020 16:01:06 -0600 Subject: [PATCH 5/9] Add setAsProfile prop for overriding default link --- .../plugins/cloud/public/user_menu_links.ts | 1 + .../nav_control/nav_control_component.tsx | 30 +++++++++++++++++-- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/cloud/public/user_menu_links.ts b/x-pack/plugins/cloud/public/user_menu_links.ts index b57f166e07cc5..99c08c69ecdb0 100644 --- a/x-pack/plugins/cloud/public/user_menu_links.ts +++ b/x-pack/plugins/cloud/public/user_menu_links.ts @@ -20,6 +20,7 @@ export const createUserMenuLinks = (config: CloudConfigType): UserMenuLink[] => iconType: 'user', href: resetPasswordUrl, order: 100, + setAsProfile: true, }); } diff --git a/x-pack/plugins/security/public/nav_control/nav_control_component.tsx b/x-pack/plugins/security/public/nav_control/nav_control_component.tsx index 8b045dc079b29..ab58e992943b9 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_component.tsx +++ b/x-pack/plugins/security/public/nav_control/nav_control_component.tsx @@ -28,6 +28,7 @@ export interface UserMenuLink { iconType: IconType; href: string; order?: number; + setAsProfile?: boolean; } interface Props { @@ -41,6 +42,7 @@ interface State { isOpen: boolean; authenticatedUser: AuthenticatedUser | null; userMenuLinks: UserMenuLink[]; + profileOverridden: boolean; } export class SecurityNavControl extends Component { @@ -53,6 +55,7 @@ export class SecurityNavControl extends Component { isOpen: false, authenticatedUser: null, userMenuLinks: [], + profileOverridden: false, }; props.user.then((authenticatedUser) => { @@ -65,6 +68,27 @@ export class SecurityNavControl extends Component { componentDidMount() { this.subscription = this.props.userMenuLinks$.subscribe(async (userMenuLinks) => { this.setState({ userMenuLinks }); + + if (userMenuLinks.length) { + let overrideCount = 0; + for (const key in userMenuLinks) { + // Check if any user links are profile links (i.e. override the default profile link) + if (userMenuLinks[key].setAsProfile) { + overrideCount++; + + this.setState({ + profileOverridden: true, + }); + } + } + // Show a warning when more than one override exits. + if (overrideCount > 1) { + // eslint-disable-next-line no-console + console.warn( + 'More than one profile link override has been found. A single override is recommended.' + ); + } + } }); } @@ -92,7 +116,7 @@ export class SecurityNavControl extends Component { render() { const { editProfileUrl, logoutUrl } = this.props; - const { authenticatedUser, userMenuLinks } = this.state; + const { authenticatedUser, userMenuLinks, profileOverridden } = this.state; const username = (authenticatedUser && (authenticatedUser.full_name || authenticatedUser.username)) || ''; @@ -135,7 +159,7 @@ export class SecurityNavControl extends Component { if (!isAnonymousUser) { const profileMenuItem = { - name: userMenuLinks.length ? ( + name: profileOverridden ? ( { defaultMessage="Profile" /> ), - icon: , + icon: , href: editProfileUrl, 'data-test-subj': 'profileLink', }; From 9443c6ec5df584ddf5a19b165840793dd0a5fb46 Mon Sep 17 00:00:00 2001 From: Ryan Keairns Date: Mon, 1 Mar 2021 08:38:40 -0600 Subject: [PATCH 6/9] Address feedback --- .../account_management_page.test.tsx | 14 +++--- .../account_management_page.tsx | 7 ++- .../nav_control/nav_control_component.tsx | 44 ++++++------------- .../nav_control/nav_control_service.tsx | 17 +++++++ 4 files changed, 39 insertions(+), 43 deletions(-) diff --git a/x-pack/plugins/security/public/account_management/account_management_page.test.tsx b/x-pack/plugins/security/public/account_management/account_management_page.test.tsx index bd9dc1eb28485..f627914855349 100644 --- a/x-pack/plugins/security/public/account_management/account_management_page.test.tsx +++ b/x-pack/plugins/security/public/account_management/account_management_page.test.tsx @@ -59,10 +59,9 @@ describe('', () => { wrapper.update(); }); - // Replace non breaking spaces ( ) with ordinary space - expect( - wrapper.find('EuiText[data-test-subj="userDisplayName"]').text().replace(/\s/g, ' ') - ).toEqual(`Settings for ${user.full_name}`); + expect(wrapper.find('EuiText[data-test-subj="userDisplayName"]').text()).toEqual( + `Settings for ${user.full_name}` + ); expect(wrapper.find('[data-test-subj="username"]').text()).toEqual(user.username); expect(wrapper.find('[data-test-subj="email"]').text()).toEqual(user.email); }); @@ -82,10 +81,9 @@ describe('', () => { wrapper.update(); }); - // Replace non breaking spaces ( ) with ordinary space - expect( - wrapper.find('EuiText[data-test-subj="userDisplayName"]').text().replace(/\s/g, ' ') - ).toEqual(`Settings for ${user.username}`); + expect(wrapper.find('EuiText[data-test-subj="userDisplayName"]').text()).toEqual( + `Settings for ${user.username}` + ); }); it(`displays a placeholder when no email address is provided`, async () => { diff --git a/x-pack/plugins/security/public/account_management/account_management_page.tsx b/x-pack/plugins/security/public/account_management/account_management_page.tsx index edecd8158887d..472c07b32cbd7 100644 --- a/x-pack/plugins/security/public/account_management/account_management_page.tsx +++ b/x-pack/plugins/security/public/account_management/account_management_page.tsx @@ -40,11 +40,10 @@ export const AccountManagementPage = ({ userAPIClient, authc, notifications }: P

{getUserDisplayName(currentUser)} }} /> -   - {getUserDisplayName(currentUser)}

diff --git a/x-pack/plugins/security/public/nav_control/nav_control_component.tsx b/x-pack/plugins/security/public/nav_control/nav_control_component.tsx index 736ef8403bb4f..fa5f7fafc8f5b 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_component.tsx +++ b/x-pack/plugins/security/public/nav_control/nav_control_component.tsx @@ -69,27 +69,6 @@ export class SecurityNavControl extends Component { componentDidMount() { this.subscription = this.props.userMenuLinks$.subscribe(async (userMenuLinks) => { this.setState({ userMenuLinks }); - - if (userMenuLinks.length) { - let overrideCount = 0; - for (const key in userMenuLinks) { - // Check if any user links are profile links (i.e. override the default profile link) - if (userMenuLinks[key].setAsProfile) { - overrideCount++; - - this.setState({ - profileOverridden: true, - }); - } - } - // Show a warning when more than one override exits. - if (overrideCount > 1) { - // eslint-disable-next-line no-console - console.warn( - 'More than one profile link override has been found. A single override is recommended.' - ); - } - } }); } @@ -117,7 +96,7 @@ export class SecurityNavControl extends Component { render() { const { editProfileUrl, logoutUrl } = this.props; - const { authenticatedUser, userMenuLinks, profileOverridden } = this.state; + const { authenticatedUser, userMenuLinks } = this.state; const username = (authenticatedUser && (authenticatedUser.full_name || authenticatedUser.username)) || ''; @@ -159,23 +138,26 @@ export class SecurityNavControl extends Component { } if (!isAnonymousUser) { + const hasCustomProfileLinks = userMenuLinks.some(({ setAsProfile }) => setAsProfile === true); const profileMenuItem = { - name: profileOverridden ? ( - - ) : ( + name: ( ), - icon: , + icon: , href: editProfileUrl, 'data-test-subj': 'profileLink', }; - items.push(profileMenuItem); + + // Set this as the first link if there is no user-defined profile link + if (!hasCustomProfileLinks) { + items.unshift(profileMenuItem); + } else { + items.push(profileMenuItem); + } } const logoutMenuItem = { diff --git a/x-pack/plugins/security/public/nav_control/nav_control_service.tsx b/x-pack/plugins/security/public/nav_control/nav_control_service.tsx index 02ef208ce42a3..01176c418c5d3 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_service.tsx +++ b/x-pack/plugins/security/public/nav_control/nav_control_service.tsx @@ -75,6 +75,23 @@ export class SecurityNavControlService { this.userMenuLinks$.pipe(map(this.sortUserMenuLinks), takeUntil(this.stop$)), addUserMenuLinks: (userMenuLinks: UserMenuLink[]) => { const currentLinks = this.userMenuLinks$.value; + const hasCustomProfileLink = currentLinks.find(({ setAsProfile }) => setAsProfile === true); + const passedCustomProfileLinkCount = userMenuLinks.reduce( + (linkCounter, { setAsProfile }) => + setAsProfile === true ? linkCounter + 1 : linkCounter, + 0 + ); + + if (hasCustomProfileLink && passedCustomProfileLinkCount > 0) { + throw new Error( + `Only one custom profile link can be set. A custom profile link named ${hasCustomProfileLink.label} (${hasCustomProfileLink.href}) already exists` + ); + } else if (passedCustomProfileLinkCount > 1) { + throw new Error( + `Only one custom profile link can be passed at a time (found ${passedCustomProfileLinkCount})` + ); + } + const newLinks = [...currentLinks, ...userMenuLinks]; this.userMenuLinks$.next(newLinks); }, From 1ebd48113614ab97f93eb5b964fedc18ea01fbaf Mon Sep 17 00:00:00 2001 From: Ryan Keairns Date: Mon, 1 Mar 2021 12:57:12 -0600 Subject: [PATCH 7/9] remove translations since message has changed --- .../security/public/nav_control/nav_control_component.tsx | 2 -- x-pack/plugins/translations/translations/ja-JP.json | 1 - x-pack/plugins/translations/translations/zh-CN.json | 1 - 3 files changed, 4 deletions(-) diff --git a/x-pack/plugins/security/public/nav_control/nav_control_component.tsx b/x-pack/plugins/security/public/nav_control/nav_control_component.tsx index fa5f7fafc8f5b..2999bbf4c2772 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_component.tsx +++ b/x-pack/plugins/security/public/nav_control/nav_control_component.tsx @@ -43,7 +43,6 @@ interface State { isOpen: boolean; authenticatedUser: AuthenticatedUser | null; userMenuLinks: UserMenuLink[]; - profileOverridden: boolean; } export class SecurityNavControl extends Component { @@ -56,7 +55,6 @@ export class SecurityNavControl extends Component { isOpen: false, authenticatedUser: null, userMenuLinks: [], - profileOverridden: false, }; props.user.then((authenticatedUser) => { diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index d68e6c375a592..64bac32a9be9b 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -16883,7 +16883,6 @@ "xpack.security.management.users.usersTitle": "ユーザー", "xpack.security.management.usersTitle": "ユーザー", "xpack.security.navControlComponent.accountMenuAriaLabel": "アカウントメニュー", - "xpack.security.navControlComponent.editProfileLinkText": "プロフィール", "xpack.security.navControlComponent.loginLinkText": "ログイン", "xpack.security.navControlComponent.logoutLinkText": "ログアウト", "xpack.security.overwrittenSession.continueAsUserText": "{username} として続行", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index dc39b7b03634b..7e79555491fb4 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -16926,7 +16926,6 @@ "xpack.security.management.users.usersTitle": "用户", "xpack.security.management.usersTitle": "用户", "xpack.security.navControlComponent.accountMenuAriaLabel": "帐户菜单", - "xpack.security.navControlComponent.editProfileLinkText": "配置文件", "xpack.security.navControlComponent.loginLinkText": "登录", "xpack.security.navControlComponent.logoutLinkText": "注销", "xpack.security.overwrittenSession.continueAsUserText": "作为 {username} 继续", From 3b478c9abdc79d05641519723b4725fe6f59dbdd Mon Sep 17 00:00:00 2001 From: Ryan Keairns Date: Wed, 10 Mar 2021 10:47:16 -0600 Subject: [PATCH 8/9] Tidying up --- .../account_management/account_management_page.tsx | 2 +- .../public/nav_control/nav_control_service.tsx | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/security/public/account_management/account_management_page.tsx b/x-pack/plugins/security/public/account_management/account_management_page.tsx index ce59f9870c8b4..60f48c01a6ff7 100644 --- a/x-pack/plugins/security/public/account_management/account_management_page.tsx +++ b/x-pack/plugins/security/public/account_management/account_management_page.tsx @@ -5,11 +5,11 @@ * 2.0. */ -import { FormattedMessage } from '@kbn/i18n/react'; import { EuiPage, EuiPageBody, EuiPanel, EuiSpacer, EuiText } from '@elastic/eui'; import React, { useEffect, useState } from 'react'; import ReactDOM from 'react-dom'; +import { FormattedMessage } from '@kbn/i18n/react'; import type { PublicMethodsOf } from '@kbn/utility-types'; import type { CoreStart, NotificationsStart } from 'src/core/public'; diff --git a/x-pack/plugins/security/public/nav_control/nav_control_service.tsx b/x-pack/plugins/security/public/nav_control/nav_control_service.tsx index fa3dbfec06e35..8a2c68a464067 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_service.tsx +++ b/x-pack/plugins/security/public/nav_control/nav_control_service.tsx @@ -78,17 +78,16 @@ export class SecurityNavControlService { addUserMenuLinks: (userMenuLinks: UserMenuLink[]) => { const currentLinks = this.userMenuLinks$.value; const hasCustomProfileLink = currentLinks.find(({ setAsProfile }) => setAsProfile === true); - const passedCustomProfileLinkCount = userMenuLinks.reduce( - (linkCounter, { setAsProfile }) => - setAsProfile === true ? linkCounter + 1 : linkCounter, - 0 - ); + const passedCustomProfileLinkCount = userMenuLinks.filter( + ({ setAsProfile }) => setAsProfile === true + ).length; if (hasCustomProfileLink && passedCustomProfileLinkCount > 0) { throw new Error( `Only one custom profile link can be set. A custom profile link named ${hasCustomProfileLink.label} (${hasCustomProfileLink.href}) already exists` ); - } else if (passedCustomProfileLinkCount > 1) { + } + if (passedCustomProfileLinkCount > 1) { throw new Error( `Only one custom profile link can be passed at a time (found ${passedCustomProfileLinkCount})` ); From b3b88728295ec5d1bb137b188b7bdb13dc73ebec Mon Sep 17 00:00:00 2001 From: Aleh Zasypkin Date: Thu, 11 Mar 2021 09:37:28 +0100 Subject: [PATCH 9/9] Add unit tests. --- .../nav_control_component.test.tsx | 56 +++++++++- .../nav_control/nav_control_service.test.ts | 101 ++++++++++++++---- .../nav_control/nav_control_service.tsx | 1 + 3 files changed, 136 insertions(+), 22 deletions(-) diff --git a/x-pack/plugins/security/public/nav_control/nav_control_component.test.tsx b/x-pack/plugins/security/public/nav_control/nav_control_component.test.tsx index bd338109a4460..f2d3fcd6ab3ca 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_component.test.tsx +++ b/x-pack/plugins/security/public/nav_control/nav_control_component.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiHeaderSectionItemButton, EuiPopover } from '@elastic/eui'; +import { EuiContextMenuItem, EuiHeaderSectionItemButton, EuiPopover } from '@elastic/eui'; import React from 'react'; import { BehaviorSubject } from 'rxjs'; @@ -181,4 +181,58 @@ describe('SecurityNavControl', () => { expect(findTestSubject(wrapper, 'logoutLink').text()).toBe('Log in'); }); + + it('properly renders without a custom profile link.', async () => { + const props = { + user: Promise.resolve(mockAuthenticatedUser({ full_name: 'foo' })), + editProfileUrl: '', + logoutUrl: '', + userMenuLinks$: new BehaviorSubject([ + { label: 'link1', href: 'path-to-link-1', iconType: 'empty', order: 1 }, + { label: 'link2', href: 'path-to-link-2', iconType: 'empty', order: 2 }, + ]), + }; + + const wrapper = mountWithIntl(); + await nextTick(); + wrapper.update(); + + expect(wrapper.find(EuiContextMenuItem).map((node) => node.text())).toEqual([]); + + wrapper.find(EuiHeaderSectionItemButton).simulate('click'); + + expect(wrapper.find(EuiContextMenuItem).map((node) => node.text())).toEqual([ + 'Profile', + 'link1', + 'link2', + 'Log out', + ]); + }); + + it('properly renders with a custom profile link.', async () => { + const props = { + user: Promise.resolve(mockAuthenticatedUser({ full_name: 'foo' })), + editProfileUrl: '', + logoutUrl: '', + userMenuLinks$: new BehaviorSubject([ + { label: 'link1', href: 'path-to-link-1', iconType: 'empty', order: 1 }, + { label: 'link2', href: 'path-to-link-2', iconType: 'empty', order: 2, setAsProfile: true }, + ]), + }; + + const wrapper = mountWithIntl(); + await nextTick(); + wrapper.update(); + + expect(wrapper.find(EuiContextMenuItem).map((node) => node.text())).toEqual([]); + + wrapper.find(EuiHeaderSectionItemButton).simulate('click'); + + expect(wrapper.find(EuiContextMenuItem).map((node) => node.text())).toEqual([ + 'link1', + 'link2', + 'Preferences', + 'Log out', + ]); + }); }); diff --git a/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts b/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts index 72a1a6f5817a5..035177d78c9c6 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts +++ b/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts @@ -178,16 +178,19 @@ describe('SecurityNavControlService', () => { }); describe(`#start`, () => { - it('should return functions to register and retrieve user menu links', () => { - const license$ = new BehaviorSubject(validLicense); + let navControlService: SecurityNavControlService; + beforeEach(() => { + const license$ = new BehaviorSubject({} as ILicense); - const navControlService = new SecurityNavControlService(); + navControlService = new SecurityNavControlService(); navControlService.setup({ securityLicense: new SecurityLicenseService().setup({ license$ }).license, authc: securityMock.createSetup().authc, logoutUrl: '/some/logout/url', }); + }); + it('should return functions to register and retrieve user menu links', () => { const coreStart = coreMock.createStart(); const navControlServiceStart = navControlService.start({ core: coreStart }); expect(navControlServiceStart).toHaveProperty('getUserMenuLinks$'); @@ -195,15 +198,6 @@ describe('SecurityNavControlService', () => { }); it('should register custom user menu links to be displayed in the nav controls', (done) => { - const license$ = new BehaviorSubject(validLicense); - - const navControlService = new SecurityNavControlService(); - navControlService.setup({ - securityLicense: new SecurityLicenseService().setup({ license$ }).license, - authc: securityMock.createSetup().authc, - logoutUrl: '/some/logout/url', - }); - const coreStart = coreMock.createStart(); const { getUserMenuLinks$, addUserMenuLinks } = navControlService.start({ core: coreStart }); const userMenuLinks$ = getUserMenuLinks$(); @@ -231,15 +225,6 @@ describe('SecurityNavControlService', () => { }); it('should retrieve user menu links sorted by order', (done) => { - const license$ = new BehaviorSubject(validLicense); - - const navControlService = new SecurityNavControlService(); - navControlService.setup({ - securityLicense: new SecurityLicenseService().setup({ license$ }).license, - authc: securityMock.createSetup().authc, - logoutUrl: '/some/logout/url', - }); - const coreStart = coreMock.createStart(); const { getUserMenuLinks$, addUserMenuLinks } = navControlService.start({ core: coreStart }); const userMenuLinks$ = getUserMenuLinks$(); @@ -305,5 +290,79 @@ describe('SecurityNavControlService', () => { done(); }); }); + + it('should allow adding a custom profile link', () => { + const coreStart = coreMock.createStart(); + const { getUserMenuLinks$, addUserMenuLinks } = navControlService.start({ core: coreStart }); + const userMenuLinks$ = getUserMenuLinks$(); + + addUserMenuLinks([ + { label: 'link3', href: 'path-to-link3', iconType: 'empty', order: 3 }, + { label: 'link1', href: 'path-to-link1', iconType: 'empty', order: 1, setAsProfile: true }, + ]); + + const onUserMenuLinksHandler = jest.fn(); + userMenuLinks$.subscribe(onUserMenuLinksHandler); + + expect(onUserMenuLinksHandler).toHaveBeenCalledTimes(1); + expect(onUserMenuLinksHandler).toHaveBeenCalledWith([ + { label: 'link1', href: 'path-to-link1', iconType: 'empty', order: 1, setAsProfile: true }, + { label: 'link3', href: 'path-to-link3', iconType: 'empty', order: 3 }, + ]); + }); + + it('should not allow adding more than one custom profile link', () => { + const coreStart = coreMock.createStart(); + const { getUserMenuLinks$, addUserMenuLinks } = navControlService.start({ core: coreStart }); + const userMenuLinks$ = getUserMenuLinks$(); + + expect(() => { + addUserMenuLinks([ + { + label: 'link3', + href: 'path-to-link3', + iconType: 'empty', + order: 3, + setAsProfile: true, + }, + { + label: 'link1', + href: 'path-to-link1', + iconType: 'empty', + order: 1, + setAsProfile: true, + }, + ]); + }).toThrowErrorMatchingInlineSnapshot( + `"Only one custom profile link can be passed at a time (found 2)"` + ); + + // Adding a single custom profile link. + addUserMenuLinks([ + { label: 'link3', href: 'path-to-link3', iconType: 'empty', order: 3, setAsProfile: true }, + ]); + + expect(() => { + addUserMenuLinks([ + { + label: 'link1', + href: 'path-to-link1', + iconType: 'empty', + order: 1, + setAsProfile: true, + }, + ]); + }).toThrowErrorMatchingInlineSnapshot( + `"Only one custom profile link can be set. A custom profile link named link3 (path-to-link3) already exists"` + ); + + const onUserMenuLinksHandler = jest.fn(); + userMenuLinks$.subscribe(onUserMenuLinksHandler); + + expect(onUserMenuLinksHandler).toHaveBeenCalledTimes(1); + expect(onUserMenuLinksHandler).toHaveBeenCalledWith([ + { label: 'link3', href: 'path-to-link3', iconType: 'empty', order: 3, setAsProfile: true }, + ]); + }); }); }); diff --git a/x-pack/plugins/security/public/nav_control/nav_control_service.tsx b/x-pack/plugins/security/public/nav_control/nav_control_service.tsx index 8a2c68a464067..7f3d93099704a 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_service.tsx +++ b/x-pack/plugins/security/public/nav_control/nav_control_service.tsx @@ -87,6 +87,7 @@ export class SecurityNavControlService { `Only one custom profile link can be set. A custom profile link named ${hasCustomProfileLink.label} (${hasCustomProfileLink.href}) already exists` ); } + if (passedCustomProfileLinkCount > 1) { throw new Error( `Only one custom profile link can be passed at a time (found ${passedCustomProfileLinkCount})`