mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-10-18 20:30:48 +00:00
feat: add tasks tab for feeds widget (#11894)
* feat: add tasks tab for feeds widget * fix: code smells * fix: styling issues for tasks tab * fix: cypress tests
This commit is contained in:
parent
2b2602b76b
commit
c6fdbc43bb
@ -0,0 +1,35 @@
|
||||
<svg viewBox="0 0 83 101" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect y="11" width="68" height="84" rx="5" fill="#292929" fill-opacity="0.05"/>
|
||||
<rect x="1" y="12" width="66" height="82" rx="4" stroke="black" stroke-opacity="0.5" stroke-width="2"/>
|
||||
<path d="M10 25H17.5H57C56.8333 17.3333 52 2 34 2C16 2 10.5 17.3333 10 25Z" fill="white"/>
|
||||
<path d="M9.00212 24.9349C8.98412 25.2109 9.08119 25.4819 9.27025 25.6837C9.45931 25.8855 9.72348 26 10 26H17.5H57C57.269 26 57.5266 25.8916 57.7147 25.6994C57.9029 25.5071 58.0056 25.2472 57.9998 24.9783C57.913 20.9887 56.6206 15.0285 52.9967 10.0375C49.3376 4.99792 43.3339 1 34 1C24.6691 1 18.5089 4.99421 14.645 10.0151C10.8134 14.9941 9.26232 20.9452 9.00212 24.9349Z" stroke="black" stroke-opacity="0.5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M37 21.5L48.5 12" stroke="black" stroke-opacity="0.5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M19 19.5C19 19.5 23 10 33.5 10C44 10 48 19.5 48 19.5" stroke="black" stroke-opacity="0.5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<circle cx="33" cy="25" r="6" fill="#D9D9D9"/>
|
||||
<circle cx="33" cy="25" r="7" stroke="black" stroke-opacity="0.5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<circle cx="14" cy="44" r="6" fill="white"/>
|
||||
<circle cx="14" cy="44" r="7" stroke="black" stroke-opacity="0.5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<circle cx="14" cy="65" r="6" fill="white"/>
|
||||
<circle cx="14" cy="65" r="7" stroke="black" stroke-opacity="0.5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M1 81H44" stroke="black" stroke-opacity="0.5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5 88H14.5" stroke="black" stroke-opacity="0.5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M19 88L44 88" stroke="black" stroke-opacity="0.5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M27 41H60.5" stroke="black" stroke-opacity="0.5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M27 47H49" stroke="black" stroke-opacity="0.5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M52.5 47H60.5" stroke="black" stroke-opacity="0.5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M27 62H60.5" stroke="black" stroke-opacity="0.5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M27 67.5H50" stroke="black" stroke-opacity="0.5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M52.5 67.5H60.5" stroke="black" stroke-opacity="0.5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M44 93.5V57C44 54.2386 46.2386 52 49 52H69.4289C70.755 52 72.0268 52.5268 72.9645 53.4645L79.0355 59.5355C79.9732 60.4732 80.5 61.745 80.5 63.0711V93.5C80.5 96.2614 78.2614 98.5 75.5 98.5H49C46.2386 98.5 44 96.2614 44 93.5Z" fill="white"/>
|
||||
<path d="M43 57V93.5C43 96.8137 45.6863 99.5 49 99.5H75.5C78.8137 99.5 81.5 96.8137 81.5 93.5V63.0711C81.5 61.4798 80.8679 59.9536 79.7426 58.8284L73.6716 52.7574C72.5464 51.6321 71.0202 51 69.4289 51H49C45.6863 51 43 53.6863 43 57Z" stroke="black" stroke-opacity="0.5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M49 65H74" stroke="black" stroke-opacity="0.5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M49 77H74" stroke="black" stroke-opacity="0.5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M49 83H55" stroke="black" stroke-opacity="0.5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M49 71H64" stroke="black" stroke-opacity="0.5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M68 71H74" stroke="black" stroke-opacity="0.5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M59 83L74 83" stroke="black" stroke-opacity="0.5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M74.002 89H63.002" stroke="black" stroke-opacity="0.5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M59 89H49" stroke="black" stroke-opacity="0.5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M11 43.5L12.5 46L16.5 42" stroke="black" stroke-opacity="0.5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M11 64.5L12.5 67L16.5 63" stroke="black" stroke-opacity="0.5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 4.5 KiB |
@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 12 13" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.00439453 6.15452C0.00439453 2.85003 2.68325 0.171143 5.98777 0.171143C9.29233 0.171143 11.9712 2.85003 11.9712 6.15452C11.9712 9.45911 9.29233 12.1379 5.98777 12.1379C2.68325 12.1379 0.00439453 9.45911 0.00439453 6.15452ZM5.98777 1.09169C3.19164 1.09169 0.924904 3.35839 0.924904 6.15452C0.924904 8.95069 3.19161 11.2174 5.98777 11.2174C8.78394 11.2174 11.0506 8.95069 11.0506 6.15452C11.0506 3.35839 8.78394 1.09169 5.98777 1.09169ZM5.98777 7.53532C6.75036 7.53532 7.36857 6.9171 7.36857 6.15452C7.36857 5.39194 6.75036 4.77376 5.98777 4.77376C5.22519 4.77376 4.60701 5.39194 4.60701 6.15452C4.60701 6.9171 5.22519 7.53532 5.98777 7.53532Z" fill="#7147E8"/>
|
||||
</svg>
|
After Width: | Height: | Size: 791 B |
@ -14,9 +14,11 @@ import { Col, Row } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import UserPopOverCard from 'components/common/PopOverCard/UserPopOverCard';
|
||||
import ProfilePicture from 'components/common/ProfilePicture/ProfilePicture';
|
||||
import Reactions from 'components/Reactions/Reactions';
|
||||
import { ReactionOperation } from 'enums/reactions.enum';
|
||||
import { compare } from 'fast-json-patch';
|
||||
import { Post, ReactionType, Thread } from 'generated/entity/feed/thread';
|
||||
import { noop } from 'lodash';
|
||||
import React, { useState } from 'react';
|
||||
import { useActivityFeedProvider } from '../ActivityFeedProvider/ActivityFeedProvider';
|
||||
import ActivityFeedActions from '../Shared/ActivityFeedActions';
|
||||
@ -81,6 +83,7 @@ const ActivityFeedCardV1 = ({
|
||||
<FeedCardHeaderV1
|
||||
about={!isPost ? feed.about : undefined}
|
||||
createdBy={post.from}
|
||||
isEntityFeed={isPost}
|
||||
timeStamp={post.postTs}
|
||||
/>
|
||||
</Col>
|
||||
@ -91,9 +94,7 @@ const ActivityFeedCardV1 = ({
|
||||
announcement={!isPost ? feed.announcement : undefined}
|
||||
isEditPost={isEditPost}
|
||||
message={post.message}
|
||||
reactions={post.reactions}
|
||||
onEditCancel={() => setIsEditPost(false)}
|
||||
onReactionUpdate={onReactionUpdate}
|
||||
onUpdate={onUpdate}
|
||||
/>
|
||||
</Col>
|
||||
@ -101,15 +102,9 @@ const ActivityFeedCardV1 = ({
|
||||
|
||||
{!showThread && !isPost && postLength > 0 && (
|
||||
<Row>
|
||||
<Col className="p-t-sm" span={24}>
|
||||
<div className="d-flex items-center">
|
||||
<div
|
||||
className="d-flex items-center thread-count cursor-pointer"
|
||||
onClick={showReplies}>
|
||||
<ThreadIcon width={18} />{' '}
|
||||
<span className="text-xs p-l-xss">{postLength}</span>
|
||||
</div>
|
||||
<div className="p-l-sm thread-users-profile-pic">
|
||||
<Col className="p-t-xs" span={24}>
|
||||
<div className="d-flex items-center gap-2 pl-8">
|
||||
<div className="thread-users-profile-pic">
|
||||
{repliedUniqueUsersList.map((user) => (
|
||||
<UserPopOverCard key={user} userName={user}>
|
||||
<span
|
||||
@ -125,6 +120,19 @@ const ActivityFeedCardV1 = ({
|
||||
</UserPopOverCard>
|
||||
))}
|
||||
</div>
|
||||
<div
|
||||
className="d-flex items-center thread-count cursor-pointer"
|
||||
onClick={showReplies}>
|
||||
<ThreadIcon width={20} />{' '}
|
||||
<span className="text-xs p-l-xss">{postLength}</span>
|
||||
</div>
|
||||
|
||||
{Boolean(post.reactions?.length) && (
|
||||
<Reactions
|
||||
reactions={post.reactions ?? []}
|
||||
onReactionSelect={onReactionUpdate ?? noop}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
|
@ -10,25 +10,15 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { ReactionOperation } from 'enums/reactions.enum';
|
||||
import {
|
||||
AnnouncementDetails,
|
||||
Reaction,
|
||||
ReactionType,
|
||||
} from 'generated/entity/feed/thread';
|
||||
import { AnnouncementDetails } from 'generated/entity/feed/thread';
|
||||
|
||||
export interface FeedCardBodyV1Props {
|
||||
isEditPost: boolean;
|
||||
className?: string;
|
||||
showSchedule?: boolean;
|
||||
showReactions?: boolean;
|
||||
announcement?: AnnouncementDetails;
|
||||
message: string;
|
||||
reactions?: Reaction[];
|
||||
isOpenInDrawer?: boolean;
|
||||
onUpdate?: (message: string) => void;
|
||||
onEditCancel?: () => void;
|
||||
onReactionUpdate?: (
|
||||
reaction: ReactionType,
|
||||
operation: ReactionOperation
|
||||
) => void;
|
||||
}
|
||||
|
@ -14,8 +14,7 @@ import { Button, Col, Row, Typography } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import ActivityFeedEditor from 'components/ActivityFeed/ActivityFeedEditor/ActivityFeedEditor';
|
||||
import RichTextEditorPreviewer from 'components/common/rich-text-editor/RichTextEditorPreviewer';
|
||||
import Reactions from 'components/Reactions/Reactions';
|
||||
import { isUndefined, noop } from 'lodash';
|
||||
import { isUndefined } from 'lodash';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { getFrontEndFormat, MarkdownToHTMLConverter } from 'utils/FeedUtils';
|
||||
@ -26,13 +25,10 @@ const FeedCardBodyV1 = ({
|
||||
isEditPost,
|
||||
className,
|
||||
showSchedule = true,
|
||||
showReactions = true,
|
||||
message,
|
||||
announcement,
|
||||
reactions = [],
|
||||
onUpdate,
|
||||
onEditCancel,
|
||||
onReactionUpdate,
|
||||
}: FeedCardBodyV1Props) => {
|
||||
const { t } = useTranslation();
|
||||
const [postMessage, setPostMessage] = useState<string>(message);
|
||||
@ -122,12 +118,6 @@ const FeedCardBodyV1 = ({
|
||||
feedBody
|
||||
)}
|
||||
</div>
|
||||
{showReactions && Boolean(reactions?.length) && (
|
||||
<Reactions
|
||||
reactions={reactions ?? []}
|
||||
onReactionSelect={onReactionUpdate ?? noop}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -19,15 +19,6 @@
|
||||
a {
|
||||
color: @link-color !important;
|
||||
}
|
||||
.feed-header-timestamp {
|
||||
font-size: 12px;
|
||||
color: @grey-2;
|
||||
}
|
||||
.feed-header-timestamp::before {
|
||||
content: '\2022';
|
||||
margin-right: 8px;
|
||||
margin-left: 4px;
|
||||
}
|
||||
.thread-author {
|
||||
font-weight: 600;
|
||||
}
|
||||
@ -46,3 +37,13 @@
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
.feed-header-timestamp {
|
||||
font-size: 12px;
|
||||
color: @grey-2;
|
||||
}
|
||||
.feed-header-timestamp::before {
|
||||
content: '\2022';
|
||||
margin-right: 8px;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
@ -58,15 +58,6 @@
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.feed-actions:hover {
|
||||
width: 130px;
|
||||
}
|
||||
|
||||
.feed-actions:hover .expand-button {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@ -78,11 +69,6 @@
|
||||
transition: opacity 0.3s, transform 0.3s;
|
||||
}
|
||||
|
||||
.feed-actions:hover .action-buttons {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.expand-button {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
@ -112,3 +98,17 @@
|
||||
margin-left: -12px;
|
||||
}
|
||||
}
|
||||
|
||||
.activity-feed-card-v1:hover {
|
||||
.feed-actions {
|
||||
width: 130px;
|
||||
.expand-button {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
.action-buttons {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -77,7 +77,11 @@ const ActivityFeedDrawer: FC<ActivityFeedDrawerProps> = ({
|
||||
<Loader />
|
||||
) : (
|
||||
<div id="feed-panel">
|
||||
<FeedPanelBodyV1 showThread feed={selectedThread as Thread} />
|
||||
<FeedPanelBodyV1
|
||||
isOpenInDrawer
|
||||
showThread
|
||||
feed={selectedThread as Thread}
|
||||
/>
|
||||
<ActivityFeedEditor
|
||||
buttonClass="tw-mr-4"
|
||||
className="tw-ml-5 tw-mr-2 tw-mb-2"
|
||||
|
@ -12,22 +12,25 @@
|
||||
*/
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { Post, Thread } from 'generated/entity/feed/thread';
|
||||
import { Post, Thread, ThreadType } from 'generated/entity/feed/thread';
|
||||
import React, { FC } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { getReplyText } from '../../../utils/FeedUtils';
|
||||
import ActivityFeedCardV1 from '../ActivityFeedCard/ActivityFeedCardV1';
|
||||
import TaskFeedCard from '../TaskFeedCard/TaskFeedCard.component';
|
||||
|
||||
interface FeedPanelBodyPropV1 {
|
||||
feed: Thread;
|
||||
className?: string;
|
||||
showThread?: boolean;
|
||||
isOpenInDrawer?: boolean;
|
||||
}
|
||||
|
||||
const FeedPanelBodyV1: FC<FeedPanelBodyPropV1> = ({
|
||||
feed,
|
||||
className,
|
||||
showThread = true,
|
||||
isOpenInDrawer = false,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const mainFeed = {
|
||||
@ -45,13 +48,24 @@ const FeedPanelBodyV1: FC<FeedPanelBodyPropV1> = ({
|
||||
'has-replies': showThread && postLength > 0,
|
||||
})}
|
||||
data-testid="message-container">
|
||||
<ActivityFeedCardV1
|
||||
feed={feed}
|
||||
isPost={false}
|
||||
key={feed.id}
|
||||
post={mainFeed}
|
||||
showThread={showThread}
|
||||
/>
|
||||
{feed.type === ThreadType.Task ? (
|
||||
<TaskFeedCard
|
||||
feed={feed}
|
||||
isOpenInDrawer={isOpenInDrawer}
|
||||
key={feed.id}
|
||||
post={mainFeed}
|
||||
showThread={showThread}
|
||||
/>
|
||||
) : (
|
||||
<ActivityFeedCardV1
|
||||
feed={feed}
|
||||
isPost={false}
|
||||
key={feed.id}
|
||||
post={mainFeed}
|
||||
showThread={showThread}
|
||||
/>
|
||||
)}
|
||||
|
||||
{showThread && postLength > 0 ? (
|
||||
<div className="feed-posts" data-testid="replies">
|
||||
<div className="d-flex">
|
||||
|
@ -119,6 +119,8 @@ const ActivityFeedActions = ({
|
||||
const editCheck = useMemo(() => {
|
||||
if (feed.type === ThreadType.Announcement && !isPost) {
|
||||
return false;
|
||||
} else if (feed.type === ThreadType.Task && !isPost) {
|
||||
return false;
|
||||
} else if (isAuthor || currentUser?.isAdmin) {
|
||||
return true;
|
||||
}
|
||||
|
@ -0,0 +1,218 @@
|
||||
/*
|
||||
* Copyright 2023 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 { Col, Row, Tooltip, Typography } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import AssigneeList from 'components/common/AssigneeList/AssigneeList';
|
||||
import EntityPopOverCard from 'components/common/PopOverCard/EntityPopOverCard';
|
||||
import UserPopOverCard from 'components/common/PopOverCard/UserPopOverCard';
|
||||
import ProfilePicture from 'components/common/ProfilePicture/ProfilePicture';
|
||||
import Reactions from 'components/Reactions/Reactions';
|
||||
import { ReactionOperation } from 'enums/reactions.enum';
|
||||
import { Post, ReactionType, Thread } from 'generated/entity/feed/thread';
|
||||
import { isUndefined, toString } from 'lodash';
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Link } from 'react-router-dom';
|
||||
import {
|
||||
entityDisplayName,
|
||||
getEntityFieldDisplay,
|
||||
getEntityFQN,
|
||||
getEntityType,
|
||||
prepareFeedLink,
|
||||
} from 'utils/FeedUtils';
|
||||
import { getTaskDetailPath } from 'utils/TasksUtils';
|
||||
import {
|
||||
getDateTimeFromMilliSeconds,
|
||||
getDayTimeByTimeStamp,
|
||||
} from 'utils/TimeUtils';
|
||||
import { useActivityFeedProvider } from '../ActivityFeedProvider/ActivityFeedProvider';
|
||||
import ActivityFeedActions from '../Shared/ActivityFeedActions';
|
||||
import './task-feed-card.less';
|
||||
import { ReactComponent as TaskOpenIcon } from '/assets/svg/ic-open-task.svg';
|
||||
import { ReactComponent as ThreadIcon } from '/assets/svg/thread.svg';
|
||||
|
||||
interface TaskFeedCardProps {
|
||||
post: Post;
|
||||
feed: Thread;
|
||||
className?: string;
|
||||
showThread?: boolean;
|
||||
isEntityFeed?: boolean;
|
||||
isOpenInDrawer?: boolean;
|
||||
}
|
||||
|
||||
const TaskFeedCard = ({
|
||||
post,
|
||||
feed,
|
||||
className = '',
|
||||
isEntityFeed = false,
|
||||
showThread = true,
|
||||
isOpenInDrawer = false,
|
||||
}: TaskFeedCardProps) => {
|
||||
const { t } = useTranslation();
|
||||
const timeStamp = feed.threadTs;
|
||||
const taskDetails = feed.task;
|
||||
const postLength = feed?.postsCount ?? 0;
|
||||
const entityType = getEntityType(feed.about) ?? '';
|
||||
const entityFQN = getEntityFQN(feed.about) ?? '';
|
||||
const entityCheck = !isUndefined(entityFQN) && !isUndefined(entityType);
|
||||
const [isEditPost, setIsEditPost] = useState(false);
|
||||
const repliedUsers = [...new Set((feed?.posts ?? []).map((f) => f.from))];
|
||||
const repliedUniqueUsersList = repliedUsers.slice(0, postLength >= 3 ? 2 : 1);
|
||||
|
||||
const { showDrawer, updateReactions } = useActivityFeedProvider();
|
||||
|
||||
const showReplies = () => {
|
||||
showDrawer?.(feed);
|
||||
};
|
||||
|
||||
const onEditPost = () => {
|
||||
setIsEditPost(!isEditPost);
|
||||
};
|
||||
|
||||
const onReactionUpdate = (
|
||||
reaction: ReactionType,
|
||||
operation: ReactionOperation
|
||||
) => {
|
||||
updateReactions(post, feed.id, true, reaction, operation);
|
||||
};
|
||||
|
||||
const getTaskLinkElement = entityCheck && (
|
||||
<Typography.Text>
|
||||
<Link
|
||||
data-testid="tasklink"
|
||||
to={getTaskDetailPath(toString(taskDetails?.id)).pathname}
|
||||
onClick={(e) => e.stopPropagation()}>
|
||||
<span>{`#${taskDetails?.id} `}</span>
|
||||
</Link>
|
||||
|
||||
<Typography.Text>{taskDetails?.type}</Typography.Text>
|
||||
<span className="m-x-xss">{t('label.for-lowercase')}</span>
|
||||
{isEntityFeed ? (
|
||||
<span className="tw-heading" data-testid="headerText-entityField">
|
||||
{getEntityFieldDisplay(feed.about)}
|
||||
</span>
|
||||
) : (
|
||||
<>
|
||||
<span className="p-r-xss">{entityType}</span>
|
||||
<EntityPopOverCard entityFQN={entityFQN} entityType={entityType}>
|
||||
<Link
|
||||
className="break-all"
|
||||
data-testid="entitylink"
|
||||
to={prepareFeedLink(entityType, entityFQN)}
|
||||
onClick={(e) => e.stopPropagation()}>
|
||||
{entityDisplayName(entityType, entityFQN)}
|
||||
</Link>
|
||||
</EntityPopOverCard>
|
||||
</>
|
||||
)}
|
||||
</Typography.Text>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={classNames(
|
||||
className,
|
||||
'task-feed-card-v1 activity-feed-card activity-feed-card-v1'
|
||||
)}>
|
||||
<Row gutter={[0, 8]}>
|
||||
<Col span={isOpenInDrawer ? 24 : 18}>
|
||||
<Row gutter={[0, 8]}>
|
||||
<Col className="d-flex items-center" span={24}>
|
||||
<TaskOpenIcon className="m-r-xs" width={14} />
|
||||
{getTaskLinkElement}
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Typography.Text className="task-feed-body text-grey-muted">
|
||||
<UserPopOverCard
|
||||
key={feed.createdBy}
|
||||
userName={feed.createdBy ?? ''}>
|
||||
<span className="p-r-xss">{feed.createdBy}</span>
|
||||
</UserPopOverCard>
|
||||
{t('message.created-this-task-lowercase')}
|
||||
{timeStamp && (
|
||||
<Tooltip title={getDateTimeFromMilliSeconds(timeStamp)}>
|
||||
<span className="p-l-xss" data-testid="timestamp">
|
||||
{getDayTimeByTimeStamp(timeStamp)}
|
||||
</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
<Col
|
||||
className={`d-flex items-center gap-2 ${
|
||||
!isOpenInDrawer ? 'justify-end' : 'ml-6'
|
||||
}`}
|
||||
span={isOpenInDrawer ? 24 : 6}>
|
||||
<Typography.Text>{t('label.assignee-plural')}</Typography.Text>
|
||||
<AssigneeList
|
||||
assignees={feed?.task?.assignees || []}
|
||||
className="d-flex gap-1"
|
||||
profilePicType="circle"
|
||||
profileWidth="24"
|
||||
showUserName={false}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
{!showThread && postLength > 0 && (
|
||||
<Row>
|
||||
<Col className="p-t-xs" span={24}>
|
||||
<div className="d-flex items-center p-l-lg gap-2">
|
||||
<div className="thread-users-profile-pic">
|
||||
{repliedUniqueUsersList.map((user) => (
|
||||
<UserPopOverCard key={user} userName={user}>
|
||||
<span
|
||||
className="profile-image-span cursor-pointer"
|
||||
data-testid="authorAvatar">
|
||||
<ProfilePicture
|
||||
id=""
|
||||
name={user}
|
||||
type="circle"
|
||||
width="24"
|
||||
/>
|
||||
</span>
|
||||
</UserPopOverCard>
|
||||
))}
|
||||
</div>
|
||||
<div
|
||||
className="d-flex items-center thread-count cursor-pointer"
|
||||
onClick={showReplies}>
|
||||
<ThreadIcon width={20} />{' '}
|
||||
<span className="text-xs p-l-xss">{postLength}</span>
|
||||
</div>
|
||||
{Boolean(feed.reactions?.length) && (
|
||||
<Reactions
|
||||
reactions={feed.reactions ?? []}
|
||||
onReactionSelect={onReactionUpdate}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
|
||||
<ActivityFeedActions
|
||||
feed={feed}
|
||||
isPost={false}
|
||||
post={post}
|
||||
onEditPost={onEditPost}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default TaskFeedCard;
|
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2023 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.
|
||||
*/
|
||||
|
||||
.task-feed-card-v1 {
|
||||
.task-feed-body {
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
.assignee-item {
|
||||
margin: 0;
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
@ -12,7 +12,9 @@
|
||||
*/
|
||||
|
||||
import { Card, Col, Row, Typography } from 'antd';
|
||||
import { ReactComponent as KPIIcon } from 'assets/svg/ic-kpi.svg';
|
||||
import { AxiosError } from 'axios';
|
||||
import { DATA_INSIGHT_DOCS } from 'constants/docs.constants';
|
||||
import { isEmpty, isUndefined } from 'lodash';
|
||||
import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@ -26,6 +28,7 @@ import {
|
||||
YAxis,
|
||||
} from 'recharts';
|
||||
import { getLatestKpiResult, getListKpiResult } from 'rest/KpiAPI';
|
||||
import { Transi18next } from 'utils/CommonUtils';
|
||||
import {
|
||||
getCurrentDateTimeMillis,
|
||||
getPastDaysDateTimeMillis,
|
||||
@ -36,7 +39,6 @@ import { Kpi, KpiResult } from '../../generated/dataInsight/kpi/kpi';
|
||||
import { UIKpiResult } from '../../interface/data-insight.interface';
|
||||
import { CustomTooltip, getKpiGraphData } from '../../utils/DataInsightUtils';
|
||||
import { showErrorToast } from '../../utils/ToastUtils';
|
||||
import { EmptyGraphPlaceholder } from './EmptyGraphPlaceholder';
|
||||
import KPILatestResultsV1 from './KPILatestResultsV1';
|
||||
|
||||
interface Props {
|
||||
@ -44,6 +46,39 @@ interface Props {
|
||||
selectedDays: number;
|
||||
}
|
||||
|
||||
const EmptyPlaceholder = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="d-flex items-center flex-col p-sm">
|
||||
<KPIIcon width={100} />
|
||||
<div className="m-t-xs text-center">
|
||||
<Typography.Paragraph style={{ marginBottom: '0' }}>
|
||||
{t('message.adding-new-entity-is-easy-just-give-it-a-spin', {
|
||||
entity: t('label.data-insight'),
|
||||
})}
|
||||
</Typography.Paragraph>
|
||||
<Typography.Paragraph>
|
||||
<Transi18next
|
||||
i18nKey="message.refer-to-our-doc"
|
||||
renderElement={
|
||||
<a
|
||||
href={DATA_INSIGHT_DOCS}
|
||||
rel="noreferrer"
|
||||
style={{ color: '#1890ff' }}
|
||||
target="_blank"
|
||||
/>
|
||||
}
|
||||
values={{
|
||||
doc: t('label.doc-plural-lowercase'),
|
||||
}}
|
||||
/>
|
||||
</Typography.Paragraph>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const KPIChartV1: FC<Props> = ({ kpiList, selectedDays }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@ -204,12 +239,12 @@ const KPIChartV1: FC<Props> = ({ kpiList, selectedDays }) => {
|
||||
</>
|
||||
) : (
|
||||
<Col className="justify-center" span={24}>
|
||||
<EmptyGraphPlaceholder />
|
||||
<EmptyPlaceholder />
|
||||
</Col>
|
||||
)}
|
||||
</Row>
|
||||
) : (
|
||||
<EmptyGraphPlaceholder />
|
||||
<EmptyPlaceholder />
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
|
@ -50,7 +50,7 @@ export const EntityHeader = ({
|
||||
<div className="w-full">
|
||||
<div
|
||||
className={classNames(
|
||||
'glossary-breadcrumb',
|
||||
'entity-breadcrumb',
|
||||
gutter === 'large' ? 'm-b-sm' : 'm-b-xss'
|
||||
)}
|
||||
data-testid="category-name">
|
||||
|
@ -54,52 +54,56 @@ const RightSidebar = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="right-panel-heading p-md p-b-xss">
|
||||
<Typography.Paragraph className="m-b-sm">
|
||||
{t('label.recent-announcement-plural')}
|
||||
</Typography.Paragraph>
|
||||
<div className="announcement-container-list">
|
||||
{announcements.map((item) => {
|
||||
return (
|
||||
<Alert
|
||||
className="m-b-xs right-panel-announcement"
|
||||
description={
|
||||
<>
|
||||
<FeedCardHeaderV1
|
||||
about={item.about}
|
||||
className="d-inline"
|
||||
createdBy={item.createdBy}
|
||||
showUserAvatar={false}
|
||||
timeStamp={item.threadTs}
|
||||
/>
|
||||
<FeedCardBodyV1
|
||||
announcement={item.announcement}
|
||||
className="p-t-xs"
|
||||
isEditPost={false}
|
||||
message={item.message}
|
||||
reactions={item.reactions}
|
||||
showReactions={false}
|
||||
showSchedule={false}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
key={item.id}
|
||||
message={
|
||||
<div className="d-flex announcement-alert-heading">
|
||||
<AnnouncementIcon width={20} />
|
||||
<span className="text-sm p-l-xss">
|
||||
{t('label.announcement')}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
type="info"
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
{announcements.length > 0 && (
|
||||
<>
|
||||
<div className="right-panel-heading p-md p-b-xss">
|
||||
<Typography.Paragraph className="m-b-sm">
|
||||
{t('label.recent-announcement-plural')}
|
||||
</Typography.Paragraph>
|
||||
<div className="announcement-container-list">
|
||||
{announcements.map((item) => {
|
||||
return (
|
||||
<Alert
|
||||
className="m-b-xs right-panel-announcement"
|
||||
description={
|
||||
<>
|
||||
<FeedCardHeaderV1
|
||||
about={item.about}
|
||||
className="d-inline"
|
||||
createdBy={item.createdBy}
|
||||
showUserAvatar={false}
|
||||
timeStamp={item.threadTs}
|
||||
/>
|
||||
<FeedCardBodyV1
|
||||
isOpenInDrawer
|
||||
announcement={item.announcement}
|
||||
className="p-t-xs"
|
||||
isEditPost={false}
|
||||
message={item.message}
|
||||
showSchedule={false}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
key={item.id}
|
||||
message={
|
||||
<div className="d-flex announcement-alert-heading">
|
||||
<AnnouncementIcon width={20} />
|
||||
<span className="text-sm p-l-xss">
|
||||
{t('label.announcement')}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
type="info"
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Divider className="m-0" />
|
||||
</>
|
||||
)}
|
||||
|
||||
<Divider className="m-0" />
|
||||
<div className="p-md" data-testid="following-data-container">
|
||||
<EntityListWithV1
|
||||
entityList={followedData}
|
||||
|
@ -14,4 +14,7 @@
|
||||
max-height: 360px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
.feed-card-body {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
@ -103,34 +103,32 @@ const Reactions: FC<ReactionsProps> = ({ reactions, onReactionSelect }) => {
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="tw-mt-2">
|
||||
<div className="d-flex">
|
||||
{emojis}
|
||||
<Popover
|
||||
align={{ targetOffset: [0, -10] }}
|
||||
content={reactionList}
|
||||
open={visible}
|
||||
overlayClassName="ant-popover-feed-reactions"
|
||||
placement="topLeft"
|
||||
trigger="click"
|
||||
zIndex={9999}
|
||||
onOpenChange={handleVisibleChange}>
|
||||
<Button
|
||||
className="ant-btn-reaction ant-btn-add-reactions"
|
||||
data-testid="add-reactions"
|
||||
shape="round"
|
||||
onClick={(e) => e.stopPropagation()}>
|
||||
<SVGIcons
|
||||
alt="add-reaction"
|
||||
icon={Icons.ADD_REACTION}
|
||||
title={t('label.add-entity', {
|
||||
entity: t('label.reaction-lowercase-plural'),
|
||||
})}
|
||||
width="18px"
|
||||
/>
|
||||
</Button>
|
||||
</Popover>
|
||||
</div>
|
||||
<div className="d-flex">
|
||||
{emojis}
|
||||
<Popover
|
||||
align={{ targetOffset: [0, -10] }}
|
||||
content={reactionList}
|
||||
open={visible}
|
||||
overlayClassName="ant-popover-feed-reactions"
|
||||
placement="topLeft"
|
||||
trigger="click"
|
||||
zIndex={9999}
|
||||
onOpenChange={handleVisibleChange}>
|
||||
<Button
|
||||
className="ant-btn-reaction ant-btn-add-reactions"
|
||||
data-testid="add-reactions"
|
||||
shape="round"
|
||||
onClick={(e) => e.stopPropagation()}>
|
||||
<SVGIcons
|
||||
alt="add-reaction"
|
||||
icon={Icons.ADD_REACTION}
|
||||
title={t('label.add-entity', {
|
||||
entity: t('label.reaction-lowercase-plural'),
|
||||
})}
|
||||
width="16px"
|
||||
/>
|
||||
</Button>
|
||||
</Popover>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -11,25 +11,35 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Tabs } from 'antd';
|
||||
import AppState from 'AppState';
|
||||
import ActivityFeedListV1 from 'components/ActivityFeed/ActivityFeedList/ActivityFeedListV1.component';
|
||||
import { useActivityFeedProvider } from 'components/ActivityFeed/ActivityFeedProvider/ActivityFeedProvider';
|
||||
import { FeedFilter } from 'enums/mydata.enum';
|
||||
import { ThreadType } from 'generated/entity/feed/thread';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { getFeedsWithFilter } from 'rest/feedsAPI';
|
||||
import { showErrorToast } from 'utils/ToastUtils';
|
||||
import './feeds-widget.less';
|
||||
|
||||
const FeedsWidget = () => {
|
||||
const { t } = useTranslation();
|
||||
const [activeTab, setActiveTab] = useState('all');
|
||||
const { loading, entityThread, getFeedData } = useActivityFeedProvider();
|
||||
const [taskCount, setTaskCount] = useState(0);
|
||||
const currentUser = useMemo(
|
||||
() => AppState.getCurrentUserDetails(),
|
||||
[AppState.userDetails, AppState.nonSecureUserDetails]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeTab === 'all') {
|
||||
getFeedData(FeedFilter.OWNER).catch(() => {
|
||||
// ignore since error is displayed in toast in the parent promise.
|
||||
// Added block for sonar code smell
|
||||
});
|
||||
getFeedData(FeedFilter.OWNER, undefined, ThreadType.Conversation).catch(
|
||||
() => {
|
||||
// ignore since error is displayed in toast in the parent promise.
|
||||
// Added block for sonar code smell
|
||||
}
|
||||
);
|
||||
} else if (activeTab === 'mentions') {
|
||||
getFeedData(FeedFilter.MENTIONS).catch(() => {
|
||||
// ignore since error is displayed in toast in the parent promise.
|
||||
@ -43,6 +53,21 @@ const FeedsWidget = () => {
|
||||
}
|
||||
}, [activeTab]);
|
||||
|
||||
useEffect(() => {
|
||||
getFeedsWithFilter(
|
||||
currentUser?.id,
|
||||
FeedFilter.OWNER,
|
||||
undefined,
|
||||
ThreadType.Task
|
||||
)
|
||||
.then((res) => {
|
||||
setTaskCount(res.data.length);
|
||||
})
|
||||
.catch((err) => {
|
||||
showErrorToast(err);
|
||||
});
|
||||
}, [currentUser]);
|
||||
|
||||
return (
|
||||
<div className="feeds-widget-container">
|
||||
<Tabs
|
||||
@ -70,7 +95,7 @@ const FeedsWidget = () => {
|
||||
),
|
||||
},
|
||||
{
|
||||
label: t('label.task-plural'),
|
||||
label: `${t('label.task-plural')} (${taskCount})`,
|
||||
key: 'tasks',
|
||||
children: (
|
||||
<ActivityFeedListV1
|
||||
|
@ -40,3 +40,6 @@
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
.feed-card-body {
|
||||
padding-left: 32px;
|
||||
}
|
||||
|
@ -12,6 +12,7 @@
|
||||
*/
|
||||
|
||||
import { uniqueId } from 'lodash';
|
||||
import { ImageShape } from 'Models';
|
||||
import React, { FC, HTMLAttributes } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { getOwnerValue } from 'utils/CommonUtils';
|
||||
@ -21,9 +22,18 @@ import ProfilePicture from '../ProfilePicture/ProfilePicture';
|
||||
|
||||
interface Props extends HTMLAttributes<HTMLDivElement> {
|
||||
assignees: EntityReference[];
|
||||
profilePicType?: ImageShape;
|
||||
showUserName?: boolean;
|
||||
profileWidth?: string;
|
||||
}
|
||||
|
||||
const AssigneeList: FC<Props> = ({ assignees, className }) => {
|
||||
const AssigneeList: FC<Props> = ({
|
||||
assignees,
|
||||
className,
|
||||
profilePicType = 'square',
|
||||
showUserName = true,
|
||||
profileWidth = '20',
|
||||
}) => {
|
||||
const history = useHistory();
|
||||
|
||||
const handleClick = (e: React.MouseEvent, assignee: EntityReference) => {
|
||||
@ -40,11 +50,18 @@ const AssigneeList: FC<Props> = ({ assignees, className }) => {
|
||||
type={assignee.type}
|
||||
userName={assignee.name || ''}>
|
||||
<span
|
||||
className="d-flex tw-m-1.5 tw-mt-0 tw-cursor-pointer"
|
||||
className="assignee-item d-flex m-xss m-t-0 cursor-pointer"
|
||||
data-testid="assignee"
|
||||
onClick={(e) => handleClick(e, assignee)}>
|
||||
<ProfilePicture id="" name={assignee.name || ''} width="20" />
|
||||
<span className="tw-ml-1">{assignee.name || ''}</span>
|
||||
<ProfilePicture
|
||||
id=""
|
||||
name={assignee.name ?? ''}
|
||||
type={profilePicType}
|
||||
width={profileWidth}
|
||||
/>
|
||||
{showUserName && (
|
||||
<span className="m-l-xs">{assignee.name ?? ''}</span>
|
||||
)}
|
||||
</span>
|
||||
</UserPopOverCard>
|
||||
))}
|
||||
|
@ -11,64 +11,66 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Button, Divider, Popover, Space, Typography } from 'antd';
|
||||
import { AxiosError } from 'axios';
|
||||
import { Popover } from 'antd';
|
||||
import { EntityUnion } from 'components/Explore/explore.interface';
|
||||
import { get, uniqueId } from 'lodash';
|
||||
import { EntityTags } from 'Models';
|
||||
import React, { FC, HTMLAttributes, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Link } from 'react-router-dom';
|
||||
import ExploreSearchCard from 'components/ExploreV1/ExploreSearchCard/ExploreSearchCard';
|
||||
import React, {
|
||||
FC,
|
||||
HTMLAttributes,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { getDashboardByFqn } from 'rest/dashboardAPI';
|
||||
import {
|
||||
getDatabaseDetailsByFQN,
|
||||
getDatabaseSchemaDetailsByFQN,
|
||||
} from 'rest/databaseAPI';
|
||||
import { getGlossaryTermByFQN } from 'rest/glossaryAPI';
|
||||
import { getMlModelByFQN } from 'rest/mlModelAPI';
|
||||
import { getPipelineByFqn } from 'rest/pipelineAPI';
|
||||
import { getTableDetailsByFQN } from 'rest/tableAPI';
|
||||
import { getTopicByFqn } from 'rest/topicsAPI';
|
||||
import { getEntityName } from 'utils/EntityUtils';
|
||||
import AppState from '../../../AppState';
|
||||
import { FQN_SEPARATOR_CHAR } from '../../../constants/char.constants';
|
||||
import { EntityType } from '../../../enums/entity.enum';
|
||||
import { Table } from '../../../generated/entity/data/table';
|
||||
import { TagSource } from '../../../generated/type/tagLabel';
|
||||
import SVGIcons from '../../../utils/SvgUtils';
|
||||
import {
|
||||
getEntityLink,
|
||||
getTagsWithoutTier,
|
||||
getTierTags,
|
||||
} from '../../../utils/TableUtils';
|
||||
import { showErrorToast } from '../../../utils/ToastUtils';
|
||||
import ProfilePicture from '../ProfilePicture/ProfilePicture';
|
||||
import RichTextEditorPreviewer from '../rich-text-editor/RichTextEditorPreviewer';
|
||||
import './popover-card.less';
|
||||
|
||||
interface Props extends HTMLAttributes<HTMLDivElement> {
|
||||
entityType: string;
|
||||
entityFQN: string;
|
||||
}
|
||||
|
||||
const PopoverContent: React.FC<{
|
||||
entityData: EntityUnion;
|
||||
entityFQN: string;
|
||||
entityType: string;
|
||||
}> = ({ entityData, entityFQN, entityType }) => {
|
||||
const name = entityData.name;
|
||||
const displayName = getEntityName(entityData);
|
||||
|
||||
return (
|
||||
<ExploreSearchCard
|
||||
id="tabledatacard"
|
||||
source={{
|
||||
name,
|
||||
displayName,
|
||||
id: entityData.id ?? '',
|
||||
description: entityData.description ?? '',
|
||||
fullyQualifiedName: entityFQN,
|
||||
tags: (entityData as Table).tags,
|
||||
entityType: entityType,
|
||||
serviceType: (entityData as Table).serviceType,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const EntityPopOverCard: FC<Props> = ({ children, entityType, entityFQN }) => {
|
||||
const { t } = useTranslation();
|
||||
const [entityData, setEntityData] = useState<EntityUnion>({} as EntityUnion);
|
||||
|
||||
const entityTier = useMemo(() => {
|
||||
const tierFQN = getTierTags((entityData as Table).tags || [])?.tagFQN;
|
||||
|
||||
return tierFQN?.split(FQN_SEPARATOR_CHAR)[1];
|
||||
}, [(entityData as Table).tags]);
|
||||
|
||||
const entityTags = useMemo(() => {
|
||||
const tags: EntityTags[] =
|
||||
getTagsWithoutTier((entityData as Table).tags || []) || [];
|
||||
|
||||
return tags.map((tag) =>
|
||||
tag.source === TagSource.Glossary ? tag.tagFQN : `#${tag.tagFQN}`
|
||||
);
|
||||
}, [(entityData as Table).tags]);
|
||||
|
||||
const getData = () => {
|
||||
const getData = useCallback(() => {
|
||||
const setEntityDetails = (entityDetail: EntityUnion) => {
|
||||
AppState.entityData[entityFQN] = entityDetail;
|
||||
};
|
||||
@ -105,6 +107,10 @@ const EntityPopOverCard: FC<Props> = ({ children, entityType, entityFQN }) => {
|
||||
case EntityType.DATABASE_SCHEMA:
|
||||
promise = getDatabaseSchemaDetailsByFQN(entityFQN, 'owner');
|
||||
|
||||
break;
|
||||
case EntityType.GLOSSARY_TERM:
|
||||
promise = getGlossaryTermByFQN(entityFQN, 'owner');
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -118,23 +124,11 @@ const EntityPopOverCard: FC<Props> = ({ children, entityType, entityFQN }) => {
|
||||
|
||||
setEntityData(res);
|
||||
})
|
||||
.catch((err: AxiosError) => showErrorToast(err));
|
||||
.catch(() => {
|
||||
// do nothing
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const PopoverTitle = () => {
|
||||
return (
|
||||
<Link data-testid="entitylink" to={getEntityLink(entityType, entityFQN)}>
|
||||
<Button
|
||||
className="p-0"
|
||||
disabled={AppState.isTourOpen}
|
||||
type="link"
|
||||
onClick={(e) => e.stopPropagation()}>
|
||||
<span>{entityFQN}</span>
|
||||
</Button>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
}, [entityType, entityFQN]);
|
||||
|
||||
const onMouseOver = () => {
|
||||
const entitydetails = AppState.entityData[entityFQN];
|
||||
@ -145,95 +139,22 @@ const EntityPopOverCard: FC<Props> = ({ children, entityType, entityFQN }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const ownerName = useMemo(() => {
|
||||
return get(entityData, 'owner');
|
||||
}, [entityData]);
|
||||
|
||||
const PopoverContent = () => {
|
||||
useEffect(() => {
|
||||
onMouseOver();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="w-500">
|
||||
<Space align="center" size="small">
|
||||
<div data-testid="owner">
|
||||
{ownerName ? (
|
||||
<Space align="center" size="small">
|
||||
<ProfilePicture
|
||||
displayName={getEntityName(ownerName)}
|
||||
id={entityData.name}
|
||||
name={getEntityName(ownerName)}
|
||||
width="20"
|
||||
/>
|
||||
<Typography.Text className="text-xs">
|
||||
{getEntityName(ownerName)}
|
||||
</Typography.Text>
|
||||
</Space>
|
||||
) : (
|
||||
<Typography.Text className="text-xs text-grey-muted">
|
||||
{t('label.no-entity', {
|
||||
entity: t('label.owner'),
|
||||
})}
|
||||
</Typography.Text>
|
||||
)}
|
||||
</div>
|
||||
<span className="text-grey-muted">|</span>
|
||||
<Typography.Text
|
||||
className="text-xs text-grey-muted"
|
||||
data-testid="tier">
|
||||
{entityTier
|
||||
? entityTier
|
||||
: t('label.no-entity', {
|
||||
entity: t('label.tier'),
|
||||
})}
|
||||
</Typography.Text>
|
||||
</Space>
|
||||
|
||||
<div className="description-text m-t-sm" data-testid="description-text">
|
||||
{entityData.description ? (
|
||||
<RichTextEditorPreviewer
|
||||
enableSeeMoreVariant={false}
|
||||
markdown={entityData.description}
|
||||
/>
|
||||
) : (
|
||||
<Typography.Text className="text-xs text-grey-muted">
|
||||
{t('label.no-entity', {
|
||||
entity: t('label.description'),
|
||||
})}
|
||||
</Typography.Text>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{entityTags.length ? (
|
||||
<>
|
||||
<Divider className="m-b-xs m-t-sm" />
|
||||
<div className="d-flex flex-start">
|
||||
<span className="w-5 m-r-xs">
|
||||
<SVGIcons alt="icon-tag" icon="icon-tag-grey" width="14" />
|
||||
</span>
|
||||
|
||||
<Space wrap align="center" size={[16, 0]}>
|
||||
{entityTags.map((tag) => (
|
||||
<span className="text-xs font-medium" key={uniqueId()}>
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
</Space>
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
useEffect(() => {
|
||||
onMouseOver();
|
||||
}, [getData, entityFQN]);
|
||||
|
||||
return (
|
||||
<Popover
|
||||
destroyTooltipOnHide
|
||||
align={{ targetOffset: [0, -10] }}
|
||||
content={<PopoverContent />}
|
||||
overlayClassName="ant-popover-card"
|
||||
title={<PopoverTitle />}
|
||||
content={
|
||||
<PopoverContent
|
||||
entityData={entityData}
|
||||
entityFQN={entityFQN}
|
||||
entityType={entityType}
|
||||
/>
|
||||
}
|
||||
overlayClassName="entity-popover-card"
|
||||
trigger="hover"
|
||||
zIndex={9999}>
|
||||
{children}
|
||||
|
@ -68,15 +68,15 @@ const UserPopOverCard: FC<Props> = ({ children, userName, type = 'user' }) => {
|
||||
const teams = getNonDeletedTeams(userData.teams ?? []);
|
||||
|
||||
return teams?.length ? (
|
||||
<p className="tw-mt-2">
|
||||
<SVGIcons alt="icon" className="tw-w-4" icon={Icons.TEAMS_GREY} />
|
||||
<span className="tw-mr-2 tw-ml-1 tw-align-middle tw-font-medium">
|
||||
<p className="m-t-xs">
|
||||
<SVGIcons alt="icon" className="w-4" icon={Icons.TEAMS_GREY} />
|
||||
<span className="m-r-xs m-l-xss align-middle font-medium">
|
||||
{t('label.team-plural')}
|
||||
</span>
|
||||
<span className="d-flex flex-wrap tw-mt-1">
|
||||
<span className="d-flex flex-wrap m-t-xss">
|
||||
{teams.map((team, i) => (
|
||||
<span
|
||||
className="tw-bg-gray-200 tw-rounded tw-px-1 tw-text-grey-body tw-m-0.5 tw-text-xs"
|
||||
className="bg-grey rounded-4 p-x-xs text-grey-body text-xs"
|
||||
key={i}>
|
||||
{team?.displayName ?? team?.name}
|
||||
</span>
|
||||
@ -91,21 +91,19 @@ const UserPopOverCard: FC<Props> = ({ children, userName, type = 'user' }) => {
|
||||
const isAdmin = userData?.isAdmin;
|
||||
|
||||
return roles?.length ? (
|
||||
<p className="tw-mt-2">
|
||||
<SVGIcons alt="icon" className="tw-w-4" icon={Icons.USERS} />
|
||||
<span className="tw-mr-2 tw-ml-1 tw-align-middle tw-font-medium">
|
||||
<p className="m-t-xs">
|
||||
<SVGIcons alt="icon" className="w-4" icon={Icons.USERS} />
|
||||
<span className="m-r-xs m-l-xss align-middle font-medium">
|
||||
{t('label.role-plural')}
|
||||
</span>
|
||||
<span className="d-flex flex-wrap tw-mt-1">
|
||||
<span className="d-flex flex-wrap m-t-xss">
|
||||
{isAdmin && (
|
||||
<span className="tw-bg-gray-200 tw-rounded tw-px-1 tw-text-grey-body tw-m-0.5 tw-text-xs">
|
||||
<span className="bg-grey rounded-4 p-x-xs text-xs">
|
||||
{TERM_ADMIN}
|
||||
</span>
|
||||
)}
|
||||
{roles.map((role, i) => (
|
||||
<span
|
||||
className="tw-bg-gray-200 tw-rounded tw-px-1 tw-text-grey-body tw-m-0.5 tw-text-xs"
|
||||
key={i}>
|
||||
<span className="bg-grey rounded-4 p-x-xs text-xs" key={i}>
|
||||
{role?.displayName ?? role?.name}
|
||||
</span>
|
||||
))}
|
||||
@ -120,17 +118,17 @@ const UserPopOverCard: FC<Props> = ({ children, userName, type = 'user' }) => {
|
||||
|
||||
return (
|
||||
<div className="d-flex">
|
||||
<div className="tw-mr-2">
|
||||
<div className="m-r-xs">
|
||||
<ProfilePicture id="" name={userName} width="24" />
|
||||
</div>
|
||||
<div className="tw-self-center">
|
||||
<div className="self-center">
|
||||
<button
|
||||
className="tw-text-info"
|
||||
className="text-info"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onTitleClickHandler(getUserPath(name));
|
||||
}}>
|
||||
<span className="tw-font-medium tw-mr-2">{displayName}</span>
|
||||
<span className="font-medium m-r-xs">{displayName}</span>
|
||||
</button>
|
||||
{displayName !== name ? (
|
||||
<span className="text-grey-muted">{name}</span>
|
||||
@ -151,7 +149,7 @@ const UserPopOverCard: FC<Props> = ({ children, userName, type = 'user' }) => {
|
||||
{isLoading ? (
|
||||
<Loader size="small" />
|
||||
) : (
|
||||
<div className="tw-w-80">
|
||||
<div className="w-40">
|
||||
{isEmpty(userData) ? (
|
||||
<span>{t('message.no-data-available')}</span>
|
||||
) : (
|
||||
@ -168,7 +166,6 @@ const UserPopOverCard: FC<Props> = ({ children, userName, type = 'user' }) => {
|
||||
|
||||
return (
|
||||
<Popover
|
||||
destroyTooltipOnHide
|
||||
align={{ targetOffset: [0, -10] }}
|
||||
content={<PopoverContent />}
|
||||
overlayClassName="ant-popover-card"
|
||||
|
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2023 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.
|
||||
*/
|
||||
.entity-popover-card {
|
||||
max-width: 500px;
|
||||
min-width: 300px;
|
||||
.explore-search-card {
|
||||
padding: 0;
|
||||
}
|
||||
.entity-breadcrumb {
|
||||
display: none;
|
||||
}
|
||||
}
|
@ -24,6 +24,9 @@
|
||||
.m-0 {
|
||||
margin: 0 !important;
|
||||
}
|
||||
.m-xss {
|
||||
margin: @margin-xss;
|
||||
}
|
||||
.m-xs {
|
||||
margin: @margin-xs;
|
||||
}
|
||||
@ -321,6 +324,10 @@
|
||||
padding-left: 0px;
|
||||
}
|
||||
|
||||
.pl-8 {
|
||||
padding-left: 2rem;
|
||||
}
|
||||
|
||||
.pt-8 {
|
||||
padding-top: 2rem;
|
||||
}
|
||||
@ -413,6 +420,9 @@
|
||||
.p-r-0 {
|
||||
padding-right: 0 !important;
|
||||
}
|
||||
.p-r-xss {
|
||||
padding-right: @padding-xss;
|
||||
}
|
||||
.p-r-xs {
|
||||
padding-right: @padding-xs;
|
||||
}
|
||||
|
@ -984,6 +984,9 @@ export const getEntityBreadcrumbs = (
|
||||
case EntityType.GLOSSARY_TERM:
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
const glossary = (entity as GlossaryTerm).glossary;
|
||||
if (!glossary) {
|
||||
return [];
|
||||
}
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
const fqnList = Fqn.split((entity as GlossaryTerm).fullyQualifiedName);
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
|
Loading…
x
Reference in New Issue
Block a user