Refactor : Refactor Table Entity Conversation and Task Flow Logic #5734 (#5736)

*  Refactor : Refactor Conversation and Task Flow Logic  #5734

* Fix emoji multiple API call issue

* Fix table query editor issue

* Addressing review comments
This commit is contained in:
Sachin Chaurasiya 2022-06-29 17:32:29 +05:30 committed by GitHub
parent e3223f6a20
commit 38b04b76a9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 409 additions and 206 deletions

View File

@ -0,0 +1,8 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.2778 2.16615H9.95472V1.57424C9.95472 1.38273 9.74582 1.29569 9.55432 1.29569H8.52718C8.28346 0.599316 7.67412 0.251127 6.97774 0.251127C6.28903 0.225235 5.66259 0.647534 5.4283 1.29569H4.41856C4.22705 1.29569 4.03554 1.38273 4.03554 1.57424V2.16615H2.7124C1.92832 2.17451 1.28685 2.79305 1.25 3.5763V12.4269C1.25 13.1929 1.94638 13.75 2.7124 13.75H11.2778C12.0439 13.75 12.7402 13.1929 12.7402 12.4269V3.57633C12.7034 2.79305 12.0619 2.17451 11.2778 2.16615ZM4.73189 1.99207H5.68942C5.85656 1.97168 5.99082 1.84452 6.02021 1.67871C6.12331 1.22972 6.51722 0.907418 6.97774 0.895289C7.43399 0.909118 7.82197 1.23241 7.91784 1.67871C7.94906 1.85025 8.09218 1.97904 8.26603 1.99207H9.25838V3.38483H4.73189V1.99207ZM12.0439 12.4269C12.0439 12.8099 11.6608 13.0537 11.2778 13.0537H2.7124C2.32939 13.0537 1.94638 12.8099 1.94638 12.4269V3.57633C1.9819 3.17766 2.31219 2.86986 2.7124 2.86256H4.03551V3.75045C4.05391 3.94552 4.22285 4.09144 4.41852 4.08124H9.55428C9.75355 4.09214 9.9278 3.94822 9.95469 3.75045V2.86253H11.2778C11.678 2.86986 12.0083 3.17763 12.0438 3.5763L12.0439 12.4269Z" fill="#7147E8" stroke="#7147E8" stroke-width="0.2"/>
<path d="M5.66293 9.52502C5.55676 9.4131 5.3805 9.40679 5.2666 9.51088L4.36072 10.3743L3.97856 9.97795C3.87239 9.86603 3.69613 9.85974 3.58223 9.9638C3.47259 10.0787 3.47259 10.2594 3.58223 10.3743L4.16254 10.9688C4.21277 11.025 4.28532 11.0561 4.3607 11.0537C4.43536 11.0526 4.50659 11.0221 4.55885 10.9688L5.66288 9.92135C5.77233 9.82095 5.77964 9.65081 5.67921 9.54139C5.67407 9.53567 5.66862 9.53022 5.66293 9.52502Z" fill="#7147E8" stroke="#7147E8" stroke-width="0.2"/>
<path d="M10.2427 9.99927H7.36768C7.2296 9.99927 7.11768 10.1671 7.11768 10.3742C7.11768 10.5813 7.2296 10.7492 7.36768 10.7492H10.2427C10.3808 10.7492 10.4927 10.5813 10.4927 10.3742C10.4927 10.1671 10.3808 9.99927 10.2427 9.99927Z" fill="#7147E8" stroke="#7147E8" stroke-width="0.2"/>
<path d="M5.66293 6.52575C5.55676 6.41384 5.3805 6.40752 5.2666 6.51161L4.36072 7.37501L3.97856 6.97868C3.87239 6.86676 3.69613 6.86045 3.58223 6.96454C3.47259 7.07941 3.47259 7.26014 3.58223 7.37501L4.16254 7.96949C4.21277 8.02574 4.28532 8.05684 4.3607 8.05443C4.43536 8.05337 4.50659 8.02284 4.55885 7.96949L5.66288 6.92209C5.77233 6.82168 5.77964 6.65154 5.67921 6.54212C5.67407 6.5364 5.66862 6.53096 5.66293 6.52575Z" fill="#7147E8" stroke="#7147E8" stroke-width="0.2"/>
<path d="M10.2427 7H7.36768C7.2296 7 7.11768 7.16788 7.11768 7.37497C7.11768 7.58207 7.2296 7.74995 7.36768 7.74995H10.2427C10.3808 7.74995 10.4927 7.58207 10.4927 7.37497C10.4927 7.16788 10.3808 7 10.2427 7Z" fill="#7147E8" stroke="#7147E8" stroke-width="0.2"/>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -66,11 +66,13 @@ export const getFeedsWithFilter: Function = (
}; };
export const getFeedCount: Function = ( export const getFeedCount: Function = (
entityLink?: string entityLink?: string,
type?: ThreadType
): Promise<AxiosResponse> => { ): Promise<AxiosResponse> => {
return APIClient.get(`/feed/count`, { return APIClient.get(`/feed/count`, {
params: { params: {
entityLink: entityLink, entityLink: entityLink,
type,
}, },
}); });
}; };

View File

