made consistent owner (#20576)

Co-authored-by: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com>
This commit is contained in:
Dhruv Parmar 2025-04-04 14:25:55 +05:30 committed by GitHub
parent acd2926413
commit 2b8106e933
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 191 additions and 24 deletions

View File

@ -47,7 +47,7 @@ import React, {
useState, useState,
} from 'react'; } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useHistory } from 'react-router-dom'; import { Link, useHistory } from 'react-router-dom';
import { ReactComponent as EditIcon } from '../../../../assets/svg/edit-new.svg'; import { ReactComponent as EditIcon } from '../../../../assets/svg/edit-new.svg';
import { ReactComponent as AssigneesIcon } from '../../../../assets/svg/ic-assignees.svg'; import { ReactComponent as AssigneesIcon } from '../../../../assets/svg/ic-assignees.svg';
import { ReactComponent as TaskCloseIcon } from '../../../../assets/svg/ic-close-task.svg'; import { ReactComponent as TaskCloseIcon } from '../../../../assets/svg/ic-close-task.svg';
@ -124,6 +124,7 @@ import {
getEntityName, getEntityName,
getEntityReferenceListFromEntities, getEntityReferenceListFromEntities,
} from '../../../../utils/EntityUtils'; } from '../../../../utils/EntityUtils';
import { getUserPath } from '../../../../utils/RouterUtils';
import { UserAvatarGroup } from '../../../common/OwnerLabel/UserAvatarGroup.component'; import { UserAvatarGroup } from '../../../common/OwnerLabel/UserAvatarGroup.component';
import EntityPopOverCard from '../../../common/PopOverCard/EntityPopOverCard'; import EntityPopOverCard from '../../../common/PopOverCard/EntityPopOverCard';
import UserPopOverCard from '../../../common/PopOverCard/UserPopOverCard'; import UserPopOverCard from '../../../common/PopOverCard/UserPopOverCard';
@ -875,13 +876,21 @@ export const TaskTabNew = ({
{t('label.created-by')} {t('label.created-by')}
</Typography.Text> </Typography.Text>
</Col> </Col>
<Col className="flex items-center gap-2" span={16}> <Col span={16}>
<Link
className="no-underline flex items-center gap-2"
to={getUserPath(taskThread.createdBy ?? '')}>
<UserPopOverCard userName={taskThread.createdBy ?? ''}> <UserPopOverCard userName={taskThread.createdBy ?? ''}>
<div className="d-flex items-center"> <div className="d-flex items-center">
<ProfilePicture name={taskThread.createdBy ?? ''} width="24" /> <ProfilePicture
name={taskThread.createdBy ?? ''}
width="24"
/>
</div> </div>
</UserPopOverCard> </UserPopOverCard>
<Typography.Text>{taskThread.createdBy}</Typography.Text> <Typography.Text>{taskThread.createdBy}</Typography.Text>
</Link>
</Col> </Col>
{isEditAssignee ? ( {isEditAssignee ? (

View File

@ -57,7 +57,10 @@ export const OwnerItem: React.FC<OwnerItemProps> = ({
}}> }}>
{!isCompactView ? ( {!isCompactView ? (
<UserPopOverCard userName={owner.name ?? ''}> <UserPopOverCard userName={owner.name ?? ''}>
<Link className="d-flex" data-testid="owner-link" to={ownerPath}> <Link
className="d-flex no-underline"
data-testid="owner-link"
to={ownerPath}>
<OwnerAvatar <OwnerAvatar
inheritedIcon={inheritedIcon} inheritedIcon={inheritedIcon}
isCompactView={isCompactView} isCompactView={isCompactView}

View File

@ -16,8 +16,10 @@ import classNames from 'classnames';
import { isEmpty } from 'lodash'; import { isEmpty } from 'lodash';
import React, { ReactNode, useMemo } from 'react'; import React, { ReactNode, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import { ReactComponent as IconUser } from '../../../assets/svg/user.svg'; import { ReactComponent as IconUser } from '../../../assets/svg/user.svg';
import { EntityReference } from '../../../generated/entity/data/table'; import { EntityReference } from '../../../generated/entity/data/table';
import { getOwnerPath } from '../../../utils/ownerUtils';
import UserPopOverCard from '../PopOverCard/UserPopOverCard'; import UserPopOverCard from '../PopOverCard/UserPopOverCard';
import ProfilePicture from '../ProfilePicture/ProfilePicture'; import ProfilePicture from '../ProfilePicture/ProfilePicture';
import './owner-label.less'; import './owner-label.less';
@ -57,7 +59,9 @@ export const UserAvatarGroup = ({
items: remainingOwners.map((owner) => ({ items: remainingOwners.map((owner) => ({
key: owner.id, key: owner.id,
label: ( label: (
<div className="d-flex items-center gap-2"> <Link
className="d-flex items-center gap-2 no-underlines"
to={getOwnerPath(owner)}>
<UserPopOverCard userName={owner.displayName ?? ''}> <UserPopOverCard userName={owner.displayName ?? ''}>
<div className="d-flex items-center"> <div className="d-flex items-center">
<ProfilePicture <ProfilePicture
@ -67,8 +71,9 @@ export const UserAvatarGroup = ({
/> />
</div> </div>
</UserPopOverCard> </UserPopOverCard>
<Typography.Text>{owner.displayName}</Typography.Text> <Typography.Text>{owner.displayName}</Typography.Text>
</div> </Link>
), ),
})), })),
}; };

View File

@ -0,0 +1,142 @@
/*
* Copyright 2022 Collate.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Avatar, Tooltip } from 'antd';
import classNames from 'classnames';
import { parseInt } from 'lodash';
import { ImageShape } from 'Models';
import React, { useMemo } from 'react';
import { Link } from 'react-router-dom';
import { usePermissionProvider } from '../../../context/PermissionProvider/PermissionProvider';
import { ResourceEntity } from '../../../context/PermissionProvider/PermissionProvider.interface';
import { User } from '../../../generated/entity/teams/user';
import { useUserProfile } from '../../../hooks/user-profile/useUserProfile';
import { getRandomColor } from '../../../utils/CommonUtils';
import { userPermissions } from '../../../utils/PermissionsUtils';
import { getUserPath } from '../../../utils/RouterUtils';
import Loader from '../Loader/Loader';
import UserPopOverCard from '../PopOverCard/UserPopOverCard';
type UserData = Pick<User, 'name' | 'displayName'>;
interface Props extends UserData {
width?: string;
type?: ImageShape;
className?: string;
height?: string;
isTeam?: boolean;
size?: number | 'small' | 'default' | 'large';
avatarType?: 'solid' | 'outlined';
}
const ProfilePictureNew = ({
name,
displayName,
className = '',
type = 'circle',
width = '36',
height,
isTeam = false,
size,
avatarType = 'solid',
}: Props) => {
const { permissions } = usePermissionProvider();
const { color, character, backgroundColor } = getRandomColor(
displayName ?? name
);
const viewUserPermission = useMemo(() => {
return userPermissions.hasViewPermissions(ResourceEntity.USER, permissions);
}, [permissions]);
const [profileURL, isPicLoading] = useUserProfile({
permission: viewUserPermission,
name,
isTeam,
});
const getAvatarByName = () => {
const avatar = (
<Avatar
className={classNames('flex-center', className)}
data-testid="profile-avatar"
icon={character}
shape={type}
size={size ?? parseInt(width)}
style={{
color: avatarType === 'solid' ? 'default' : color,
backgroundColor: avatarType === 'solid' ? color : backgroundColor,
fontWeight: avatarType === 'solid' ? 400 : 500,
border: `0.5px solid ${avatarType === 'solid' ? 'default' : color}`,
}}
/>
);
return (
<UserPopOverCard userName={name ?? ''}>
<Link className="no-underline" to={getUserPath(name ?? '')}>
{avatar}
</Link>
</UserPopOverCard>
);
};
const getAvatarElement = () => {
return isPicLoading ? (
<div
className="d-inline-block relative"
style={{
height: typeof size === 'number' ? `${size}px` : height,
width: typeof size === 'number' ? `${size}px` : width,
}}>
{getAvatarByName()}
<div
className="absolute inset-0 opacity-60 bg-grey-4 rounded-full"
data-testid="loader-cntnr">
<Loader
className="absolute inset-0"
size="small"
style={{
height: typeof size === 'number' ? `${size}px` : `${+width}px`,
width: typeof size === 'number' ? `${size}px` : `${+width}px`,
}}
type="white"
/>
</div>
</div>
) : (
getAvatarByName()
);
};
return profileURL ? (
<Tooltip placement="top" title={displayName ?? name}>
<Link
className="d-flex no-underline"
data-testid="owner-link"
to={getUserPath(name ?? '')}>
<Avatar
className={className}
data-testid="profile-image"
shape={type}
size={size ?? parseInt(width)}
src={profileURL}
/>
</Link>
</Tooltip>
) : (
getAvatarElement()
);
};
export default ProfilePictureNew;

View File

@ -10,10 +10,12 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import { Button, Dropdown } from 'antd'; import { Button, Dropdown, Typography } from 'antd';
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import { getEntityName } from '../../../utils/EntityUtils'; import { getEntityName } from '../../../utils/EntityUtils';
import { getOwnerPath } from '../../../utils/ownerUtils';
import UserPopOverCard from '../PopOverCard/UserPopOverCard'; import UserPopOverCard from '../PopOverCard/UserPopOverCard';
import ProfilePicture from '../ProfilePicture/ProfilePicture'; import ProfilePicture from '../ProfilePicture/ProfilePicture';
import { OwnerRevealProps } from './OwnerReveal.interface'; import { OwnerRevealProps } from './OwnerReveal.interface';
@ -60,8 +62,10 @@ export const OwnerReveal: React.FC<OwnerRevealProps> = ({
key: owner.id, key: owner.id,
label: ( label: (
<UserPopOverCard userName={owner.name ?? ''}> <UserPopOverCard userName={owner.name ?? ''}>
<div className="flex items-center gap-2"> <Link
<div className="relative"> className="d-flex no-underline items-center gap-2 relative"
data-testid="owner-link"
to={getOwnerPath(owner)}>
<ProfilePicture <ProfilePicture
displayName={getEntityName(owner)} displayName={getEntityName(owner)}
key="profile-picture" key="profile-picture"
@ -69,9 +73,13 @@ export const OwnerReveal: React.FC<OwnerRevealProps> = ({
type="circle" type="circle"
width="32" width="32"
/> />
</div>
<span>{getEntityName(owner)}</span> <Typography.Text
</div> className="w-36"
ellipsis={{ tooltip: true }}>
{getEntityName(owner)}{' '}
</Typography.Text>
</Link>
</UserPopOverCard> </UserPopOverCard>
), ),
})), })),