[UsersProfilePopover] Fix email sometimes is not visible (#184318)

## Summary

fix https://github.com/elastic/kibana/issues/182945


- This fixes `UsersProfilePopover` to always show email on the 2nd line.
To keep the list virtualized **had to increase the height a bit for
every row**

Before:


![image](ee42530f-9b39-4139-bd81-12c0a8fd6498)


After:

![Screenshot 2024-05-27 at 15 28
05](5d869cf3-194f-4b08-b4a8-945d5904452e)


- Also adds email in a label to make it appear in a browser tooltip 

![Screenshot 2024-05-27 at 16 20
12](a9687ec2-a92a-44e9-a64d-e64a4e87b497)


- Also fixes https://github.com/elastic/kibana/issues/182561 and adds
email to Avatar "name" so it appears in a native tooltip

![Screenshot 2024-05-28 at 15 57
21](67b0bb65-4ce4-4561-85cc-356175bc86c2)



- Increase popover width in TableListView
This commit is contained in:
Anton Dosov 2024-06-05 14:19:30 +02:00 committed by GitHub
parent 8a5ed874af
commit db9e530da2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 64 additions and 62 deletions

View file

@ -164,7 +164,7 @@ export const UserFilterPanel: FC<{}> = () => {
},
onSearchChange: setSearchTerm,
}}
panelProps={{ css: { minWidth: euiTheme.base * 18 } }}
panelProps={{ css: { minWidth: euiTheme.base * 22 } }}
/>
</>
);

View file