@ -20,6 +20,7 @@ import { ConfirmState } from '../ActivityFeedCard/ActivityFeedCard.interface';
export interface ActivityThreadPanelProp export interface ActivityThreadPanelProp
extends HTMLAttributes<HTMLDivElement> { extends HTMLAttributes<HTMLDivElement> {
threadLink: string; threadLink: string;
threadType?: ThreadType;
open?: boolean; open?: boolean;
postFeedHandler: (value: string, id: string) => void; postFeedHandler: (value: string, id: string) => void;
createThread: (data: CreateThread) => void; createThread: (data: CreateThread) => void;

View File

@ -13,8 +13,10 @@
import { Tabs } from 'antd'; import { Tabs } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import { isEqual } from 'lodash';
import React, { FC, useEffect, useState } from 'react'; import React, { FC, useEffect, useState } from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import { PanelTab } from '../../../constants/feed.constants';
import { ThreadType } from '../../../generated/entity/feed/thread'; import { ThreadType } from '../../../generated/entity/feed/thread';
import FeedPanelOverlay from '../ActivityFeedPanel/FeedPanelOverlay'; import FeedPanelOverlay from '../ActivityFeedPanel/FeedPanelOverlay';
import { ActivityThreadPanelProp } from './ActivityThreadPanel.interface'; import { ActivityThreadPanelProp } from './ActivityThreadPanel.interface';
@ -29,14 +31,21 @@ const ActivityThreadPanel: FC<ActivityThreadPanelProp> = ({
createThread, createThread,
deletePostHandler, deletePostHandler,
updateThreadHandler, updateThreadHandler,
threadType,
}) => { }) => {
const { TabPane } = Tabs; const { TabPane } = Tabs;
const [activeTab, setActiveTab] = useState<string>('1'); const [activeTab, setActiveTab] = useState<PanelTab>(PanelTab.TASKS);
const onTabChange = (key: string) => { const onTabChange = (key: string) => {
setActiveTab(key); setActiveTab(key as PanelTab);
}; };
useEffect(() => {
if (isEqual(threadType, ThreadType.Conversation)) {
setActiveTab(PanelTab.CONVERSATIONS);
}
}, [threadType]);
useEffect(() => { useEffect(() => {
document.body.style.overflow = 'hidden'; document.body.style.overflow = 'hidden';
}, []); }, []);
@ -60,7 +69,7 @@ const ActivityThreadPanel: FC<ActivityThreadPanelProp> = ({
activeKey={activeTab} activeKey={activeTab}
className="ant-tabs-custom-line ant-tabs-custom-threadpanel" className="ant-tabs-custom-line ant-tabs-custom-threadpanel"
onChange={onTabChange}> onChange={onTabChange}>
<TabPane key="1" tab="Tasks"> <TabPane key={PanelTab.TASKS} tab="Tasks">
<ActivityThreadPanelBody <ActivityThreadPanelBody
createThread={createThread} createThread={createThread}
deletePostHandler={deletePostHandler} deletePostHandler={deletePostHandler}
@ -72,7 +81,7 @@ const ActivityThreadPanel: FC<ActivityThreadPanelProp> = ({
onTabChange={onTabChange} onTabChange={onTabChange}
/> />
</TabPane> </TabPane>
<TabPane key="2" tab="Conversations"> <TabPane key={PanelTab.CONVERSATIONS} tab="Conversations">
<ActivityThreadPanelBody <ActivityThreadPanelBody
createThread={createThread} createThread={createThread}
deletePostHandler={deletePostHandler} deletePostHandler={deletePostHandler}

View File

@ -18,7 +18,10 @@ import { isEqual, isUndefined } from 'lodash';
import React, { FC, Fragment, RefObject, useEffect, useState } from 'react'; import React, { FC, Fragment, RefObject, useEffect, useState } from 'react';
import AppState from '../../../AppState'; import AppState from '../../../AppState';
import { getAllFeeds } from '../../../axiosAPIs/feedsAPI'; import { getAllFeeds } from '../../../axiosAPIs/feedsAPI';
import { confirmStateInitialValue } from '../../../constants/feed.constants'; import {
confirmStateInitialValue,
PanelTab,
} from '../../../constants/feed.constants';
import { observerOptions } from '../../../constants/Mydata.constants'; import { observerOptions } from '../../../constants/Mydata.constants';
import { Thread, ThreadType } from '../../../generated/entity/feed/thread'; import { Thread, ThreadType } from '../../../generated/entity/feed/thread';
import { Paging } from '../../../generated/type/paging'; import { Paging } from '../../../generated/type/paging';
@ -200,7 +203,9 @@ const ActivityThreadPanelBody: FC<ActivityThreadPanelBodyProp> = ({
useAfterMount(() => { useAfterMount(() => {
if (threadType === ThreadType.Task && !isThreadLoading) { if (threadType === ThreadType.Task && !isThreadLoading) {
isEqual(threads.length, 0) && onTabChange && onTabChange('2'); isEqual(threads.length, 0) &&
onTabChange &&
onTabChange(PanelTab.CONVERSATIONS);
} }
}, [threads, isThreadLoading]); }, [threads, isThreadLoading]);

View File

@ -20,6 +20,7 @@ import { Link } from 'react-router-dom';
import AppState from '../../AppState'; import AppState from '../../AppState';
import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants'; import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants';
import { getTeamAndUserDetailsPath } from '../../constants/constants'; import { getTeamAndUserDetailsPath } from '../../constants/constants';
import { EntityField } from '../../constants/feed.constants';
import { observerOptions } from '../../constants/Mydata.constants'; import { observerOptions } from '../../constants/Mydata.constants';
import { SettledStatus } from '../../enums/axios.enum'; import { SettledStatus } from '../../enums/axios.enum';
import { EntityType } from '../../enums/entity.enum'; import { EntityType } from '../../enums/entity.enum';
@ -497,7 +498,7 @@ const DashboardDetails = ({
<Description <Description
description={description} description={description}
entityFieldThreads={getEntityFieldThreadCounts( entityFieldThreads={getEntityFieldThreadCounts(
'description', EntityField.DESCRIPTION,
entityFieldThreadCount entityFieldThreadCount
)} )}
entityFqn={dashboardFQN} entityFqn={dashboardFQN}

View File

@ -17,6 +17,7 @@ import { ExtraInfo } from 'Models';
import React, { FC, useEffect, useState } from 'react'; import React, { FC, useEffect, useState } from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants'; import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants';
import { EntityField } from '../../constants/feed.constants';
import { OwnerType } from '../../enums/user.enum'; import { OwnerType } from '../../enums/user.enum';
import { ChangeDescription } from '../../generated/entity/data/dashboard'; import { ChangeDescription } from '../../generated/entity/data/dashboard';
import { TagLabel } from '../../generated/type/tagLabel'; import { TagLabel } from '../../generated/type/tagLabel';
@ -68,7 +69,7 @@ const DashboardVersion: FC<DashboardVersionProp> = ({
const getDashboardDescription = () => { const getDashboardDescription = () => {
const descriptionDiff = getDiffByFieldName( const descriptionDiff = getDiffByFieldName(
'description', EntityField.DESCRIPTION,
changeDescription changeDescription
); );
const oldDescription = const oldDescription =

View File

@ -24,6 +24,7 @@ import React, {
import AppState from '../../AppState'; import AppState from '../../AppState';
import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants'; import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants';
import { getTeamAndUserDetailsPath, ROUTES } from '../../constants/constants'; import { getTeamAndUserDetailsPath, ROUTES } from '../../constants/constants';
import { EntityField } from '../../constants/feed.constants';
import { observerOptions } from '../../constants/Mydata.constants'; import { observerOptions } from '../../constants/Mydata.constants';
import { CSMode } from '../../enums/codemirror.enum'; import { CSMode } from '../../enums/codemirror.enum';
import { EntityType, FqnPart } from '../../enums/entity.enum'; import { EntityType, FqnPart } from '../../enums/entity.enum';
@ -34,6 +35,7 @@ import {
TableJoins, TableJoins,
TypeUsedToReturnUsageDetailsOfAnEntity, TypeUsedToReturnUsageDetailsOfAnEntity,
} from '../../generated/entity/data/table'; } from '../../generated/entity/data/table';
import { ThreadType } from '../../generated/entity/feed/thread';
import { EntityReference } from '../../generated/type/entityReference'; import { EntityReference } from '../../generated/type/entityReference';
import { Paging } from '../../generated/type/paging'; import { Paging } from '../../generated/type/paging';
import { LabelType, State } from '../../generated/type/tagLabel'; import { LabelType, State } from '../../generated/type/tagLabel';
@ -138,6 +140,7 @@ const DatasetDetails: React.FC<DatasetDetailsProps> = ({
fetchFeedHandler, fetchFeedHandler,
handleExtentionUpdate, handleExtentionUpdate,
updateThreadHandler, updateThreadHandler,
entityFieldTaskCount,
}: DatasetDetailsProps) => { }: DatasetDetailsProps) => {
const [isEdit, setIsEdit] = useState(false); const [isEdit, setIsEdit] = useState(false);
const [followersCount, setFollowersCount] = useState(0); const [followersCount, setFollowersCount] = useState(0);
@ -152,6 +155,9 @@ const DatasetDetails: React.FC<DatasetDetailsProps> = ({
}); });
const [threadLink, setThreadLink] = useState<string>(''); const [threadLink, setThreadLink] = useState<string>('');
const [threadType, setThreadType] = useState<ThreadType>(
ThreadType.Conversation
);
const [selectedField, setSelectedField] = useState<string>(''); const [selectedField, setSelectedField] = useState<string>('');
const [elementRef, isInView] = useInfiniteScroll(observerOptions); const [elementRef, isInView] = useInfiniteScroll(observerOptions);
@ -527,8 +533,11 @@ const DatasetDetails: React.FC<DatasetDetailsProps> = ({
}; };
}; };
const onThreadLinkSelect = (link: string) => { const onThreadLinkSelect = (link: string, threadType?: ThreadType) => {
setThreadLink(link); setThreadLink(link);
if (threadType) {
setThreadType(threadType);
}
}; };
const onThreadPanelClose = () => { const onThreadPanelClose = () => {
@ -615,8 +624,12 @@ const DatasetDetails: React.FC<DatasetDetailsProps> = ({
<div className="tw-col-span-3 tw--ml-5"> <div className="tw-col-span-3 tw--ml-5">
<Description <Description
description={description} description={description}
entityFieldTasks={getEntityFieldThreadCounts(
EntityField.DESCRIPTION,
entityFieldTaskCount
)}
entityFieldThreads={getEntityFieldThreadCounts( entityFieldThreads={getEntityFieldThreadCounts(
'description', EntityField.DESCRIPTION,
entityFieldThreadCount entityFieldThreadCount
)} )}
entityFqn={datasetFQN} entityFqn={datasetFQN}
@ -647,8 +660,12 @@ const DatasetDetails: React.FC<DatasetDetailsProps> = ({
FQN_SEPARATOR_CHAR FQN_SEPARATOR_CHAR
)} )}
columns={columns} columns={columns}
entityFieldTasks={getEntityFieldThreadCounts(
EntityField.COLUMNS,
entityFieldTaskCount
)}
entityFieldThreads={getEntityFieldThreadCounts( entityFieldThreads={getEntityFieldThreadCounts(
'columns', EntityField.COLUMNS,
entityFieldThreadCount entityFieldThreadCount
)} )}
entityFqn={datasetFQN} entityFqn={datasetFQN}
@ -818,6 +835,7 @@ const DatasetDetails: React.FC<DatasetDetailsProps> = ({
open={Boolean(threadLink)} open={Boolean(threadLink)}
postFeedHandler={postFeedHandler} postFeedHandler={postFeedHandler}
threadLink={threadLink} threadLink={threadLink}
threadType={threadType}
updateThreadHandler={updateThreadHandler} updateThreadHandler={updateThreadHandler}
onCancel={onThreadPanelClose} onCancel={onThreadPanelClose}
/> />

View File

@ -75,6 +75,7 @@ export interface DatasetDetailsProps {
isentityThreadLoading: boolean; isentityThreadLoading: boolean;
feedCount: number; feedCount: number;
entityFieldThreadCount: EntityFieldThreadCount[]; entityFieldThreadCount: EntityFieldThreadCount[];
entityFieldTaskCount: EntityFieldThreadCount[];
testMode: DatasetTestModeType; testMode: DatasetTestModeType;
tableTestCase: TableTest[]; tableTestCase: TableTest[];
showTestForm: boolean; showTestForm: boolean;

View File

@ -145,6 +145,7 @@ const DatasetDetailsProps = {
postFeedHandler: jest.fn(), postFeedHandler: jest.fn(),
feedCount: 0, feedCount: 0,
entityFieldThreadCount: [], entityFieldThreadCount: [],
entityFieldTaskCount: [],
showTestForm: false, showTestForm: false,
testMode: 'table' as DatasetTestModeType, testMode: 'table' as DatasetTestModeType,
handleAddTableTestCase: jest.fn(), handleAddTableTestCase: jest.fn(),

View File

@ -16,6 +16,7 @@ import { cloneDeep, isEqual, isUndefined } from 'lodash';
import { ExtraInfo } from 'Models'; import { ExtraInfo } from 'Models';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants'; import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants';
import { EntityField } from '../../constants/feed.constants';
import { FqnPart } from '../../enums/entity.enum'; import { FqnPart } from '../../enums/entity.enum';
import { OwnerType } from '../../enums/user.enum'; import { OwnerType } from '../../enums/user.enum';
import { import {
@ -138,7 +139,7 @@ const DatasetVersion: React.FC<DatasetVersionProp> = ({
const getTableDescription = () => { const getTableDescription = () => {
const descriptionDiff = getDiffByFieldName( const descriptionDiff = getDiffByFieldName(
'description', EntityField.DESCRIPTION,
changeDescription changeDescription
); );
const oldDescription = const oldDescription =
@ -159,7 +160,10 @@ const DatasetVersion: React.FC<DatasetVersionProp> = ({
const updatedColumns = (): Table['columns'] => { const updatedColumns = (): Table['columns'] => {
const colList = cloneDeep(currentVersionData.columns); const colList = cloneDeep(currentVersionData.columns);
const columnsDiff = getDiffByFieldName('columns', changeDescription); const columnsDiff = getDiffByFieldName(
EntityField.COLUMNS,
changeDescription
);
const changedColName = getChangeColName( const changedColName = getChangeColName(
columnsDiff?.added?.name ?? columnsDiff?.added?.name ??
columnsDiff?.deleted?.name ?? columnsDiff?.deleted?.name ??
@ -171,7 +175,7 @@ const DatasetVersion: React.FC<DatasetVersionProp> = ({
columnsDiff?.added?.name ?? columnsDiff?.added?.name ??
columnsDiff?.deleted?.name ?? columnsDiff?.deleted?.name ??
columnsDiff?.updated?.name, columnsDiff?.updated?.name,
'description' EntityField.DESCRIPTION
) )
) { ) {
const oldDescription = const oldDescription =
@ -249,7 +253,7 @@ const DatasetVersion: React.FC<DatasetVersionProp> = ({
return colList ?? []; return colList ?? [];
} else { } else {
const columnsDiff = getDiffByFieldName( const columnsDiff = getDiffByFieldName(
'columns', EntityField.COLUMNS,
changeDescription, changeDescription,
true true
); );

View File

@ -13,6 +13,7 @@
import { faCaretDown, faCaretRight } from '@fortawesome/free-solid-svg-icons'; import { faCaretDown, faCaretRight } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Popover } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import { cloneDeep, isNil, isUndefined, lowerCase } from 'lodash'; import { cloneDeep, isNil, isUndefined, lowerCase } from 'lodash';
import { EntityFieldThreads, EntityTags, TagOption } from 'Models'; import { EntityFieldThreads, EntityTags, TagOption } from 'Models';
@ -22,6 +23,7 @@ import { useExpanded, useTable } from 'react-table';
import { useAuthContext } from '../../authentication/auth-provider/AuthProvider'; import { useAuthContext } from '../../authentication/auth-provider/AuthProvider';
import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants'; import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants';
import { getTableDetailsPath } from '../../constants/constants'; import { getTableDetailsPath } from '../../constants/constants';
import { EntityField } from '../../constants/feed.constants';
import { SettledStatus } from '../../enums/axios.enum'; import { SettledStatus } from '../../enums/axios.enum';
import { EntityType, FqnPart } from '../../enums/entity.enum'; import { EntityType, FqnPart } from '../../enums/entity.enum';
import { import {
@ -31,6 +33,7 @@ import {
JoinedWith, JoinedWith,
Table, Table,
} from '../../generated/entity/data/table'; } from '../../generated/entity/data/table';
import { ThreadType } from '../../generated/entity/feed/thread';
import { Operation } from '../../generated/entity/policies/accessControl/rule'; import { Operation } from '../../generated/entity/policies/accessControl/rule';
import { TestCaseStatus } from '../../generated/tests/tableTest'; import { TestCaseStatus } from '../../generated/tests/tableTest';
import { LabelType, State, TagLabel } from '../../generated/type/tagLabel'; import { LabelType, State, TagLabel } from '../../generated/type/tagLabel';
@ -43,7 +46,6 @@ import {
} from '../../utils/CommonUtils'; } from '../../utils/CommonUtils';
import { ENTITY_LINK_SEPARATOR } from '../../utils/EntityUtils'; import { ENTITY_LINK_SEPARATOR } from '../../utils/EntityUtils';
import { getFieldThreadElement } from '../../utils/FeedElementUtils'; import { getFieldThreadElement } from '../../utils/FeedElementUtils';
import { getThreadValue } from '../../utils/FeedUtils';
import { import {
fetchGlossaryTerms, fetchGlossaryTerms,
getGlossaryTermlist, getGlossaryTermlist,
@ -77,8 +79,9 @@ interface Props {
isReadOnly?: boolean; isReadOnly?: boolean;
entityFqn?: string; entityFqn?: string;
entityFieldThreads?: EntityFieldThreads[]; entityFieldThreads?: EntityFieldThreads[];
entityFieldTasks?: EntityFieldThreads[];
onUpdate?: (columns: ModifiedTableColumn[]) => void; onUpdate?: (columns: ModifiedTableColumn[]) => void;
onThreadLinkSelect?: (value: string) => void; onThreadLinkSelect?: (value: string, threadType?: ThreadType) => void;
onEntityFieldSelect?: (value: string) => void; onEntityFieldSelect?: (value: string) => void;
} }
@ -94,6 +97,7 @@ const EntityTable = ({
onThreadLinkSelect, onThreadLinkSelect,
entityFqn, entityFqn,
tableConstraints, tableConstraints,
entityFieldTasks,
}: Props) => { }: Props) => {
const { isAdminUser, userPermissions } = useAuth(); const { isAdminUser, userPermissions } = useAuth();
const { isAuthDisabled } = useAuthContext(); const { isAuthDisabled } = useAuthContext();
@ -356,7 +360,13 @@ const EntityTable = ({
return searchedValue; return searchedValue;
}; };
/* eslint-disable-next-line */ const checkPermission = () =>
isAdminUser ||
hasEditAccess ||
isAuthDisabled ||
userPermissions[Operation.UpdateDescription];
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
const getColumnName = (cell: any) => { const getColumnName = (cell: any) => {
const fqn = cell?.row?.original?.fullyQualifiedName || ''; const fqn = cell?.row?.original?.fullyQualifiedName || '';
const columnName = getPartialNameFromTableFQN(fqn, [FqnPart.NestedColumn]); const columnName = getPartialNameFromTableFQN(fqn, [FqnPart.NestedColumn]);
@ -367,9 +377,9 @@ const EntityTable = ({
: columnName; : columnName;
}; };
/* eslint-disable-next-line */ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
const onRequestDescriptionHandler = (cell: any) => { const onRequestDescriptionHandler = (cell: any) => {
const field = 'columns'; const field = EntityField.COLUMNS;
const value = getColumnName(cell); const value = getColumnName(cell);
history.push( history.push(
getRequestDescriptionPath( getRequestDescriptionPath(
@ -381,6 +391,20 @@ const EntityTable = ({
); );
}; };
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
const onUpdateDescriptionHandler = (cell: any) => {
const field = EntityField.COLUMNS;
const value = getColumnName(cell);
history.push(
getUpdateDescriptionPath(
EntityType.TABLE,
entityFqn as string,
field,
value
)
);
};
const prepareConstraintIcon = ( const prepareConstraintIcon = (
columnName: string, columnName: string,
columnConstraint?: string columnConstraint?: string
@ -399,28 +423,41 @@ const EntityTable = ({
} }
}; };
/* eslint-disable-next-line */ const handleUpdate = (column: Column, index: number) => {
const handleUpdate = (column: Column, index: number, cell: any) => { handleEditColumn(column, index);
const check = };
isAdminUser ||
hasEditAccess ||
isAuthDisabled ||
userPermissions[Operation.UpdateDescription];
if (check) {
handleEditColumn(column, index);
} else {
const field = 'columns';
const value = getColumnName(cell);
history.push( /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
getUpdateDescriptionPath( const getRequestDescriptionElement = (cell: any) => {
EntityType.TABLE, const hasDescription = Boolean(cell.value);
entityFqn as string,
field, return (
value <button
) className="tw-w-8 tw-h-8 tw-mr-1 tw-flex-none link-text focus:tw-outline-none tw-opacity-0 group-hover:tw-opacity-100"
); data-testid="request-description"
} onClick={() =>
hasDescription
? onUpdateDescriptionHandler(cell)
: onRequestDescriptionHandler(cell)
}>
<Popover
destroyTooltipOnHide
content={
hasDescription
? 'Request update description'
: 'Request description'
}
overlayClassName="ant-popover-request-description"
trigger="hover"
zIndex={9999}>
<SVGIcons
alt="request-description"
icon={Icons.REQUEST}
width="16px"
/>
</Popover>
</button>
);
}; };
useEffect(() => { useEffect(() => {
@ -675,60 +712,52 @@ const EntityTable = ({
</span> </span>
)} )}
</div> </div>
{!isReadOnly ? ( <div className="tw-flex tw--mt-1.5">
<Fragment> {!isReadOnly ? (
<button <Fragment>
className="tw-self-start tw-w-8 tw-h-auto tw-opacity-0 tw-ml-1 group-hover:tw-opacity-100 focus:tw-outline-none" {checkPermission() && (
onClick={() => <button
handleUpdate(row.original, row.id, cell) className="tw-self-start tw-w-8 tw-h-8 tw-opacity-0 tw-ml-1 group-hover:tw-opacity-100 focus:tw-outline-none tw-flex-none"
}> onClick={() =>
<SVGIcons handleUpdate(row.original, row.id)
alt="edit" }>
icon="icon-edit"
title="Edit"
width="12px"
/>
</button>
{isNil(
getThreadValue(
getColumnName(cell),
'description',
entityFieldThreads as EntityFieldThreads[]
)
) && !cell.value ? (
<button
className="focus:tw-outline-none tw-ml-1 tw-opacity-0 group-hover:tw-opacity-100 tw--mt-2"
data-testid="request-description"
onClick={() =>
onRequestDescriptionHandler(cell)
}>
<PopOver
position="top"
title="Request description"
trigger="mouseenter">
<SVGIcons <SVGIcons
alt="request-description" alt="edit"
className="tw-mt-2.5" icon="icon-edit"
icon={Icons.REQUEST} title="Edit"
width="14px"
/> />
</PopOver> </button>
</button> )}
) : null} {getRequestDescriptionElement(cell)}
{getFieldThreadElement( {getFieldThreadElement(
getColumnName(cell), getColumnName(cell),
'description', EntityField.DESCRIPTION,
entityFieldThreads as EntityFieldThreads[], entityFieldThreads as EntityFieldThreads[],
onThreadLinkSelect, onThreadLinkSelect,
EntityType.TABLE, EntityType.TABLE,
entityFqn, entityFqn,
`columns${ENTITY_LINK_SEPARATOR}${getColumnName( `columns${ENTITY_LINK_SEPARATOR}${getColumnName(
cell cell
)}${ENTITY_LINK_SEPARATOR}description`, )}${ENTITY_LINK_SEPARATOR}description`,
Boolean(cell.value) Boolean(cell.value)
)} )}
</Fragment> {getFieldThreadElement(
) : null} getColumnName(cell),
EntityField.DESCRIPTION,
entityFieldTasks as EntityFieldThreads[],
onThreadLinkSelect,
EntityType.TABLE,
entityFqn,
`columns${ENTITY_LINK_SEPARATOR}${getColumnName(
cell
)}${ENTITY_LINK_SEPARATOR}description`,
Boolean(cell.value),
ThreadType.Task
)}
</Fragment>
) : null}
</div>
</div> </div>
</div> </div>
{checkIfJoinsAvailable(row.original.name) && ( {checkIfJoinsAvailable(row.original.name) && (

View File

@ -17,6 +17,7 @@ import React, { RefObject, useCallback, useEffect, useState } from 'react';
import AppState from '../../AppState'; import AppState from '../../AppState';
import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants'; import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants';
import { getTeamAndUserDetailsPath } from '../../constants/constants'; import { getTeamAndUserDetailsPath } from '../../constants/constants';
import { EntityField } from '../../constants/feed.constants';
import { observerOptions } from '../../constants/Mydata.constants'; import { observerOptions } from '../../constants/Mydata.constants';
import { EntityType } from '../../enums/entity.enum'; import { EntityType } from '../../enums/entity.enum';
import { OwnerType } from '../../enums/user.enum'; import { OwnerType } from '../../enums/user.enum';
@ -392,7 +393,7 @@ const PipelineDetails = ({
<Description <Description
description={description} description={description}
entityFieldThreads={getEntityFieldThreadCounts( entityFieldThreads={getEntityFieldThreadCounts(
'description', EntityField.DESCRIPTION,
entityFieldThreadCount entityFieldThreadCount
)} )}
entityFqn={pipelineFQN} entityFqn={pipelineFQN}

View File

@ -17,6 +17,7 @@ import { ExtraInfo } from 'Models';
import React, { FC, useEffect, useState } from 'react'; import React, { FC, useEffect, useState } from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants'; import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants';
import { EntityField } from '../../constants/feed.constants';
import { OwnerType } from '../../enums/user.enum'; import { OwnerType } from '../../enums/user.enum';
import { ChangeDescription } from '../../generated/entity/data/pipeline'; import { ChangeDescription } from '../../generated/entity/data/pipeline';
import { TagLabel } from '../../generated/type/tagLabel'; import { TagLabel } from '../../generated/type/tagLabel';
@ -68,7 +69,7 @@ const PipelineVersion: FC<PipelineVersionProp> = ({
const getPipelineDescription = () => { const getPipelineDescription = () => {
const descriptionDiff = getDiffByFieldName( const descriptionDiff = getDiffByFieldName(
'description', EntityField.DESCRIPTION,
changeDescription changeDescription
); );
const oldDescription = const oldDescription =

View File

@ -16,17 +16,20 @@ import { Button, Popover } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { useMemo } from 'react'; import React, { useEffect, useMemo, useState } from 'react';
import AppState from '../../AppState'; import AppState from '../../AppState';
import { REACTION_LIST } from '../../constants/reactions.constant'; import { REACTION_LIST } from '../../constants/reactions.constant';
import { ReactionOperation } from '../../enums/reactions.enum'; import { ReactionOperation } from '../../enums/reactions.enum';
import useImage from '../../hooks/useImage'; import useImage from '../../hooks/useImage';
const Emoji = ({ reaction, reactionList, onReactionSelect }) => { const Emoji = ({ reaction, reactionList, onReactionSelect }) => {
// get reaction object based on cureent reaction const [reactionType, setReactionType] = useState(reaction);
const [isClicked, setIsClicked] = useState(false);
// get reaction object based on cureent reactionType
const reactionObject = useMemo( const reactionObject = useMemo(
() => REACTION_LIST.find((value) => value.reaction === reaction), () => REACTION_LIST.find((value) => value.reaction === reactionType),
[reaction] [reactionType]
); );
const { image } = useImage(`emojis/${reactionObject.reaction}.png`); const { image } = useImage(`emojis/${reactionObject.reaction}.png`);
@ -45,10 +48,13 @@ const Emoji = ({ reaction, reactionList, onReactionSelect }) => {
const userList = reactionList.map((reactionItem) => reactionItem.user.name); const userList = reactionList.map((reactionItem) => reactionItem.user.name);
const handleOnClick = () => { const handleOnClick = () => {
const operation = isReacted if (!isClicked) {
? ReactionOperation.REMOVE const operation = isReacted
: ReactionOperation.ADD; ? ReactionOperation.REMOVE
onReactionSelect(reactionObject.reaction, operation); : ReactionOperation.ADD;
onReactionSelect(reactionObject.reaction, operation);
setIsClicked(true);
}
}; };
const popoverContent = ( const popoverContent = (
@ -56,10 +62,15 @@ const Emoji = ({ reaction, reactionList, onReactionSelect }) => {
className="tw-w-44 tw-break-normal tw-m-0 tw-p-0" className="tw-w-44 tw-break-normal tw-m-0 tw-p-0"
data-testid="popover-content"> data-testid="popover-content">
{`${userList.join(', ')}`}{' '} {`${userList.join(', ')}`}{' '}
<span className="tw-font-semibold">{`reacted with ${reaction} emoji`}</span> <span className="tw-font-semibold">{`reacted with ${reactionType} emoji`}</span>
</p> </p>
); );
useEffect(() => {
setReactionType(reaction);
setIsClicked(false);
}, [reaction]);
return ( return (
<Popover <Popover
destroyTooltipOnHide destroyTooltipOnHide

View File

@ -19,6 +19,7 @@ import {
Table, Table,
TableData, TableData,
} from '../../generated/entity/data/table'; } from '../../generated/entity/data/table';
import { ThreadType } from '../../generated/entity/feed/thread';
import Searchbar from '../common/searchbar/Searchbar'; import Searchbar from '../common/searchbar/Searchbar';
import EntityTable from '../EntityTable/EntityTable.component'; import EntityTable from '../EntityTable/EntityTable.component';
@ -33,7 +34,8 @@ type Props = {
isReadOnly?: boolean; isReadOnly?: boolean;
entityFqn?: string; entityFqn?: string;
entityFieldThreads?: EntityFieldThreads[]; entityFieldThreads?: EntityFieldThreads[];
onThreadLinkSelect?: (value: string) => void; entityFieldTasks?: EntityFieldThreads[];
onThreadLinkSelect?: (value: string, threadType?: ThreadType) => void;
onEntityFieldSelect?: (value: string) => void; onEntityFieldSelect?: (value: string) => void;
onUpdate?: (columns: Table['columns']) => void; onUpdate?: (columns: Table['columns']) => void;
}; };
@ -51,6 +53,7 @@ const SchemaTab: FunctionComponent<Props> = ({
isReadOnly = false, isReadOnly = false,
entityFqn, entityFqn,
tableConstraints, tableConstraints,
entityFieldTasks,
}: Props) => { }: Props) => {
const [searchText, setSearchText] = useState(''); const [searchText, setSearchText] = useState('');
@ -75,6 +78,7 @@ const SchemaTab: FunctionComponent<Props> = ({
<div className="col-sm-12"> <div className="col-sm-12">
<EntityTable <EntityTable
columnName={columnName} columnName={columnName}
entityFieldTasks={entityFieldTasks}
entityFieldThreads={entityFieldThreads} entityFieldThreads={entityFieldThreads}
entityFqn={entityFqn} entityFqn={entityFqn}
hasEditAccess={Boolean(hasEditAccess)} hasEditAccess={Boolean(hasEditAccess)}

View File

@ -22,6 +22,7 @@ import React, {
import AppState from '../../AppState'; import AppState from '../../AppState';
import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants'; import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants';
import { getTeamAndUserDetailsPath } from '../../constants/constants'; import { getTeamAndUserDetailsPath } from '../../constants/constants';
import { EntityField } from '../../constants/feed.constants';
import { observerOptions } from '../../constants/Mydata.constants'; import { observerOptions } from '../../constants/Mydata.constants';
import { EntityType } from '../../enums/entity.enum'; import { EntityType } from '../../enums/entity.enum';
import { OwnerType } from '../../enums/user.enum'; import { OwnerType } from '../../enums/user.enum';
@ -407,7 +408,7 @@ const TopicDetails: React.FC<TopicDetailsProps> = ({
<Description <Description
description={description} description={description}
entityFieldThreads={getEntityFieldThreadCounts( entityFieldThreads={getEntityFieldThreadCounts(
'description', EntityField.DESCRIPTION,
entityFieldThreadCount entityFieldThreadCount
)} )}
entityFqn={topicFQN} entityFqn={topicFQN}

View File

@ -16,6 +16,7 @@ import { isUndefined } from 'lodash';
import { ExtraInfo } from 'Models'; import { ExtraInfo } from 'Models';
import React, { FC, useEffect, useState } from 'react'; import React, { FC, useEffect, useState } from 'react';
import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants'; import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants';
import { EntityField } from '../../constants/feed.constants';
import { OwnerType } from '../../enums/user.enum'; import { OwnerType } from '../../enums/user.enum';
import { ChangeDescription } from '../../generated/entity/data/topic'; import { ChangeDescription } from '../../generated/entity/data/topic';
import { TagLabel } from '../../generated/type/tagLabel'; import { TagLabel } from '../../generated/type/tagLabel';
@ -99,7 +100,7 @@ const TopicVersion: FC<TopicVersionProp> = ({
const getTableDescription = () => { const getTableDescription = () => {
const descriptionDiff = getDiffByFieldName( const descriptionDiff = getDiffByFieldName(
'description', EntityField.DESCRIPTION,
changeDescription changeDescription
); );
const oldDescription = const oldDescription =

View File

@ -13,6 +13,7 @@
import { EntityFieldThreads } from 'Models'; import { EntityFieldThreads } from 'Models';
import { Table } from '../../../generated/entity/data/table'; import { Table } from '../../../generated/entity/data/table';
import { ThreadType } from '../../../generated/entity/feed/thread';
export interface DescriptionProps { export interface DescriptionProps {
entityName?: string; entityName?: string;
@ -26,7 +27,8 @@ export interface DescriptionProps {
entityType?: string; entityType?: string;
entityFqn?: string; entityFqn?: string;
entityFieldThreads?: EntityFieldThreads[]; entityFieldThreads?: EntityFieldThreads[];
onThreadLinkSelect?: (value: string) => void; entityFieldTasks?: EntityFieldThreads[];
onThreadLinkSelect?: (value: string, threadType?: ThreadType) => void;
onDescriptionEdit?: () => void; onDescriptionEdit?: () => void;
onCancel?: () => void; onCancel?: () => void;
onDescriptionUpdate?: (value: string) => void; onDescriptionUpdate?: (value: string) => void;

View File

@ -11,14 +11,15 @@
* limitations under the License. * limitations under the License.
*/ */
import { Popover } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import { isUndefined } from 'lodash'; import { isUndefined } from 'lodash';
import { EntityFieldThreads } from 'Models'; import { EntityFieldThreads } from 'Models';
import React, { FC, Fragment } from 'react'; import React, { FC, Fragment } from 'react';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import { useAuthContext } from '../../../authentication/auth-provider/AuthProvider'; import { useAuthContext } from '../../../authentication/auth-provider/AuthProvider';
import { TITLE_FOR_UPDATE_DESCRIPTION } from '../../../constants/constants'; import { EntityField } from '../../../constants/feed.constants';
import { EntityType } from '../../../enums/entity.enum'; import { ThreadType } from '../../../generated/entity/feed/thread';
import { Operation } from '../../../generated/entity/policies/accessControl/rule'; import { Operation } from '../../../generated/entity/policies/accessControl/rule';
import { useAuth } from '../../../hooks/authHooks'; import { useAuth } from '../../../hooks/authHooks';
import { getEntityFeedLink } from '../../../utils/EntityUtils'; import { getEntityFeedLink } from '../../../utils/EntityUtils';
@ -26,11 +27,8 @@ import SVGIcons, { Icons } from '../../../utils/SvgUtils';
import { import {
getRequestDescriptionPath, getRequestDescriptionPath,
getUpdateDescriptionPath, getUpdateDescriptionPath,
TASK_ENTITIES,
} from '../../../utils/TasksUtils'; } from '../../../utils/TasksUtils';
import { ModalWithMarkdownEditor } from '../../Modals/ModalWithMarkdownEditor/ModalWithMarkdownEditor'; import { ModalWithMarkdownEditor } from '../../Modals/ModalWithMarkdownEditor/ModalWithMarkdownEditor';
import NonAdminAction from '../non-admin-action/NonAdminAction';
import PopOver from '../popover/PopOver';
import RichTextEditorPreviewer from '../rich-text-editor/RichTextEditorPreviewer'; import RichTextEditorPreviewer from '../rich-text-editor/RichTextEditorPreviewer';
import { DescriptionProps } from './Description.interface'; import { DescriptionProps } from './Description.interface';
@ -50,6 +48,7 @@ const Description: FC<DescriptionProps> = ({
onEntityFieldSelect, onEntityFieldSelect,
entityType, entityType,
entityFqn, entityFqn,
entityFieldTasks,
}) => { }) => {
const history = useHistory(); const history = useHistory();
@ -57,6 +56,7 @@ const Description: FC<DescriptionProps> = ({
const { isAuthDisabled } = useAuthContext(); const { isAuthDisabled } = useAuthContext();
const thread = entityFieldThreads?.[0]; const thread = entityFieldThreads?.[0];
const tasks = entityFieldTasks?.[0];
const handleRequestDescription = () => { const handleRequestDescription = () => {
history.push( history.push(
@ -71,45 +71,44 @@ const Description: FC<DescriptionProps> = ({
}; };
const checkPermission = () => { const checkPermission = () => {
if (!isAuthDisabled && !isAdminUser) { return (
return Boolean( isAdminUser ||
hasEditAccess || userPermissions[Operation.UpdateDescription] Boolean(hasEditAccess) ||
); userPermissions[Operation.UpdateDescription] ||
} isAuthDisabled
);
return true;
}; };
const handleUpdate = () => { const handleUpdate = () => {
if (checkPermission()) { onDescriptionEdit && onDescriptionEdit();
onDescriptionEdit && onDescriptionEdit();
} else if (TASK_ENTITIES.includes(entityType as EntityType)) {
handleUpdateDescription();
}
}; };
const RequestDescriptionEl = ({ const RequestDescriptionEl = () => {
descriptionThread, const hasDescription = Boolean(description.trim());
}: {
descriptionThread?: EntityFieldThreads; return onEntityFieldSelect ? (
}) => {
return isUndefined(descriptionThread) &&
onEntityFieldSelect &&
!description?.trim() ? (
<button <button
className="focus:tw-outline-none tw-ml-2 tw--mt-6" className="tw-w-8 tw-h-8 tw-mr-1 tw-flex-none link-text focus:tw-outline-none"
data-testid="request-description" data-testid="request-description"
onClick={handleRequestDescription}> onClick={
<PopOver hasDescription ? handleUpdateDescription : handleRequestDescription
position="top" }>
title="Request description" <Popover
trigger="mouseenter"> destroyTooltipOnHide
content={
hasDescription
? 'Request update description'
: 'Request description'
}
overlayClassName="ant-popover-request-description"
trigger="hover"
zIndex={9999}>
<SVGIcons <SVGIcons
alt="request-description" alt="request-description"
className="tw-mt-2"
icon={Icons.REQUEST} icon={Icons.REQUEST}
width="16px"
/> />
</PopOver> </Popover>
</button> </button>
) : null; ) : null;
}; };
@ -120,8 +119,8 @@ const Description: FC<DescriptionProps> = ({
descriptionThread?: EntityFieldThreads; descriptionThread?: EntityFieldThreads;
}) => { }) => {
return !isUndefined(descriptionThread) ? ( return !isUndefined(descriptionThread) ? (
<p <button
className="link-text tw-ml-2 tw-w-8 tw-h-8 tw-flex-none" className="tw-w-8 tw-h-8 tw-mr-2 tw-flex-none link-text focus:tw-outline-none"
data-testid="description-thread" data-testid="description-thread"
onClick={() => onThreadLinkSelect?.(descriptionThread.entityLink)}> onClick={() => onThreadLinkSelect?.(descriptionThread.entityLink)}>
<span className="tw-flex"> <span className="tw-flex">
@ -131,48 +130,61 @@ const Description: FC<DescriptionProps> = ({
{descriptionThread.count} {descriptionThread.count}
</span> </span>
</span> </span>
</p> </button>
) : ( ) : (
<Fragment> <Fragment>
{description?.trim() && onThreadLinkSelect ? ( {description?.trim() && onThreadLinkSelect ? (
<p <button
className="link-text tw-flex-none tw-ml-2" className="tw-w-8 tw-h-8 tw-mr-2 tw-flex-none link-text focus:tw-outline-none"
data-testid="start-description-thread" data-testid="start-description-thread"
onClick={() => onClick={() =>
onThreadLinkSelect?.( onThreadLinkSelect?.(
getEntityFeedLink(entityType, entityFqn, 'description') getEntityFeedLink(
entityType,
entityFqn,
EntityField.DESCRIPTION
)
) )
}> }>
<SVGIcons alt="comments" icon={Icons.COMMENT_PLUS} width="20px" /> <SVGIcons alt="comments" icon={Icons.COMMENT_PLUS} width="20px" />
</p> </button>
) : null} ) : null}
</Fragment> </Fragment>
); );
}; };
const getDescriptionTaskElement = () => {
return !isUndefined(tasks) ? (
<button
className="tw-w-8 tw-h-8 tw-mr-2 tw-flex-none link-text focus:tw-outline-none"
data-testid="description-task"
onClick={() => onThreadLinkSelect?.(tasks.entityLink, ThreadType.Task)}>
<span className="tw-flex">
<SVGIcons alt="tasks" icon={Icons.TASK_ICON} width="16px" />{' '}
<span className="tw-ml-1" data-testid="description-tasks-count">
{' '}
{tasks.count}
</span>
</span>
</button>
) : null;
};
const DescriptionActions = () => { const DescriptionActions = () => {
return !isReadOnly ? ( return !isReadOnly ? (
<div <div className={classNames('tw-w-5 tw-min-w-max tw-flex tw--mt-0.5')}>
className={classNames( {checkPermission() && (
'tw-w-5 tw-min-w-max tw-flex',
description?.trim() ? 'tw-pl-1' : ''
)}>
<NonAdminAction
isOwner={
checkPermission() ||
TASK_ENTITIES.includes(entityType as EntityType)
}
title={TITLE_FOR_UPDATE_DESCRIPTION}>
<button <button
className="focus:tw-outline-none tw-self-baseline" className="tw-w-7 tw-h-8 tw-flex-none focus:tw-outline-none"
data-testid="edit-description" data-testid="edit-description"
onClick={handleUpdate}> onClick={handleUpdate}>
<SVGIcons alt="edit" icon="icon-edit" title="Edit" width="16px" /> <SVGIcons alt="edit" icon="icon-edit" title="Edit" width="16px" />
</button> </button>
</NonAdminAction> )}
<RequestDescriptionEl descriptionThread={thread} /> <RequestDescriptionEl />
<DescriptionThreadEl descriptionThread={thread} /> <DescriptionThreadEl descriptionThread={thread} />
{getDescriptionTaskElement()}
</div> </div>
) : null; ) : null;
}; };

View File

@ -15,6 +15,7 @@ import classNames from 'classnames';
import { isUndefined } from 'lodash'; import { isUndefined } from 'lodash';
import { EntityFieldThreads } from 'Models'; import { EntityFieldThreads } from 'Models';
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import { EntityField } from '../../../constants/feed.constants';
import { Table } from '../../../generated/entity/data/table'; import { Table } from '../../../generated/entity/data/table';
import { Operation } from '../../../generated/entity/policies/accessControl/rule'; import { Operation } from '../../../generated/entity/policies/accessControl/rule';
import { getHtmlForNonAdminAction } from '../../../utils/CommonUtils'; import { getHtmlForNonAdminAction } from '../../../utils/CommonUtils';
@ -130,7 +131,7 @@ const DescriptionV1 = ({
<button <button
className="focus:tw-outline-none tw-ml-2 tw--mt-6" className="focus:tw-outline-none tw-ml-2 tw--mt-6"
data-testid="request-description" data-testid="request-description"
onClick={() => onEntityFieldSelect?.('description')}> onClick={() => onEntityFieldSelect?.(EntityField.DESCRIPTION)}>
<PopOver <PopOver
position="top" position="top"
title="Request description" title="Request description"
@ -168,7 +169,11 @@ const DescriptionV1 = ({
data-testid="start-description-thread" data-testid="start-description-thread"
onClick={() => onClick={() =>
onThreadLinkSelect?.( onThreadLinkSelect?.(
getEntityFeedLink(entityType, entityFqn, 'description') getEntityFeedLink(
entityType,
entityFqn,
EntityField.DESCRIPTION
)
) )
}> }>
<SVGIcons <SVGIcons

View File

@ -62,3 +62,15 @@ export enum TaskOperation {
RESOLVE = 'resolve', RESOLVE = 'resolve',
REJECT = 'close', REJECT = 'close',
} }
export enum PanelTab {
TASKS = 'tasks',
CONVERSATIONS = 'conversations',
}
export enum EntityField {
DESCRIPTION = 'description',
COLUMNS = 'columns',
TAGS = 'tags',
TASKS = 'tasks',
}

View File

@ -56,6 +56,7 @@ import {
getServiceDetailsPath, getServiceDetailsPath,
getTeamAndUserDetailsPath, getTeamAndUserDetailsPath,
} from '../../constants/constants'; } from '../../constants/constants';
import { EntityField } from '../../constants/feed.constants';
import { observerOptions } from '../../constants/Mydata.constants'; import { observerOptions } from '../../constants/Mydata.constants';
import { EntityType, FqnPart, TabSpecificField } from '../../enums/entity.enum'; import { EntityType, FqnPart, TabSpecificField } from '../../enums/entity.enum';
import { ServiceCategory } from '../../enums/service.enum'; import { ServiceCategory } from '../../enums/service.enum';
@ -657,7 +658,7 @@ const DatabaseSchemaPage: FunctionComponent = () => {
blurWithBodyBG blurWithBodyBG
description={description} description={description}
entityFieldThreads={getEntityFieldThreadCounts( entityFieldThreads={getEntityFieldThreadCounts(
'description', EntityField.DESCRIPTION,
entityFieldThreadCount entityFieldThreadCount
)} )}
entityFqn={databaseSchemaFQN} entityFqn={databaseSchemaFQN}

View File

@ -27,7 +27,6 @@ import { useHistory, useParams } from 'react-router-dom';
import AppState from '../../AppState'; import AppState from '../../AppState';
import { import {
getAllFeeds, getAllFeeds,
getFeedCount,
postFeedById, postFeedById,
postThread, postThread,
} from '../../axiosAPIs/feedsAPI'; } from '../../axiosAPIs/feedsAPI';
@ -90,6 +89,7 @@ import {
getCurrentUserId, getCurrentUserId,
getEntityMissingError, getEntityMissingError,
getEntityName, getEntityName,
getFeedCounts,
getFields, getFields,
getPartialNameFromTableFQN, getPartialNameFromTableFQN,
} from '../../utils/CommonUtils'; } from '../../utils/CommonUtils';
@ -174,6 +174,9 @@ const DatasetDetailsPage: FunctionComponent = () => {
const [entityFieldThreadCount, setEntityFieldThreadCount] = useState< const [entityFieldThreadCount, setEntityFieldThreadCount] = useState<
EntityFieldThreadCount[] EntityFieldThreadCount[]
>([]); >([]);
const [entityFieldTaskCount, setEntityFieldTaskCount] = useState<
EntityFieldThreadCount[]
>([]);
// Data Quality tab state // Data Quality tab state
const [testMode, setTestMode] = useState<DatasetTestModeType>('table'); const [testMode, setTestMode] = useState<DatasetTestModeType>('table');
@ -490,23 +493,13 @@ const DatasetDetailsPage: FunctionComponent = () => {
}, [activeTab]); }, [activeTab]);
const getEntityFeedCount = () => { const getEntityFeedCount = () => {
getFeedCount(getEntityFeedLink(EntityType.TABLE, tableFQN)) getFeedCounts(
.then((res: AxiosResponse) => { EntityType.TABLE,
if (res.data) { tableFQN,
setFeedCount(res.data.totalCount); setEntityFieldThreadCount,
setEntityFieldThreadCount(res.data.counts); setEntityFieldTaskCount,
} else { setFeedCount
showErrorToast( );
jsonData['api-error-messages']['fetch-entity-feed-count-error']
);
}
})
.catch((err: AxiosError) => {
showErrorToast(
err,
jsonData['api-error-messages']['fetch-entity-feed-count-error']
);
});
}; };
const saveUpdatedTableData = (updatedData: Table): Promise<AxiosResponse> => { const saveUpdatedTableData = (updatedData: Table): Promise<AxiosResponse> => {
@ -1030,6 +1023,7 @@ const DatasetDetailsPage: FunctionComponent = () => {
deleted={deleted} deleted={deleted}
description={description} description={description}
descriptionUpdateHandler={descriptionUpdateHandler} descriptionUpdateHandler={descriptionUpdateHandler}
entityFieldTaskCount={entityFieldTaskCount}
entityFieldThreadCount={entityFieldThreadCount} entityFieldThreadCount={entityFieldThreadCount}
entityLineage={entityLineage} entityLineage={entityLineage}
entityLineageHandler={entityLineageHandler} entityLineageHandler={entityLineageHandler}

View File

@ -31,6 +31,7 @@ import ProfilePicture from '../../../components/common/ProfilePicture/ProfilePic
import RichTextEditor from '../../../components/common/rich-text-editor/RichTextEditor'; import RichTextEditor from '../../../components/common/rich-text-editor/RichTextEditor';
import TitleBreadcrumb from '../../../components/common/title-breadcrumb/title-breadcrumb.component'; import TitleBreadcrumb from '../../../components/common/title-breadcrumb/title-breadcrumb.component';
import { FQN_SEPARATOR_CHAR } from '../../../constants/char.constants'; import { FQN_SEPARATOR_CHAR } from '../../../constants/char.constants';
import { EntityField } from '../../../constants/feed.constants';
import { EntityType } from '../../../enums/entity.enum'; import { EntityType } from '../../../enums/entity.enum';
import { import {
CreateThread, CreateThread,
@ -99,7 +100,7 @@ const RequestDescription = () => {
const back = () => history.goBack(); const back = () => history.goBack();
const getColumnDetails = useCallback(() => { const getColumnDetails = useCallback(() => {
if (!isNil(field) && !isNil(value) && field === 'columns') { if (!isNil(field) && !isNil(value) && field === EntityField.COLUMNS) {
const column = getSanitizeValue.split(FQN_SEPARATOR_CHAR).slice(-1); const column = getSanitizeValue.split(FQN_SEPARATOR_CHAR).slice(-1);
const columnObject = getColumnObject(column[0], entityData.columns || []); const columnObject = getColumnObject(column[0], entityData.columns || []);
@ -127,7 +128,7 @@ const RequestDescription = () => {
if (field && value) { if (field && value) {
return `${field}${ENTITY_LINK_SEPARATOR}${value}${ENTITY_LINK_SEPARATOR}description`; return `${field}${ENTITY_LINK_SEPARATOR}${value}${ENTITY_LINK_SEPARATOR}description`;
} else { } else {
return 'description'; return EntityField.DESCRIPTION;
} }
}; };

View File

@ -29,6 +29,7 @@ import { postThread } from '../../../axiosAPIs/feedsAPI';
import ProfilePicture from '../../../components/common/ProfilePicture/ProfilePicture'; import ProfilePicture from '../../../components/common/ProfilePicture/ProfilePicture';
import TitleBreadcrumb from '../../../components/common/title-breadcrumb/title-breadcrumb.component'; import TitleBreadcrumb from '../../../components/common/title-breadcrumb/title-breadcrumb.component';
import { FQN_SEPARATOR_CHAR } from '../../../constants/char.constants'; import { FQN_SEPARATOR_CHAR } from '../../../constants/char.constants';
import { EntityField } from '../../../constants/feed.constants';
import { EntityType } from '../../../enums/entity.enum'; import { EntityType } from '../../../enums/entity.enum';
import { import {
CreateThread, CreateThread,
@ -104,7 +105,7 @@ const UpdateDescription = () => {
}, [field, entityData]); }, [field, entityData]);
const getColumnDetails = useCallback(() => { const getColumnDetails = useCallback(() => {
if (!isNil(field) && !isNil(value) && field === 'columns') { if (!isNil(field) && !isNil(value) && field === EntityField.COLUMNS) {
return ( return (
<div data-testid="column-details"> <div data-testid="column-details">
<p className="tw-font-semibold">Column Details</p> <p className="tw-font-semibold">Column Details</p>
@ -136,7 +137,7 @@ const UpdateDescription = () => {
if (field && value) { if (field && value) {
return `${field}${ENTITY_LINK_SEPARATOR}${value}${ENTITY_LINK_SEPARATOR}description`; return `${field}${ENTITY_LINK_SEPARATOR}${value}${ENTITY_LINK_SEPARATOR}description`;
} else { } else {
return 'description'; return EntityField.DESCRIPTION;
} }
}; };

View File

@ -61,6 +61,7 @@ import {
PAGE_SIZE, PAGE_SIZE,
pagingObject, pagingObject,
} from '../../constants/constants'; } from '../../constants/constants';
import { EntityField } from '../../constants/feed.constants';
import { observerOptions } from '../../constants/Mydata.constants'; import { observerOptions } from '../../constants/Mydata.constants';
import { EntityType, TabSpecificField } from '../../enums/entity.enum'; import { EntityType, TabSpecificField } from '../../enums/entity.enum';
import { ServiceCategory } from '../../enums/service.enum'; import { ServiceCategory } from '../../enums/service.enum';
@ -674,7 +675,7 @@ const DatabaseDetails: FunctionComponent = () => {
blurWithBodyBG blurWithBodyBG
description={description} description={description}
entityFieldThreads={getEntityFieldThreadCounts( entityFieldThreads={getEntityFieldThreadCounts(
'description', EntityField.DESCRIPTION,
entityFieldThreadCount entityFieldThreadCount
)} )}
entityFqn={databaseFQN} entityFqn={databaseFQN}

View File

@ -193,6 +193,7 @@ const TourPage = () => {
deletePostHandler={handleCountChange} deletePostHandler={handleCountChange}
description={mockDatasetData.description} description={mockDatasetData.description}
descriptionUpdateHandler={handleCountChange} descriptionUpdateHandler={handleCountChange}
entityFieldTaskCount={[]}
entityFieldThreadCount={[]} entityFieldThreadCount={[]}
entityLineage={mockDatasetData.entityLineage} entityLineage={mockDatasetData.entityLineage}
entityLineageHandler={handleCountChange} entityLineageHandler={handleCountChange}

View File

@ -1132,6 +1132,7 @@ code {
} }
.ant-popover-feed > .ant-popover-content > .ant-popover-inner, .ant-popover-feed > .ant-popover-content > .ant-popover-inner,
.ant-popover-card > .ant-popover-content > .ant-popover-inner, .ant-popover-card > .ant-popover-content > .ant-popover-inner,
.ant-popover-request-description > .ant-popover-content > .ant-popover-inner,
.ant-popover-feed-reactions > .ant-popover-content > .ant-popover-inner { .ant-popover-feed-reactions > .ant-popover-content > .ant-popover-inner {
border-radius: 6px; border-radius: 6px;
border: 1px solid #dde3ea; border: 1px solid #dde3ea;
@ -1341,3 +1342,7 @@ div.ant-typography-ellipsis-custom {
color: #7147e8; color: #7147e8;
background-color: #7147e825; background-color: #7147e825;
} }
.table-query-editor pre.CodeMirror-line {
padding-right: 60px !important;
}

View File

@ -11,9 +11,11 @@
* limitations under the License. * limitations under the License.
*/ */
import { AxiosError, AxiosResponse } from 'axios';
import classNames from 'classnames'; import classNames from 'classnames';
import { capitalize, isEmpty, isNull, isUndefined } from 'lodash'; import { capitalize, isEmpty, isNull, isUndefined } from 'lodash';
import { import {
EntityFieldThreadCount,
RecentlySearched, RecentlySearched,
RecentlySearchedData, RecentlySearchedData,
RecentlyViewed, RecentlyViewed,
@ -22,6 +24,7 @@ import {
import React, { FormEvent } from 'react'; import React, { FormEvent } from 'react';
import { reactLocalStorage } from 'reactjs-localstorage'; import { reactLocalStorage } from 'reactjs-localstorage';
import AppState from '../AppState'; import AppState from '../AppState';
import { getFeedCount } from '../axiosAPIs/feedsAPI';
import { Button } from '../components/buttons/Button/Button'; import { Button } from '../components/buttons/Button/Button';
import { FQN_SEPARATOR_CHAR } from '../constants/char.constants'; import { FQN_SEPARATOR_CHAR } from '../constants/char.constants';
import { import {
@ -36,12 +39,15 @@ import {
} from '../constants/regex.constants'; } from '../constants/regex.constants';
import { EntityType, FqnPart, TabSpecificField } from '../enums/entity.enum'; import { EntityType, FqnPart, TabSpecificField } from '../enums/entity.enum';
import { Ownership } from '../enums/mydata.enum'; import { Ownership } from '../enums/mydata.enum';
import { ThreadType } from '../generated/entity/feed/thread';
import { EntityReference, User } from '../generated/entity/teams/user'; import { EntityReference, User } from '../generated/entity/teams/user';
import { getTitleCase } from './EntityUtils'; import jsonData from '../jsons/en';
import { getEntityFeedLink, getTitleCase } from './EntityUtils';
import Fqn from './Fqn'; import Fqn from './Fqn';
import { getExplorePathWithInitFilters } from './RouterUtils'; import { getExplorePathWithInitFilters } from './RouterUtils';
import { serviceTypeLogo } from './ServiceUtils'; import { serviceTypeLogo } from './ServiceUtils';
import SVGIcons, { Icons } from './SvgUtils'; import SVGIcons, { Icons } from './SvgUtils';
import { showErrorToast } from './ToastUtils';
export const arraySorterByKey = ( export const arraySorterByKey = (
key: string, key: string,
@ -631,3 +637,48 @@ export const getExploreLinkByFilter = (
export const replaceSpaceWith_ = (text: string) => { export const replaceSpaceWith_ = (text: string) => {
return text.replace(/\s/g, '_'); return text.replace(/\s/g, '_');
}; };
export const getFeedCounts = (
entityType: string,
entityFQN: string,
conversationCallback: (
value: React.SetStateAction<EntityFieldThreadCount[]>
) => void,
taskCallback: (value: React.SetStateAction<EntityFieldThreadCount[]>) => void,
entityCallback: (value: React.SetStateAction<number>) => void
) => {
getFeedCount(
getEntityFeedLink(entityType, entityFQN),
ThreadType.Conversation
)
.then((res: AxiosResponse) => {
if (res.data) {
entityCallback(res.data.totalCount);
conversationCallback(res.data.counts);
} else {
throw jsonData['api-error-messages']['fetch-entity-feed-count-error'];
}
})
.catch((err: AxiosError) => {
showErrorToast(
err,
jsonData['api-error-messages']['fetch-entity-feed-count-error']
);
});
getFeedCount(getEntityFeedLink(entityType, entityFQN), ThreadType.Task)
.then((res: AxiosResponse) => {
if (res.data) {
entityCallback(res.data.totalCount);
taskCallback(res.data.counts);
} else {
throw jsonData['api-error-messages']['fetch-entity-feed-count-error'];
}
})
.catch((err: AxiosError) => {
showErrorToast(
err,
jsonData['api-error-messages']['fetch-entity-feed-count-error']
);
});
};

View File

@ -26,6 +26,7 @@ import {
DESCRIPTIONLENGTH, DESCRIPTIONLENGTH,
getTeamAndUserDetailsPath, getTeamAndUserDetailsPath,
} from '../constants/constants'; } from '../constants/constants';
import { EntityField } from '../constants/feed.constants';
import { ChangeType } from '../enums/entity.enum'; import { ChangeType } from '../enums/entity.enum';
import { Column } from '../generated/entity/data/table'; import { Column } from '../generated/entity/data/table';
import { import {
@ -301,7 +302,7 @@ export const feedSummaryFromatter = (
); );
break; break;
} else if (fieldChange?.name?.endsWith('description')) { } else if (fieldChange?.name?.endsWith(EntityField.DESCRIPTION)) {
summary = ( summary = (
<p key={uniqueId()}> <p key={uniqueId()}>
{`${ {`${
@ -321,7 +322,7 @@ export const feedSummaryFromatter = (
); );
break; break;
} else if (fieldChange?.name === 'columns') { } else if (fieldChange?.name === EntityField.COLUMNS) {
const length = value?.length ?? 0; const length = value?.length ?? 0;
summary = ( summary = (
<p key={uniqueId()}> <p key={uniqueId()}>
@ -419,7 +420,7 @@ export const feedSummaryFromatter = (
break; break;
} }
case fieldChange?.name === 'description': { case fieldChange?.name === EntityField.DESCRIPTION: {
summary = ( summary = (
<p key={uniqueId()}> <p key={uniqueId()}>
{`${ {`${
@ -523,7 +524,7 @@ export const summaryFormatter = (fieldChange: FieldChange) => {
? fieldChange?.oldValue ? fieldChange?.oldValue
: '{}' : '{}'
); );
if (fieldChange.name === 'columns') { if (fieldChange.name === EntityField.COLUMNS) {
return `columns ${value?.map((val: any) => val?.name).join(', ')}`; return `columns ${value?.map((val: any) => val?.name).join(', ')}`;
} else if ( } else if (
fieldChange.name === 'tags' || fieldChange.name === 'tags' ||

View File

@ -11,10 +11,11 @@
* limitations under the License. * limitations under the License.
*/ */
import { isEmpty, isUndefined } from 'lodash'; import { isEmpty, isEqual, isUndefined } from 'lodash';
import { EntityFieldThreads } from 'Models'; import { EntityFieldThreads } from 'Models';
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import { entityUrlMap } from '../constants/feed.constants'; import { entityUrlMap } from '../constants/feed.constants';
import { ThreadType } from '../generated/entity/feed/thread';
import { EntityReference } from '../generated/entity/teams/user'; import { EntityReference } from '../generated/entity/teams/user';
import { getEntityFeedLink } from './EntityUtils'; import { getEntityFeedLink } from './EntityUtils';
import { getThreadField } from './FeedUtils'; import { getThreadField } from './FeedUtils';
@ -24,11 +25,12 @@ export const getFieldThreadElement = (
columnName: string, columnName: string,
columnField: string, columnField: string,
entityFieldThreads: EntityFieldThreads[], entityFieldThreads: EntityFieldThreads[],
onThreadLinkSelect?: (value: string) => void, onThreadLinkSelect?: (value: string, threadType?: ThreadType) => void,
entityType?: string, entityType?: string,
entityFqn?: string, entityFqn?: string,
entityField?: string, entityField?: string,
flag = true flag = true,
threadType?: ThreadType
) => { ) => {
let threadValue: EntityFieldThreads = {} as EntityFieldThreads; let threadValue: EntityFieldThreads = {} as EntityFieldThreads;
@ -39,27 +41,36 @@ export const getFieldThreadElement = (
} }
}); });
const isTaskType = isEqual(threadType, ThreadType.Task);
return !isEmpty(threadValue) ? ( return !isEmpty(threadValue) ? (
<p <button
className="link-text tw-w-8 tw-h-8 tw-flex-none" className="link-text tw-self-start tw-w-8 tw-h-8 tw-flex-none tw-mx-1"
data-testid="field-thread" data-testid="field-thread"
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
onThreadLinkSelect?.(threadValue.entityLink); onThreadLinkSelect?.(
threadValue.entityLink,
isTaskType ? ThreadType.Task : ThreadType.Conversation
);
}}> }}>
<span className="tw-flex"> <span className="tw-flex">
<SVGIcons alt="comments" icon={Icons.COMMENT} width="20px" /> <SVGIcons
alt="comments"
icon={isTaskType ? Icons.TASK_ICON : Icons.COMMENT}
width={isTaskType ? '16px' : '20px'}
/>
<span className="tw-ml-1" data-testid="field-thread-count"> <span className="tw-ml-1" data-testid="field-thread-count">
{threadValue.count} {threadValue.count}
</span> </span>
</span> </span>
</p> </button>
) : ( ) : (
<Fragment> <Fragment>
{entityType && entityFqn && entityField && flag ? ( {entityType && entityFqn && entityField && flag && !isTaskType ? (
<p <button
className="link-text tw-self-start tw-w-8 tw-h-8 tw-opacity-0 tw-ml-1 group-hover:tw-opacity-100 tw-flex-none" className="link-text tw-self-start tw-w-8 tw-h-8 tw-flex-none tw-mx-1 tw-opacity-0 group-hover:tw-opacity-100"
data-testid="start-field-thread" data-testid="start-field-thread"
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
@ -69,7 +80,7 @@ export const getFieldThreadElement = (
); );
}}> }}>
<SVGIcons alt="comments" icon={Icons.COMMENT_PLUS} width="20px" /> <SVGIcons alt="comments" icon={Icons.COMMENT_PLUS} width="20px" />
</p> </button>
) : null} ) : null}
</Fragment> </Fragment>
); );

View File

@ -144,6 +144,7 @@ import IconTableGrey from '../assets/svg/table-grey.svg';
import IconTable from '../assets/svg/table.svg'; import IconTable from '../assets/svg/table.svg';
import IconTagGrey from '../assets/svg/tag-grey.svg'; import IconTagGrey from '../assets/svg/tag-grey.svg';
import IconTag from '../assets/svg/tag.svg'; import IconTag from '../assets/svg/tag.svg';
import IconTaskColor from '../assets/svg/Task-ic.svg';
import IconTeamsGrey from '../assets/svg/teams-grey.svg'; import IconTeamsGrey from '../assets/svg/teams-grey.svg';
import IconTerns from '../assets/svg/terms.svg'; import IconTerns from '../assets/svg/terms.svg';
import IconTier from '../assets/svg/tier.svg'; import IconTier from '../assets/svg/tier.svg';
@ -313,6 +314,7 @@ export const Icons = {
STAR: 'ic-star', STAR: 'ic-star',
MENTIONS: 'ic-mentions', MENTIONS: 'ic-mentions',
COMMENT_GREY: 'ic-comment-grey', COMMENT_GREY: 'ic-comment-grey',
TASK_ICON: 'task-icon',
}; };
const SVGIcons: FunctionComponent<Props> = ({ const SVGIcons: FunctionComponent<Props> = ({
@ -890,6 +892,10 @@ const SVGIcons: FunctionComponent<Props> = ({
case Icons.ALERT_BELL: case Icons.ALERT_BELL:
IconComponent = IconAlertBell; IconComponent = IconAlertBell;
break;
case Icons.TASK_ICON:
IconComponent = IconTaskColor;
break; break;
case Icons.TASK: case Icons.TASK: