From 10ba29796189cdc9256b8cf9cd5d0a830eed10c5 Mon Sep 17 00:00:00 2001 From: Ashish Gupta Date: Thu, 22 Jun 2023 11:22:03 +0530 Subject: [PATCH] fix(ui):supported separate tags for glossary and tags (#12068) * supported separate tags for glossary and tags * fix cypress issue * fix cypress issue --- .../resources/ui/cypress/common/common.js | 2 +- .../ui/cypress/e2e/Flow/TagsAddRemove.spec.js | 11 +- .../ui/cypress/e2e/Pages/Tags.spec.js | 2 +- .../DashboardDetails.component.tsx | 45 ++- .../DataModels/DataModelDetails.component.tsx | 40 ++- .../MlModelDetail/MlModelDetail.component.tsx | 47 +-- .../PipelineDetails.component.tsx | 43 ++- .../TagsContainerV1.interface.ts | 21 +- .../Tag/TagsContainerV1/TagsContainerV1.tsx | 280 ++++++++---------- .../TagsContainerV1/TagsTree.component.tsx | 47 +++ .../TopicDetails/TopicDetails.component.tsx | 45 ++- .../src/pages/ContainerPage/ContainerPage.tsx | 42 ++- .../TableDetailsPageV1/TableDetailsPageV1.tsx | 43 ++- .../resources/ui/src/utils/EntityUtils.tsx | 14 + .../resources/ui/src/utils/GlossaryUtils.ts | 8 +- 15 files changed, 411 insertions(+), 279 deletions(-) create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/Tag/TagsContainerV1/TagsTree.component.tsx diff --git a/openmetadata-ui/src/main/resources/ui/cypress/common/common.js b/openmetadata-ui/src/main/resources/ui/cypress/common/common.js index c07e2feb4b1..71310e07eda 100644 --- a/openmetadata-ui/src/main/resources/ui/cypress/common/common.js +++ b/openmetadata-ui/src/main/resources/ui/cypress/common/common.js @@ -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); diff --git a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Flow/TagsAddRemove.spec.js b/openmetadata-ui/src/main/resources/ui/cypress/e2e/Flow/TagsAddRemove.spec.js index 08e6a1c0f51..6dc7a497b5f 100644 --- a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Flow/TagsAddRemove.spec.js +++ b/openmetadata-ui/src/main/resources/ui/cypress/e2e/Flow/TagsAddRemove.spec.js @@ -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(); diff --git a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/Tags.spec.js b/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/Tags.spec.js index bb59c9b08da..aaa5c73a42e 100644 --- a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/Tags.spec.js +++ b/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/Tags.spec.js @@ -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); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DashboardDetails/DashboardDetails.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DashboardDetails/DashboardDetails.component.tsx index ec2ad986355..47c9b443207 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DashboardDetails/DashboardDetails.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DashboardDetails/DashboardDetails.component.tsx @@ -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"> - + + + + + ), diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataModels/DataModelDetails.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataModels/DataModelDetails.component.tsx index dfcb1ef9833..b50b937c075 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataModels/DataModelDetails.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataModels/DataModelDetails.component.tsx @@ -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"> - + + + + ); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/MlModelDetail/MlModelDetail.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/MlModelDetail/MlModelDetail.component.tsx index d376e6f553f..57c703e9c9d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/MlModelDetail/MlModelDetail.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/MlModelDetail/MlModelDetail.component.tsx @@ -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 = ({ className="entity-tag-right-panel-container" data-testid="entity-right-panel" flex="320px"> - + + + + + ), diff --git a/openmetadata-ui/src/main/resources/ui/src/components/PipelineDetails/PipelineDetails.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/PipelineDetails/PipelineDetails.component.tsx index ccef6eae041..c62d2dd4f6b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/PipelineDetails/PipelineDetails.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/PipelineDetails/PipelineDetails.component.tsx @@ -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"> - + + + + + ), diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Tag/TagsContainerV1/TagsContainerV1.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/Tag/TagsContainerV1/TagsContainerV1.interface.ts index 2473458b09b..ab14c2a092a 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Tag/TagsContainerV1/TagsContainerV1.interface.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/Tag/TagsContainerV1/TagsContainerV1.interface.ts @@ -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; onSelectionChange: (selectedTags: Array) => 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; }; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Tag/TagsContainerV1/TagsContainerV1.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Tag/TagsContainerV1/TagsContainerV1.tsx index 1b95944c129..4b0fff9e8c1 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Tag/TagsContainerV1/TagsContainerV1.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Tag/TagsContainerV1/TagsContainerV1.tsx @@ -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(); - 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) => ( - - ); + }, [isGlossaryType, fetchGlossaryList, fetchTags]); const addTagButton = useMemo( () => @@ -225,47 +248,19 @@ const TagsContainerV1 = ({ ); const renderTags = useMemo( - () => - showLimited ? ( - - ) : ( - <> - {!showAddTagButton && isEmpty(selectedTags) ? ( - - {NO_DATA_PLACEHOLDER} - - ) : null} - {selectedTags.map(getTagsElement)} - - ), - [ - showLimited, - showNoDataPlaceholder, - selectedTags, - getTagsElement, - showAddTagButton, - ] + () => ( + + ), + [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 ? ( ) : (
@@ -292,32 +287,10 @@ const TagsContainerV1 = ({ - - } - showCheckedStrategy={TreeSelect.SHOW_ALL} + @@ -325,23 +298,26 @@ const TagsContainerV1 = ({
); }, [ + 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) ? ( + + ), + [ + entityType, + entityFqn, + entityThreadLink, + getEntityFeedLink, + onThreadLinkSelect, + ] + ); + + useEffect(() => { + setTags(getFilterTags(selectedTags)); }, [selectedTags]); - const getThreadElements = () => { - if (!isUndefined(entityFieldThreads)) { - return !isUndefined(tagThread) ? ( - - - - ) : ( - -