@ -31,7 +31,7 @@ describe('UserAvatar', () => {
<EuiAvatar
color="plain"
imageUrl="https://source.unsplash.com/64x64/?cat"
name="Delighted Nightingale"
name="Delighted Nightingale (delighted_nightingale@elastic.co)"
/>
`);
});
@ -56,7 +56,7 @@ describe('UserAvatar', () => {
color="#09e8ca"
initials="DN"
initialsLength={2}
name="Delighted Nightingale"
name="Delighted Nightingale (delighted_nightingale@elastic.co)"
/>
`);
});
@ -76,7 +76,7 @@ describe('UserAvatar', () => {
color="#AA6556"
initials="DN"
initialsLength={2}
name="Delighted Nightingale"
name="Delighted Nightingale (delighted_nightingale@elastic.co)"
/>
`);
});

View file

@ -16,7 +16,7 @@ import type { UserProfile, UserProfileUserInfo } from './user_profile';
import {
getUserAvatarColor,
getUserAvatarInitials,
getUserDisplayName,
getUserDisplayLabel,
USER_AVATAR_MAX_INITIALS,
} from './user_profile';
@ -62,15 +62,15 @@ export const UserAvatar: FunctionComponent<UserAvatarProps> = ({ user, avatar, .
return <EuiAvatar name="" color={euiTheme.colors.lightestShade} initials="?" {...rest} />;
}
const displayName = getUserDisplayName(user);
const displayLabel = getUserDisplayLabel(user);
if (avatar?.imageUrl) {
return <EuiAvatar name={displayName} imageUrl={avatar.imageUrl} color="plain" {...rest} />;
return <EuiAvatar name={displayLabel} imageUrl={avatar.imageUrl} color="plain" {...rest} />;
}
return (
<EuiAvatar
name={displayName}
name={displayLabel}
initials={getUserAvatarInitials(user, avatar)}
initialsLength={USER_AVATAR_MAX_INITIALS}
color={getUserAvatarColor(user, avatar)}

View file

@ -136,3 +136,16 @@ export interface GetUserDisplayNameParams {
export function getUserDisplayName(params: GetUserDisplayNameParams) {
return params.full_name || params.email || params.username;
}
/**
* Determines the display label for the provided user information.
* Includes the email if it is different from the display name.
* @param params Set of available user's name-related fields.
*/
export function getUserDisplayLabel(user: GetUserDisplayNameParams): string {
const displayName = getUserDisplayName(user);
if (user.email && user.email !== displayName) {
return `${displayName} (${user.email})`;
}
return displayName;
}

View file

@ -18,7 +18,6 @@ import {
EuiText,
EuiCallOut,
EuiHighlight,
EuiTextColor,
} from '@elastic/eui';
import type { ReactNode } from 'react';
import React, { useEffect, useState } from 'react';
@ -26,7 +25,7 @@ import React, { useEffect, useState } from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { getUserDisplayName } from './user_profile';
import { getUserDisplayLabel, getUserDisplayName } from './user_profile';
import type { UserProfileWithAvatar } from './user_avatar';
import { UserAvatar } from './user_avatar';
@ -326,40 +325,33 @@ export const UserProfilesSelectable = <Option extends UserProfileWithAvatar | nu
id: searchInputId,
}}
isPreFiltered
listProps={{ onFocusBadge: false }}
listProps={{ onFocusBadge: false, rowHeight: 48 }}
loadingMessage={loadingMessage}
noMatchesMessage={noMatchesMessage}
emptyMessage={emptyMessage}
errorMessage={errorMessage}
renderOption={(option, searchValue) => {
if (option.user) {
const displayName = getUserDisplayName(option.user);
return (
<EuiFlexGroup
alignItems="center"
justifyContent="spaceBetween"
gutterSize="s"
responsive={false}
>
<EuiFlexItem css={{ maxWidth: '100%' }}>
<EuiHighlight className="eui-textTruncate" search={searchValue}>
{option.label}
</EuiHighlight>
</EuiFlexItem>
{option.user.email && option.user.email !== option.label ? (
<EuiFlexItem grow={false} css={{ minWidth: 0 }}>
<EuiTextColor
color={option.disabled ? 'disabled' : 'subdued'}
className="eui-textTruncate"
>
{searchValue ? (
<EuiHighlight search={searchValue}>{option.user.email}</EuiHighlight>
) : (
option.user.email
)}
</EuiTextColor>
</EuiFlexItem>
<>
<div className="eui-textTruncate">
<EuiHighlight search={searchValue}>{displayName}</EuiHighlight>
</div>
{option.user.email && option.user.email !== displayName ? (
<EuiText
size={'xs'}
color={option.disabled ? 'disabled' : 'subdued'}
className="eui-textTruncate"
>
{searchValue ? (
<EuiHighlight search={searchValue}>{option.user.email}</EuiHighlight>
) : (
option.user.email
)}
</EuiText>
) : undefined}
</EuiFlexGroup>
</>
);
}
return <EuiHighlight search={searchValue}>{option.label}</EuiHighlight>;
@ -451,7 +443,7 @@ function toSelectableOption(
if (userProfile) {
return {
key: userProfile.uid,
label: getUserDisplayName(userProfile.user),
label: getUserDisplayLabel(userProfile.user),
data: userProfile,
'data-test-subj': `userProfileSelectableOption-${userProfile.user.username}`,
};

View file

@ -61,20 +61,20 @@ describe('UserToolTip', () => {
</EuiFlexItem>
<EuiFlexItem
grow={true}
style={
Object {
"minWidth": 0,
}
}
>
<EuiFlexGroup
direction="column"
gutterSize="none"
<div>
Delighted Nightingale
</div>
<EuiText
size="xs"
>
<EuiFlexItem>
<strong>
Delighted Nightingale
</strong>
</EuiFlexItem>
<EuiFlexItem>
delighted_nightingale@elastic.co
</EuiFlexItem>
</EuiFlexGroup>
delighted_nightingale@elastic.co
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
}

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import type { EuiToolTipProps } from '@elastic/eui';
import { EuiText, EuiToolTipProps } from '@elastic/eui';
import { EuiToolTip, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import type { FunctionComponent } from 'react';
import React from 'react';
@ -43,15 +43,11 @@ export const UserToolTip: FunctionComponent<UserToolTipProps> = ({ user, avatar,
<EuiFlexItem grow={false}>
<UserAvatar user={user} avatar={avatar} size="l" />
</EuiFlexItem>
<EuiFlexItem grow>
<EuiFlexGroup direction="column" gutterSize="none">
<EuiFlexItem>
<strong>{displayName}</strong>
</EuiFlexItem>
{user.email && user.email !== displayName ? (
<EuiFlexItem>{user.email}</EuiFlexItem>
) : undefined}
</EuiFlexGroup>
<EuiFlexItem grow style={{ minWidth: 0 }}>
<div>{displayName}</div>
{user.email && user.email !== displayName ? (
<EuiText size={'xs'}>{user.email}</EuiText>
) : undefined}
</EuiFlexItem>
</EuiFlexGroup>
}

View file

@ -4,7 +4,8 @@
"outDir": "target/types",
"types": [
"jest",
"node"
"node",
"@emotion/react/types/css-prop",
]
},
"include": [

View file

@ -112,7 +112,7 @@ const AssigneesFilterPopoverComponent: React.FC<AssigneesFilterPopoverProps> = (
isOpen={isPopoverOpen}
closePopover={togglePopover}
panelStyle={{
minWidth: 520,
width: 400,
}}
button={
<EuiFilterButton

View file

@ -113,7 +113,7 @@ const SuggestUsersPopoverComponent: React.FC<SuggestUsersPopoverProps> = ({
isOpen={isPopoverOpen}
closePopover={onClosePopover}
panelStyle={{
minWidth: 520,
width: 400,
}}
selectableProps={{
onChange,