Adding check to show/hide Avatar form based on whether the user is a … (#135743)

* Adding check to show/hide Avatar form based on whether the user is a cloud user or not

* Adding tests to verify the avatar doesnt show up in the UserProfile if the AuthorizedUser is a cloud user

* Changing the name of the link to 'Edit Profile' and making it available only for non-cloud users

* Adding/Updating unit tests and fixing translation files

* Removing unused values from FormattedText and related tests

* Updating unit test to work with merge from main

* Updating link to read Edit Profile to match wireframes per PR review

* Reverting changes to translation files and changing the cloud Edit Profile link and unit tests

* Changing capitalization of 'profile' so it follows the naming convention

* Changing nav menu logic to only render the default Edit Profile link if there is no custom Edit Profile link passed in by another plugin

* Updating logic to display custom nav links if user is a cloud user and related unit tests

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Kurt 2022-07-20 07:32:14 -04:00 committed by GitHub
parent 46697d5ecd
commit 2f732ae0c3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 85 additions and 59 deletions

View file

@ -434,7 +434,7 @@ describe('Cloud Plugin', () => {
const securityStart = securityMock.createStart();
securityStart.authc.getCurrentUser.mockResolvedValue(
securityMock.createMockAuthenticatedUser({
roles: ['superuser'],
elastic_cloud_user: true,
})
);
@ -446,14 +446,15 @@ describe('Cloud Plugin', () => {
expect(securityStart.authc.getCurrentUser).not.toHaveBeenCalled();
});
it('registers a custom nav link for superusers', async () => {
it('registers a custom nav link for cloud users', async () => {
const { plugin } = startPlugin();
const coreStart = coreMock.createStart();
const securityStart = securityMock.createStart();
securityStart.authc.getCurrentUser.mockResolvedValue(
securityMock.createMockAuthenticatedUser({
roles: ['superuser'],
elastic_cloud_user: true,
})
);
plugin.start(coreStart, { security: securityStart });
@ -494,14 +495,14 @@ describe('Cloud Plugin', () => {
`);
});
it('does not register a custom nav link for non-superusers', async () => {
it('does not register a custom nav link for non-cloud users', async () => {
const { plugin } = startPlugin();
const coreStart = coreMock.createStart();
const securityStart = securityMock.createStart();
securityStart.authc.getCurrentUser.mockResolvedValue(
securityMock.createMockAuthenticatedUser({
roles: ['not-a-superuser'],
elastic_cloud_user: false,
})
);
plugin.start(coreStart, { security: securityStart });
@ -511,14 +512,14 @@ describe('Cloud Plugin', () => {
expect(coreStart.chrome.setCustomNavLink).not.toHaveBeenCalled();
});
it('registers user profile links for superusers', async () => {
it('registers user profile links for cloud users', async () => {
const { plugin } = startPlugin();
const coreStart = coreMock.createStart();
const securityStart = securityMock.createStart();
securityStart.authc.getCurrentUser.mockResolvedValue(
securityMock.createMockAuthenticatedUser({
roles: ['superuser'],
elastic_cloud_user: true,
})
);
plugin.start(coreStart, { security: securityStart });
@ -532,7 +533,7 @@ describe('Cloud Plugin', () => {
Object {
"href": "https://cloud.elastic.co/profile/alice",
"iconType": "user",
"label": "Profile",
"label": "Edit profile",
"order": 100,
"setAsProfile": true,
},
@ -564,7 +565,7 @@ describe('Cloud Plugin', () => {
Object {
"href": "https://cloud.elastic.co/profile/alice",
"iconType": "user",
"label": "Profile",
"label": "Edit profile",
"order": 100,
"setAsProfile": true,
},
@ -579,14 +580,14 @@ describe('Cloud Plugin', () => {
`);
});
it('does not register profile links for non-superusers', async () => {
it('does not register profile links for non-cloud users', async () => {
const { plugin } = startPlugin();
const coreStart = coreMock.createStart();
const securityStart = securityMock.createStart();
securityStart.authc.getCurrentUser.mockResolvedValue(
securityMock.createMockAuthenticatedUser({
roles: ['not-a-superuser'],
elastic_cloud_user: false,
})
);
plugin.start(coreStart, { security: securityStart });

View file

@ -212,10 +212,12 @@ export class CloudPlugin implements Plugin<CloudSetup> {
}
// Security plugin is disabled
if (!security) return true;
// Otherwise check roles. If user is not defined due to an unexpected error, then fail *open*.
// Otherwise check if user is a cloud user.
// If user is not defined due to an unexpected error, then fail *open*.
// Cloud admin console will always perform the actual authorization checks.
const user = await security.authc.getCurrentUser().catch(() => null);
return user?.roles.includes('superuser') ?? true;
return user?.elastic_cloud_user ?? true;
}
/**

View file

@ -17,7 +17,7 @@ export const createUserMenuLinks = (config: CloudConfigType): UserMenuLink[] =>
if (baseUrl && profileUrl) {
userMenuLinks.push({
label: i18n.translate('xpack.cloud.userMenuLinks.profileLinkText', {
defaultMessage: 'Profile',
defaultMessage: 'Edit profile',
}),
iconType: 'user',
href: getFullCloudUrl(baseUrl, profileUrl),

View file

@ -6,6 +6,7 @@
*/
import { act, renderHook } from '@testing-library/react-hooks';
import { mount } from 'enzyme';
import type { FunctionComponent } from 'react';
import React from 'react';
@ -14,10 +15,11 @@ import { coreMock, scopedHistoryMock, themeServiceMock } from '@kbn/core/public/
import { UserProfileAPIClient } from '..';
import type { UserData } from '../../../common';
import { mockAuthenticatedUser } from '../../../common/model/authenticated_user.mock';
import { UserAvatar } from '../../components';
import { UserAPIClient } from '../../management';
import { securityMock } from '../../mocks';
import { Providers } from '../account_management_app';
import { useUserProfileForm } from './user_profile';
import { UserProfile, useUserProfileForm } from './user_profile';
const user = mockAuthenticatedUser();
const coreStart = coreMock.createStart();
@ -181,4 +183,52 @@ describe('useUserProfileForm', () => {
expect(result.current.initialValues.user.full_name).toEqual('Another Name');
});
describe('User Avatar Form', () => {
it('should display if the User is not a cloud user', () => {
const data: UserData = {};
const nonCloudUser = mockAuthenticatedUser({ elastic_cloud_user: false });
const testWrapper = mount(
<Providers
services={coreStart}
theme$={theme$}
history={history}
authc={authc}
securityApiClients={{
userProfiles: new UserProfileAPIClient(coreStart.http),
users: new UserAPIClient(coreStart.http),
}}
>
<UserProfile user={nonCloudUser} data={data} />
</Providers>
);
expect(testWrapper.exists(UserAvatar)).toBeTruthy();
});
it('should not display if the User is a cloud user', () => {
const data: UserData = {};
const cloudUser = mockAuthenticatedUser({ elastic_cloud_user: true });
const testWrapper = mount(
<Providers
services={coreStart}
theme$={theme$}
history={history}
authc={authc}
securityApiClients={{
userProfiles: new UserProfileAPIClient(coreStart.http),
users: new UserAPIClient(coreStart.http),
}}
>
<UserProfile user={cloudUser} data={data} />
</Providers>
);
expect(testWrapper.exists(UserAvatar)).toBeFalsy();
});
});
});

View file

@ -439,6 +439,8 @@ export const UserProfile: FunctionComponent<UserProfileProps> = ({ user, data })
const canChangeDetails = canUserChangeDetails(user, services.application.capabilities);
const isCloudUser = user.elastic_cloud_user;
const rightSideItems = [
{
title: (
@ -559,7 +561,7 @@ export const UserProfile: FunctionComponent<UserProfileProps> = ({ user, data })
>
<Form aria-labelledby={titleId}>
<UserDetailsEditor user={user} />
<UserAvatarEditor user={user} formik={formik} />
{isCloudUser ? null : <UserAvatarEditor user={user} formik={formik} />}
<UserPasswordEditor
user={user}
onShowPasswordForm={() => setShowChangePasswordForm(true)}

View file

@ -173,10 +173,10 @@ describe('SecurityNavControl', () => {
expect(wrapper.prop<boolean>('isOpen')).toEqual(false);
});
it('should render additional user menu links registered by other plugins', async () => {
it('should render additional user menu links registered by other plugins and should render the default Edit Profile link as the first link when no custom profile link is provided', async () => {
const wrapper = shallow(
<SecurityNavControl
editProfileUrl=""
editProfileUrl="edit-profile-link"
logoutUrl=""
userMenuLinks$={
new BehaviorSubject([
@ -195,19 +195,15 @@ describe('SecurityNavControl', () => {
"items": Array [
Object {
"data-test-subj": "profileLink",
"href": "",
"href": "edit-profile-link",
"icon": <EuiIcon
size="m"
type="user"
/>,
"name": <FormattedMessage
defaultMessage="{profileOverridden, select, true{Preferences} other{Profile}}"
defaultMessage="Edit profile"
id="xpack.security.navControlComponent.editProfileLinkText"
values={
Object {
"profileOverridden": false,
}
}
values={Object {}}
/>,
"onClick": [Function],
},
@ -258,10 +254,10 @@ describe('SecurityNavControl', () => {
`);
});
it('should render custom profile link registered by other plugins', async () => {
it('should render custom profile link registered by other plugins and not render default Edit Profile link', async () => {
const wrapper = shallow(
<SecurityNavControl
editProfileUrl=""
editProfileUrl="edit-profile-link"
logoutUrl=""
userMenuLinks$={
new BehaviorSubject([
@ -311,24 +307,6 @@ describe('SecurityNavControl', () => {
/>,
"name": "link3",
},
Object {
"data-test-subj": "profileLink",
"href": "",
"icon": <EuiIcon
size="m"
type="controlsHorizontal"
/>,
"name": <FormattedMessage
defaultMessage="{profileOverridden, select, true{Preferences} other{Profile}}"
id="xpack.security.navControlComponent.editProfileLinkText"
values={
Object {
"profileOverridden": true,
}
}
/>,
"onClick": [Function],
},
Object {
"data-test-subj": "logoutLink",
"href": "",

View file

@ -79,7 +79,6 @@ export const SecurityNavControl: FunctionComponent<SecurityNavControlProps> = ({
</EuiHeaderSectionItemButton>
);
const isAnonymous = currentUser.value ? isUserAnonymous(currentUser.value) : false;
const items: EuiContextMenuPanelItemDescriptor[] = [];
if (userMenuLinks.length) {
const userMenuLinkMenuItems = userMenuLinks
@ -93,17 +92,18 @@ export const SecurityNavControl: FunctionComponent<SecurityNavControlProps> = ({
items.push(...userMenuLinkMenuItems);
}
if (!isAnonymous) {
const hasCustomProfileLinks = userMenuLinks.some(({ setAsProfile }) => setAsProfile === true);
const isAnonymous = currentUser.value ? isUserAnonymous(currentUser.value) : false;
const hasCustomProfileLinks = userMenuLinks.some(({ setAsProfile }) => setAsProfile === true);
if (!isAnonymous && !hasCustomProfileLinks) {
const profileMenuItem: EuiContextMenuPanelItemDescriptor = {
name: (
<FormattedMessage
id="xpack.security.navControlComponent.editProfileLinkText"
defaultMessage="{profileOverridden, select, true{Preferences} other{Profile}}"
values={{ profileOverridden: hasCustomProfileLinks }}
defaultMessage="Edit profile"
/>
),
icon: <EuiIcon type={hasCustomProfileLinks ? 'controlsHorizontal' : 'user'} size="m" />,
icon: <EuiIcon type="user" size="m" />,
href: editProfileUrl,
onClick: () => {
setIsPopoverOpen(false);
@ -112,11 +112,7 @@ export const SecurityNavControl: FunctionComponent<SecurityNavControlProps> = ({
};
// Set this as the first link if there is no user-defined profile link
if (!hasCustomProfileLinks) {
items.unshift(profileMenuItem);
} else {
items.push(profileMenuItem);
}
items.unshift(profileMenuItem);
}
items.push({

View file

@ -24135,7 +24135,6 @@
"xpack.security.management.users.usersTitle": "Utilisateurs",
"xpack.security.management.usersTitle": "Utilisateurs",
"xpack.security.navControlComponent.accountMenuAriaLabel": "Menu Compte",
"xpack.security.navControlComponent.editProfileLinkText": "{profileOverridden, select, true{Préférences} other{Profil}}",
"xpack.security.navControlComponent.loginLinkText": "Connexion",
"xpack.security.navControlComponent.logoutLinkText": "Déconnexion",
"xpack.security.overwrittenSession.continueAsUserText": "Continuer en tant que {username}",

View file

@ -24120,7 +24120,6 @@
"xpack.security.management.users.usersTitle": "ユーザー",
"xpack.security.management.usersTitle": "ユーザー",
"xpack.security.navControlComponent.accountMenuAriaLabel": "アカウントメニュー",
"xpack.security.navControlComponent.editProfileLinkText": "{profileOverridden, select, true{設定} other{プロファイル}}",
"xpack.security.navControlComponent.loginLinkText": "ログイン",
"xpack.security.navControlComponent.logoutLinkText": "ログアウト",
"xpack.security.overwrittenSession.continueAsUserText": "{username} として続行",

View file

@ -24146,7 +24146,6 @@
"xpack.security.management.users.usersTitle": "用户",
"xpack.security.management.usersTitle": "用户",
"xpack.security.navControlComponent.accountMenuAriaLabel": "帐户菜单",
"xpack.security.navControlComponent.editProfileLinkText": "{profileOverridden, select, true{首选项} other{配置文件}}",
"xpack.security.navControlComponent.loginLinkText": "登录",
"xpack.security.navControlComponent.logoutLinkText": "注销",
"xpack.security.overwrittenSession.continueAsUserText": "作为 {username} 继续",