Skip to content

Commit

Permalink
Update text and icons to align with Cloud (#86394)
Browse files Browse the repository at this point in the history
* Update text and icons to align with Cloud

* Update test to reflect new page title prefix

* Change links conditionally

* Simplify profile link logic

* Add setAsProfile prop for overriding default link

* Address feedback

* remove translations since message has changed

* Tidying up

* Add unit tests.

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Aleh Zasypkin <aleh.zasypkin@gmail.com>
  • Loading branch information
3 people committed Mar 11, 2021
1 parent 40b648c commit f61657c
Show file tree
Hide file tree
Showing 9 changed files with 190 additions and 47 deletions.
5 changes: 3 additions & 2 deletions x-pack/plugins/cloud/public/user_menu_links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@ 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,
setAsProfile: true,
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ describe('<AccountManagementPage>', () => {
});

expect(wrapper.find('EuiText[data-test-subj="userDisplayName"]').text()).toEqual(
user.full_name
`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);
Expand All @@ -83,7 +83,9 @@ describe('<AccountManagementPage>', () => {
wrapper.update();
});

expect(wrapper.find('EuiText[data-test-subj="userDisplayName"]').text()).toEqual(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 () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ 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';

Expand Down Expand Up @@ -40,7 +41,13 @@ export const AccountManagementPage = ({ userAPIClient, authc, notifications }: P
<EuiPageBody restrictWidth>
<EuiPanel>
<EuiText data-test-subj={'userDisplayName'}>
<h1>{getUserDisplayName(currentUser)}</h1>
<h1>
<FormattedMessage
id="xpack.security.account.pageTitle"
defaultMessage="Settings for {strongUsername}"
values={{ strongUsername: <strong>{getUserDisplayName(currentUser)}</strong> }}
/>
</h1>
</EuiText>

<EuiSpacer size="xl" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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(<SecurityNavControl {...props} />);
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(<SecurityNavControl {...props} />);
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',
]);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export interface UserMenuLink {
iconType: IconType;
href: string;
order?: number;
setAsProfile?: boolean;
}

interface Props {
Expand Down Expand Up @@ -123,35 +124,39 @@ export class SecurityNavControl extends Component<Props, State> {
const isAnonymousUser = authenticatedUser?.authentication_provider.type === 'anonymous';
const items: EuiContextMenuPanelItemDescriptor[] = [];

if (userMenuLinks.length) {
const userMenuLinkMenuItems = userMenuLinks
.sort(({ order: orderA = Infinity }, { order: orderB = Infinity }) => orderA - orderB)
.map(({ label, iconType, href }: UserMenuLink) => ({
name: <EuiText>{label}</EuiText>,
icon: <EuiIcon type={iconType} size="m" />,
href,
'data-test-subj': `userMenuLink__${label}`,
}));
items.push(...userMenuLinkMenuItems);
}

if (!isAnonymousUser) {
const hasCustomProfileLinks = userMenuLinks.some(({ setAsProfile }) => setAsProfile === true);
const profileMenuItem = {
name: (
<FormattedMessage
id="xpack.security.navControlComponent.editProfileLinkText"
defaultMessage="Profile"
defaultMessage="{profileOverridden, select, true{Preferences} other{Profile}}"
values={{ profileOverridden: hasCustomProfileLinks }}
/>
),
icon: <EuiIcon type="user" size="m" />,
icon: <EuiIcon type={hasCustomProfileLinks ? 'controlsHorizontal' : 'user'} size="m" />,
href: editProfileUrl,
'data-test-subj': 'profileLink',
};
items.push(profileMenuItem);
}

if (userMenuLinks.length) {
const userMenuLinkMenuItems = userMenuLinks
.sort(({ order: orderA = Infinity }, { order: orderB = Infinity }) => orderA - orderB)
.map(({ label, iconType, href }: UserMenuLink) => ({
name: <EuiText>{label}</EuiText>,
icon: <EuiIcon type={iconType} size="m" />,
href,
'data-test-subj': `userMenuLink__${label}`,
}));

items.push(...userMenuLinkMenuItems, {
isSeparator: true,
key: 'securityNavControlComponent__userMenuLinksSeparator',
});
// 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 = {
Expand Down
101 changes: 80 additions & 21 deletions x-pack/plugins/security/public/nav_control/nav_control_service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,32 +178,26 @@ describe('SecurityNavControlService', () => {
});

describe(`#start`, () => {
it('should return functions to register and retrieve user menu links', () => {
const license$ = new BehaviorSubject<ILicense>(validLicense);
let navControlService: SecurityNavControlService;
beforeEach(() => {
const license$ = new BehaviorSubject<ILicense>({} 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$');
expect(navControlServiceStart).toHaveProperty('addUserMenuLinks');
});

it('should register custom user menu links to be displayed in the nav controls', (done) => {
const license$ = new BehaviorSubject<ILicense>(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$();
Expand Down Expand Up @@ -231,15 +225,6 @@ describe('SecurityNavControlService', () => {
});

it('should retrieve user menu links sorted by order', (done) => {
const license$ = new BehaviorSubject<ILicense>(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$();
Expand Down Expand Up @@ -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 },
]);
});
});
});
17 changes: 17 additions & 0 deletions x-pack/plugins/security/public/nav_control/nav_control_service.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,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.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`
);
}

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);
},
Expand Down
1 change: 0 additions & 1 deletion x-pack/plugins/translations/translations/ja-JP.json
Original file line number Diff line number Diff line change
Expand Up @@ -17946,7 +17946,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} として続行",
Expand Down
1 change: 0 additions & 1 deletion x-pack/plugins/translations/translations/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -18196,7 +18196,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} 继续",
Expand Down

0 comments on commit f61657c

Please sign in to comment.