fix(ui):supported separate tags for glossary and tags (#12068)

* supported separate tags for glossary and tags

* fix cypress issue

* fix cypress issue
This commit is contained in:
Ashish Gupta 2023-06-22 11:22:03 +05:30 committed by GitHub
parent 5197682921
commit 10ba297961
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 411 additions and 279 deletions

View File

@ -640,7 +640,7 @@ export const addNewTagToEntity = (entityObj, term) => {
.scrollIntoView()
.should('be.visible')
.click();
cy.get('[data-testid="entity-tags"]')
cy.get('[data-testid="tags-container"] [data-testid="entity-tags"]')
.scrollIntoView()
.should('be.visible')
.contains(term);

View File

@ -38,8 +38,9 @@ const addTags = (tag, parent) => {
const checkTags = (tag, checkForParentEntity) => {
if (checkForParentEntity) {
cy.get(
'[data-testid="entity-right-panel"] [data-testid="tag-container"] [data-testid="entity-tags"] '
'[data-testid="entity-right-panel"] [data-testid="tags-container"] [data-testid="entity-tags"] '
)
.scrollIntoView()
.should('be.visible')
.contains(tag);
@ -48,9 +49,9 @@ const checkTags = (tag, checkForParentEntity) => {
}
};
const removeTags = (checkForParentEntity, separate) => {
const removeTags = (checkForParentEntity) => {
if (checkForParentEntity) {
cy.get('[data-testid="entity-right-panel"] [data-testid="edit-button"] ')
cy.get('[data-testid="entity-right-panel"] [data-testid="edit-button"]')
.scrollIntoView()
.should('be.visible')
.click();
@ -86,7 +87,9 @@ describe('Check if tags addition and removal flow working properly from tables',
entityDetails.entity
);
cy.get('[data-testid="entity-right-panel"] [data-testid="add-tag"]')
cy.get(
'[data-testid="entity-right-panel"] [data-testid="tags-container"] [data-testid="add-tag"]'
)
.should('be.visible')
.click();

View File

@ -281,7 +281,7 @@ describe('Tags page should work', () => {
cy.get('[data-testid="tag-selector"] > .ant-select-selector').contains(tag);
cy.get('[data-testid="saveAssociatedTag"]').scrollIntoView().click();
verifyResponseStatusCode('@addTags', 200);
cy.get('[data-testid="entity-tags"]')
cy.get('[data-testid="tag-container"]')
.scrollIntoView()
.should('be.visible')
.contains(tag);

View File

@ -36,7 +36,7 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useHistory, useParams } from 'react-router-dom';
import { restoreDashboard } from 'rest/dashboardAPI';
import { getEntityName } from 'utils/EntityUtils';
import { getEntityName, getEntityThreadLink } from 'utils/EntityUtils';
import { getFilterTags } from 'utils/TableTags/TableTags.utils';
import { ReactComponent as ExternalLinkIcon } from '../../assets/svg/external-links.svg';
import { EntityField } from '../../constants/Feeds.constants';
@ -645,20 +645,35 @@ const DashboardDetails = ({
className="entity-tag-right-panel-container"
data-testid="entity-right-panel"
flex="320px">
<TagsContainerV1
editable={
dashboardPermissions.EditAll || dashboardPermissions.EditTags
}
entityFieldThreads={getEntityFieldThreadCounts(
EntityField.TAGS,
entityFieldThreadCount
)}
entityFqn={dashboardDetails.fullyQualifiedName}
entityType={EntityType.DASHBOARD}
selectedTags={dashboardTags}
onSelectionChange={handleTagSelection}
onThreadLinkSelect={onThreadLinkSelect}
/>
<Space className="w-full" direction="vertical" size="large">
<TagsContainerV1
entityFqn={dashboardDetails.fullyQualifiedName}
entityThreadLink={getEntityThreadLink(entityFieldThreadCount)}
entityType={EntityType.DASHBOARD}
permission={
dashboardPermissions.EditAll ||
dashboardPermissions.EditTags
}
selectedTags={dashboardTags}
tagType={TagSource.Classification}
onSelectionChange={handleTagSelection}
onThreadLinkSelect={onThreadLinkSelect}
/>
<TagsContainerV1
entityFqn={dashboardDetails.fullyQualifiedName}
entityThreadLink={getEntityThreadLink(entityFieldThreadCount)}
entityType={EntityType.DASHBOARD}
permission={
dashboardPermissions.EditAll ||
dashboardPermissions.EditTags
}
selectedTags={dashboardTags}
tagType={TagSource.Glossary}
onSelectionChange={handleTagSelection}
onThreadLinkSelect={onThreadLinkSelect}
/>
</Space>
</Col>
</Row>
),

View File

@ -11,7 +11,7 @@
* limitations under the License.
*/
import { Card, Col, Row, Tabs } from 'antd';
import { Card, Col, Row, Space, Tabs } from 'antd';
import ActivityFeedProvider, {
useActivityFeedProvider,
} from 'components/ActivityFeed/ActivityFeedProvider/ActivityFeedProvider';
@ -29,13 +29,13 @@ import { getDataModelDetailsPath, getVersionPath } from 'constants/constants';
import { EntityField } from 'constants/Feeds.constants';
import { CSMode } from 'enums/codemirror.enum';
import { EntityTabs, EntityType } from 'enums/entity.enum';
import { LabelType, State, TagLabel } from 'generated/type/tagLabel';
import { LabelType, State, TagLabel, TagSource } from 'generated/type/tagLabel';
import { isUndefined, toString } from 'lodash';
import { EntityTags } from 'Models';
import React, { useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useHistory, useParams } from 'react-router-dom';
import { getEntityName } from 'utils/EntityUtils';
import { getEntityName, getEntityThreadLink } from 'utils/EntityUtils';
import { getEntityFieldThreadCounts } from 'utils/FeedUtils';
import { getTagsWithoutTier } from 'utils/TableUtils';
import { DataModelDetailsProps } from './DataModelDetails.interface';
@ -176,18 +176,28 @@ const DataModelDetails = ({
className="entity-tag-right-panel-container"
data-testid="entity-right-panel"
flex="320px">
<TagsContainerV1
editable={hasEditTagsPermission}
entityFieldThreads={getEntityFieldThreadCounts(
EntityField.TAGS,
entityFieldThreadCount
)}
entityFqn={dashboardDataModelFQN}
entityType={EntityType.DASHBOARD_DATA_MODEL}
selectedTags={tags}
onSelectionChange={handleTagSelection}
onThreadLinkSelect={onThreadLinkSelect}
/>
<Space className="w-full" direction="vertical" size="large">
<TagsContainerV1
entityFqn={dashboardDataModelFQN}
entityThreadLink={getEntityThreadLink(entityFieldThreadCount)}
entityType={EntityType.DASHBOARD_DATA_MODEL}
permission={hasEditTagsPermission}
selectedTags={tags}
tagType={TagSource.Classification}
onSelectionChange={handleTagSelection}
onThreadLinkSelect={onThreadLinkSelect}
/>
<TagsContainerV1
entityFqn={dashboardDataModelFQN}
entityThreadLink={getEntityThreadLink(entityFieldThreadCount)}
entityType={EntityType.DASHBOARD_DATA_MODEL}
permission={hasEditTagsPermission}
selectedTags={tags}
tagType={TagSource.Glossary}
onSelectionChange={handleTagSelection}
onThreadLinkSelect={onThreadLinkSelect}
/>
</Space>
</Col>
</Row>
);

View File

@ -11,7 +11,7 @@
* limitations under the License.
*/
import { Card, Col, Row, Table, Tabs, Typography } from 'antd';
import { Card, Col, Row, Space, Table, Tabs, Typography } from 'antd';
import { ColumnsType } from 'antd/lib/table';
import { AxiosError } from 'axios';
import ActivityFeedProvider, {
@ -24,14 +24,14 @@ import { DataAssetsHeader } from 'components/DataAssets/DataAssetsHeader/DataAss
import { EntityName } from 'components/Modals/EntityNameModal/EntityNameModal.interface';
import TabsLabel from 'components/TabsLabel/TabsLabel.component';
import TagsContainerV1 from 'components/Tag/TagsContainerV1/TagsContainerV1';
import { TagLabel } from 'generated/type/schema';
import { TagLabel, TagSource } from 'generated/type/schema';
import { isEmpty } from 'lodash';
import { EntityTags } from 'Models';
import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useHistory, useParams } from 'react-router-dom';
import { restoreMlmodel } from 'rest/mlModelAPI';
import { getEntityName } from 'utils/EntityUtils';
import { getEntityName, getEntityThreadLink } from 'utils/EntityUtils';
import AppState from '../../AppState';
import { getMlModelDetailsPath } from '../../constants/constants';
import { EntityField } from '../../constants/Feeds.constants';
@ -392,20 +392,33 @@ const MlModelDetail: FC<MlModelDetailProp> = ({
className="entity-tag-right-panel-container"
data-testid="entity-right-panel"
flex="320px">
<TagsContainerV1
editable={
mlModelPermissions.EditAll || mlModelPermissions.EditTags
}
entityFieldThreads={getEntityFieldThreadCounts(
EntityField.TAGS,
entityFieldThreadCount
)}
entityFqn={mlModelDetail.fullyQualifiedName}
entityType={EntityType.MLMODEL}
selectedTags={mlModelTags}
onSelectionChange={handleTagSelection}
onThreadLinkSelect={handleThreadLinkSelect}
/>
<Space className="w-full" direction="vertical" size="large">
<TagsContainerV1
entityFqn={mlModelDetail.fullyQualifiedName}
entityThreadLink={getEntityThreadLink(entityFieldThreadCount)}
entityType={EntityType.MLMODEL}
permission={
mlModelPermissions.EditAll || mlModelPermissions.EditTags
}
selectedTags={mlModelTags}
tagType={TagSource.Classification}
onSelectionChange={handleTagSelection}
onThreadLinkSelect={handleThreadLinkSelect}
/>
<TagsContainerV1
entityFqn={mlModelDetail.fullyQualifiedName}
entityThreadLink={getEntityThreadLink(entityFieldThreadCount)}
entityType={EntityType.MLMODEL}
permission={
mlModelPermissions.EditAll || mlModelPermissions.EditTags
}
selectedTags={mlModelTags}
tagType={TagSource.Glossary}
onSelectionChange={handleTagSelection}
onThreadLinkSelect={handleThreadLinkSelect}
/>
</Space>
</Col>
</Row>
),

View File

@ -65,7 +65,7 @@ import {
getFeedCounts,
refreshPage,
} from '../../utils/CommonUtils';
import { getEntityName } from '../../utils/EntityUtils';
import { getEntityName, getEntityThreadLink } from '../../utils/EntityUtils';
import { getEntityFieldThreadCounts } from '../../utils/FeedUtils';
import { DEFAULT_ENTITY_PERMISSION } from '../../utils/PermissionsUtils';
import { getTagsWithoutTier, getTierTags } from '../../utils/TableUtils';
@ -651,20 +651,33 @@ const PipelineDetails = ({
className="entity-tag-right-panel-container"
data-testid="entity-right-panel"
flex="320px">
<TagsContainerV1
editable={
pipelinePermissions.EditAll || pipelinePermissions.EditTags
}
entityFieldThreads={getEntityFieldThreadCounts(
EntityField.TAGS,
entityFieldThreadCount
)}
entityFqn={pipelineFQN}
entityType={EntityType.TOPIC}
selectedTags={tags}
onSelectionChange={handleTagSelection}
onThreadLinkSelect={onThreadLinkSelect}
/>
<Space className="w-full" direction="vertical" size="large">
<TagsContainerV1
entityFqn={pipelineFQN}
entityThreadLink={getEntityThreadLink(entityFieldThreadCount)}
entityType={EntityType.PIPELINE}
permission={
pipelinePermissions.EditAll || pipelinePermissions.EditTags
}
selectedTags={tags}
tagType={TagSource.Classification}
onSelectionChange={handleTagSelection}
onThreadLinkSelect={onThreadLinkSelect}
/>
<TagsContainerV1
entityFqn={pipelineFQN}
entityThreadLink={getEntityThreadLink(entityFieldThreadCount)}
entityType={EntityType.PIPELINE}
permission={
pipelinePermissions.EditAll || pipelinePermissions.EditTags
}
selectedTags={tags}
tagType={TagSource.Glossary}
onSelectionChange={handleTagSelection}
onThreadLinkSelect={onThreadLinkSelect}
/>
</Space>
</Col>
</Row>
),

View File

@ -15,7 +15,6 @@ import { ThreadType } from 'generated/api/feed/createThread';
import { Tag } from 'generated/entity/classification/tag';
import { GlossaryTerm } from 'generated/entity/data/glossaryTerm';
import { TagSource } from 'generated/type/tagLabel';
import { EntityFieldThreads } from 'interface/feed.interface';
import { EntityTags } from 'Models';
interface TagsTreeProps {
@ -29,10 +28,6 @@ export interface HierarchyTagsProps extends TagsTreeProps {
children: TagsTreeProps[];
}
export interface GlossaryTermNodeProps extends TagsTreeProps {
children: TagsTreeProps[] | undefined;
}
export type TagDetailsProps = {
isLoading: boolean;
options: {
@ -60,13 +55,19 @@ export type GlossaryTermDetailsProps = {
};
export type TagsContainerV1Props = {
editable: boolean;
permission: boolean;
selectedTags: Array<EntityTags>;
onSelectionChange: (selectedTags: Array<EntityTags>) => void;
placeholder?: string;
showLimited?: boolean;
onThreadLinkSelect?: (value: string, threadType?: ThreadType) => void;
onThreadLinkSelect: (value: string, threadType?: ThreadType) => void;
entityType?: string;
entityFieldThreads?: EntityFieldThreads[];
entityThreadLink?: string;
entityFqn?: string;
tagType: TagSource;
};
export type TagsTreeComponentProps = {
placeholder: string;
treeData: HierarchyTagsProps[];
defaultValue: string[];
onChange?: (value: string[]) => void;
};

View File

@ -20,40 +20,37 @@ import {
Popover,
Row,
Space,
TreeSelect,
Typography,
} from 'antd';
import { ReactComponent as EditIcon } from 'assets/svg/edit-new.svg';
import classNames from 'classnames';
import Loader from 'components/Loader/Loader';
import { TableTagsProps } from 'components/TableTags/TableTags.interface';
import Tags from 'components/Tag/Tags/tags';
import {
API_RES_MAX_SIZE,
DE_ACTIVE_COLOR,
NO_DATA_PLACEHOLDER,
PAGE_SIZE_LARGE,
} from 'constants/constants';
import { TAG_CONSTANT, TAG_START_WITH } from 'constants/Tag.constants';
import { EntityType } from 'enums/entity.enum';
import { TagSource } from 'generated/type/tagLabel';
import { isEmpty, isUndefined } from 'lodash';
import { isEmpty } from 'lodash';
import { EntityTags } from 'Models';
import React, { useCallback, useMemo, useState } from 'react';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useHistory } from 'react-router-dom';
import { getGlossariesList, getGlossaryTerms } from 'rest/glossaryAPI';
import { getEntityFeedLink } from 'utils/EntityUtils';
import { getGlossaryTermHierarchy } from 'utils/GlossaryUtils';
import { getFilterTags } from 'utils/TableTags/TableTags.utils';
import { getAllTagsForOptions, getTagsHierarchy } from 'utils/TagsUtils';
import {
getRequestTagsPath,
getUpdateTagsPath,
TASK_ENTITIES,
} from 'utils/TasksUtils';
import { ReactComponent as IconCommentPlus } from '../../../assets/svg/add-chat.svg';
import { ReactComponent as IconComments } from '../../../assets/svg/comment.svg';
import { ReactComponent as IconRequest } from '../../../assets/svg/request-icon.svg';
import TagsV1 from '../TagsV1/TagsV1.component';
import TagsViewer from '../TagsViewer/tags-viewer';
import {
GlossaryDetailsProps,
@ -61,17 +58,17 @@ import {
TagDetailsProps,
TagsContainerV1Props,
} from './TagsContainerV1.interface';
import TagTree from './TagsTree.component';
const TagsContainerV1 = ({
editable,
permission,
selectedTags,
onSelectionChange,
placeholder,
showLimited,
onThreadLinkSelect,
entityType,
entityFieldThreads,
entityThreadLink,
entityFqn,
tagType,
onSelectionChange,
onThreadLinkSelect,
}: TagsContainerV1Props) => {
const history = useHistory();
const [form] = Form.useForm();
@ -90,19 +87,41 @@ const TagsContainerV1 = ({
options: [],
});
const tagThread = entityFieldThreads?.[0];
const [tags, setTags] = useState<TableTagsProps>();
const showAddTagButton = useMemo(
() => editable && isEmpty(selectedTags),
[editable, selectedTags]
const isGlossaryType = useMemo(
() => tagType === TagSource.Glossary,
[tagType]
);
const handleRequestTags = () => {
history.push(getRequestTagsPath(entityType as string, entityFqn as string));
};
const handleUpdateTags = () => {
history.push(getUpdateTagsPath(entityType as string, entityFqn as string));
};
const searchPlaceholder = useMemo(
() =>
isGlossaryType
? t('label.search-entity', {
entity: t('label.glossary-term-plural'),
})
: t('label.search-entity', {
entity: t('label.tag-plural'),
}),
[isGlossaryType]
);
const showAddTagButton = useMemo(
() => permission && isEmpty(tags?.[tagType]),
[permission, tags?.[tagType]]
);
const selectedTagsInternal = useMemo(
() => tags?.[tagType].map(({ tagFQN }) => tagFQN as string),
[tags, tagType]
);
const getTreeData = useMemo(() => {
const tags = getTagsHierarchy(tagDetails.options);
const glossary = getGlossaryTermHierarchy(glossaryDetails.options);
return [...tags, ...glossary];
}, [tagDetails.options, glossaryDetails.options]);
const fetchTags = async () => {
if (isEmpty(tagDetails.options) || tagDetails.isError) {
@ -172,8 +191,8 @@ const TagsContainerV1 = ({
};
const showNoDataPlaceholder = useMemo(
() => !showAddTagButton && selectedTags.length === 0,
[showAddTagButton, selectedTags]
() => !showAddTagButton && isEmpty(tags?.[tagType]),
[showAddTagButton, tags?.[tagType]]
);
const getUpdatedTags = (selectedTag: string[]): EntityTags[] => {
@ -188,8 +207,13 @@ const TagsContainerV1 = ({
};
const handleSave: FormProps['onFinish'] = (data) => {
const tags = getUpdatedTags(data.tags);
onSelectionChange(tags);
const updatedTags = getUpdatedTags(data.tags);
onSelectionChange([
...updatedTags,
...((isGlossaryType
? tags?.[TagSource.Classification]
: tags?.[TagSource.Glossary]) ?? []),
]);
form.resetFields();
setIsEditTags(false);
};
@ -199,15 +223,14 @@ const TagsContainerV1 = ({
form.resetFields();
}, [form]);
const handleAddClick = () => {
fetchTags();
fetchGlossaryList();
const handleAddClick = useCallback(() => {
if (isGlossaryType) {
fetchGlossaryList();
} else {
fetchTags();
}
setIsEditTags(true);
};
const getTagsElement = (tag: EntityTags) => (
<TagsV1 key={tag.tagFQN} tag={tag} />
);
}, [isGlossaryType, fetchGlossaryList, fetchTags]);
const addTagButton = useMemo(
() =>
@ -225,47 +248,19 @@ const TagsContainerV1 = ({
);
const renderTags = useMemo(
() =>
showLimited ? (
<TagsViewer
isTextPlaceholder
showNoDataPlaceholder={showNoDataPlaceholder}
tags={selectedTags}
type="border"
/>
) : (
<>
{!showAddTagButton && isEmpty(selectedTags) ? (
<Typography.Text data-testid="no-tags">
{NO_DATA_PLACEHOLDER}
</Typography.Text>
) : null}
{selectedTags.map(getTagsElement)}
</>
),
[
showLimited,
showNoDataPlaceholder,
selectedTags,
getTagsElement,
showAddTagButton,
]
() => (
<TagsViewer
isTextPlaceholder
showNoDataPlaceholder={showNoDataPlaceholder}
tags={tags?.[tagType] ?? []}
type="border"
/>
),
[showNoDataPlaceholder, tags?.[tagType]]
);
const selectedTagsInternal = useMemo(
() => selectedTags.map(({ tagFQN }) => tagFQN as string),
[selectedTags]
);
const getTreeData = useMemo(() => {
const tags = getTagsHierarchy(tagDetails.options);
const glossary = getGlossaryTermHierarchy(glossaryDetails.options);
return [...tags, ...glossary];
}, [tagDetails.options, glossaryDetails.options]);
const tagsSelectContainer = useMemo(() => {
return tagDetails.isLoading && glossaryDetails.isLoading ? (
return tagDetails.isLoading || glossaryDetails.isLoading ? (
<Loader size="small" />
) : (
<Form form={form} name="tagsForm" onFinish={handleSave}>
@ -292,32 +287,10 @@ const TagsContainerV1 = ({
<Col className="gutter-row" span={24}>
<Form.Item noStyle name="tags">
<TreeSelect
autoFocus
multiple
showSearch
treeDefaultExpandAll
treeLine
className={classNames('w-full')}
data-testid="tag-selector"
defaultValue={selectedTagsInternal}
placeholder={
placeholder
? placeholder
: t('label.select-field', {
field: t('label.tag-plural'),
})
}
removeIcon={
<CloseOutlined
data-testid="remove-tags"
height={8}
width={8}
/>
}
showCheckedStrategy={TreeSelect.SHOW_ALL}
<TagTree
defaultValue={selectedTagsInternal ?? []}
placeholder={searchPlaceholder}
treeData={getTreeData}
treeNodeFilterProp="title"
/>
</Form.Item>
</Col>
@ -325,23 +298,26 @@ const TagsContainerV1 = ({
</Form>
);
}, [
searchPlaceholder,
selectedTagsInternal,
handleCancel,
handleSave,
placeholder,
glossaryDetails,
tagDetails,
getTreeData,
handleCancel,
handleSave,
]);
const getRequestTagsElements = useCallback(() => {
const hasTags = !isEmpty(selectedTags);
const text = hasTags
? t('label.update-request-tag-plural')
: t('label.request-tag-plural');
const handleRequestTags = () => {
history.push(getRequestTagsPath(entityType as string, entityFqn as string));
};
const handleUpdateTags = () => {
history.push(getUpdateTagsPath(entityType as string, entityFqn as string));
};
return onThreadLinkSelect &&
TASK_ENTITIES.includes(entityType as EntityType) ? (
const requestTagElement = useMemo(() => {
const hasTags = !isEmpty(tags?.[tagType]);
return TASK_ENTITIES.includes(entityType as EntityType) ? (
<Col>
<Button
className="p-0 flex-center"
@ -351,7 +327,11 @@ const TagsContainerV1 = ({
onClick={hasTags ? handleUpdateTags : handleRequestTags}>
<Popover
destroyTooltipOnHide
content={text}
content={
hasTags
? t('label.update-request-tag-plural')
: t('label.request-tag-plural')
}
overlayClassName="ant-popover-request-description"
placement="topLeft"
trigger="hover"
@ -366,57 +346,53 @@ const TagsContainerV1 = ({
</Button>
</Col>
) : null;
}, [tags?.[tagType], handleUpdateTags, handleRequestTags]);
const conversationThreadElement = useMemo(
() => (
<Col>
<Button
className="p-0 flex-center"
data-testid="tag-thread"
size="small"
type="text"
onClick={() =>
onThreadLinkSelect(
entityThreadLink ??
getEntityFeedLink(entityType, entityFqn, 'tags')
)
}>
<Space align="center" className="w-full h-full" size={2}>
<IconComments height={16} name="comments" width={16} />
</Space>
</Button>
</Col>
),
[
entityType,
entityFqn,
entityThreadLink,
getEntityFeedLink,
onThreadLinkSelect,
]
);
useEffect(() => {
setTags(getFilterTags(selectedTags));
}, [selectedTags]);
const getThreadElements = () => {
if (!isUndefined(entityFieldThreads)) {
return !isUndefined(tagThread) ? (
<Col>
<Button
className="p-0 flex-center"
data-testid="tag-thread"
size="small"
type="text"
onClick={() => onThreadLinkSelect?.(tagThread.entityLink)}>
<Space align="center" className="w-full h-full" size={2}>
<IconComments height={16} name="comments" width={16} />
<span data-testid="tag-thread-count">{tagThread.count}</span>
</Space>
</Button>
</Col>
) : (
<Col>
<Button
className="p-0 flex-center"
data-testid="start-tag-thread"
icon={<IconCommentPlus height={16} name="comments" width={16} />}
size="small"
type="text"
onClick={() =>
onThreadLinkSelect?.(
getEntityFeedLink(entityType, entityFqn, 'tags')
)
}
/>
</Col>
);
} else {
return null;
}
};
return (
<div data-testid="tag-container">
<div data-testid={isGlossaryType ? 'glossary-container' : 'tags-container'}>
<div className="d-flex justify-between m-b-xs">
<div className="d-flex items-center">
<Typography.Text className="right-panel-label">
{t('label.tag-plural')}
{isGlossaryType ? t('label.glossary-term') : t('label.tag-plural')}
</Typography.Text>
{editable && selectedTags.length > 0 && (
{permission && !isEmpty(tags?.[tagType]) && (
<Button
className="cursor-pointer flex-center m-l-xss"
data-testid="edit-button"
disabled={!editable}
disabled={!permission}
icon={<EditIcon color={DE_ACTIVE_COLOR} width="14px" />}
size="small"
type="text"
@ -425,8 +401,8 @@ const TagsContainerV1 = ({
)}
</div>
<Row gutter={8}>
{getRequestTagsElements()}
{getThreadElements()}
{requestTagElement}
{conversationThreadElement}
</Row>
</div>

View File

@ -0,0 +1,47 @@
/*
* 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 { CloseOutlined } from '@ant-design/icons';
import { TreeSelect } from 'antd';
import classNames from 'classnames';
import React from 'react';
import { TagsTreeComponentProps } from './TagsContainerV1.interface';
const TagTree = ({
defaultValue,
placeholder,
treeData,
onChange,
}: TagsTreeComponentProps) => {
return (
<TreeSelect
autoFocus
multiple
showSearch
treeDefaultExpandAll
treeLine
className={classNames('w-full')}
data-testid="tag-selector"
defaultValue={defaultValue}
placeholder={placeholder}
removeIcon={
<CloseOutlined data-testid="remove-tags" height={8} width={8} />
}
showCheckedStrategy={TreeSelect.SHOW_ALL}
treeData={treeData}
treeNodeFilterProp="title"
onChange={onChange}
/>
);
};
export default TagTree;

View File

@ -11,7 +11,7 @@
* limitations under the License.
*/
import { Card, Col, Row, Tabs } from 'antd';
import { Card, Col, Row, Space, Tabs } from 'antd';
import { AxiosError } from 'axios';
import ActivityFeedProvider, {
useActivityFeedProvider,
@ -30,12 +30,12 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useHistory, useParams } from 'react-router-dom';
import { restoreTopic } from 'rest/topicsAPI';
import { getEntityName } from 'utils/EntityUtils';
import { getEntityName, getEntityThreadLink } from 'utils/EntityUtils';
import { EntityField } from '../../constants/Feeds.constants';
import { EntityTabs, EntityType } from '../../enums/entity.enum';
import { Topic } from '../../generated/entity/data/topic';
import { ThreadType } from '../../generated/entity/feed/thread';
import { LabelType, State } from '../../generated/type/tagLabel';
import { LabelType, State, TagSource } from '../../generated/type/tagLabel';
import { getCurrentUserId, refreshPage } from '../../utils/CommonUtils';
import { getEntityFieldThreadCounts } from '../../utils/FeedUtils';
import { DEFAULT_ENTITY_PERMISSION } from '../../utils/PermissionsUtils';
@ -307,18 +307,33 @@ const TopicDetails: React.FC<TopicDetailsProps> = ({
className="entity-tag-right-panel-container"
data-testid="entity-right-panel"
flex="320px">
<TagsContainerV1
editable={topicPermissions.EditAll || topicPermissions.EditTags}
entityFieldThreads={getEntityFieldThreadCounts(
EntityField.TAGS,
entityFieldThreadCount
)}
entityFqn={topicDetails.fullyQualifiedName}
entityType={EntityType.TOPIC}
selectedTags={topicTags}
onSelectionChange={handleTagSelection}
onThreadLinkSelect={onThreadLinkSelect}
/>
<Space className="w-full" direction="vertical" size="large">
<TagsContainerV1
entityFqn={topicDetails.fullyQualifiedName}
entityThreadLink={getEntityThreadLink(entityFieldThreadCount)}
entityType={EntityType.TOPIC}
permission={
topicPermissions.EditAll || topicPermissions.EditTags
}
selectedTags={topicTags}
tagType={TagSource.Classification}
onSelectionChange={handleTagSelection}
onThreadLinkSelect={onThreadLinkSelect}
/>
<TagsContainerV1
entityFqn={topicDetails.fullyQualifiedName}
entityThreadLink={getEntityThreadLink(entityFieldThreadCount)}
entityType={EntityType.TOPIC}
permission={
topicPermissions.EditAll || topicPermissions.EditTags
}
selectedTags={topicTags}
tagType={TagSource.Glossary}
onSelectionChange={handleTagSelection}
onThreadLinkSelect={onThreadLinkSelect}
/>
</Space>
</Col>
</Row>
),

View File

@ -10,7 +10,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Card, Col, Row, Tabs } from 'antd';
import { Card, Col, Row, Space, Tabs } from 'antd';
import AppState from 'AppState';
import { AxiosError } from 'axios';
import ActivityFeedProvider, {
@ -77,7 +77,11 @@ import {
refreshPage,
sortTagsCaseInsensitive,
} from 'utils/CommonUtils';
import { getEntityLineage, getEntityName } from 'utils/EntityUtils';
import {
getEntityLineage,
getEntityName,
getEntityThreadLink,
} from 'utils/EntityUtils';
import { getEntityFieldThreadCounts } from 'utils/FeedUtils';
import { DEFAULT_ENTITY_PERMISSION } from 'utils/PermissionsUtils';
import { getLineageViewPath } from 'utils/RouterUtils';
@ -623,18 +627,28 @@ const ContainerPage = () => {
className="entity-tag-right-panel-container"
data-testid="entity-right-panel"
flex="320px">
<TagsContainerV1
editable={hasEditDescriptionPermission}
entityFieldThreads={getEntityFieldThreadCounts(
EntityField.TAGS,
entityFieldThreadCount
)}
entityFqn={containerName}
entityType={EntityType.CONTAINER}
selectedTags={tags}
onSelectionChange={handleTagSelection}
onThreadLinkSelect={onThreadLinkSelect}
/>
<Space className="w-full" direction="vertical" size="large">
<TagsContainerV1
entityFqn={containerName}
entityThreadLink={getEntityThreadLink(entityFieldThreadCount)}
entityType={EntityType.CONTAINER}
permission={hasEditDescriptionPermission}
selectedTags={tags}
tagType={TagSource.Classification}
onSelectionChange={handleTagSelection}
onThreadLinkSelect={onThreadLinkSelect}
/>
<TagsContainerV1
entityFqn={containerName}
entityThreadLink={getEntityThreadLink(entityFieldThreadCount)}
entityType={EntityType.CONTAINER}
permission={hasEditDescriptionPermission}
selectedTags={tags}
tagType={TagSource.Glossary}
onSelectionChange={handleTagSelection}
onThreadLinkSelect={onThreadLinkSelect}
/>
</Space>
</Col>
</Row>
),

View File

@ -10,7 +10,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Card, Col, Divider, Row, Tabs } from 'antd';
import { Card, Col, Divider, Row, Space, Tabs } from 'antd';
import { AxiosError } from 'axios';
import ActivityFeedProvider, {
useActivityFeedProvider,
@ -46,7 +46,7 @@ import { compare } from 'fast-json-patch';
import { CreateThread } from 'generated/api/feed/createThread';
import { JoinedWith, Table } from 'generated/entity/data/table';
import { ThreadType } from 'generated/entity/feed/thread';
import { LabelType, State, TagLabel } from 'generated/type/tagLabel';
import { LabelType, State, TagLabel, TagSource } from 'generated/type/tagLabel';
import { EntityFieldThreadCount } from 'interface/feed.interface';
import { isEmpty, isEqual } from 'lodash';
import { EntityTags } from 'Models';
@ -71,7 +71,7 @@ import {
sortTagsCaseInsensitive,
} from 'utils/CommonUtils';
import { defaultFields } from 'utils/DatasetDetailsUtils';
import { getEntityName } from 'utils/EntityUtils';
import { getEntityName, getEntityThreadLink } from 'utils/EntityUtils';
import { getEntityFieldThreadCounts } from 'utils/FeedUtils';
import { DEFAULT_ENTITY_PERMISSION } from 'utils/PermissionsUtils';
import { createQueryFilter } from 'utils/Query/QueryUtils';
@ -448,19 +448,30 @@ const TableDetailsPageV1 = () => {
<Divider className="m-y-sm" />
</>
) : null}
<TagsContainerV1
showLimited
editable={tablePermissions.EditAll || tablePermissions.EditTags}
entityFieldThreads={getEntityFieldThreadCounts(
EntityField.TAGS,
entityFieldThreadCount
)}
entityFqn={datasetFQN}
entityType={EntityType.TABLE}
selectedTags={tableTags}
onSelectionChange={handleTagSelection}
onThreadLinkSelect={onThreadLinkSelect}
/>
<Space className="w-full" direction="vertical" size="large">
<TagsContainerV1
entityFqn={datasetFQN}
entityThreadLink={getEntityThreadLink(entityFieldThreadCount)}
entityType={EntityType.TABLE}
permission={tablePermissions.EditAll || tablePermissions.EditTags}
selectedTags={tableTags}
tagType={TagSource.Classification}
onSelectionChange={handleTagSelection}
onThreadLinkSelect={onThreadLinkSelect}
/>
<TagsContainerV1
entityFqn={datasetFQN}
entityThreadLink={getEntityThreadLink(entityFieldThreadCount)}
entityType={EntityType.TABLE}
permission={tablePermissions.EditAll || tablePermissions.EditTags}
selectedTags={tableTags}
tagType={TagSource.Glossary}
onSelectionChange={handleTagSelection}
onThreadLinkSelect={onThreadLinkSelect}
/>
</Space>
</Col>
</Row>
),

View File

@ -27,6 +27,7 @@ import {
SearchedDataProps,
SourceType,
} from 'components/searched-data/SearchedData.interface';
import { EntityField } from 'constants/Feeds.constants';
import { ExplorePageTabs } from 'enums/Explore.enum';
import { Container } from 'generated/entity/data/container';
import { DashboardDataModel } from 'generated/entity/data/dashboardDataModel';
@ -34,6 +35,7 @@ import { GlossaryTerm } from 'generated/entity/data/glossaryTerm';
import { Mlmodel } from 'generated/entity/data/mlmodel';
import { Topic } from 'generated/entity/data/topic';
import i18next from 'i18next';
import { EntityFieldThreadCount } from 'interface/feed.interface';
import { get, isEmpty, isNil, isUndefined, lowerCase, startCase } from 'lodash';
import { Bucket, EntityDetailUnion } from 'Models';
import React, { Fragment } from 'react';
@ -74,6 +76,7 @@ import {
getPartialNameFromTableFQN,
getTableFQNFromColumnFQN,
} from './CommonUtils';
import { getEntityFieldThreadCounts } from './FeedUtils';
import Fqn from './Fqn';
import { getGlossaryPath } from './RouterUtils';
import {
@ -1064,3 +1067,14 @@ export const getEntityLinkFromType = (
return '';
}
};
export const getEntityThreadLink = (
entityFieldThreadCount: EntityFieldThreadCount[]
) => {
const thread = getEntityFieldThreadCounts(
EntityField.TAGS,
entityFieldThreadCount
);
return thread[0]?.entityLink;
};

View File

@ -15,7 +15,7 @@ import { AxiosError } from 'axios';
import { ModifiedGlossaryTerm } from 'components/Glossary/GlossaryTermTab/GlossaryTermTab.interface';
import {
GlossaryTermDetailsProps,
GlossaryTermNodeProps,
HierarchyTagsProps,
} from 'components/Tag/TagsContainerV1/TagsContainerV1.interface';
import { isUndefined, omit } from 'lodash';
import { ListGlossaryTermsParams } from 'rest/glossaryAPI';
@ -224,9 +224,9 @@ export const formatRelatedTermOptions = (
export const getGlossaryTermHierarchy = (
data: GlossaryTermDetailsProps[]
): GlossaryTermNodeProps[] => {
const nodes: Record<string, GlossaryTermNodeProps> = {};
const tree: GlossaryTermNodeProps[] = [];
): HierarchyTagsProps[] => {
const nodes: Record<string, HierarchyTagsProps> = {};
const tree: HierarchyTagsProps[] = [];
data.forEach((obj) => {
if (obj.fqn) {