From acc6b925052df1e54834f1cc7edc4bb20181f6a0 Mon Sep 17 00:00:00 2001 From: Sachin Chaurasiya Date: Tue, 21 Jun 2022 14:40:16 +0530 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Feat=20:=20Show=20entity=20card=20i?= =?UTF-8?q?n=20popover=20on=20hover=20of=20entity=20links=20in=20activity?= =?UTF-8?q?=20feed=20(#5526)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ✨ Feat : Show entity card in popover on hover of entity links in activity feed * Style Changes --- .../resources/ui/src/assets/svg/Reaction.svg | 12 +- .../resources/ui/src/assets/svg/Reply.svg | 4 +- .../ui/src/assets/svg/delete-white.svg | 8 +- .../ActivityFeedCard/ActivityFeedCard.tsx | 10 +- .../FeedCardBody/FeedCardBody.tsx | 2 +- .../FeedCardHeader/FeedCardHeader.test.tsx | 2 + .../FeedCardHeader/FeedCardHeader.tsx | 162 +----------- .../ActivityFeedList/FeedListBody.tsx | 1 + .../ActivityThreadList.tsx | 1 + .../common/PopOverCard/EntityPopOverCard.tsx | 233 ++++++++++++++++++ .../common/PopOverCard/UserPopOverCard.tsx | 154 ++++++++++++ .../main/resources/ui/src/styles/x-master.css | 17 ++ 12 files changed, 439 insertions(+), 167 deletions(-) create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/common/PopOverCard/EntityPopOverCard.tsx create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/common/PopOverCard/UserPopOverCard.tsx diff --git a/openmetadata-ui/src/main/resources/ui/src/assets/svg/Reaction.svg b/openmetadata-ui/src/main/resources/ui/src/assets/svg/Reaction.svg index 604c3cb05bd..79ee007d0d1 100644 --- a/openmetadata-ui/src/main/resources/ui/src/assets/svg/Reaction.svg +++ b/openmetadata-ui/src/main/resources/ui/src/assets/svg/Reaction.svg @@ -1,14 +1,14 @@ - - - - - + + + + + - + diff --git a/openmetadata-ui/src/main/resources/ui/src/assets/svg/Reply.svg b/openmetadata-ui/src/main/resources/ui/src/assets/svg/Reply.svg index 4912c8bea61..3b6e940297d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/assets/svg/Reply.svg +++ b/openmetadata-ui/src/main/resources/ui/src/assets/svg/Reply.svg @@ -1,3 +1,3 @@ - - + + diff --git a/openmetadata-ui/src/main/resources/ui/src/assets/svg/delete-white.svg b/openmetadata-ui/src/main/resources/ui/src/assets/svg/delete-white.svg index b5623f574de..5584a41f5c3 100644 --- a/openmetadata-ui/src/main/resources/ui/src/assets/svg/delete-white.svg +++ b/openmetadata-ui/src/main/resources/ui/src/assets/svg/delete-white.svg @@ -1,6 +1,6 @@ - - - - + + + + diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCard/ActivityFeedCard.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCard/ActivityFeedCard.tsx index 9aa1c60d650..660d57ee749 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCard/ActivityFeedCard.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCard/ActivityFeedCard.tsx @@ -26,6 +26,7 @@ import { getEntityFQN, getEntityType, } from '../../../utils/FeedUtils'; +import UserPopOverCard from '../../common/PopOverCard/UserPopOverCard'; import ProfilePicture from '../../common/ProfilePicture/ProfilePicture'; import { ActivityFeedCardProp } from './ActivityFeedCard.interface'; import FeedCardBody from './FeedCardBody/FeedCardBody'; @@ -117,11 +118,10 @@ const ActivityFeedCard: FC = ({ }, [feed]); return ( -
+
= ({ zIndex={9999} onVisibleChange={handleVisibleChange}>
- } trigger="click"> + - + - +
= ({ return (
-
+
({ jest.mock('../../../../utils/TableUtils', () => ({ getEntityLink: jest.fn(), + getTierTags: jest.fn(), + getTagsWithoutTier: jest.fn(), })); jest.mock('../../../../utils/TimeUtils', () => ({ diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCard/FeedCardHeader/FeedCardHeader.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCard/FeedCardHeader/FeedCardHeader.tsx index 75f58b6ee85..22d4d3f78da 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCard/FeedCardHeader/FeedCardHeader.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCard/FeedCardHeader/FeedCardHeader.tsx @@ -11,36 +11,26 @@ * limitations under the License. */ -import { AxiosResponse } from 'axios'; import classNames from 'classnames'; import { isUndefined } from 'lodash'; -import React, { FC, Fragment, useState } from 'react'; -import { Link, useHistory } from 'react-router-dom'; +import React, { FC, Fragment } from 'react'; +import { Link } from 'react-router-dom'; import AppState from '../../../../AppState'; -import { getUserByName } from '../../../../axiosAPIs/userAPI'; import { FQN_SEPARATOR_CHAR } from '../../../../constants/char.constants'; -import { getUserPath, TERM_ADMIN } from '../../../../constants/constants'; import { EntityType, FqnPart, TabSpecificField, } from '../../../../enums/entity.enum'; -import { User } from '../../../../generated/entity/teams/user'; -import { EntityReference } from '../../../../generated/type/entityReference'; import { - getEntityName, - getNonDeletedTeams, getPartialNameFromFQN, getPartialNameFromTableFQN, } from '../../../../utils/CommonUtils'; import { getEntityFieldDisplay } from '../../../../utils/FeedUtils'; -import SVGIcons, { Icons } from '../../../../utils/SvgUtils'; import { getEntityLink } from '../../../../utils/TableUtils'; import { getDayTimeByTimeStamp } from '../../../../utils/TimeUtils'; -import { Button } from '../../../buttons/Button/Button'; -import PopOver from '../../../common/popover/PopOver'; -import ProfilePicture from '../../../common/ProfilePicture/ProfilePicture'; -import Loader from '../../../Loader/Loader'; +import EntityPopOverCard from '../../../common/PopOverCard/EntityPopOverCard'; +import UserPopOverCard from '../../../common/PopOverCard/UserPopOverCard'; import { FeedHeaderProp } from '../ActivityFeedCard.interface'; import './FeedCardHeader.style.css'; @@ -53,122 +43,6 @@ const FeedCardHeader: FC = ({ entityField, isEntityFeed, }) => { - const history = useHistory(); - const [userData, setUserData] = useState({} as User); - const [isLoading, setIsLoading] = useState(true); - const [isError, setIsError] = useState(false); - - const onClickHandler = () => { - getUserByName(createdBy, 'profile,roles,teams,follows,owns') - .then((res: AxiosResponse) => { - setUserData(res.data); - }) - .catch(() => { - setIsError(true); - }) - .finally(() => setIsLoading(false)); - }; - - const onTitleClickHandler = (path: string) => { - history.push(path); - }; - - const getUserData = () => { - const name = userData.name ?? ''; - const displayName = getEntityName(userData as unknown as EntityReference); - const teams = getNonDeletedTeams(userData.teams ?? []); - const roles = userData.roles; - const isAdmin = userData?.isAdmin; - - return ( - - {isError ? ( -

Error while getting user data.

- ) : ( -
- {isLoading ? ( - - ) : ( -
-
-
- -
-
- - {displayName !== name ? ( - {name} - ) : null} -
-
-
- {teams?.length || roles?.length ? ( -
- ) : null} - {teams?.length ? ( -

- - - Teams - - - {teams.map((team, i) => ( - - {team?.displayName ?? team?.name} - - ))} - -

- ) : null} - {roles?.length ? ( -

- - - Roles - - - {isAdmin && ( - - {TERM_ADMIN} - - )} - {roles.map((role, i) => ( - - {role?.displayName ?? role?.name} - - ))} - -

- ) : null} -
-
- )} -
- )} -
- ); - }; - const entityDisplayName = () => { let displayName; if (entityType === EntityType.TABLE) { @@ -186,6 +60,7 @@ const FeedCardHeader: FC = ({ EntityType.MESSAGING_SERVICE, EntityType.PIPELINE_SERVICE, EntityType.TYPE, + EntityType.MLMODEL, ].includes(entityType as EntityType) ) { displayName = getPartialNameFromFQN(entityFQN, ['service']); @@ -238,14 +113,11 @@ const FeedCardHeader: FC = ({ {entityType} @@ -260,18 +132,10 @@ const FeedCardHeader: FC = ({ return (
- - - {createdBy} - - + + {createdBy} + + {getFeedLinkElement()} = ({ return ( = ({ return ( { + entityType: string; + entityFQN: string; +} + +const EntityPopOverCard: FC = ({ children, entityType, entityFQN }) => { + const [entityData, setEntityData] = useState({} as EntityData); + + const entityTier = useMemo(() => { + const tierFQN = getTierTags(entityData.tags || [])?.tagFQN; + + return tierFQN?.split(FQN_SEPARATOR_CHAR)[1]; + }, [entityData.tags]); + + const entityTags = useMemo(() => { + const tags: EntityTags[] = getTagsWithoutTier(entityData.tags || []) || []; + + return tags.map((tag) => `#${tag.tagFQN}`); + }, [entityData.tags]); + + const getData = () => { + const fields = 'tags,owner'; + + switch (entityType) { + case EntityType.TABLE: + getTableDetailsByFQN(entityFQN, fields) + .then((res: AxiosResponse) => { + setEntityData(res.data); + }) + .catch((err: AxiosError) => showErrorToast(err)); + + break; + case EntityType.TOPIC: + getTopicByFqn(entityFQN, fields) + .then((res: AxiosResponse) => { + setEntityData(res.data); + }) + .catch((err: AxiosError) => showErrorToast(err)); + + break; + case EntityType.DASHBOARD: + getDashboardByFqn(entityFQN, fields) + .then((res: AxiosResponse) => { + setEntityData(res.data); + }) + .catch((err: AxiosError) => showErrorToast(err)); + + break; + case EntityType.PIPELINE: + getPipelineByFqn(entityFQN, fields) + .then((res: AxiosResponse) => { + setEntityData(res.data); + }) + .catch((err: AxiosError) => showErrorToast(err)); + + break; + case EntityType.MLMODEL: + getMlModelByFQN(entityFQN, fields) + .then((res: AxiosResponse) => { + setEntityData(res.data); + }) + .catch((err: AxiosError) => showErrorToast(err)); + + break; + case EntityType.DATABASE: + getDatabaseDetailsByFQN(entityFQN, 'owner') + .then((res: AxiosResponse) => { + setEntityData(res.data); + }) + .catch((err: AxiosError) => showErrorToast(err)); + + break; + case EntityType.DATABASE_SCHEMA: + getDatabaseSchemaDetailsByFQN(entityFQN, 'owner') + .then((res: AxiosResponse) => { + setEntityData(res.data); + }) + .catch((err: AxiosError) => showErrorToast(err)); + + break; + + default: + break; + } + }; + + const PopoverTitle = () => { + return ( + + + + ); + }; + + const PopoverContent = () => { + return ( +
+
+
+ + {entityData.owner ? ( + + Owner:{' '} + + + + {getEntityName(entityData.owner)} + + + + ) : ( + No Owner + )} + +
+ | +
+ {entityTier ? ( + entityTier + ) : ( + No Tier + )} +
+
+ +
+ {entityData.description ? ( + + ) : ( + No description + )} +
+ + {entityData.tags ? ( +
+ {entityTags.map((tag) => ( + + {tag} + + ))} +
+ ) : null} +
+ ); + }; + + return ( + } + overlayClassName="ant-popover-card" + title={} + trigger="hover" + zIndex={9999}> +
{children}
+
+ ); +}; + +export default EntityPopOverCard; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/PopOverCard/UserPopOverCard.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/PopOverCard/UserPopOverCard.tsx new file mode 100644 index 00000000000..cf8284d03d3 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/PopOverCard/UserPopOverCard.tsx @@ -0,0 +1,154 @@ +/* + * 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 { Popover } from 'antd'; +import { AxiosError, AxiosResponse } from 'axios'; +import React, { FC, Fragment, HTMLAttributes, useState } from 'react'; +import { useHistory } from 'react-router-dom'; +import { getUserByName } from '../../../axiosAPIs/userAPI'; +import { getUserPath, TERM_ADMIN } from '../../../constants/constants'; +import { User } from '../../../generated/entity/teams/user'; +import { EntityReference } from '../../../generated/type/entityReference'; +import { getEntityName, getNonDeletedTeams } from '../../../utils/CommonUtils'; +import SVGIcons, { Icons } from '../../../utils/SvgUtils'; +import { showErrorToast } from '../../../utils/ToastUtils'; +import Loader from '../../Loader/Loader'; +import ProfilePicture from '../ProfilePicture/ProfilePicture'; + +interface Props extends HTMLAttributes { + userName: string; +} + +const UserPopOverCard: FC = ({ children, userName }) => { + const history = useHistory(); + const [userData, setUserData] = useState({} as User); + const [isLoading, setIsLoading] = useState(true); + + const getData = () => { + getUserByName(userName, 'profile,roles,teams,follows,owns') + .then((res: AxiosResponse) => { + setUserData(res.data); + }) + .catch((err: AxiosError) => { + showErrorToast(err); + }) + .finally(() => setIsLoading(false)); + }; + + const onTitleClickHandler = (path: string) => { + history.push(path); + }; + + const UserTeams = () => { + const teams = getNonDeletedTeams(userData.teams ?? []); + + return teams?.length ? ( +

+ + + Teams + + + {teams.map((team, i) => ( + + {team?.displayName ?? team?.name} + + ))} + +

+ ) : null; + }; + + const UserRoles = () => { + const roles = userData.roles; + const isAdmin = userData?.isAdmin; + + return roles?.length ? ( +

+ + + Roles + + + {isAdmin && ( + + {TERM_ADMIN} + + )} + {roles.map((role, i) => ( + + {role?.displayName ?? role?.name} + + ))} + +

+ ) : null; + }; + + const PopoverTitle = () => { + const name = userData.name ?? ''; + const displayName = getEntityName(userData as unknown as EntityReference); + + return ( +
+
+ +
+
+ + {displayName !== name ? ( + {name} + ) : null} +
+
+ ); + }; + + const PopoverContent = () => { + return ( + + {isLoading ? ( + + ) : ( +
+ + +
+ )} +
+ ); + }; + + return ( + } + overlayClassName="ant-popover-card" + title={} + trigger="hover" + zIndex={9999}> +
{children}
+
+ ); +}; + +export default UserPopOverCard; diff --git a/openmetadata-ui/src/main/resources/ui/src/styles/x-master.css b/openmetadata-ui/src/main/resources/ui/src/styles/x-master.css index 5e44801b585..20ecb3067ac 100644 --- a/openmetadata-ui/src/main/resources/ui/src/styles/x-master.css +++ b/openmetadata-ui/src/main/resources/ui/src/styles/x-master.css @@ -920,6 +920,10 @@ body .profiler-graph .recharts-active-dot circle { grid-template-columns: 200px auto 200px; } +.feed-meesage > div > p:last-child { + margin-bottom: 0px; +} + code { padding: 2px 3px; border-radius: 4px; @@ -971,6 +975,10 @@ code { padding: 14px; } +.ant-card-feed > .ant-card-body { + padding-bottom: 0px; +} + .ant-card-head-title { padding: 8px 0px; } @@ -1077,15 +1085,24 @@ code { } .ant-popover-feed > .ant-popover-content > .ant-popover-arrow, +.ant-popover-card > .ant-popover-content > .ant-popover-arrow, .ant-popover-feed-reactions > .ant-popover-content > .ant-popover-arrow { display: none; } .ant-popover-feed > .ant-popover-content > .ant-popover-inner, +.ant-popover-card > .ant-popover-content > .ant-popover-inner, .ant-popover-feed-reactions > .ant-popover-content > .ant-popover-inner { border-radius: 6px; border: 1px solid #dde3ea; } +.ant-popover-card + > .ant-popover-content + > .ant-popover-inner + > .ant-popover-title { + padding: 4px 8px; +} + /* Feed popover CSS end */ .data-box {