mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-11-02 19:48:17 +00:00
UI: Add support for User's profile image everywhere (#4856)
This commit is contained in:
parent
cd340a4d76
commit
5acb443d4a
@ -11,15 +11,18 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { isEmpty, isNil } from 'lodash';
|
||||
import { isEmpty, isNil, isUndefined } from 'lodash';
|
||||
import { action, makeAutoObservable } from 'mobx';
|
||||
import { ClientAuth, NewUser, UserPermissions } from 'Models';
|
||||
import { reactLocalStorage } from 'reactjs-localstorage';
|
||||
import { LOCALSTORAGE_USER_PROFILES } from './constants/constants';
|
||||
import { CurrentTourPageType } from './enums/tour.enum';
|
||||
import { Role } from './generated/entity/teams/role';
|
||||
import {
|
||||
EntityReference as UserTeams,
|
||||
User,
|
||||
} from './generated/entity/teams/user';
|
||||
import { ImageList } from './generated/type/profile';
|
||||
|
||||
class AppState {
|
||||
users: Array<User> = [];
|
||||
@ -36,6 +39,15 @@ class AppState {
|
||||
userTeams: Array<UserTeams> = [];
|
||||
userRoles: Array<Role> = [];
|
||||
userPermissions: UserPermissions = {} as UserPermissions;
|
||||
userProfilePics: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
profile: ImageList['image512'];
|
||||
}> = [];
|
||||
userProfilePicsLoading: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
}> = [];
|
||||
|
||||
inPageSearchText = '';
|
||||
explorePageTab = 'tables';
|
||||
@ -60,6 +72,13 @@ class AppState {
|
||||
getAllTeams: action,
|
||||
getAllRoles: action,
|
||||
getAllPermissions: action,
|
||||
getUserProfilePic: action,
|
||||
updateUserProfilePic: action,
|
||||
loadUserProfilePics: action,
|
||||
getProfilePicsLoading: action,
|
||||
updateProfilePicsLoading: action,
|
||||
isProfilePicLoading: action,
|
||||
removeProfilePicsLoading: action,
|
||||
});
|
||||
}
|
||||
|
||||
@ -96,6 +115,98 @@ class AppState {
|
||||
this.explorePageTab = tab;
|
||||
}
|
||||
|
||||
updateUserProfilePic(
|
||||
id?: string,
|
||||
username?: string,
|
||||
profile?: ImageList['image512']
|
||||
) {
|
||||
if (!id && !username) {
|
||||
return;
|
||||
}
|
||||
|
||||
const filteredList = this.userProfilePics.filter((item) => {
|
||||
// compare id only if present
|
||||
if (item.id && id) {
|
||||
return item.id !== id;
|
||||
} else {
|
||||
return item.name !== username;
|
||||
}
|
||||
});
|
||||
this.userProfilePics = [
|
||||
...filteredList,
|
||||
{
|
||||
id: id || '',
|
||||
name: username || '',
|
||||
profile,
|
||||
},
|
||||
];
|
||||
|
||||
reactLocalStorage.setObject(LOCALSTORAGE_USER_PROFILES, {
|
||||
data: this.userProfilePics,
|
||||
});
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
updateProfilePicsLoading(id?: string, username?: string) {
|
||||
if (!id && !username) {
|
||||
return;
|
||||
}
|
||||
|
||||
const alreadyLoading = !isUndefined(
|
||||
this.userProfilePicsLoading.find((loadingItem) => {
|
||||
// compare id only if present
|
||||
if (loadingItem.id && id) {
|
||||
return loadingItem.id === id;
|
||||
} else {
|
||||
return loadingItem.name === username;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
if (!alreadyLoading) {
|
||||
this.userProfilePicsLoading = [
|
||||
...this.userProfilePicsLoading,
|
||||
{
|
||||
id: id || '',
|
||||
name: username || '',
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
removeProfilePicsLoading(id?: string, username?: string) {
|
||||
if (!id && !username) {
|
||||
return;
|
||||
}
|
||||
|
||||
const filteredList = this.userProfilePicsLoading.filter((loadingItem) => {
|
||||
// compare id only if present
|
||||
if (loadingItem.id && id) {
|
||||
return loadingItem.id !== id;
|
||||
} else {
|
||||
return loadingItem.name !== username;
|
||||
}
|
||||
});
|
||||
|
||||
this.userProfilePicsLoading = filteredList;
|
||||
}
|
||||
|
||||
loadUserProfilePics() {
|
||||
const { data } = reactLocalStorage.getObject(
|
||||
LOCALSTORAGE_USER_PROFILES
|
||||
) as {
|
||||
data: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
profile: ImageList['image512'];
|
||||
}>;
|
||||
};
|
||||
if (data) {
|
||||
this.userProfilePics = data;
|
||||
}
|
||||
}
|
||||
|
||||
getCurrentUserDetails() {
|
||||
if (!isEmpty(this.userDetails) && !isNil(this.userDetails)) {
|
||||
return this.userDetails;
|
||||
@ -109,6 +220,40 @@ class AppState {
|
||||
}
|
||||
}
|
||||
|
||||
getUserProfilePic(id?: string, username?: string) {
|
||||
const data = this.userProfilePics.find((item) => {
|
||||
// compare id only if present
|
||||
if (item.id && id) {
|
||||
return item.id === id;
|
||||
} else {
|
||||
return item.name === username;
|
||||
}
|
||||
});
|
||||
|
||||
return data?.profile;
|
||||
}
|
||||
|
||||
getAllUserProfilePics() {
|
||||
return this.userProfilePics;
|
||||
}
|
||||
|
||||
getProfilePicsLoading() {
|
||||
return this.userProfilePicsLoading;
|
||||
}
|
||||
|
||||
isProfilePicLoading(id?: string, username?: string) {
|
||||
const data = this.userProfilePicsLoading.find((loadingPic) => {
|
||||
// compare id only if present
|
||||
if (loadingPic.id && id) {
|
||||
return loadingPic.id === id;
|
||||
} else {
|
||||
return loadingPic.name === username;
|
||||
}
|
||||
});
|
||||
|
||||
return Boolean(data);
|
||||
}
|
||||
|
||||
getAllUsers() {
|
||||
return this.users;
|
||||
}
|
||||
|
||||
@ -64,6 +64,15 @@ export const getUserByName = (
|
||||
return APIClient.get(url);
|
||||
};
|
||||
|
||||
export const getUserById = (
|
||||
id: string,
|
||||
arrQueryFields?: string
|
||||
): Promise<AxiosResponse> => {
|
||||
const url = getURLWithQueryFields(`/users/${id}`, arrQueryFields);
|
||||
|
||||
return APIClient.get(url);
|
||||
};
|
||||
|
||||
export const getLoggedInUser = (arrQueryFields?: string) => {
|
||||
const url = getURLWithQueryFields('/users/loggedInUser', arrQueryFields);
|
||||
|
||||
@ -98,10 +107,6 @@ export const updateUserTeam: Function = (
|
||||
return APIClient.post(`/users/${id}/teams`, options);
|
||||
};
|
||||
|
||||
export const getUserById: Function = (id: string): Promise<AxiosResponse> => {
|
||||
return APIClient.get(`/users/${id}`);
|
||||
};
|
||||
|
||||
export const createUser = (
|
||||
userDetails: Record<string, string | Array<string> | UserProfile> | CreateUser
|
||||
): Promise<AxiosResponse> => {
|
||||
|
||||
@ -30,8 +30,10 @@ jest.mock('../../../utils/TimeUtils', () => ({
|
||||
getDayTimeByTimeStamp: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../common/avatar/Avatar', () => {
|
||||
return jest.fn().mockReturnValue(<p data-testid="replied-user">Avatar</p>);
|
||||
jest.mock('../../common/ProfilePicture/ProfilePicture', () => {
|
||||
return jest
|
||||
.fn()
|
||||
.mockReturnValue(<p data-testid="replied-user">ProfilePicture</p>);
|
||||
});
|
||||
|
||||
const mockFeedCardFooterPorps = {
|
||||
|
||||
@ -15,7 +15,7 @@ import { isUndefined, toLower } from 'lodash';
|
||||
import React, { FC } from 'react';
|
||||
import { getReplyText } from '../../../utils/FeedUtils';
|
||||
import { getDayTimeByTimeStamp } from '../../../utils/TimeUtils';
|
||||
import Avatar from '../../common/avatar/Avatar';
|
||||
import ProfilePicture from '../../common/ProfilePicture/ProfilePicture';
|
||||
import { FeedFooterProp } from '../ActivityFeedCard/ActivityFeedCard.interface';
|
||||
|
||||
const FeedCardFooter: FC<FeedFooterProp> = ({
|
||||
@ -36,12 +36,12 @@ const FeedCardFooter: FC<FeedFooterProp> = ({
|
||||
isFooterVisible ? (
|
||||
<div className="tw-flex tw-group">
|
||||
{repliedUsers?.map((u, i) => (
|
||||
<Avatar
|
||||
<ProfilePicture
|
||||
className="tw-mt-0.5 tw-mx-0.5"
|
||||
data-testid="replied-user"
|
||||
id=""
|
||||
key={i}
|
||||
name={u}
|
||||
type="square"
|
||||
width="22"
|
||||
/>
|
||||
))}
|
||||
|
||||
@ -39,8 +39,8 @@ jest.mock('../../../utils/TimeUtils', () => ({
|
||||
getDayTimeByTimeStamp: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../common/avatar/Avatar', () => {
|
||||
return jest.fn().mockReturnValue(<p>Avatar</p>);
|
||||
jest.mock('../../common/ProfilePicture/ProfilePicture', () => {
|
||||
return jest.fn().mockReturnValue(<p>ProfilePicture</p>);
|
||||
});
|
||||
|
||||
const mockFeedHeaderProps = {
|
||||
|
||||
@ -38,8 +38,8 @@ import SVGIcons, { Icons } from '../../../utils/SvgUtils';
|
||||
import { getEntityLink } from '../../../utils/TableUtils';
|
||||
import { getDayTimeByTimeStamp } from '../../../utils/TimeUtils';
|
||||
import { Button } from '../../buttons/Button/Button';
|
||||
import Avatar from '../../common/avatar/Avatar';
|
||||
import PopOver from '../../common/popover/PopOver';
|
||||
import ProfilePicture from '../../common/ProfilePicture/ProfilePicture';
|
||||
import Loader from '../../Loader/Loader';
|
||||
import { FeedHeaderProp } from '../ActivityFeedCard/ActivityFeedCard.interface';
|
||||
import './FeedCardHeader.style.css';
|
||||
@ -92,7 +92,7 @@ const FeedCardHeader: FC<FeedHeaderProp> = ({
|
||||
<div>
|
||||
<div className="tw-flex">
|
||||
<div className="tw-mr-2">
|
||||
<Avatar name={createdBy} type="square" width="30" />
|
||||
<ProfilePicture id="" name={createdBy} width="30" />
|
||||
</div>
|
||||
<div className="tw-self-center">
|
||||
<Button
|
||||
@ -256,7 +256,7 @@ const FeedCardHeader: FC<FeedHeaderProp> = ({
|
||||
className="tw-cursor-pointer"
|
||||
data-testid="authorAvatar"
|
||||
onClick={onClickHandler}>
|
||||
<Avatar name={createdBy} type="square" width="30" />
|
||||
<ProfilePicture id="" name={createdBy} width="30" />
|
||||
</span>
|
||||
</PopOver>
|
||||
<h6 className="tw-flex tw-items-center tw-m-0 tw-heading tw-pl-2">
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { compare } from 'fast-json-patch';
|
||||
import { EntityTags, TagOption } from 'Models';
|
||||
import { EntityTags, ExtraInfo, TagOption } from 'Models';
|
||||
import React, { RefObject, useEffect, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useAuthContext } from '../../authentication/auth-provider/AuthProvider';
|
||||
@ -21,6 +21,7 @@ import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants';
|
||||
import { getTeamAndUserDetailsPath } from '../../constants/constants';
|
||||
import { observerOptions } from '../../constants/Mydata.constants';
|
||||
import { EntityType } from '../../enums/entity.enum';
|
||||
import { OwnerType } from '../../enums/user.enum';
|
||||
import { Dashboard } from '../../generated/entity/data/dashboard';
|
||||
import { Operation } from '../../generated/entity/policies/accessControl/rule';
|
||||
import { EntityReference } from '../../generated/type/entityReference';
|
||||
@ -197,7 +198,7 @@ const DashboardDetails = ({
|
||||
},
|
||||
];
|
||||
|
||||
const extraInfo = [
|
||||
const extraInfo: Array<ExtraInfo> = [
|
||||
{
|
||||
key: 'Owner',
|
||||
value:
|
||||
@ -210,6 +211,7 @@ const DashboardDetails = ({
|
||||
),
|
||||
isLink: owner?.type === 'team',
|
||||
openInNewTab: false,
|
||||
profileName: owner?.type === OwnerType.USER ? owner?.name : undefined,
|
||||
},
|
||||
{
|
||||
key: 'Tier',
|
||||
|
||||
@ -17,6 +17,7 @@ import { ExtraInfo } from 'Models';
|
||||
import React, { FC, useEffect, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants';
|
||||
import { OwnerType } from '../../enums/user.enum';
|
||||
import { ChangeDescription } from '../../generated/entity/data/dashboard';
|
||||
import { TagLabel } from '../../generated/type/tagLabel';
|
||||
import { isEven } from '../../utils/CommonUtils';
|
||||
@ -141,6 +142,8 @@ const DashboardVersion: FC<DashboardVersionProp> = ({
|
||||
: ownerPlaceHolder
|
||||
? getDiffValue(ownerPlaceHolder, ownerPlaceHolder)
|
||||
: '',
|
||||
profileName:
|
||||
newOwner?.type === OwnerType.USER ? newOwner?.name : undefined,
|
||||
},
|
||||
{
|
||||
key: 'Tier',
|
||||
|
||||
@ -21,6 +21,7 @@ import { getTeamAndUserDetailsPath, ROUTES } from '../../constants/constants';
|
||||
import { observerOptions } from '../../constants/Mydata.constants';
|
||||
import { CSMode } from '../../enums/codemirror.enum';
|
||||
import { EntityType, FqnPart } from '../../enums/entity.enum';
|
||||
import { OwnerType } from '../../enums/user.enum';
|
||||
import {
|
||||
JoinedWith,
|
||||
Table,
|
||||
@ -371,6 +372,7 @@ const DatasetDetails: React.FC<DatasetDetailsProps> = ({
|
||||
),
|
||||
isLink: owner?.type === 'team',
|
||||
openInNewTab: false,
|
||||
profileName: owner?.type === OwnerType.USER ? owner?.name : undefined,
|
||||
},
|
||||
{
|
||||
key: 'Tier',
|
||||
|
||||
@ -17,6 +17,7 @@ import { ExtraInfo } from 'Models';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants';
|
||||
import { FqnPart } from '../../enums/entity.enum';
|
||||
import { OwnerType } from '../../enums/user.enum';
|
||||
import {
|
||||
ChangeDescription,
|
||||
Column,
|
||||
@ -120,6 +121,8 @@ const DatasetVersion: React.FC<DatasetVersionProp> = ({
|
||||
: ownerPlaceHolder
|
||||
? getDiffValue(ownerPlaceHolder, ownerPlaceHolder)
|
||||
: '',
|
||||
profileName:
|
||||
newOwner?.type === OwnerType.USER ? newOwner?.name : undefined,
|
||||
},
|
||||
{
|
||||
key: 'Tier',
|
||||
|
||||
@ -35,9 +35,9 @@ import {
|
||||
} from '../../utils/TagsUtils';
|
||||
import { showErrorToast } from '../../utils/ToastUtils';
|
||||
import { Button } from '../buttons/Button/Button';
|
||||
import Avatar from '../common/avatar/Avatar';
|
||||
import Description from '../common/description/Description';
|
||||
import NonAdminAction from '../common/non-admin-action/NonAdminAction';
|
||||
import ProfilePicture from '../common/ProfilePicture/ProfilePicture';
|
||||
import TabsPane from '../common/TabsPane/TabsPane';
|
||||
import ManageTabComponent from '../ManageTab/ManageTab.component';
|
||||
import ReviewerModal from '../Modals/ReviewerModal/ReviewerModal.component';
|
||||
@ -277,8 +277,10 @@ const GlossaryDetails = ({
|
||||
<div className="tw-mb-3 tw-flex tw-items-center">
|
||||
{glossary.owner && getEntityName(glossary.owner) && (
|
||||
<div className="tw-inline-block tw-mr-2">
|
||||
<Avatar
|
||||
name={getEntityName(glossary.owner)}
|
||||
<ProfilePicture
|
||||
displayName={getEntityName(glossary.owner)}
|
||||
id={glossary.owner?.id || ''}
|
||||
name={glossary.owner?.name || ''}
|
||||
textClass="tw-text-xs"
|
||||
width="20"
|
||||
/>
|
||||
|
||||
@ -55,6 +55,10 @@ jest.mock('../common/rich-text-editor/RichTextEditorPreviewer', () => {
|
||||
return jest.fn().mockReturnValue(<p>RichTextEditorPreviewer</p>);
|
||||
});
|
||||
|
||||
jest.mock('../common/ProfilePicture/ProfilePicture', () => {
|
||||
return jest.fn().mockReturnValue(<p>ProfilePicture</p>);
|
||||
});
|
||||
|
||||
const mockProps = {
|
||||
glossary: mockedGlossaries[0],
|
||||
isHasAccess: true,
|
||||
|
||||
@ -98,9 +98,10 @@ const RelatedTermsModal = ({
|
||||
isCheckBoxes
|
||||
item={{
|
||||
name: '',
|
||||
description: d.displayName || d.name,
|
||||
displayName: d.displayName || d.name,
|
||||
id: d.id,
|
||||
isChecked: isIncludeInOptions(d.id),
|
||||
type: d.type,
|
||||
}}
|
||||
key={d.id}
|
||||
onSelect={selectionHandler}
|
||||
|
||||
@ -116,9 +116,11 @@ const ReviewerModal = ({
|
||||
isIconVisible
|
||||
item={{
|
||||
name: d.name,
|
||||
description: d.displayName,
|
||||
displayName: d.displayName || d.name,
|
||||
email: d.email,
|
||||
id: d.id,
|
||||
isChecked: isIncludeInOptions(d.id),
|
||||
type: d.type,
|
||||
}}
|
||||
key={d.id}
|
||||
onSelect={selectionHandler}
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
import classNames from 'classnames';
|
||||
import { compare } from 'fast-json-patch';
|
||||
import { isNil } from 'lodash';
|
||||
import { EntityFieldThreads, EntityTags } from 'Models';
|
||||
import { EntityFieldThreads, EntityTags, ExtraInfo } from 'Models';
|
||||
import React, { Fragment, RefObject, useEffect, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useAuthContext } from '../../authentication/auth-provider/AuthProvider';
|
||||
@ -22,6 +22,7 @@ import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants';
|
||||
import { getTeamAndUserDetailsPath } from '../../constants/constants';
|
||||
import { observerOptions } from '../../constants/Mydata.constants';
|
||||
import { EntityType } from '../../enums/entity.enum';
|
||||
import { OwnerType } from '../../enums/user.enum';
|
||||
import { Pipeline, Task } from '../../generated/entity/data/pipeline';
|
||||
import { Operation } from '../../generated/entity/policies/accessControl/rule';
|
||||
import { EntityReference } from '../../generated/type/entityReference';
|
||||
@ -202,7 +203,7 @@ const PipelineDetails = ({
|
||||
},
|
||||
];
|
||||
|
||||
const extraInfo = [
|
||||
const extraInfo: Array<ExtraInfo> = [
|
||||
{
|
||||
key: 'Owner',
|
||||
value:
|
||||
@ -215,6 +216,7 @@ const PipelineDetails = ({
|
||||
),
|
||||
isLink: owner?.type === 'team',
|
||||
openInNewTab: false,
|
||||
profileName: owner?.type === OwnerType.USER ? owner?.name : undefined,
|
||||
},
|
||||
{
|
||||
key: 'Tier',
|
||||
|
||||
@ -17,6 +17,7 @@ import { ExtraInfo } from 'Models';
|
||||
import React, { FC, useEffect, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants';
|
||||
import { OwnerType } from '../../enums/user.enum';
|
||||
import { ChangeDescription } from '../../generated/entity/data/pipeline';
|
||||
import { TagLabel } from '../../generated/type/tagLabel';
|
||||
import { isEven } from '../../utils/CommonUtils';
|
||||
@ -141,6 +142,8 @@ const PipelineVersion: FC<PipelineVersionProp> = ({
|
||||
: ownerPlaceHolder
|
||||
? getDiffValue(ownerPlaceHolder, ownerPlaceHolder)
|
||||
: '',
|
||||
profileName:
|
||||
newOwner?.type === OwnerType.USER ? newOwner?.name : undefined,
|
||||
},
|
||||
{
|
||||
key: 'Tier',
|
||||
|
||||
@ -151,6 +151,10 @@ const TeamDetails = ({
|
||||
currentTeam?.owner?.displayName || currentTeam?.owner?.name || '',
|
||||
isLink: currentTeam?.owner?.type === 'team',
|
||||
openInNewTab: false,
|
||||
profileName:
|
||||
currentTeam?.owner?.type === OwnerType.USER
|
||||
? currentTeam?.owner?.name
|
||||
: undefined,
|
||||
};
|
||||
|
||||
const isActionAllowed = (operation = false) => {
|
||||
|
||||
@ -11,13 +11,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { EntityTags } from 'Models';
|
||||
import { EntityTags, ExtraInfo } from 'Models';
|
||||
import React, { Fragment, RefObject, useEffect, useState } from 'react';
|
||||
import { useAuthContext } from '../../authentication/auth-provider/AuthProvider';
|
||||
import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants';
|
||||
import { getTeamAndUserDetailsPath } from '../../constants/constants';
|
||||
import { observerOptions } from '../../constants/Mydata.constants';
|
||||
import { EntityType } from '../../enums/entity.enum';
|
||||
import { OwnerType } from '../../enums/user.enum';
|
||||
import { Topic } from '../../generated/entity/data/topic';
|
||||
import { EntityReference } from '../../generated/type/entityReference';
|
||||
import { Paging } from '../../generated/type/paging';
|
||||
@ -207,7 +208,7 @@ const TopicDetails: React.FC<TopicDetailsProps> = ({
|
||||
position: 5,
|
||||
},
|
||||
];
|
||||
const extraInfo = [
|
||||
const extraInfo: Array<ExtraInfo> = [
|
||||
{
|
||||
key: 'Owner',
|
||||
value:
|
||||
@ -220,6 +221,7 @@ const TopicDetails: React.FC<TopicDetailsProps> = ({
|
||||
),
|
||||
isLink: owner?.type === 'team',
|
||||
openInNewTab: false,
|
||||
profileName: owner?.type === OwnerType.USER ? owner?.name : undefined,
|
||||
},
|
||||
{
|
||||
key: 'Tier',
|
||||
|
||||
@ -16,6 +16,7 @@ import { isUndefined } from 'lodash';
|
||||
import { ExtraInfo } from 'Models';
|
||||
import React, { FC, useEffect, useState } from 'react';
|
||||
import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants';
|
||||
import { OwnerType } from '../../enums/user.enum';
|
||||
import { ChangeDescription } from '../../generated/entity/data/topic';
|
||||
import { TagLabel } from '../../generated/type/tagLabel';
|
||||
import {
|
||||
@ -172,6 +173,8 @@ const TopicVersion: FC<TopicVersionProp> = ({
|
||||
: ownerPlaceHolder
|
||||
? getDiffValue(ownerPlaceHolder, ownerPlaceHolder)
|
||||
: '',
|
||||
profileName:
|
||||
newOwner?.type === OwnerType.USER ? newOwner?.name : undefined,
|
||||
},
|
||||
{
|
||||
key: 'Tier',
|
||||
|
||||
@ -17,7 +17,7 @@ import { MemoryRouter } from 'react-router-dom';
|
||||
import UserDataCard from './UserDataCard';
|
||||
|
||||
const mockItem = {
|
||||
description: 'description1',
|
||||
displayName: 'description1',
|
||||
name: 'name1',
|
||||
id: 'id1',
|
||||
email: 'string@email.com',
|
||||
@ -41,8 +41,10 @@ jest.mock('../../authentication/auth-provider/AuthProvider', () => {
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('../../components/common/avatar/Avatar', () => {
|
||||
return jest.fn().mockReturnValue(<p data-testid="avatar">Avatar</p>);
|
||||
jest.mock('../../components/common/ProfilePicture/ProfilePicture', () => {
|
||||
return jest
|
||||
.fn()
|
||||
.mockReturnValue(<p data-testid="profile-picture">ProfilePicture</p>);
|
||||
});
|
||||
|
||||
jest.mock('../../utils/SvgUtils', () => {
|
||||
@ -69,7 +71,7 @@ describe('Test UserDataCard component', () => {
|
||||
);
|
||||
|
||||
const cardContainer = await findByTestId(container, 'user-card-container');
|
||||
const avatar = await findByTestId(container, 'avatar');
|
||||
const avatar = await findByTestId(container, 'profile-picture');
|
||||
|
||||
expect(avatar).toBeInTheDocument();
|
||||
expect(cardContainer).toBeInTheDocument();
|
||||
|
||||
@ -15,11 +15,11 @@ import classNames from 'classnames';
|
||||
import { isNil } from 'lodash';
|
||||
import React from 'react';
|
||||
import SVGIcons, { Icons } from '../../utils/SvgUtils';
|
||||
import Avatar from '../common/avatar/Avatar';
|
||||
import NonAdminAction from '../common/non-admin-action/NonAdminAction';
|
||||
import ProfilePicture from '../common/ProfilePicture/ProfilePicture';
|
||||
|
||||
type Item = {
|
||||
description: string;
|
||||
displayName: string;
|
||||
name: string;
|
||||
id?: string;
|
||||
email: string;
|
||||
@ -41,13 +41,11 @@ const UserDataCard = ({ item, onClick, onDelete, showTeams = true }: Props) => {
|
||||
className="tw-card tw-flex tw-justify-between tw-py-2 tw-px-3 tw-group"
|
||||
data-testid="user-card-container">
|
||||
<div className="tw-flex tw-gap-1">
|
||||
{item.profilePhoto ? (
|
||||
<div className="tw-h-9 tw-w-9">
|
||||
<img alt="profile" className="tw-w-full" src={item.profilePhoto} />
|
||||
</div>
|
||||
) : (
|
||||
<Avatar name={item.description} />
|
||||
)}
|
||||
<ProfilePicture
|
||||
displayName={item?.displayName}
|
||||
id={item?.id || ''}
|
||||
name={item?.name || ''}
|
||||
/>
|
||||
|
||||
<div
|
||||
className="tw-flex tw-flex-col tw-flex-1 tw-pl-2"
|
||||
@ -60,7 +58,7 @@ const UserDataCard = ({ item, onClick, onDelete, showTeams = true }: Props) => {
|
||||
onClick={() => {
|
||||
onClick?.(item.name);
|
||||
}}>
|
||||
{item.description}
|
||||
{item.displayName}
|
||||
</p>
|
||||
{!item?.isActiveUser && (
|
||||
<span className="tw-text-xs tw-bg-badge tw-border tw-px-2 tw-py-0.5 tw-rounded">
|
||||
@ -83,7 +81,7 @@ const UserDataCard = ({ item, onClick, onDelete, showTeams = true }: Props) => {
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
onDelete(item.id as string, item.description);
|
||||
onDelete(item.id as string, item.displayName);
|
||||
}}>
|
||||
<SVGIcons
|
||||
alt="delete"
|
||||
|
||||
@ -107,7 +107,7 @@ const UserDetails = ({
|
||||
data-testid="user-card-container">
|
||||
{selectedUserList.map((user, index) => {
|
||||
const User = {
|
||||
description: getEntityName(user as unknown as EntityReference),
|
||||
displayName: getEntityName(user as unknown as EntityReference),
|
||||
name: user.name || '',
|
||||
id: user.id,
|
||||
email: user.email || '',
|
||||
|
||||
@ -347,7 +347,7 @@ const UserList: FunctionComponent<Props> = ({
|
||||
data-testid="user-card-container">
|
||||
{listUserData.map((user, index) => {
|
||||
const User = {
|
||||
description: getEntityName(user as unknown as EntityReference),
|
||||
displayName: getEntityName(user as unknown as EntityReference),
|
||||
name: user.name || '',
|
||||
id: user.id,
|
||||
email: user.email || '',
|
||||
|
||||
@ -109,8 +109,8 @@ const mockUserData = {
|
||||
],
|
||||
};
|
||||
|
||||
jest.mock('../common/avatar/Avatar', () => {
|
||||
return jest.fn().mockReturnValue(<p>Avatar</p>);
|
||||
jest.mock('../common/ProfilePicture/ProfilePicture', () => {
|
||||
return jest.fn().mockReturnValue(<p>ProfilePicture</p>);
|
||||
});
|
||||
|
||||
jest.mock('../../pages/teams/UserCard', () => {
|
||||
|
||||
@ -45,8 +45,8 @@ import SVGIcons, { Icons } from '../../utils/SvgUtils';
|
||||
import { showErrorToast } from '../../utils/ToastUtils';
|
||||
import ActivityFeedList from '../ActivityFeed/ActivityFeedList/ActivityFeedList';
|
||||
import { Button } from '../buttons/Button/Button';
|
||||
import Avatar from '../common/avatar/Avatar';
|
||||
import Description from '../common/description/Description';
|
||||
import ProfilePicture from '../common/ProfilePicture/ProfilePicture';
|
||||
import { reactSingleSelectCustomStyle } from '../common/react-select-component/reactSelectCustomStyle';
|
||||
import TabsPane from '../common/TabsPane/TabsPane';
|
||||
import PageLayout from '../containers/PageLayout';
|
||||
@ -525,12 +525,15 @@ const Users = ({
|
||||
<img
|
||||
alt="profile"
|
||||
className="tw-w-full"
|
||||
referrerPolicy="no-referrer"
|
||||
src={userData.profile?.images?.image}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<Avatar
|
||||
name={userData?.displayName || userData.name}
|
||||
<ProfilePicture
|
||||
displayName={userData?.displayName || userData.name}
|
||||
id={userData?.id || ''}
|
||||
name={userData?.name || ''}
|
||||
textClass="tw-text-5xl"
|
||||
width="112"
|
||||
/>
|
||||
|
||||
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2021 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 { findByTestId, findByText, render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import AppState from '../../../AppState';
|
||||
import { getUserProfilePic } from '../../../utils/UserDataUtils';
|
||||
import ProfilePicture from './ProfilePicture';
|
||||
|
||||
jest.mock('../avatar/Avatar', () => {
|
||||
return jest.fn().mockImplementation(() => <div>Avatar</div>);
|
||||
});
|
||||
|
||||
jest.mock('../../../utils/UserDataUtils', () => {
|
||||
return {
|
||||
fetchAllUsers: jest.fn(),
|
||||
fetchUserProfilePic: jest.fn(),
|
||||
getUserDataFromOidc: jest.fn(),
|
||||
getUserProfilePic: jest.fn(),
|
||||
matchUserDetails: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
const mockData = {
|
||||
id: 'test-1',
|
||||
name: 'test-name',
|
||||
};
|
||||
|
||||
const mockGetUserProfilePic = jest.fn(() => 'mockedProfilePic');
|
||||
|
||||
beforeAll(() => {
|
||||
jest.spyOn(AppState, 'isProfilePicLoading').mockImplementation(() => false);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe('Test ProfilePicture component', () => {
|
||||
it('ProfilePicture component should render with Avatar', async () => {
|
||||
const { container } = render(<ProfilePicture {...mockData} />);
|
||||
|
||||
const avatar = await findByText(container, 'Avatar');
|
||||
|
||||
expect(avatar).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Profile image should load', async () => {
|
||||
(getUserProfilePic as jest.Mock).mockImplementationOnce(
|
||||
mockGetUserProfilePic
|
||||
);
|
||||
const { container } = render(<ProfilePicture {...mockData} />);
|
||||
|
||||
const image = await findByTestId(container, 'profile-image');
|
||||
|
||||
expect(image).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Profile Avatar should be loading', async () => {
|
||||
jest.spyOn(AppState, 'isProfilePicLoading').mockImplementation(() => true);
|
||||
|
||||
const { container } = render(<ProfilePicture {...mockData} />);
|
||||
|
||||
const loader = await findByTestId(container, 'loader-cntnr');
|
||||
|
||||
expect(loader).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright 2021 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 classNames from 'classnames';
|
||||
import { observer } from 'mobx-react';
|
||||
import { ImageShape } from 'Models';
|
||||
import React, { useMemo } from 'react';
|
||||
import AppState from '../../../AppState';
|
||||
import { EntityReference, User } from '../../../generated/entity/teams/user';
|
||||
import { getEntityName } from '../../../utils/CommonUtils';
|
||||
import { getUserProfilePic } from '../../../utils/UserDataUtils';
|
||||
import Loader from '../../Loader/Loader';
|
||||
import Avatar from '../avatar/Avatar';
|
||||
|
||||
type UserData = Pick<User, 'id' | 'name' | 'displayName'>;
|
||||
|
||||
interface Props extends UserData {
|
||||
width?: string;
|
||||
type?: ImageShape;
|
||||
textClass?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const ProfilePicture = ({
|
||||
id,
|
||||
name,
|
||||
displayName,
|
||||
className = '',
|
||||
textClass = '',
|
||||
type = 'square',
|
||||
width = '36',
|
||||
}: Props) => {
|
||||
const profilePic = useMemo(() => {
|
||||
return getUserProfilePic(id, name);
|
||||
}, [id, name, AppState.userProfilePics]);
|
||||
|
||||
const isPicLoading = useMemo(() => {
|
||||
return AppState.isProfilePicLoading(id, name);
|
||||
}, [id, name, AppState.userProfilePicsLoading]);
|
||||
|
||||
const getAvatarByName = () => {
|
||||
return (
|
||||
<Avatar
|
||||
className={className}
|
||||
name={getEntityName({ name, displayName } as EntityReference)}
|
||||
textClass={textClass}
|
||||
type={type}
|
||||
width={width}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const getAvatarElement = () => {
|
||||
return isPicLoading ? (
|
||||
<div
|
||||
className="tw-inline-block tw-relative"
|
||||
style={{ height: `${width}px`, width: `${width}px` }}>
|
||||
{getAvatarByName()}
|
||||
<div
|
||||
className="tw-absolute tw-inset-0 tw-opacity-60 tw-bg-grey-backdrop tw-rounded"
|
||||
data-testid="loader-cntnr">
|
||||
<Loader
|
||||
className="tw-absolute tw-inset-0"
|
||||
size="small"
|
||||
style={{ height: `${+width - 2}px`, width: `${+width - 2}px` }}
|
||||
type="white"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
getAvatarByName()
|
||||
);
|
||||
};
|
||||
|
||||
return profilePic ? (
|
||||
<div
|
||||
className={classNames('profile-image', type)}
|
||||
style={{ height: `${width}px`, width: `${width}px` }}>
|
||||
<img
|
||||
alt="user"
|
||||
data-testid="profile-image"
|
||||
referrerPolicy="no-referrer"
|
||||
src={profilePic}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
getAvatarElement()
|
||||
);
|
||||
};
|
||||
|
||||
export default observer(ProfilePicture);
|
||||
@ -12,6 +12,7 @@
|
||||
*/
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { ImageShape } from 'Models';
|
||||
import React from 'react';
|
||||
import { getRandomColor } from '../../../utils/CommonUtils';
|
||||
|
||||
@ -26,7 +27,7 @@ const Avatar = ({
|
||||
width?: string;
|
||||
textClass?: string;
|
||||
className?: string;
|
||||
type?: 'circle' | 'square';
|
||||
type?: ImageShape;
|
||||
}) => {
|
||||
const { color, character } = getRandomColor(name);
|
||||
|
||||
|
||||
@ -153,8 +153,8 @@ jest.mock('../../tags/tags', () => {
|
||||
return jest.fn().mockReturnValue(<p data-testid="tier-tag">Tag</p>);
|
||||
});
|
||||
|
||||
jest.mock('../avatar/Avatar', () => {
|
||||
return jest.fn().mockReturnValue(<p>Avatar</p>);
|
||||
jest.mock('../ProfilePicture/ProfilePicture', () => {
|
||||
return jest.fn().mockReturnValue(<p>ProfilePicture</p>);
|
||||
});
|
||||
|
||||
jest.mock('./FollowersModal', () => {
|
||||
|
||||
@ -34,9 +34,9 @@ import { getTagCategories, getTaglist } from '../../../utils/TagsUtils';
|
||||
import TagsContainer from '../../tags-container/tags-container';
|
||||
import TagsViewer from '../../tags-viewer/tags-viewer';
|
||||
import Tags from '../../tags/tags';
|
||||
import Avatar from '../avatar/Avatar';
|
||||
import NonAdminAction from '../non-admin-action/NonAdminAction';
|
||||
import PopOver from '../popover/PopOver';
|
||||
import ProfilePicture from '../ProfilePicture/ProfilePicture';
|
||||
import TitleBreadcrumb from '../title-breadcrumb/title-breadcrumb.component';
|
||||
import { TitleBreadcrumbProps } from '../title-breadcrumb/title-breadcrumb.interface';
|
||||
import FollowersModal from './FollowersModal';
|
||||
@ -157,8 +157,10 @@ const EntityPageInfo = ({
|
||||
})}>
|
||||
{list.slice(0, FOLLOWERS_VIEW_CAP).map((follower, index) => (
|
||||
<div className="tw-flex" key={index}>
|
||||
<Avatar
|
||||
name={(follower?.displayName || follower?.name) as string}
|
||||
<ProfilePicture
|
||||
displayName={follower?.displayName || follower?.name}
|
||||
id={follower?.id || ''}
|
||||
name={follower?.name || ''}
|
||||
width="20"
|
||||
/>
|
||||
<span className="tw-self-center tw-ml-2">
|
||||
|
||||
@ -22,8 +22,11 @@ import { FQN_SEPARATOR_CHAR } from '../../../constants/char.constants';
|
||||
import { ROUTES } from '../../../constants/constants';
|
||||
import { SearchIndex } from '../../../enums/search.enum';
|
||||
import { CurrentTourPageType } from '../../../enums/tour.enum';
|
||||
import { OwnerType } from '../../../enums/user.enum';
|
||||
import { TableType } from '../../../generated/entity/data/table';
|
||||
import { EntityReference } from '../../../generated/type/entityReference';
|
||||
import { TagLabel } from '../../../generated/type/tagLabel';
|
||||
import { getEntityName } from '../../../utils/CommonUtils';
|
||||
import { serviceTypeLogo } from '../../../utils/ServiceUtils';
|
||||
import { stringToHTML } from '../../../utils/StringsUtils';
|
||||
import { getEntityLink, getUsagePercentile } from '../../../utils/TableUtils';
|
||||
@ -32,7 +35,7 @@ import TableDataCardBody from './TableDataCardBody';
|
||||
|
||||
type Props = {
|
||||
name: string;
|
||||
owner?: string;
|
||||
owner?: EntityReference;
|
||||
description?: string;
|
||||
tableType?: TableType;
|
||||
id?: string;
|
||||
@ -53,7 +56,7 @@ type Props = {
|
||||
};
|
||||
|
||||
const TableDataCard: FunctionComponent<Props> = ({
|
||||
owner = '',
|
||||
owner,
|
||||
description,
|
||||
id,
|
||||
tier = '',
|
||||
@ -80,7 +83,12 @@ const TableDataCard: FunctionComponent<Props> = ({
|
||||
};
|
||||
|
||||
const OtherDetails: Array<ExtraInfo> = [
|
||||
{ key: 'Owner', value: owner, avatarWidth: '16' },
|
||||
{
|
||||
key: 'Owner',
|
||||
value: getEntityName(owner),
|
||||
avatarWidth: '16',
|
||||
profileName: owner?.type === OwnerType.USER ? owner?.name : undefined,
|
||||
},
|
||||
{ key: 'Tier', value: getTier() },
|
||||
];
|
||||
if (indexType !== SearchIndex.DASHBOARD && usage !== undefined) {
|
||||
|
||||
@ -177,9 +177,10 @@ const NavBar = ({
|
||||
title="Profile"
|
||||
trigger="mouseenter">
|
||||
{AppState?.userDetails?.profile?.images?.image512 ? (
|
||||
<div className="profile-image tw--mr-2">
|
||||
<div className="profile-image square tw--mr-2">
|
||||
<img
|
||||
alt="user"
|
||||
referrerPolicy="no-referrer"
|
||||
src={AppState.userDetails.profile.images.image512}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -112,7 +112,7 @@ const SearchedData: React.FC<SearchedDataProp> = ({
|
||||
indexType={table.index}
|
||||
matches={matches}
|
||||
name={name}
|
||||
owner={table.owner?.displayName || table.owner?.name}
|
||||
owner={table.owner}
|
||||
service={table.service}
|
||||
serviceType={table.serviceType || '--'}
|
||||
tableType={table.tableType as TableType}
|
||||
|
||||
@ -36,6 +36,16 @@ const tagListWithTier = [
|
||||
const onCancel = jest.fn();
|
||||
const onSelectionChange = jest.fn();
|
||||
|
||||
jest.mock('../../utils/UserDataUtils', () => {
|
||||
return {
|
||||
fetchAllUsers: jest.fn(),
|
||||
fetchUserProfilePic: jest.fn(),
|
||||
getUserDataFromOidc: jest.fn(),
|
||||
getUserProfilePic: jest.fn(),
|
||||
matchUserDetails: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('../tags/tags', () => {
|
||||
return jest.fn().mockReturnValue(<p>tags</p>);
|
||||
});
|
||||
|
||||
@ -31,6 +31,7 @@ export const INGESTION_PROGRESS_END_VAL = 80;
|
||||
export const DEPLOYED_PROGRESS_VAL = 100;
|
||||
export const LOCALSTORAGE_RECENTLY_VIEWED = `recentlyViewedData_${COOKIE_VERSION}`;
|
||||
export const LOCALSTORAGE_RECENTLY_SEARCHED = `recentlySearchedData_${COOKIE_VERSION}`;
|
||||
export const LOCALSTORAGE_USER_PROFILES = 'userProfiles';
|
||||
export const oidcTokenKey = 'oidcIdToken';
|
||||
export const TERM_ADMIN = 'Admin';
|
||||
export const TERM_USER = 'User';
|
||||
|
||||
@ -495,6 +495,7 @@ declare module 'Models' {
|
||||
openInNewTab?: boolean;
|
||||
showLabel?: boolean;
|
||||
avatarWidth?: string;
|
||||
profileName?: string;
|
||||
};
|
||||
|
||||
export type TourSteps = {
|
||||
@ -603,4 +604,6 @@ declare module 'Models' {
|
||||
count: number;
|
||||
entityField: string;
|
||||
}
|
||||
|
||||
export type ImageShape = 'circle' | 'square';
|
||||
}
|
||||
|
||||
@ -64,6 +64,7 @@ import {
|
||||
import { observerOptions } from '../../constants/Mydata.constants';
|
||||
import { EntityType, FqnPart, TabSpecificField } from '../../enums/entity.enum';
|
||||
import { ServiceCategory } from '../../enums/service.enum';
|
||||
import { OwnerType } from '../../enums/user.enum';
|
||||
import { CreateThread } from '../../generated/api/feed/createThread';
|
||||
import { DatabaseSchema } from '../../generated/entity/data/databaseSchema';
|
||||
import { Table } from '../../generated/entity/data/table';
|
||||
@ -189,6 +190,10 @@ const DatabaseSchemaPage: FunctionComponent = () => {
|
||||
databaseSchema?.owner?.displayName || databaseSchema?.owner?.name || '',
|
||||
isLink: databaseSchema?.owner?.type === 'team',
|
||||
openInNewTab: false,
|
||||
profileName:
|
||||
databaseSchema?.owner?.type === OwnerType.USER
|
||||
? databaseSchema?.owner?.name
|
||||
: undefined,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@ -64,6 +64,7 @@ import {
|
||||
import { observerOptions } from '../../constants/Mydata.constants';
|
||||
import { EntityType, TabSpecificField } from '../../enums/entity.enum';
|
||||
import { ServiceCategory } from '../../enums/service.enum';
|
||||
import { OwnerType } from '../../enums/user.enum';
|
||||
import { CreateThread } from '../../generated/api/feed/createThread';
|
||||
import { Database } from '../../generated/entity/data/database';
|
||||
import { DatabaseSchema } from '../../generated/entity/data/databaseSchema';
|
||||
@ -194,6 +195,10 @@ const DatabaseDetails: FunctionComponent = () => {
|
||||
database?.owner?.displayName || database?.owner?.name || '',
|
||||
isLink: database?.owner?.type === 'team',
|
||||
openInNewTab: false,
|
||||
profileName:
|
||||
database?.owner?.type === OwnerType.USER
|
||||
? database?.owner?.name
|
||||
: undefined,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@ -53,6 +53,7 @@ import {
|
||||
} from '../../constants/constants';
|
||||
import { SearchIndex } from '../../enums/search.enum';
|
||||
import { ServiceCategory } from '../../enums/service.enum';
|
||||
import { OwnerType } from '../../enums/user.enum';
|
||||
import { Dashboard } from '../../generated/entity/data/dashboard';
|
||||
import { Database } from '../../generated/entity/data/database';
|
||||
import { Pipeline } from '../../generated/entity/data/pipeline';
|
||||
@ -193,6 +194,10 @@ const ServicePage: FunctionComponent = () => {
|
||||
placeholderText: serviceDetails?.owner?.displayName || '',
|
||||
isLink: serviceDetails?.owner?.type === 'team',
|
||||
openInNewTab: false,
|
||||
profileName:
|
||||
serviceDetails?.owner?.type === OwnerType.USER
|
||||
? serviceDetails?.owner?.name
|
||||
: undefined,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@ -13,12 +13,16 @@
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { capitalize } from 'lodash';
|
||||
import { FormattedUsersData } from 'Models';
|
||||
import React, { useState } from 'react';
|
||||
import Avatar from '../../components/common/avatar/Avatar';
|
||||
import ProfilePicture from '../../components/common/ProfilePicture/ProfilePicture';
|
||||
import SVGIcons from '../../utils/SvgUtils';
|
||||
|
||||
type Props = {
|
||||
item: { description: string; name: string; id: string; isChecked: boolean };
|
||||
item: Pick<FormattedUsersData, 'displayName' | 'id' | 'name' | 'type'> & {
|
||||
email?: string;
|
||||
isChecked: boolean;
|
||||
};
|
||||
isActionVisible?: boolean;
|
||||
isIconVisible?: boolean;
|
||||
isCheckBoxes?: boolean;
|
||||
@ -44,7 +48,11 @@ const CheckboxUserCard = ({
|
||||
data-testid="user-card-container">
|
||||
{isIconVisible && (
|
||||
<div className="tw-flex tw-mr-2">
|
||||
<Avatar name={item.description || item.name} />
|
||||
<ProfilePicture
|
||||
displayName={item.displayName || item.name}
|
||||
id={item.id || ''}
|
||||
name={item.name || ''}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
@ -56,8 +64,8 @@ const CheckboxUserCard = ({
|
||||
'tw-font-normal',
|
||||
isActionVisible ? 'tw-truncate tw-w-32' : null
|
||||
)}
|
||||
title={item.description}>
|
||||
{item.description}
|
||||
title={item.displayName}>
|
||||
{item.displayName}
|
||||
</p>
|
||||
{item.name && (
|
||||
<p
|
||||
|
||||
@ -37,8 +37,10 @@ jest.mock('../../authentication/auth-provider/AuthProvider', () => {
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('../../components/common/avatar/Avatar', () => {
|
||||
return jest.fn().mockReturnValue(<p data-testid="avatar">Avatar</p>);
|
||||
jest.mock('../../components/common/ProfilePicture/ProfilePicture', () => {
|
||||
return jest
|
||||
.fn()
|
||||
.mockReturnValue(<p data-testid="profile-picture">ProfilePicture</p>);
|
||||
});
|
||||
|
||||
jest.mock('../../utils/SvgUtils', () => {
|
||||
@ -60,7 +62,7 @@ describe('Test userCard component', () => {
|
||||
});
|
||||
|
||||
const cardContainer = await findByTestId(container, 'user-card-container');
|
||||
const avatar = await findByTestId(container, 'avatar');
|
||||
const avatar = await findByTestId(container, 'profile-picture');
|
||||
|
||||
expect(avatar).toBeInTheDocument();
|
||||
expect(cardContainer).toBeInTheDocument();
|
||||
|
||||
@ -17,8 +17,8 @@ import { capitalize } from 'lodash';
|
||||
import React, { Fragment } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useAuthContext } from '../../authentication/auth-provider/AuthProvider';
|
||||
import Avatar from '../../components/common/avatar/Avatar';
|
||||
import NonAdminAction from '../../components/common/non-admin-action/NonAdminAction';
|
||||
import ProfilePicture from '../../components/common/ProfilePicture/ProfilePicture';
|
||||
import { AssetsType, FqnPart } from '../../enums/entity.enum';
|
||||
import { SearchIndex } from '../../enums/search.enum';
|
||||
import { Operation } from '../../generated/entity/policies/accessControl/rule';
|
||||
@ -157,7 +157,11 @@ const UserCard = ({
|
||||
data-testid="user-card-container">
|
||||
<div className={`tw-flex ${isCheckBoxes ? 'tw-mr-2' : 'tw-gap-1'}`}>
|
||||
{isIconVisible && !isDataset ? (
|
||||
<Avatar name={item.displayName} />
|
||||
<ProfilePicture
|
||||
displayName={item.displayName || item.name}
|
||||
id={item.id || ''}
|
||||
name={item.name || ''}
|
||||
/>
|
||||
) : (
|
||||
<Fragment>{getDatasetIcon(item.type)}</Fragment>
|
||||
)}
|
||||
|
||||
@ -43,6 +43,7 @@ import {
|
||||
getTeamAndUserDetailsPath,
|
||||
TITLE_FOR_NON_ADMIN_ACTION,
|
||||
} from '../../constants/constants';
|
||||
import { OwnerType } from '../../enums/user.enum';
|
||||
import { Operation } from '../../generated/entity/policies/accessControl/rule';
|
||||
import { Team } from '../../generated/entity/teams/team';
|
||||
import {
|
||||
@ -103,6 +104,10 @@ const TeamsPage = () => {
|
||||
currentTeam?.owner?.displayName || currentTeam?.owner?.name || '',
|
||||
isLink: currentTeam?.owner?.type === 'team',
|
||||
openInNewTab: false,
|
||||
profileName:
|
||||
currentTeam?.owner?.type === OwnerType.USER
|
||||
? currentTeam?.owner?.name
|
||||
: undefined,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@ -24,10 +24,19 @@
|
||||
|
||||
.profile-image svg,
|
||||
.profile-image img {
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.profile-image.circle svg,
|
||||
.profile-image.circle img {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.profile-image.square svg,
|
||||
.profile-image.square img {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.teams-dropdown {
|
||||
height: 36px;
|
||||
background-color: transparent !important;
|
||||
|
||||
@ -622,7 +622,7 @@ export const getEntityPlaceHolder = (value: string, isDeleted?: boolean) => {
|
||||
* @param entity - entity reference
|
||||
* @returns - entity name
|
||||
*/
|
||||
export const getEntityName = (entity: EntityReference) => {
|
||||
export const getEntityName = (entity?: EntityReference) => {
|
||||
return entity?.displayName || entity?.name || '';
|
||||
};
|
||||
|
||||
|
||||
@ -15,7 +15,7 @@ import classNames from 'classnames';
|
||||
import { isEmpty, isNil, isString, isUndefined, startCase } from 'lodash';
|
||||
import { Bucket, ExtraInfo, LeafNodes, LineagePos } from 'Models';
|
||||
import React from 'react';
|
||||
import Avatar from '../components/common/avatar/Avatar';
|
||||
import ProfilePicture from '../components/common/ProfilePicture/ProfilePicture';
|
||||
import TableProfilerGraph from '../components/TableProfiler/TableProfilerGraph.component';
|
||||
import { FQN_SEPARATOR_CHAR } from '../constants/char.constants';
|
||||
import {
|
||||
@ -382,9 +382,10 @@ export const getInfoElements = (data: ExtraInfo) => {
|
||||
displayVal && displayVal !== '--' ? (
|
||||
isString(displayVal) ? (
|
||||
<div className="tw-inline-block tw-mr-2">
|
||||
<Avatar
|
||||
name={displayVal}
|
||||
textClass="tw-text-xs"
|
||||
<ProfilePicture
|
||||
displayName={displayVal}
|
||||
id=""
|
||||
name={data.profileName || ''}
|
||||
width={data.avatarWidth || '20'}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -11,13 +11,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { isEmpty, isEqual } from 'lodash';
|
||||
import { AxiosError, AxiosResponse } from 'axios';
|
||||
import { isEmpty, isEqual, isUndefined } from 'lodash';
|
||||
import AppState from '../AppState';
|
||||
import { OidcUser } from '../authentication/auth-provider/AuthProvider.interface';
|
||||
import { getRoles } from '../axiosAPIs/rolesAPI';
|
||||
import { getTeams } from '../axiosAPIs/teamsAPI';
|
||||
import { getUsers } from '../axiosAPIs/userAPI';
|
||||
import { getUserById, getUserByName, getUsers } from '../axiosAPIs/userAPI';
|
||||
import { API_RES_MAX_SIZE } from '../constants/constants';
|
||||
import { User } from '../generated/entity/teams/user';
|
||||
import { getImages } from './CommonUtils';
|
||||
@ -54,6 +54,7 @@ const getAllRoles = (): void => {
|
||||
};
|
||||
|
||||
export const fetchAllUsers = () => {
|
||||
AppState.loadUserProfilePics();
|
||||
getAllUsersList('profile,teams,roles');
|
||||
getAllTeams();
|
||||
getAllRoles();
|
||||
@ -90,3 +91,52 @@ export const matchUserDetails = (
|
||||
|
||||
return isMatch;
|
||||
};
|
||||
|
||||
export const fetchUserProfilePic = (userId?: string, username?: string) => {
|
||||
let promise;
|
||||
|
||||
if (userId) {
|
||||
promise = getUserById(userId, 'profile');
|
||||
} else if (username) {
|
||||
promise = getUserByName(username, 'profile');
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
AppState.updateProfilePicsLoading(userId, username);
|
||||
|
||||
promise
|
||||
.then((res) => {
|
||||
const userData = res.data as User;
|
||||
const profile = userData.profile?.images?.image512 || '';
|
||||
|
||||
AppState.updateUserProfilePic(userData.id, userData.name, profile);
|
||||
})
|
||||
.catch((err: AxiosError) => {
|
||||
// ignore exception
|
||||
AppState.updateUserProfilePic(
|
||||
userId,
|
||||
username,
|
||||
err.response?.status === 404 ? '' : undefined
|
||||
);
|
||||
})
|
||||
.finally(() => {
|
||||
AppState.removeProfilePicsLoading(userId, username);
|
||||
});
|
||||
};
|
||||
|
||||
export const getUserProfilePic = (userId?: string, username?: string) => {
|
||||
let profile;
|
||||
if (userId || username) {
|
||||
profile = AppState.getUserProfilePic(userId, username);
|
||||
|
||||
if (
|
||||
isUndefined(profile) &&
|
||||
!AppState.isProfilePicLoading(userId, username)
|
||||
) {
|
||||
fetchUserProfilePic(userId, username);
|
||||
}
|
||||
}
|
||||
|
||||
return profile;
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user