Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[7.x] Update text and icons to align with Cloud (#86394) #94436

Merged
merged 1 commit into from
Mar 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -17940,7 +17940,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 @@ -18189,7 +18189,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