mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2026-01-06 12:36:56 +00:00
Feat: Added new user profile page ui. (#3229)
* Feat: Added user profile page * miner fix * connected mention users to respective page
This commit is contained in:
parent
6ee31eb21f
commit
903e2f6246
@ -0,0 +1,167 @@
|
||||
import classNames from 'classnames';
|
||||
import React, { useState } from 'react';
|
||||
import { EntityReference, User } from '../../generated/entity/teams/user';
|
||||
import UserCard from '../../pages/teams/UserCard';
|
||||
import SVGIcons, { Icons } from '../../utils/SvgUtils';
|
||||
import Avatar from '../common/avatar/Avatar';
|
||||
import TabsPane from '../common/TabsPane/TabsPane';
|
||||
import PageLayout from '../containers/PageLayout';
|
||||
|
||||
type Props = {
|
||||
userData: User;
|
||||
};
|
||||
|
||||
const Users = ({ userData }: Props) => {
|
||||
const [activeTab, setActiveTab] = useState(1);
|
||||
|
||||
const activeTabHandler = (tab: number) => {
|
||||
setActiveTab(tab);
|
||||
};
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
name: 'Owned Data',
|
||||
icon: {
|
||||
alt: 'owned-data',
|
||||
name: 'owned-data',
|
||||
title: 'Owned Data',
|
||||
selectedName: 'owned-data',
|
||||
},
|
||||
isProtected: false,
|
||||
position: 1,
|
||||
},
|
||||
{
|
||||
name: 'Following',
|
||||
icon: {
|
||||
alt: 'following',
|
||||
name: 'following',
|
||||
title: 'following',
|
||||
selectedName: 'following',
|
||||
},
|
||||
isProtected: false,
|
||||
position: 2,
|
||||
},
|
||||
];
|
||||
|
||||
const fetchLeftPanel = () => {
|
||||
return (
|
||||
<div className="tw-pt-4">
|
||||
<div className="tw-pb-4 tw-mb-4 tw-border-b tw-flex tw-flex-col tw-items-center">
|
||||
{userData.profile?.images?.image ? (
|
||||
<div className="tw-h-28 tw-w-28">
|
||||
<img
|
||||
alt="profile"
|
||||
className="tw-rounded-full tw-w-full"
|
||||
src={userData.profile?.images?.image}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<Avatar
|
||||
name={userData?.displayName || userData.name}
|
||||
textClass="tw-text-5xl"
|
||||
width="112"
|
||||
/>
|
||||
)}
|
||||
<p className="tw-mt-4">
|
||||
<span className="tw-text-base tw-font-medium tw-mr-2">
|
||||
{userData.displayName || userData.name}
|
||||
</span>
|
||||
<span
|
||||
className={classNames(
|
||||
'tw-text-xs tw-border tw-px-1 tw-py-0.5 tw-rounded',
|
||||
userData.deleted ? 'tw-border-grey-muted' : 'tw-border-success'
|
||||
)}>
|
||||
{userData.deleted ? (
|
||||
<span className="tw-text-grey-muted">Inactive</span>
|
||||
) : (
|
||||
<span className="tw-text-success">Active</span>
|
||||
)}
|
||||
</span>
|
||||
</p>
|
||||
<p className="tw-mt-2">{userData.email}</p>
|
||||
</div>
|
||||
<div className="tw-pb-4 tw-mb-4 tw-border-b">
|
||||
<h6 className="tw-heading tw-mb-3">Teams</h6>
|
||||
|
||||
{userData.teams?.map((team, i) => (
|
||||
<div className="tw-mb-2 tw-flex tw-items-center tw-gap-2" key={i}>
|
||||
<SVGIcons alt="icon" className="tw-w-4" icon={Icons.TEAMS_GREY} />
|
||||
<span>{team?.displayName || team?.name}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="tw-pb-4 tw-mb-4 tw-border-b">
|
||||
<h6 className="tw-heading tw-mb-3">Roles</h6>
|
||||
|
||||
{userData.roles?.map((role, i) => (
|
||||
<div className="tw-mb-2 tw-flex tw-items-center tw-gap-2" key={i}>
|
||||
<SVGIcons alt="icon" className="tw-w-4" icon={Icons.USERS} />
|
||||
<span>{role?.displayName || role?.name}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const getEntityData = (data: EntityReference[], placeholder: string) => {
|
||||
if ((data?.length as number) <= 0) {
|
||||
return (
|
||||
<div className="tw-flex tw-flex-col tw-items-center tw-place-content-center tw-mt-40 tw-gap-1">
|
||||
<p className="tw-text-base">{placeholder}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className="tw-grid xxl:tw-grid-cols-4 md:tw-grid-cols-3 tw-gap-4"
|
||||
data-testid="dataset-card">
|
||||
{' '}
|
||||
{data?.map((dataset, index) => {
|
||||
const Dataset = {
|
||||
description: dataset.name || '',
|
||||
name: dataset.type,
|
||||
};
|
||||
|
||||
return (
|
||||
<UserCard isDataset isIconVisible item={Dataset} key={index} />
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<PageLayout classes="tw-h-full tw-px-6" leftPanel={fetchLeftPanel()}>
|
||||
<div className="tw-mb-3">
|
||||
<TabsPane
|
||||
activeTab={activeTab}
|
||||
className="tw-flex-initial"
|
||||
setActiveTab={activeTabHandler}
|
||||
tabs={tabs}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
{activeTab === 1 &&
|
||||
getEntityData(
|
||||
userData?.owns || [],
|
||||
`${
|
||||
userData?.displayName || userData?.name || 'User'
|
||||
} does not own anything yet`
|
||||
)}
|
||||
{activeTab === 2 &&
|
||||
getEntityData(
|
||||
userData?.follows || [],
|
||||
`${
|
||||
userData?.displayName || userData?.name || 'User'
|
||||
} does not follow anything yet`
|
||||
)}
|
||||
</div>
|
||||
</PageLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export default Users;
|
||||
@ -50,6 +50,7 @@ const PLACEHOLDER_ROUTE_ENTITY_FQN = ':entityFQN';
|
||||
const PLACEHOLDER_WEBHOOK_NAME = ':webhookName';
|
||||
const PLACEHOLDER_GLOSSARY_NAME = ':glossaryName';
|
||||
const PLACEHOLDER_GLOSSARY_TERMS_FQN = ':glossaryTermsFQN';
|
||||
const PLACEHOLDER_USER_NAME = ':username';
|
||||
|
||||
export const pagingObject = { after: '', before: '' };
|
||||
|
||||
@ -154,6 +155,7 @@ export const ROUTES = {
|
||||
PIPELINE_DETAILS: `/pipeline/${PLACEHOLDER_ROUTE_PIPELINE_FQN}`,
|
||||
PIPELINE_DETAILS_WITH_TAB: `/pipeline/${PLACEHOLDER_ROUTE_PIPELINE_FQN}/${PLACEHOLDER_ROUTE_TAB}`,
|
||||
USER_LIST: '/user-list',
|
||||
USER_PROFILE: `/users/${PLACEHOLDER_USER_NAME}`,
|
||||
ROLES: '/roles',
|
||||
WEBHOOKS: '/webhooks',
|
||||
ADD_WEBHOOK: '/add-webhook',
|
||||
@ -284,6 +286,13 @@ export const getEditWebhookPath = (webhookName: string) => {
|
||||
return path;
|
||||
};
|
||||
|
||||
export const getUserPath = (username: string) => {
|
||||
let path = ROUTES.USER_PROFILE;
|
||||
path = path.replace(PLACEHOLDER_USER_NAME, username);
|
||||
|
||||
return path;
|
||||
};
|
||||
|
||||
export const getGlossaryTermsPath = (
|
||||
glossaryName: string,
|
||||
glossaryTerm = ''
|
||||
|
||||
@ -23,7 +23,7 @@ export const entityRegex = /<#E\/([^<>]+?)\/([^<>]+?)\|(\[(.+?)?\]\((.+?)?\))>/;
|
||||
|
||||
export const entityUrlMap = {
|
||||
team: 'teams',
|
||||
user: 'user',
|
||||
user: 'users',
|
||||
};
|
||||
|
||||
export const EditorPlaceHolder = `Use @mention to tag a user or a team.
|
||||
|
||||
@ -179,6 +179,7 @@ declare module 'Models' {
|
||||
name: string;
|
||||
profile: UserProfile;
|
||||
teams: Array<UserTeam>;
|
||||
follows?: Array<UserTeam>;
|
||||
timezone: string;
|
||||
href: string;
|
||||
};
|
||||
|
||||
@ -0,0 +1,55 @@
|
||||
import { AxiosResponse } from 'axios';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { getUserByName } from '../../axiosAPIs/userAPI';
|
||||
import PageContainerV1 from '../../components/containers/PageContainerV1';
|
||||
import Loader from '../../components/Loader/Loader';
|
||||
import Users from '../../components/Users/Users.component';
|
||||
import { User } from '../../generated/entity/teams/user';
|
||||
|
||||
const UserPage = () => {
|
||||
const { username } = useParams<{ [key: string]: string }>();
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [userData, setUserData] = useState<User>({} as User);
|
||||
const [isError, setIsError] = useState(false);
|
||||
|
||||
const fetchUserData = () => {
|
||||
getUserByName(username, 'profile,roles,teams,follows,owns')
|
||||
.then((res: AxiosResponse) => {
|
||||
setUserData(res.data);
|
||||
})
|
||||
.catch(() => {
|
||||
setIsError(true);
|
||||
})
|
||||
.finally(() => setIsLoading(false));
|
||||
};
|
||||
|
||||
const errorPlaceholder = () => {
|
||||
return (
|
||||
<div className="tw-flex tw-flex-col tw-items-center tw-place-content-center tw-mt-40 tw-gap-1">
|
||||
<p className="tw-text-base">
|
||||
No user available with{' '}
|
||||
<span className="tw-font-medium">{username}</span> username.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchUserData();
|
||||
}, [username]);
|
||||
|
||||
return (
|
||||
<PageContainerV1 className="tw-pt-4">
|
||||
{isLoading ? (
|
||||
<Loader />
|
||||
) : !isError ? (
|
||||
<Users userData={userData} />
|
||||
) : (
|
||||
errorPlaceholder()
|
||||
)}
|
||||
</PageContainerV1>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserPage;
|
||||
@ -39,6 +39,7 @@ import TeamsPage from '../pages/teams';
|
||||
import TopicDetailsPage from '../pages/TopicDetails/TopicDetailsPage.component';
|
||||
import TourPageComponent from '../pages/tour-page/TourPage.component';
|
||||
import UserListPage from '../pages/UserListPage/UserListPage';
|
||||
import UserPage from '../pages/UserPage/UserPage.component';
|
||||
import WebhooksPage from '../pages/WebhooksPage/WebhooksPage.component';
|
||||
const AuthenticatedAppRouter: FunctionComponent = () => {
|
||||
const { isAuthDisabled, isAdminUser } = useAuth();
|
||||
@ -111,6 +112,7 @@ const AuthenticatedAppRouter: FunctionComponent = () => {
|
||||
path={ROUTES.GLOSSARY_DETAILS}
|
||||
/> */}
|
||||
{/* <Route exact component={GlossaryTermPage} path={ROUTES.GLOSSARY_TERMS} /> */}
|
||||
<Route exact component={UserPage} path={ROUTES.USER_PROFILE} />
|
||||
{isAuthDisabled || isAdminUser ? (
|
||||
<>
|
||||
<Route exact component={AddGlossaryPage} path={ROUTES.ADD_GLOSSARY} />
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user