[User Profile] Update edit profile header layout (#202902)

## Summary

This PR updates layout of `User Profile` header according to [this
design](https://github.com/elastic/kibana/issues/200059#issuecomment-2512452474).
Since those changes break the layout pattern suggested by EUI, I had to
move the content to be `children` of the header [as the EUI docs
suggest.](https://eui.elastic.co/#/layout/page-header#customizing-the-page-header)

Closes: #200059

---------

Co-authored-by: Ryan Keairns <contactryank@gmail.com>
This commit is contained in:
Krzysztof Kowalczyk 2024-12-12 22:02:27 +01:00 committed by GitHub
parent 55b5baae64
commit 780316832b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 98 additions and 62 deletions

View file

@ -414,14 +414,14 @@ describe('useUserProfileForm', () => {
expect(testWrapper.exists('EuiBadgeGroup[data-test-subj="remainingRoles"]')).toBeFalsy();
});
it('should display a popover for users with more than one role', () => {
it('should display a popover for users with more than three roles', () => {
const data: UserProfileData = {};
const nonCloudUser = mockAuthenticatedUser({ elastic_cloud_user: false });
coreStart.settings.client.get.mockReturnValue(false);
coreStart.settings.client.isOverridden.mockReturnValue(true);
nonCloudUser.roles = [...nonCloudUser.roles, 'user-role-1', 'user-role-2'];
nonCloudUser.roles = [...nonCloudUser.roles, 'user-role-1', 'user-role-2', 'user-role-3'];
const testWrapper = mount(
<Providers
services={coreStart}
@ -436,7 +436,7 @@ describe('useUserProfileForm', () => {
</Providers>
);
const extraRoles = nonCloudUser.roles.splice(1);
const extraRoles = nonCloudUser.roles.splice(3);
const userRolesExpandButton = testWrapper.find(
'EuiButtonEmpty[data-test-subj="userRolesExpand"]'

View file

@ -22,9 +22,12 @@ import {
EuiIconTip,
EuiKeyPadMenu,
EuiKeyPadMenuItem,
EuiPageHeaderSection,
EuiPopover,
EuiSpacer,
EuiText,
EuiTextTruncate,
EuiTitle,
EuiToolTip,
useEuiTheme,
useGeneratedHtmlId,
@ -72,6 +75,28 @@ const formRowCSS = css`
}
`;
const pageHeaderCSS = css`
max-width: 1248px;
margin: auto;
border-bottom: none;
`;
const pageTitleCSS = css`
min-width: 120px;
`;
const rightSideItemsCSS = css`
justify-content: flex-start;
@include euiBreakpoint('m') {
justify-content: flex-end;
}
`;
const rightSideItemCSS = css`
min-width: 160px;
`;
export interface UserProfileProps {
user: AuthenticatedUser;
data?: UserProfileData;
@ -607,14 +632,13 @@ function UserPasswordEditor({
}
const UserRoles: FunctionComponent<UserRoleProps> = ({ user }) => {
const { euiTheme } = useEuiTheme();
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const onButtonClick = () => setIsPopoverOpen((isOpen) => !isOpen);
const closePopover = () => setIsPopoverOpen(false);
const [firstRole] = user.roles;
const remainingRoles = user.roles.slice(1);
const firstThreeRoles = user.roles.slice(0, 3);
const remainingRoles = user.roles.slice(3);
const renderMoreRoles = () => {
const button = (
@ -653,16 +677,13 @@ const UserRoles: FunctionComponent<UserRoleProps> = ({ user }) => {
return (
<>
<div
style={{
maxWidth: euiTheme.breakpoint.m / 6,
display: 'inline-block',
}}
>
<EuiBadge key={firstRole} color="hollow" data-test-subj={`role${firstRole}`}>
{firstRole}
</EuiBadge>
</div>
<EuiBadgeGroup gutterSize="xs" data-test-subj="displayedRoles">
{firstThreeRoles.map((role) => (
<EuiBadge key={role} color="hollow" data-test-subj={`role${role}`}>
{role}
</EuiBadge>
))}
</EuiBadgeGroup>
{remainingRoles.length ? renderMoreRoles() : null}
</>
);
@ -693,7 +714,9 @@ export const UserProfile: FunctionComponent<UserProfileProps> = ({ user, data })
defaultMessage="Username"
/>
),
description: user.username as string | undefined | JSX.Element,
description:
user.username &&
((<EuiTextTruncate text={user.username} />) as string | undefined | JSX.Element),
helpText: (
<FormattedMessage
id="xpack.security.accountManagement.userProfile.usernameHelpText"
@ -712,7 +735,7 @@ export const UserProfile: FunctionComponent<UserProfileProps> = ({ user, data })
defaultMessage="Full name"
/>
),
description: user.full_name,
description: user.full_name && <EuiTextTruncate text={user.full_name} />,
helpText: (
<FormattedMessage
id="xpack.security.accountManagement.userProfile.fullNameHelpText"
@ -729,7 +752,7 @@ export const UserProfile: FunctionComponent<UserProfileProps> = ({ user, data })
defaultMessage="Email address"
/>
),
description: user.email,
description: user.email && <EuiTextTruncate text={user.email} />,
helpText: (
<FormattedMessage
id="xpack.security.accountManagement.userProfile.emailHelpText"
@ -778,48 +801,61 @@ export const UserProfile: FunctionComponent<UserProfileProps> = ({ user, data })
/>
) : null}
<KibanaPageTemplate className="eui-fullHeight" restrictWidth={1000}>
<KibanaPageTemplate.Header
pageTitle={
<FormattedMessage
id="xpack.security.accountManagement.userProfile.title"
defaultMessage="Profile"
/>
}
id={titleId}
rightSideItems={rightSideItems.reverse().map((item) => (
<EuiDescriptionList
textStyle="reverse"
listItems={[
{
title: (
<EuiText color={euiTheme.colors.darkestShade} size="s">
<EuiFlexGroup responsive={false} alignItems="center" gutterSize="none">
<EuiFlexItem grow={false}>{item.title}</EuiFlexItem>
<EuiFlexItem grow={false} style={{ marginLeft: '0.33em' }}>
<EuiIconTip type="questionInCircle" content={item.helpText} />
</EuiFlexItem>
</EuiFlexGroup>
</EuiText>
),
description: (
<span data-test-subj={item.testSubj}>
{item.description || (
<EuiText color={euiTheme.colors.textDisabled} size="s">
<FormattedMessage
id="xpack.security.accountManagement.userProfile.noneProvided"
defaultMessage="None provided"
/>
<KibanaPageTemplate className="eui-fullHeight" restrictWidth={true}>
<KibanaPageTemplate.Header id={titleId} css={pageHeaderCSS}>
<EuiPageHeaderSection>
<EuiTitle size="l" css={pageTitleCSS}>
<h1>
<FormattedMessage
id="xpack.security.accountManagement.userProfile.title"
defaultMessage="Profile"
/>
</h1>
</EuiTitle>
</EuiPageHeaderSection>
<EuiPageHeaderSection>
<EuiFlexGroup alignItems="flexStart" css={rightSideItemsCSS}>
{rightSideItems.map((item) => (
<EuiDescriptionList
key={item.testSubj}
textStyle="reverse"
css={rightSideItemCSS}
listItems={[
{
title: (
<EuiText color={euiTheme.colors.darkestShade} size="s">
<EuiFlexGroup
responsive={false}
alignItems="center"
gutterSize="none"
>
<EuiFlexItem grow={false}>{item.title}</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiIconTip type="questionInCircle" content={item.helpText} />
</EuiFlexItem>
</EuiFlexGroup>
</EuiText>
)}
</span>
),
},
]}
compressed
/>
))}
/>
),
description: (
<span data-test-subj={item.testSubj}>
{item.description || (
<EuiText color={euiTheme.colors.textDisabled} size="s">
<FormattedMessage
id="xpack.security.accountManagement.userProfile.noneProvided"
defaultMessage="None provided"
/>
</EuiText>
)}
</span>
),
},
]}
compressed
/>
))}
</EuiFlexGroup>
</EuiPageHeaderSection>
</KibanaPageTemplate.Header>
<KibanaPageTemplate.Section>
<Form aria-labelledby={titleId}>
<UserDetailsEditor user={user} />

View file

@ -29,8 +29,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
const actualFullname = await pageObjects.userProfiles.getProfileFullname();
const actualEmail = await pageObjects.userProfiles.getProfileEmail();
expect(actualFullname).to.be(userData.full_name);
expect(actualEmail).to.be(userData.email);
expect(actualFullname).to.contain(userData.full_name);
expect(actualEmail).to.contain(userData.email);
});
it('should not have edit actions', async () => {