mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 01:13:23 -04:00
Update text and icons to align with Cloud (#86394)
* 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>
This commit is contained in:
parent
40b648cd06
commit
f61657c5e9
9 changed files with 191 additions and 48 deletions
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
@ -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 () => {
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
@ -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" />
|
||||
|
|
|
@ -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(<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',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -30,6 +30,7 @@ export interface UserMenuLink {
|
|||
iconType: IconType;
|
||||
href: string;
|
||||
order?: number;
|
||||
setAsProfile?: boolean;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
|
@ -123,21 +124,6 @@ export class SecurityNavControl extends Component<Props, State> {
|
|||
const isAnonymousUser = authenticatedUser?.authentication_provider.type === 'anonymous';
|
||||
const items: EuiContextMenuPanelItemDescriptor[] = [];
|
||||
|
||||
if (!isAnonymousUser) {
|
||||
const profileMenuItem = {
|
||||
name: (
|
||||
<FormattedMessage
|
||||
id="xpack.security.navControlComponent.editProfileLinkText"
|
||||
defaultMessage="Profile"
|
||||
/>
|
||||
),
|
||||
icon: <EuiIcon type="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)
|
||||
|
@ -147,11 +133,30 @@ export class SecurityNavControl extends Component<Props, State> {
|
|||
href,
|
||||
'data-test-subj': `userMenuLink__${label}`,
|
||||
}));
|
||||
items.push(...userMenuLinkMenuItems);
|
||||
}
|
||||
|
||||
items.push(...userMenuLinkMenuItems, {
|
||||
isSeparator: true,
|
||||
key: 'securityNavControlComponent__userMenuLinksSeparator',
|
||||
});
|
||||
if (!isAnonymousUser) {
|
||||
const hasCustomProfileLinks = userMenuLinks.some(({ setAsProfile }) => setAsProfile === true);
|
||||
const profileMenuItem = {
|
||||
name: (
|
||||
<FormattedMessage
|
||||
id="xpack.security.navControlComponent.editProfileLinkText"
|
||||
defaultMessage="{profileOverridden, select, true{Preferences} other{Profile}}"
|
||||
values={{ profileOverridden: hasCustomProfileLinks }}
|
||||
/>
|
||||
),
|
||||
icon: <EuiIcon type={hasCustomProfileLinks ? 'controlsHorizontal' : 'user'} size="m" />,
|
||||
href: editProfileUrl,
|
||||
'data-test-subj': 'profileLink',
|
||||
};
|
||||
|
||||
// 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 = {
|
||||
|
|
|
@ -178,16 +178,19 @@ 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$');
|
||||
|
@ -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<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$();
|
||||
|
@ -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$();
|
||||
|
@ -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 },
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
|
|
|
@ -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} として続行",
|
||||
|
|
|
@ -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} 继续",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue