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 71310e07eda6..1d8cc779bacf 100644 --- a/openmetadata-ui/src/main/resources/ui/cypress/common/common.js +++ b/openmetadata-ui/src/main/resources/ui/cypress/common/common.js @@ -623,7 +623,9 @@ export const addNewTagToEntity = (entityObj, term) => { entityObj.entity ); cy.wait(500); - cy.get('[data-testid="entity-tags"] [data-testid="add-tag"]') + cy.get( + '[data-testid="classification-tags-0"] [data-testid="entity-tags"] [data-testid="add-tag"]' + ) .eq(0) .should('be.visible') .scrollIntoView() @@ -640,34 +642,8 @@ export const addNewTagToEntity = (entityObj, term) => { .scrollIntoView() .should('be.visible') .click(); - cy.get('[data-testid="tags-container"] [data-testid="entity-tags"]') + cy.get('[data-testid="classification-tags-0"] [data-testid="tags-container"]') .scrollIntoView() - .should('be.visible') - .contains(term); - - cy.get('[data-testid="classification-tags-0"] [data-testid="tag-container"]') - .scrollIntoView() - .contains('Add') - .should('be.visible') - .click(); - - cy.get('[data-testid="tag-selector"]') - .scrollIntoView() - .should('be.visible') - .type(term); - cy.wait(500); - cy.get('.ant-select-item-option-content') - .contains(term) - .should('be.visible') - .click(); - // to close popup - cy.clickOutside(); - - cy.get('[data-testid="saveAssociatedTag"]') - .scrollIntoView() - .should('be.visible') - .click(); - cy.get('[data-testid="classification-tags-0"] [data-testid="tag-container"]') .contains(term) .should('exist'); }; diff --git a/openmetadata-ui/src/main/resources/ui/cypress/constants/tagsAddRemove.constants.js b/openmetadata-ui/src/main/resources/ui/cypress/constants/tagsAddRemove.constants.js index 898342bd94ea..26f3e5ffc8d8 100644 --- a/openmetadata-ui/src/main/resources/ui/cypress/constants/tagsAddRemove.constants.js +++ b/openmetadata-ui/src/main/resources/ui/cypress/constants/tagsAddRemove.constants.js @@ -18,7 +18,7 @@ export const TAGS_ADD_REMOVE_ENTITIES = [ entity: 'tables', serviceName: 'sample_data', fieldName: 'SKU', - tags: ['PersonalData.Personal', 'PII.Sensitive'], + tags: ['Personal', 'Sensitive'], entityTags: 'Personal', }, { @@ -27,7 +27,7 @@ export const TAGS_ADD_REMOVE_ENTITIES = [ entity: 'topics', serviceName: 'sample_kafka', fieldName: 'AddressBook', - tags: ['PersonalData.Personal', 'PII.Sensitive'], + tags: ['Personal', 'Sensitive'], entityTags: 'Personal', }, { @@ -37,7 +37,7 @@ export const TAGS_ADD_REMOVE_ENTITIES = [ insideEntity: 'charts', serviceName: 'sample_superset', fieldName: 'e3cfd274-44f8-4bf3-b75d-d40cf88869ba', - tags: ['PersonalData.Personal', 'PII.Sensitive'], + tags: ['Personal', 'Sensitive'], entityTags: 'Personal', }, { @@ -46,7 +46,7 @@ export const TAGS_ADD_REMOVE_ENTITIES = [ entity: 'pipelines', serviceName: 'sample_airflow', fieldName: 'dim_address_task', - tags: ['PersonalData.Personal', 'PII.Sensitive'], + tags: ['Personal', 'Sensitive'], entityTags: 'Personal', }, { @@ -55,7 +55,7 @@ export const TAGS_ADD_REMOVE_ENTITIES = [ entity: 'mlmodels', serviceName: 'mlflow_svc', fieldName: 'sales', - tags: ['PersonalData.Personal', 'PII.Sensitive'], + tags: ['Personal', 'Sensitive'], entityTags: 'Personal', }, ]; 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 6dc7a497b5fd..0c7da7d6bf33 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 @@ -18,20 +18,14 @@ import { } from '../../common/common'; import { TAGS_ADD_REMOVE_ENTITIES } from '../../constants/tagsAddRemove.constants'; -const addTags = (tag, parent) => { +const addTags = (tag) => { cy.get('[data-testid="tag-selector"]') .scrollIntoView() .should('be.visible') .click() .type(tag); - if (parent) { - cy.get(`[title="${tag}"]`).should('be.visible').click(); - } else { - cy.get('.ant-select-item-option-content') - .should('be.visible') - .click({ multiple: true }); - } + cy.get(`[title="${tag}"]`).should('be.visible').click(); cy.get('[data-testid="tag-selector"] > .ant-select-selector').contains(tag); }; @@ -40,18 +34,22 @@ const checkTags = (tag, checkForParentEntity) => { cy.get( '[data-testid="entity-right-panel"] [data-testid="tags-container"] [data-testid="entity-tags"] ' ) - .scrollIntoView() - .should('be.visible') .contains(tag); } else { - cy.get(`[data-testid="tag-${tag}"]`).should('be.visible'); + cy.get( + '[data-testid="classification-tags-0"] [data-testid="tags-container"] [data-testid="entity-tags"] ' + ) + .scrollIntoView() + .contains(tag); } }; 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="tags-container"] [data-testid="edit-button"]' + ) .scrollIntoView() .should('be.visible') .click(); @@ -93,7 +91,7 @@ describe('Check if tags addition and removal flow working properly from tables', .should('be.visible') .click(); - addTags(entityDetails.entityTags, true); + addTags(entityDetails.entityTags); interceptURL('PATCH', `/api/v1/${entityDetails.entity}/*`, 'tagsChange'); @@ -113,9 +111,8 @@ describe('Check if tags addition and removal flow working properly from tables', .click(); } else { cy.get( - `.ant-table-tbody [data-testid="tag-container"] [data-testid="add-tag"]` + `.ant-table-tbody [data-testid="classification-tags-0"] [data-testid="tags-container"] [data-testid="entity-tags"]` ) - .eq(0) .scrollIntoView() .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 aaa5c73a42e0..ec405933cefa 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 @@ -233,7 +233,7 @@ describe('Tags page should work', () => { }); it('Use newly created tag to any entity should work', () => { - const entity = SEARCH_ENTITY_TABLE.table_1; + const entity = SEARCH_ENTITY_TABLE.table_3; addNewTagToEntity(entity, `${NEW_TAG.name}`); }); @@ -251,7 +251,7 @@ describe('Tags page should work', () => { ); interceptURL('PATCH', '/api/v1/databaseSchemas/*', 'addTags'); - const entity = SEARCH_ENTITY_TABLE.table_2; + const entity = SEARCH_ENTITY_TABLE.table_3; const tag = 'Sensitive'; visitEntityDetailsPage(entity.term, entity.serviceName, entity.entity); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ContainerDetail/ContainerDataModel/ContainerDataModel.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ContainerDetail/ContainerDataModel/ContainerDataModel.test.tsx index 875a6190fa72..6baf02ebd071 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ContainerDetail/ContainerDataModel/ContainerDataModel.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ContainerDetail/ContainerDataModel/ContainerDataModel.test.tsx @@ -75,14 +75,14 @@ const props = { onUpdate: jest.fn(), }; -jest.mock('../../../utils/TagsUtils', () => ({ - getClassifications: jest.fn().mockReturnValue([]), - getTaglist: jest.fn().mockReturnValue([]), +jest.mock('utils/TagsUtils', () => ({ + getAllTagsList: jest.fn(() => Promise.resolve([])), + getTagsHierarchy: jest.fn().mockReturnValue([]), })); jest.mock('utils/GlossaryUtils', () => ({ - fetchGlossaryTerms: jest.fn().mockReturnValue([]), - getGlossaryTermlist: jest.fn().mockReturnValue([]), + getGlossaryTermsList: jest.fn(() => Promise.resolve([])), + getGlossaryTermHierarchy: jest.fn().mockReturnValue([]), })); jest.mock('utils/TableTags/TableTags.utils', () => ({ @@ -122,6 +122,14 @@ jest.mock('components/TableTags/TableTags.component', () => )) ); +jest.mock('components/common/error-with-placeholder/ErrorPlaceHolder', () => + jest + .fn() + .mockImplementation(() => ( +
ErrorPlaceHolder
+ )) +); + describe('ContainerDataModel', () => { it('Should render the Container data model component', async () => { render(); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ContainerDetail/ContainerDataModel/ContainerDataModel.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ContainerDetail/ContainerDataModel/ContainerDataModel.tsx index 538b51efd332..e7e0be7c78c9 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ContainerDetail/ContainerDataModel/ContainerDataModel.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ContainerDetail/ContainerDataModel/ContainerDataModel.tsx @@ -17,6 +17,10 @@ import ErrorPlaceHolder from 'components/common/error-with-placeholder/ErrorPlac import RichTextEditorPreviewer from 'components/common/rich-text-editor/RichTextEditorPreviewer'; import { ModalWithMarkdownEditor } from 'components/Modals/ModalWithMarkdownEditor/ModalWithMarkdownEditor'; import TableTags from 'components/TableTags/TableTags.component'; +import { + GlossaryTermDetailsProps, + TagsDetailsProps, +} from 'components/Tag/TagsContainerV1/TagsContainerV1.interface'; import { Column, TagLabel } from 'generated/entity/data/container'; import { TagSource } from 'generated/type/tagLabel'; import { cloneDeep, isEmpty, isUndefined, map, toLower } from 'lodash'; @@ -28,10 +32,12 @@ import { updateContainerColumnTags, } from 'utils/ContainerDetailUtils'; import { getEntityName } from 'utils/EntityUtils'; -import { fetchGlossaryTerms, getGlossaryTermlist } from 'utils/GlossaryUtils'; -import { getFilterTags } from 'utils/TableTags/TableTags.utils'; +import { + getGlossaryTermHierarchy, + getGlossaryTermsList, +} from 'utils/GlossaryUtils'; import { getTableExpandableConfig } from 'utils/TableUtils'; -import { getClassifications, getTaglist } from 'utils/TagsUtils'; +import { getAllTagsList, getTagsHierarchy } from 'utils/TagsUtils'; import { CellRendered, ContainerDataModelProps, @@ -52,18 +58,18 @@ const ContainerDataModel: FC = ({ const [isTagLoading, setIsTagLoading] = useState(false); const [isGlossaryLoading, setIsGlossaryLoading] = useState(false); const [tagFetchFailed, setTagFetchFailed] = useState(false); - const [glossaryTags, setGlossaryTags] = useState([]); - const [classificationTags, setClassificationTags] = useState([]); + const [glossaryTags, setGlossaryTags] = useState( + [] + ); + const [classificationTags, setClassificationTags] = useState< + TagsDetailsProps[] + >([]); const fetchGlossaryTags = async () => { setIsGlossaryLoading(true); try { - const res = await fetchGlossaryTerms(); - - const glossaryTerms: TagOption[] = getGlossaryTermlist(res).map( - (tag) => ({ fqn: tag, source: TagSource.Glossary }) - ); - setGlossaryTags(glossaryTerms); + const glossaryTermList = await getGlossaryTermsList(); + setGlossaryTags(glossaryTermList); } catch { setTagFetchFailed(true); } finally { @@ -74,15 +80,8 @@ const ContainerDataModel: FC = ({ const fetchClassificationTags = async () => { setIsTagLoading(true); try { - const res = await getClassifications(); - const tagList = await getTaglist(res.data); - - const classificationTag: TagOption[] = map(tagList, (tag) => ({ - fqn: tag, - source: TagSource.Classification, - })); - - setClassificationTags(classificationTag); + const tags = await getAllTagsList(); + setClassificationTags(tags); } catch { setTagFetchFailed(true); } finally { @@ -91,15 +90,11 @@ const ContainerDataModel: FC = ({ }; const handleFieldTagsChange = useCallback( - async ( - selectedTags: EntityTags[], - editColumnTag: Column, - otherTags: TagLabel[] - ) => { - const newSelectedTags: TagOption[] = map( - [...selectedTags, ...otherTags], - (tag) => ({ fqn: tag.tagFQN, source: tag.source }) - ); + async (selectedTags: EntityTags[], editColumnTag: Column) => { + const newSelectedTags: TagOption[] = map(selectedTags, (tag) => ({ + fqn: tag.tagFQN, + source: tag.source, + })); if (newSelectedTags && editColumnTag) { const containerDataModel = cloneDeep(dataModel); @@ -233,8 +228,8 @@ const ContainerDataModel: FC = ({ isTagLoading={isTagLoading} record={record} tagFetchFailed={tagFetchFailed} - tagList={classificationTags} - tags={getFilterTags(tags)} + tagList={getTagsHierarchy(classificationTags)} + tags={tags} type={TagSource.Classification} /> ), @@ -256,8 +251,8 @@ const ContainerDataModel: FC = ({ isTagLoading={isGlossaryLoading} record={record} tagFetchFailed={tagFetchFailed} - tagList={glossaryTags} - tags={getFilterTags(tags)} + tagList={getGlossaryTermHierarchy(glossaryTags)} + tags={tags} type={TagSource.Glossary} /> ), 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 47c9b4432075..2b7ef544489d 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 @@ -27,6 +27,10 @@ import { EntityName } from 'components/Modals/EntityNameModal/EntityNameModal.in import TableTags from 'components/TableTags/TableTags.component'; import TabsLabel from 'components/TabsLabel/TabsLabel.component'; import TagsContainerV1 from 'components/Tag/TagsContainerV1/TagsContainerV1'; +import { + GlossaryTermDetailsProps, + TagsDetailsProps, +} from 'components/Tag/TagsContainerV1/TagsContainerV1.interface'; import { getDashboardDetailsPath } from 'constants/constants'; import { compare } from 'fast-json-patch'; import { TagSource } from 'generated/type/schema'; @@ -37,7 +41,11 @@ import { useTranslation } from 'react-i18next'; import { useHistory, useParams } from 'react-router-dom'; import { restoreDashboard } from 'rest/dashboardAPI'; import { getEntityName, getEntityThreadLink } from 'utils/EntityUtils'; -import { getFilterTags } from 'utils/TableTags/TableTags.utils'; +import { + getGlossaryTermHierarchy, + getGlossaryTermsList, +} from 'utils/GlossaryUtils'; +import { getAllTagsList, getTagsHierarchy } from 'utils/TagsUtils'; import { ReactComponent as ExternalLinkIcon } from '../../assets/svg/external-links.svg'; import { EntityField } from '../../constants/Feeds.constants'; import { EntityTabs, EntityType } from '../../enums/entity.enum'; @@ -46,13 +54,8 @@ import { ThreadType } from '../../generated/entity/feed/thread'; import { LabelType, State, TagLabel } from '../../generated/type/tagLabel'; import { getCurrentUserId, refreshPage } from '../../utils/CommonUtils'; import { getEntityFieldThreadCounts } from '../../utils/FeedUtils'; -import { - fetchGlossaryTerms, - getGlossaryTermlist, -} from '../../utils/GlossaryUtils'; import { DEFAULT_ENTITY_PERMISSION } from '../../utils/PermissionsUtils'; import { getTagsWithoutTier, getTierTags } from '../../utils/TableUtils'; -import { getClassifications, getTaglist } from '../../utils/TagsUtils'; import { showErrorToast, showSuccessToast } from '../../utils/ToastUtils'; import ActivityThreadPanel from '../ActivityFeed/ActivityThreadPanel/ActivityThreadPanel'; import { CustomPropertyTable } from '../common/CustomPropertyTable/CustomPropertyTable'; @@ -110,8 +113,12 @@ const DashboardDetails = ({ Array >([]); - const [glossaryTags, setGlossaryTags] = useState([]); - const [classificationTags, setClassificationTags] = useState([]); + const [glossaryTags, setGlossaryTags] = useState( + [] + ); + const [classificationTags, setClassificationTags] = useState< + TagsDetailsProps[] + >([]); const { owner, @@ -204,12 +211,8 @@ const DashboardDetails = ({ const fetchGlossaryTags = async () => { setIsGlossaryLoading(true); try { - const res = await fetchGlossaryTerms(); - - const glossaryTerms: TagOption[] = getGlossaryTermlist(res).map( - (tag) => ({ fqn: tag, source: TagSource.Glossary }) - ); - setGlossaryTags(glossaryTerms); + const glossaryTermList = await getGlossaryTermsList(); + setGlossaryTags(glossaryTermList); } catch { setTagFetchFailed(true); } finally { @@ -220,15 +223,8 @@ const DashboardDetails = ({ const fetchClassificationTags = async () => { setIsTagLoading(true); try { - const res = await getClassifications(); - const tagList = await getTaglist(res.data); - - const classificationTag: TagOption[] = map(tagList, (tag) => ({ - fqn: tag, - source: TagSource.Classification, - })); - - setClassificationTags(classificationTag); + const tags = await getAllTagsList(); + setClassificationTags(tags); } catch { setTagFetchFailed(true); } finally { @@ -371,14 +367,13 @@ const DashboardDetails = ({ const handleChartTagSelection = async ( selectedTags: Array, - editColumnTag: ChartType, - otherTags: TagLabel[] + editColumnTag: ChartType ) => { if (selectedTags && editColumnTag) { - const newSelectedTags: TagOption[] = map( - [...selectedTags, ...otherTags], - (tag) => ({ fqn: tag.tagFQN, source: tag.source }) - ); + const newSelectedTags: TagOption[] = map(selectedTags, (tag) => ({ + fqn: tag.tagFQN, + source: tag.source, + })); const prevTags = editColumnTag.tags?.filter((tag) => newSelectedTags.some((selectedTag) => selectedTag.fqn === tag.tagFQN) @@ -546,8 +541,8 @@ const DashboardDetails = ({ isTagLoading={isTagLoading} record={record} tagFetchFailed={tagFetchFailed} - tagList={classificationTags} - tags={getFilterTags(tags)} + tagList={getTagsHierarchy(classificationTags)} + tags={tags} type={TagSource.Classification} /> ); @@ -570,8 +565,8 @@ const DashboardDetails = ({ isTagLoading={isGlossaryLoading} record={record} tagFetchFailed={tagFetchFailed} - tagList={glossaryTags} - tags={getFilterTags(tags)} + tagList={getGlossaryTermHierarchy(glossaryTags)} + tags={tags} type={TagSource.Glossary} /> ), @@ -651,8 +646,9 @@ const DashboardDetails = ({ entityThreadLink={getEntityThreadLink(entityFieldThreadCount)} entityType={EntityType.DASHBOARD} permission={ - dashboardPermissions.EditAll || - dashboardPermissions.EditTags + (dashboardPermissions.EditAll || + dashboardPermissions.EditTags) && + !dashboardDetails.deleted } selectedTags={dashboardTags} tagType={TagSource.Classification} @@ -665,8 +661,9 @@ const DashboardDetails = ({ entityThreadLink={getEntityThreadLink(entityFieldThreadCount)} entityType={EntityType.DASHBOARD} permission={ - dashboardPermissions.EditAll || - dashboardPermissions.EditTags + (dashboardPermissions.EditAll || + dashboardPermissions.EditTags) && + !dashboardDetails.deleted } selectedTags={dashboardTags} tagType={TagSource.Glossary} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DashboardDetails/DashboardDetails.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DashboardDetails/DashboardDetails.test.tsx index 7c39b3b5650f..0a734efa37b4 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DashboardDetails/DashboardDetails.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DashboardDetails/DashboardDetails.test.tsx @@ -19,7 +19,6 @@ import { } from '@testing-library/react'; import { EntityTabs } from 'enums/entity.enum'; import { ChartType } from 'generated/entity/data/chart'; -import { GlossaryTerm } from 'generated/entity/data/glossaryTerm'; import { mockGlossaryList } from 'mocks/Glossary.mock'; import { mockTagList } from 'mocks/Tags.mock'; import React from 'react'; @@ -162,24 +161,14 @@ jest.mock('../../utils/CommonUtils', () => ({ getOwnerValue: jest.fn().mockReturnValue('Owner'), })); -jest.mock('../../utils/GlossaryUtils', () => ({ - fetchGlossaryTerms: jest - .fn() - .mockImplementation(() => Promise.resolve(mockGlossaryList)), - getGlossaryTermlist: jest.fn().mockImplementation((terms) => { - return terms.map((term: GlossaryTerm) => term?.fullyQualifiedName); - }), +jest.mock('../../utils/TagsUtils', () => ({ + getAllTagsList: jest.fn(() => Promise.resolve(mockTagList)), + getTagsHierarchy: jest.fn().mockReturnValue([]), })); -jest.mock('../../utils/TagsUtils', () => ({ - getClassifications: jest - .fn() - .mockImplementation(() => Promise.resolve({ data: mockTagList })), - getTaglist: jest - .fn() - .mockImplementation(() => - Promise.resolve(['PersonalData.Personal', 'PersonalData.SpecialCategory']) - ), +jest.mock('../../utils/GlossaryUtils', () => ({ + getGlossaryTermsList: jest.fn(() => Promise.resolve(mockGlossaryList)), + getGlossaryTermHierarchy: jest.fn().mockReturnValue([]), })); describe.skip('Test DashboardDetails component', () => { 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 b50b937c075b..b9756f57e32d 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 @@ -181,7 +181,7 @@ const DataModelDetails = ({ entityFqn={dashboardDataModelFQN} entityThreadLink={getEntityThreadLink(entityFieldThreadCount)} entityType={EntityType.DASHBOARD_DATA_MODEL} - permission={hasEditTagsPermission} + permission={hasEditTagsPermission && !dataModelData.deleted} selectedTags={tags} tagType={TagSource.Classification} onSelectionChange={handleTagSelection} @@ -191,7 +191,7 @@ const DataModelDetails = ({ entityFqn={dashboardDataModelFQN} entityThreadLink={getEntityThreadLink(entityFieldThreadCount)} entityType={EntityType.DASHBOARD_DATA_MODEL} - permission={hasEditTagsPermission} + permission={hasEditTagsPermission && !dataModelData.deleted} selectedTags={tags} tagType={TagSource.Glossary} onSelectionChange={handleTagSelection} @@ -202,6 +202,7 @@ const DataModelDetails = ({ ); }, [ + dataModelData, description, dashboardDataModelFQN, entityFieldThreadCount, diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataModels/ModelTab/ModelTab.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataModels/ModelTab/ModelTab.component.tsx index 2940a2ab1de3..94c11275f522 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataModels/ModelTab/ModelTab.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataModels/ModelTab/ModelTab.component.tsx @@ -17,6 +17,10 @@ import RichTextEditorPreviewer from 'components/common/rich-text-editor/RichText import { CellRendered } from 'components/ContainerDetail/ContainerDataModel/ContainerDataModel.interface'; import { ModalWithMarkdownEditor } from 'components/Modals/ModalWithMarkdownEditor/ModalWithMarkdownEditor'; import TableTags from 'components/TableTags/TableTags.component'; +import { + GlossaryTermDetailsProps, + TagsDetailsProps, +} from 'components/Tag/TagsContainerV1/TagsContainerV1.interface'; import { Column } from 'generated/entity/data/dashboardDataModel'; import { TagLabel, TagSource } from 'generated/type/tagLabel'; import { cloneDeep, isUndefined, map } from 'lodash'; @@ -28,9 +32,11 @@ import { updateDataModelColumnTags, } from 'utils/DataModelsUtils'; import { getEntityName } from 'utils/EntityUtils'; -import { fetchGlossaryTerms, getGlossaryTermlist } from 'utils/GlossaryUtils'; -import { getFilterTags } from 'utils/TableTags/TableTags.utils'; -import { getClassifications, getTaglist } from 'utils/TagsUtils'; +import { + getGlossaryTermHierarchy, + getGlossaryTermsList, +} from 'utils/GlossaryUtils'; +import { getAllTagsList, getTagsHierarchy } from 'utils/TagsUtils'; import { ModelTabProps } from './ModelTab.interface'; const ModelTab = ({ @@ -46,18 +52,18 @@ const ModelTab = ({ const [isGlossaryLoading, setIsGlossaryLoading] = useState(false); const [tagFetchFailed, setTagFetchFailed] = useState(false); - const [glossaryTags, setGlossaryTags] = useState([]); - const [classificationTags, setClassificationTags] = useState([]); + const [glossaryTags, setGlossaryTags] = useState( + [] + ); + const [classificationTags, setClassificationTags] = useState< + TagsDetailsProps[] + >([]); const fetchGlossaryTags = async () => { setIsGlossaryLoading(true); try { - const res = await fetchGlossaryTerms(); - - const glossaryTerms: TagOption[] = getGlossaryTermlist(res).map( - (tag) => ({ fqn: tag, source: TagSource.Glossary }) - ); - setGlossaryTags(glossaryTerms); + const glossaryTermList = await getGlossaryTermsList(); + setGlossaryTags(glossaryTermList); } catch { setTagFetchFailed(true); } finally { @@ -68,15 +74,8 @@ const ModelTab = ({ const fetchClassificationTags = async () => { setIsTagLoading(true); try { - const res = await getClassifications(); - const tagList = await getTaglist(res.data); - - const classificationTag: TagOption[] = map(tagList, (tag) => ({ - fqn: tag, - source: TagSource.Classification, - })); - - setClassificationTags(classificationTag); + const tags = await getAllTagsList(); + setClassificationTags(tags); } catch { setTagFetchFailed(true); } finally { @@ -85,15 +84,11 @@ const ModelTab = ({ }; const handleFieldTagsChange = useCallback( - async ( - selectedTags: EntityTags[], - editColumnTag: Column, - otherTags: TagLabel[] - ) => { - const newSelectedTags: TagOption[] = map( - [...selectedTags, ...otherTags], - (tag) => ({ fqn: tag.tagFQN, source: tag.source }) - ); + async (selectedTags: EntityTags[], editColumnTag: Column) => { + const newSelectedTags: TagOption[] = map(selectedTags, (tag) => ({ + fqn: tag.tagFQN, + source: tag.source, + })); const dataModelData = cloneDeep(data); @@ -206,8 +201,8 @@ const ModelTab = ({ isTagLoading={isTagLoading} record={record} tagFetchFailed={tagFetchFailed} - tagList={classificationTags} - tags={getFilterTags(tags)} + tagList={getTagsHierarchy(classificationTags)} + tags={tags} type={TagSource.Classification} /> ), @@ -229,8 +224,8 @@ const ModelTab = ({ isTagLoading={isGlossaryLoading} record={record} tagFetchFailed={tagFetchFailed} - tagList={glossaryTags} - tags={getFilterTags(tags)} + tagList={getGlossaryTermHierarchy(glossaryTags)} + tags={tags} type={TagSource.Glossary} /> ), diff --git a/openmetadata-ui/src/main/resources/ui/src/components/EntityTable/EntityTable.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/EntityTable/EntityTable.component.tsx index cbf0590dd1a5..781a0824bcea 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/EntityTable/EntityTable.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/EntityTable/EntityTable.component.tsx @@ -16,6 +16,10 @@ import { ColumnsType } from 'antd/lib/table'; import { ReactComponent as IconEdit } from 'assets/svg/edit-new.svg'; import FilterTablePlaceHolder from 'components/common/error-with-placeholder/FilterTablePlaceHolder'; import TableTags from 'components/TableTags/TableTags.component'; +import { + GlossaryTermDetailsProps, + TagsDetailsProps, +} from 'components/Tag/TagsContainerV1/TagsContainerV1.interface'; import { LabelType, State, TagSource } from 'generated/type/schema'; import { cloneDeep, @@ -31,7 +35,11 @@ import { EntityTags, TagOption } from 'Models'; import React, { Fragment, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useHistory } from 'react-router-dom'; -import { getFilterTags } from 'utils/TableTags/TableTags.utils'; +import { + getGlossaryTermHierarchy, + getGlossaryTermsList, +} from 'utils/GlossaryUtils'; +import { getAllTagsList, getTagsHierarchy } from 'utils/TagsUtils'; import { ReactComponent as IconRequest } from '../../assets/svg/request-icon.svg'; import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants'; import { EntityField } from '../../constants/Feeds.constants'; @@ -47,17 +55,12 @@ import { getFrequentlyJoinedColumns, } from '../../utils/EntityUtils'; import { getFieldThreadElement } from '../../utils/FeedElementUtils'; -import { - fetchGlossaryTerms, - getGlossaryTermlist, -} from '../../utils/GlossaryUtils'; import { getDataTypeString, getTableExpandableConfig, makeData, prepareConstraintIcon, } from '../../utils/TableUtils'; -import { getClassifications, getTaglist } from '../../utils/TagsUtils'; import { getRequestDescriptionPath, getRequestTagsPath, @@ -87,8 +90,12 @@ const EntityTable = ({ const { t } = useTranslation(); const [searchedColumns, setSearchedColumns] = useState([]); - const [glossaryTags, setGlossaryTags] = useState([]); - const [classificationTags, setClassificationTags] = useState([]); + const [glossaryTags, setGlossaryTags] = useState( + [] + ); + const [classificationTags, setClassificationTags] = useState< + TagsDetailsProps[] + >([]); const sortByOrdinalPosition = useMemo( () => sortBy(tableColumns, 'ordinalPosition'), @@ -112,12 +119,8 @@ const EntityTable = ({ const fetchGlossaryTags = async () => { setIsGlossaryLoading(true); try { - const res = await fetchGlossaryTerms(); - - const glossaryTerms: TagOption[] = getGlossaryTermlist(res).map( - (tag) => ({ fqn: tag, source: TagSource.Glossary }) - ); - setGlossaryTags(glossaryTerms); + const glossaryTermList = await getGlossaryTermsList(); + setGlossaryTags(glossaryTermList); } catch { setTagFetchFailed(true); } finally { @@ -128,15 +131,8 @@ const EntityTable = ({ const fetchClassificationTags = async () => { setIsTagLoading(true); try { - const res = await getClassifications(); - const tagList = await getTaglist(res.data); - - const classificationTag: TagOption[] = map(tagList, (tag) => ({ - fqn: tag, - source: TagSource.Classification, - })); - - setClassificationTags(classificationTag); + const tags = await getAllTagsList(); + setClassificationTags(tags); } catch { setTagFetchFailed(true); } finally { @@ -233,13 +229,12 @@ const EntityTable = ({ const handleTagSelection = async ( selectedTags: EntityTags[], - editColumnTag: Column, - otherTags: TagLabel[] + editColumnTag: Column ) => { - const newSelectedTags: TagOption[] = map( - [...selectedTags, ...otherTags], - (tag) => ({ fqn: tag.tagFQN, source: tag.source }) - ); + const newSelectedTags: TagOption[] = map(selectedTags, (tag) => ({ + fqn: tag.tagFQN, + source: tag.source, + })); if (newSelectedTags && editColumnTag) { const tableCols = cloneDeep(tableColumns); updateColumnTags( @@ -543,8 +538,8 @@ const EntityTable = ({ isTagLoading={isTagLoading} record={record} tagFetchFailed={tagFetchFailed} - tagList={classificationTags} - tags={getFilterTags(tags)} + tagList={getTagsHierarchy(classificationTags)} + tags={tags} type={TagSource.Classification} onRequestTagsHandler={onRequestTagsHandler} onThreadLinkSelect={onThreadLinkSelect} @@ -574,8 +569,8 @@ const EntityTable = ({ isTagLoading={isGlossaryLoading} record={record} tagFetchFailed={tagFetchFailed} - tagList={glossaryTags} - tags={getFilterTags(tags)} + tagList={getGlossaryTermHierarchy(glossaryTags)} + tags={tags} type={TagSource.Glossary} onRequestTagsHandler={onRequestTagsHandler} onThreadLinkSelect={onThreadLinkSelect} @@ -598,7 +593,6 @@ const EntityTable = ({ fetchGlossaryTags, getColumnName, handleTagSelection, - getFilterTags, hasTagEditAccess, isReadOnly, tagFetchFailed, diff --git a/openmetadata-ui/src/main/resources/ui/src/components/EntityTable/EntityTable.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/EntityTable/EntityTable.interface.ts index 6e051867e783..dfd2db1b413b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/EntityTable/EntityTable.interface.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/EntityTable/EntityTable.interface.ts @@ -21,7 +21,7 @@ export interface EntityTableProps { joins: Array; columnName: string; hasDescriptionEditAccess?: boolean; - hasTagEditAccess?: boolean; + hasTagEditAccess: boolean; tableConstraints: Table['tableConstraints']; searchText?: string; isReadOnly?: boolean; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/EntityTable/EntityTable.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/EntityTable/EntityTable.test.tsx index 0a0ef99f8327..47beed656492 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/EntityTable/EntityTable.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/EntityTable/EntityTable.test.tsx @@ -16,7 +16,6 @@ import { TagOption } from 'Models'; import React from 'react'; import { MemoryRouter } from 'react-router-dom'; import { Column } from '../../generated/api/data/createTable'; -import { GlossaryTerm } from '../../generated/entity/data/glossaryTerm'; import { Table } from '../../generated/entity/data/table'; import EntityTableV1 from './EntityTable.component'; @@ -112,62 +111,13 @@ const mockEntityTableProp = { entityFqn: 'bigquery_gcp.ecommerce.shopify.raw_product_catalog', owner: {} as Table['owner'], columnName: '', + hasTagEditAccess: true, tableConstraints: mockTableConstraints, onEntityFieldSelect, onThreadLinkSelect, onUpdate, }; -const mockTagList = [ - { - id: 'tagCatId1', - name: 'TagCat1', - description: '', - children: [ - { - id: 'tagId1', - name: 'Tag1', - fullyQualifiedName: 'TagCat1.Tag1', - description: '', - deprecated: false, - deleted: false, - }, - ], - }, - { - id: 'tagCatId2', - name: 'TagCat2', - description: '', - children: [ - { - id: 'tagId2', - name: 'Tag2', - fullyQualifiedName: 'TagCat2.Tag2', - description: '', - deprecated: false, - deleted: false, - }, - ], - }, -]; - -const mockGlossaryList = [ - { - name: 'Tag1', - displayName: 'Tag1', - fullyQualifiedName: 'Glossary.Tag1', - type: 'glossaryTerm', - id: 'glossaryTagId1', - }, - { - name: 'Tag2', - displayName: 'Tag2', - fullyQualifiedName: 'Glossary.Tag2', - type: 'glossaryTerm', - id: 'glossaryTagId2', - }, -]; - jest.mock('../../hooks/authHooks', () => { return { useAuth: jest.fn().mockReturnValue({ @@ -205,17 +155,27 @@ jest.mock('components/Tag/Tags/tags', () => { return jest.fn().mockReturnValue(

Tag

); }); -jest.mock('../../utils/GlossaryUtils', () => ({ - fetchGlossaryTerms: jest.fn(() => Promise.resolve(mockGlossaryList)), - getGlossaryTermlist: jest.fn((terms) => { - return terms.map((term: GlossaryTerm) => term?.fullyQualifiedName); - }), +jest.mock('../../utils/TagsUtils', () => ({ + getAllTagsList: jest.fn(() => Promise.resolve([])), + getTagsHierarchy: jest.fn().mockReturnValue([]), })); -jest.mock('../../utils/TagsUtils', () => ({ - getClassifications: jest.fn(() => Promise.resolve({ data: mockTagList })), +jest.mock('../../utils/GlossaryUtils', () => ({ + getGlossaryTermsList: jest.fn(() => Promise.resolve([])), + getGlossaryTermHierarchy: jest.fn().mockReturnValue([]), })); +jest.mock( + 'components/common/error-with-placeholder/FilterTablePlaceHolder', + () => { + return jest.fn().mockReturnValue(

FilterTablePlaceHolder

); + } +); + +jest.mock('components/TableTags/TableTags.component', () => { + return jest.fn().mockReturnValue(

TableTags

); +}); + describe('Test EntityTable Component', () => { it('Initially, Table should load', async () => { render(, { 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 57c703e9c9dc..abdd28105ee9 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 @@ -398,7 +398,9 @@ const MlModelDetail: FC = ({ entityThreadLink={getEntityThreadLink(entityFieldThreadCount)} entityType={EntityType.MLMODEL} permission={ - mlModelPermissions.EditAll || mlModelPermissions.EditTags + (mlModelPermissions.EditAll || + mlModelPermissions.EditTags) && + !mlModelDetail.deleted } selectedTags={mlModelTags} tagType={TagSource.Classification} @@ -411,7 +413,9 @@ const MlModelDetail: FC = ({ entityThreadLink={getEntityThreadLink(entityFieldThreadCount)} entityType={EntityType.MLMODEL} permission={ - mlModelPermissions.EditAll || mlModelPermissions.EditTags + (mlModelPermissions.EditAll || + mlModelPermissions.EditTags) && + !mlModelDetail.deleted } selectedTags={mlModelTags} tagType={TagSource.Glossary} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/MlModelDetail/MlModelFeaturesList.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/MlModelDetail/MlModelFeaturesList.test.tsx index 442a0eabada3..0b39da5432b1 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/MlModelDetail/MlModelFeaturesList.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/MlModelDetail/MlModelFeaturesList.test.tsx @@ -122,21 +122,6 @@ jest.mock('../../utils/CommonUtils', () => ({ getHtmlForNonAdminAction: jest.fn().mockReturnValue('admin action'), })); -jest.mock('../../utils/GlossaryUtils', () => ({ - fetchGlossaryTerms: jest.fn(), - getGlossaryTermlist: jest.fn().mockReturnValue([]), -})); - -jest.mock('../../utils/TableUtils', () => ({ - getEntityLink: jest.fn().mockReturnValue('entityLink'), -})); - -jest.mock('../../utils/TagsUtils', () => ({ - getClassifications: jest.fn(), - getTaglist: jest.fn().mockReturnValue([]), - getTagDisplay: jest.fn(), -})); - jest.mock('../common/rich-text-editor/RichTextEditorPreviewer', () => { return jest.fn().mockReturnValue(

RichTextEditorPreviewer

); }); @@ -151,6 +136,16 @@ jest.mock('components/Tag/Tags/tags', () => { return jest.fn().mockImplementation(({ tag }) => {tag.tagFQN}); }); +jest.mock('utils/TableUtils', () => ({ + getAllTagsList: jest.fn().mockImplementation(() => Promise.resolve([])), + getTagsHierarchy: jest.fn().mockReturnValue([]), +})); + +jest.mock('utils/GlossaryUtils', () => ({ + getGlossaryTermHierarchy: jest.fn().mockReturnValue([]), + getGlossaryTermsList: jest.fn().mockImplementation(() => Promise.resolve([])), +})); + const handleFeaturesUpdate = jest.fn(); const mockProp = { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/MlModelDetail/MlModelFeaturesList.tsx b/openmetadata-ui/src/main/resources/ui/src/components/MlModelDetail/MlModelFeaturesList.tsx index 0f7f79c8a2f3..042688980288 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/MlModelDetail/MlModelFeaturesList.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/MlModelDetail/MlModelFeaturesList.tsx @@ -23,19 +23,22 @@ import { } from 'antd'; import { ReactComponent as EditIcon } from 'assets/svg/edit-new.svg'; import TableTags from 'components/TableTags/TableTags.component'; -import { TagLabel, TagSource } from 'generated/type/schema'; -import { isEmpty, map } from 'lodash'; -import { EntityTags, TagOption } from 'Models'; +import { + GlossaryTermDetailsProps, + TagsDetailsProps, +} from 'components/Tag/TagsContainerV1/TagsContainerV1.interface'; +import { TagSource } from 'generated/type/schema'; +import { isEmpty } from 'lodash'; +import { EntityTags } from 'Models'; import React, { Fragment, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { getFilterTags } from 'utils/TableTags/TableTags.utils'; +import { + getGlossaryTermHierarchy, + getGlossaryTermsList, +} from 'utils/GlossaryUtils'; +import { getAllTagsList, getTagsHierarchy } from 'utils/TagsUtils'; import { MlFeature } from '../../generated/entity/data/mlmodel'; import { LabelType, State } from '../../generated/type/tagLabel'; -import { - fetchGlossaryTerms, - getGlossaryTermlist, -} from '../../utils/GlossaryUtils'; -import { getClassifications, getTaglist } from '../../utils/TagsUtils'; import ErrorPlaceHolder from '../common/error-with-placeholder/ErrorPlaceHolder'; import RichTextEditorPreviewer from '../common/rich-text-editor/RichTextEditorPreviewer'; import { ModalWithMarkdownEditor } from '../Modals/ModalWithMarkdownEditor/ModalWithMarkdownEditor'; @@ -57,9 +60,12 @@ const MlModelFeaturesList = ({ const [isGlossaryLoading, setIsGlossaryLoading] = useState(false); const [tagFetchFailed, setTagFetchFailed] = useState(false); - const [glossaryTags, setGlossaryTags] = useState([]); - const [classificationTags, setClassificationTags] = useState([]); - + const [glossaryTags, setGlossaryTags] = useState( + [] + ); + const [classificationTags, setClassificationTags] = useState< + TagsDetailsProps[] + >([]); const hasEditPermission = useMemo( () => permissions.EditTags || permissions.EditAll, [permissions] @@ -89,10 +95,9 @@ const MlModelFeaturesList = ({ const handleTagsChange = async ( selectedTags: EntityTags[], - targetFeature: MlFeature, - otherTags: TagLabel[] + targetFeature: MlFeature ) => { - const newSelectedTags = [...selectedTags, ...otherTags].map((tag) => { + const newSelectedTags = selectedTags.map((tag) => { return { tagFQN: tag.tagFQN, source: tag.source, @@ -119,12 +124,8 @@ const MlModelFeaturesList = ({ const fetchGlossaryTags = async () => { setIsGlossaryLoading(true); try { - const res = await fetchGlossaryTerms(); - - const glossaryTerms: TagOption[] = getGlossaryTermlist(res).map( - (tag) => ({ fqn: tag, source: TagSource.Glossary }) - ); - setGlossaryTags(glossaryTerms); + const glossaryTermList = await getGlossaryTermsList(); + setGlossaryTags(glossaryTermList); } catch { setTagFetchFailed(true); } finally { @@ -135,15 +136,8 @@ const MlModelFeaturesList = ({ const fetchClassificationTags = async () => { setIsTagLoading(true); try { - const res = await getClassifications(); - const tagList = await getTaglist(res.data); - - const classificationTag: TagOption[] = map(tagList, (tag) => ({ - fqn: tag, - source: TagSource.Classification, - })); - - setClassificationTags(classificationTag); + const tags = await getAllTagsList(); + setClassificationTags(tags); } catch { setTagFetchFailed(true); } finally { @@ -219,8 +213,8 @@ const MlModelFeaturesList = ({ isTagLoading={isGlossaryLoading} record={feature} tagFetchFailed={tagFetchFailed} - tagList={glossaryTags} - tags={getFilterTags(feature.tags ?? [])} + tagList={getGlossaryTermHierarchy(glossaryTags)} + tags={feature.tags ?? []} type={TagSource.Glossary} /> @@ -246,8 +240,8 @@ const MlModelFeaturesList = ({ isTagLoading={isTagLoading} record={feature} tagFetchFailed={tagFetchFailed} - tagList={classificationTags} - tags={getFilterTags(feature.tags ?? [])} + tagList={getTagsHierarchy(classificationTags)} + tags={feature.tags ?? []} type={TagSource.Classification} /> 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 c62d2dd4f6b0..b9e271e1c300 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 @@ -30,6 +30,10 @@ import { EntityName } from 'components/Modals/EntityNameModal/EntityNameModal.in import TableTags from 'components/TableTags/TableTags.component'; import TabsLabel from 'components/TabsLabel/TabsLabel.component'; import TagsContainerV1 from 'components/Tag/TagsContainerV1/TagsContainerV1'; +import { + GlossaryTermDetailsProps, + TagsDetailsProps, +} from 'components/Tag/TagsContainerV1/TagsContainerV1.interface'; import TasksDAGView from 'components/TasksDAGView/TasksDAGView'; import { EntityField } from 'constants/Feeds.constants'; import { compare } from 'fast-json-patch'; @@ -41,8 +45,11 @@ import { useTranslation } from 'react-i18next'; import { Link, useHistory, useParams } from 'react-router-dom'; import { postThread } from 'rest/feedsAPI'; import { restorePipeline } from 'rest/pipelineAPI'; -import { fetchGlossaryTerms, getGlossaryTermlist } from 'utils/GlossaryUtils'; -import { getFilterTags } from 'utils/TableTags/TableTags.utils'; +import { + getGlossaryTermHierarchy, + getGlossaryTermsList, +} from 'utils/GlossaryUtils'; +import { getAllTagsList, getTagsHierarchy } from 'utils/TagsUtils'; import { ReactComponent as ExternalLinkIcon } from '../../assets/svg/external-links.svg'; import { getPipelineDetailsPath, @@ -69,7 +76,6 @@ import { getEntityName, getEntityThreadLink } from '../../utils/EntityUtils'; import { getEntityFieldThreadCounts } from '../../utils/FeedUtils'; import { DEFAULT_ENTITY_PERMISSION } from '../../utils/PermissionsUtils'; import { getTagsWithoutTier, getTierTags } from '../../utils/TableUtils'; -import { getClassifications, getTaglist } from '../../utils/TagsUtils'; import { showErrorToast, showSuccessToast } from '../../utils/ToastUtils'; import ActivityThreadPanel from '../ActivityFeed/ActivityThreadPanel/ActivityThreadPanel'; import RichTextEditorPreviewer from '../common/rich-text-editor/RichTextEditorPreviewer'; @@ -147,8 +153,13 @@ const PipelineDetails = ({ const [isTagLoading, setIsTagLoading] = useState(false); const [isGlossaryLoading, setIsGlossaryLoading] = useState(false); const [tagFetchFailed, setTagFetchFailed] = useState(false); - const [glossaryTags, setGlossaryTags] = useState([]); - const [classificationTags, setClassificationTags] = useState([]); + + const [glossaryTags, setGlossaryTags] = useState( + [] + ); + const [classificationTags, setClassificationTags] = useState< + TagsDetailsProps[] + >([]); const { getEntityPermission } = usePermissionProvider(); @@ -194,12 +205,8 @@ const PipelineDetails = ({ const fetchGlossaryTags = async () => { setIsGlossaryLoading(true); try { - const res = await fetchGlossaryTerms(); - - const glossaryTerms: TagOption[] = getGlossaryTermlist(res).map( - (tag) => ({ fqn: tag, source: TagSource.Glossary }) - ); - setGlossaryTags(glossaryTerms); + const glossaryTermList = await getGlossaryTermsList(); + setGlossaryTags(glossaryTermList); } catch { setTagFetchFailed(true); } finally { @@ -210,15 +217,8 @@ const PipelineDetails = ({ const fetchClassificationTags = async () => { setIsTagLoading(true); try { - const res = await getClassifications(); - const tagList = await getTaglist(res.data); - - const classificationTag: TagOption[] = map(tagList, (tag) => ({ - fqn: tag, - source: TagSource.Classification, - })); - - setClassificationTags(classificationTag); + const tags = await getAllTagsList(); + setClassificationTags(tags); } catch { setTagFetchFailed(true); } finally { @@ -358,13 +358,12 @@ const PipelineDetails = ({ const handleTableTagSelection = async ( selectedTags: EntityTags[], - editColumnTag: Task, - otherTags: TagLabel[] + editColumnTag: Task ) => { - const newSelectedTags: TagOption[] = map( - [...selectedTags, ...otherTags], - (tag) => ({ fqn: tag.tagFQN, source: tag.source }) - ); + const newSelectedTags: TagOption[] = map(selectedTags, (tag) => ({ + fqn: tag.tagFQN, + source: tag.source, + })); const prevTags = editColumnTag.tags?.filter((tag) => newSelectedTags.some((selectedTag) => selectedTag.fqn === tag.tagFQN) @@ -487,8 +486,8 @@ const PipelineDetails = ({ isTagLoading={isTagLoading} record={record} tagFetchFailed={tagFetchFailed} - tagList={classificationTags} - tags={getFilterTags(tags)} + tagList={getTagsHierarchy(classificationTags)} + tags={tags} type={TagSource.Classification} /> ), @@ -510,8 +509,8 @@ const PipelineDetails = ({ isTagLoading={isGlossaryLoading} record={record} tagFetchFailed={tagFetchFailed} - tagList={glossaryTags} - tags={getFilterTags(tags)} + tagList={getGlossaryTermHierarchy(glossaryTags)} + tags={tags} type={TagSource.Glossary} /> ), @@ -657,7 +656,9 @@ const PipelineDetails = ({ entityThreadLink={getEntityThreadLink(entityFieldThreadCount)} entityType={EntityType.PIPELINE} permission={ - pipelinePermissions.EditAll || pipelinePermissions.EditTags + (pipelinePermissions.EditAll || + pipelinePermissions.EditTags) && + !pipelineDetails.deleted } selectedTags={tags} tagType={TagSource.Classification} @@ -670,7 +671,9 @@ const PipelineDetails = ({ entityThreadLink={getEntityThreadLink(entityFieldThreadCount)} entityType={EntityType.PIPELINE} permission={ - pipelinePermissions.EditAll || pipelinePermissions.EditTags + (pipelinePermissions.EditAll || + pipelinePermissions.EditTags) && + !pipelineDetails.deleted } selectedTags={tags} tagType={TagSource.Glossary} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/PipelineDetails/PipelineDetails.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/PipelineDetails/PipelineDetails.test.tsx index 2607e5a2b9c1..fff2294b42ed 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/PipelineDetails/PipelineDetails.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/PipelineDetails/PipelineDetails.test.tsx @@ -181,6 +181,16 @@ jest.mock('../../utils/CommonUtils', () => ({ getCountBadge: jest.fn().mockImplementation((count) =>

{count}

), })); +jest.mock('utils/TagsUtils', () => ({ + getAllTagsList: jest.fn().mockImplementation(() => Promise.resolve([])), + getTagsHierarchy: jest.fn().mockReturnValue([]), +})); + +jest.mock('utils/GlossaryUtils', () => ({ + getGlossaryTermHierarchy: jest.fn().mockReturnValue([]), + getGlossaryTermsList: jest.fn().mockImplementation(() => Promise.resolve([])), +})); + jest.mock('../Execution/Execution.component', () => { return jest.fn().mockImplementation(() =>

Executions

); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/SchemaTab/SchemaTab.interfaces.ts b/openmetadata-ui/src/main/resources/ui/src/components/SchemaTab/SchemaTab.interfaces.ts index 3254ce78c889..451913a54e7e 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/SchemaTab/SchemaTab.interfaces.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/SchemaTab/SchemaTab.interfaces.ts @@ -26,7 +26,7 @@ export type Props = { tableConstraints: Table['tableConstraints']; sampleData?: TableData; hasDescriptionEditAccess?: boolean; - hasTagEditAccess?: boolean; + hasTagEditAccess: boolean; isReadOnly?: boolean; entityFqn?: string; entityFieldThreads?: EntityFieldThreads[]; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/TableQueries/TableQueryRightPanel/TableQueryRightPanel.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/TableQueries/TableQueryRightPanel/TableQueryRightPanel.interface.ts index 47821bc02a66..cb1c33334074 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/TableQueries/TableQueryRightPanel/TableQueryRightPanel.interface.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/TableQueries/TableQueryRightPanel/TableQueryRightPanel.interface.ts @@ -12,8 +12,8 @@ */ import { OperationPermission } from 'components/PermissionProvider/PermissionProvider.interface'; +import { TagsDetailsProps } from 'components/Tag/TagsContainerV1/TagsContainerV1.interface'; import { Query } from 'generated/entity/data/query'; -import { TagOption } from 'Models'; export interface TableQueryRightPanelProps { query: Query; @@ -24,6 +24,6 @@ export interface TableQueryRightPanelProps { export type TagDetails = { isLoading: boolean; - options: TagOption[]; + options: TagsDetailsProps[]; isError: boolean; }; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/TableTags/TableTags.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/TableTags/TableTags.component.tsx index 5d04d0c8af1f..9ac0eef2256f 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/TableTags/TableTags.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/TableTags/TableTags.component.tsx @@ -14,12 +14,10 @@ import { Button, Popover } from 'antd'; import { ReactComponent as IconEdit } from 'assets/svg/edit-new.svg'; import classNames from 'classnames'; -import TagsContainer from 'components/Tag/TagsContainer/tags-container'; -import TagsViewer from 'components/Tag/TagsViewer/tags-viewer'; +import TagsContainerEntityTable from 'components/Tag/TagsContainerEntityTable/TagsContainerEntityTable.component'; import { EntityField } from 'constants/Feeds.constants'; import { EntityType } from 'enums/entity.enum'; import { ThreadType } from 'generated/entity/feed/thread'; -import { TagSource } from 'generated/type/schema'; import { EntityFieldThreads } from 'interface/feed.interface'; import { isEmpty } from 'lodash'; import React, { useCallback, useMemo, useState } from 'react'; @@ -54,14 +52,9 @@ const TableTags = ({ const { t } = useTranslation(); const [isEdit, setIsEdit] = useState(false); - const isGlossaryType = useMemo(() => type === TagSource.Glossary, [type]); - const showEditTagButton = useMemo( () => - tags[type].length && - hasTagEditAccess && - !isEdit && - !showInlineEditTagButton, + tags.length && hasTagEditAccess && !isEdit && !showInlineEditTagButton, [tags, type, hasTagEditAccess, isEdit, showInlineEditTagButton] ); @@ -79,26 +72,6 @@ const TableTags = ({ ] ); - const otherTags = useMemo( - () => - isGlossaryType - ? tags[TagSource.Classification] - : tags[TagSource.Glossary], - [tags, isGlossaryType] - ); - - 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 addButtonHandler = useCallback(() => { setIsEdit(true); // Fetch Classification or Glossary only once @@ -109,14 +82,15 @@ const TableTags = ({ const getRequestTagsElement = useMemo(() => { const hasTags = !isEmpty(record.tags || []); - const text = hasTags - ? t('label.update-request-tag-plural') - : t('label.request-tag-plural'); return ( @@ -143,35 +117,29 @@ const TableTags = ({ return (
- {isReadOnly ? ( - - ) : ( -
- setIsEdit(false)} - onEditButtonClick={addButtonHandler} - onSelectionChange={async (selectedTags) => { - await handleTagSelection(selectedTags, record, otherTags); - setIsEdit(false); - }} - /> +
+ setIsEdit(false)} + onSelectionChange={async (selectedTags) => { + await handleTagSelection(selectedTags, record); + setIsEdit(false); + }} + /> + {!isReadOnly && (
{showEditTagButton ? (
-
- )} + )} +
); }; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/TableTags/TableTags.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/TableTags/TableTags.interface.ts index b26fa6d3f353..bd0622e478d1 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/TableTags/TableTags.interface.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/TableTags/TableTags.interface.ts @@ -11,6 +11,7 @@ * limitations under the License. */ +import { HierarchyTagsProps } from 'components/Tag/TagsContainerV1/TagsContainerV1.interface'; import { MlFeature } from 'generated/entity/data/mlmodel'; import { Task } from 'generated/entity/data/pipeline'; import { Field } from 'generated/entity/data/topic'; @@ -22,19 +23,18 @@ import { Column } from '../../generated/entity/data/table'; import { EntityFieldThreads } from '../../interface/feed.interface'; export interface TableTagsComponentProps { - tags: TableTagsProps; - tagList: TagOption[]; + tags: TagLabel[]; + tagList: HierarchyTagsProps[]; onUpdateTagsHandler?: (cell: T) => void; isReadOnly?: boolean; entityFqn?: string; record: T; index: number; isTagLoading: boolean; - hasTagEditAccess?: boolean; + hasTagEditAccess: boolean; handleTagSelection: ( - selectedTags: Array, - editColumnTag: T, - otherTags: TagLabel[] + selectedTags: EntityTags[], + editColumnTag: T ) => Promise; onRequestTagsHandler?: (cell: T) => void; getColumnName?: (cell: T) => string; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/TableTags/TableTags.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/TableTags/TableTags.test.tsx index 5e1ebaa985cb..4242447c4299 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/TableTags/TableTags.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/TableTags/TableTags.test.tsx @@ -18,10 +18,6 @@ import React from 'react'; import { MemoryRouter } from 'react-router-dom'; import TableTags from './TableTags.component'; -jest.mock('components/Tag/TagsViewer/tags-viewer', () => { - return jest.fn().mockReturnValue(

TagViewer

); -}); - jest.mock('utils/FeedElementUtils', () => ({ getFieldThreadElement: jest .fn() @@ -68,10 +64,7 @@ const requestUpdateTags = { const mockProp = { placeholder: 'Search Tags', dataTestId: 'tag-container', - tags: { - Classification: [], - Glossary: [], - }, + tags: [], record: { constraint: Constraint.Null, dataLength: 1, @@ -128,10 +121,7 @@ describe('Test EntityTableTags Component', () => { ...mockProp.record, tags: [...classificationTags, ...glossaryTags], }} - tags={{ - Classification: classificationTags, - Glossary: glossaryTags, - }} + tags={[...classificationTags, ...glossaryTags]} />, { wrapper: MemoryRouter, @@ -139,10 +129,8 @@ describe('Test EntityTableTags Component', () => { ); const tagContainer = await screen.findByTestId('tag-container-0'); - const tagViewer = await screen.findByTestId('tags-viewer'); expect(tagContainer).toBeInTheDocument(); - expect(tagViewer).toBeInTheDocument(); }); it('Tags list should be visible', async () => { @@ -153,10 +141,7 @@ describe('Test EntityTableTags Component', () => { ...mockProp.record, tags: [...classificationTags, ...glossaryTags], }} - tags={{ - Classification: classificationTags, - Glossary: glossaryTags, - }} + tags={[...classificationTags, ...glossaryTags]} />, { wrapper: MemoryRouter, @@ -178,10 +163,7 @@ describe('Test EntityTableTags Component', () => { ...mockProp.record, tags: [...classificationTags, ...glossaryTags], }} - tags={{ - Classification: classificationTags, - Glossary: glossaryTags, - }} + tags={[...classificationTags, ...glossaryTags]} />, { wrapper: MemoryRouter, @@ -204,10 +186,7 @@ describe('Test EntityTableTags Component', () => { ...mockProp.record, tags: [...classificationTags, ...glossaryTags], }} - tags={{ - Classification: classificationTags, - Glossary: glossaryTags, - }} + tags={[...classificationTags, ...glossaryTags]} />, { wrapper: MemoryRouter, diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Tag/TagsContainerEntityTable/TagsContainerEntityTable.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Tag/TagsContainerEntityTable/TagsContainerEntityTable.component.tsx new file mode 100644 index 000000000000..2d2f6f124228 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/Tag/TagsContainerEntityTable/TagsContainerEntityTable.component.tsx @@ -0,0 +1,162 @@ +/* + * Copyright 2022 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 { Button, Space } from 'antd'; +import { ReactComponent as IconEdit } from 'assets/svg/edit-new.svg'; +import Loader from 'components/Loader/Loader'; +import { TableTagsProps } from 'components/TableTags/TableTags.interface'; +import Tags from 'components/Tag/Tags/tags'; +import { TAG_CONSTANT, TAG_START_WITH } from 'constants/Tag.constants'; +import { TagSource } from 'generated/type/tagLabel'; +import { isEmpty } from 'lodash'; +import { EntityTags } from 'Models'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { getFilterTags } from 'utils/TableTags/TableTags.utils'; +import { getTagPlaceholder } from 'utils/TagsUtils'; +import TagTree from '../TagsTree/TagsTreeForm.component'; +import TagsViewer from '../TagsViewer/tags-viewer'; +import { TagsContainerEntityTableProps } from './TagsContainerEntityTable.interface'; + +const TagsContainerEntityTable = ({ + isLoading, + isEditing, + permission, + selectedTags, + showEditButton, + tagType, + treeData, + onCancel, + onSelectionChange, + onAddButtonClick, +}: TagsContainerEntityTableProps) => { + const [tags, setTags] = useState(); + + const isGlossaryType = useMemo( + () => tagType === TagSource.Glossary, + [tagType] + ); + + const showAddTagButton = useMemo( + () => permission && isEmpty(tags?.[tagType]), + [permission, tags?.[tagType]] + ); + + const selectedTagsInternal = useMemo( + () => tags?.[tagType].map(({ tagFQN }) => tagFQN as string), + [tags, tagType] + ); + + const showNoDataPlaceholder = useMemo( + () => !showAddTagButton && isEmpty(tags?.[tagType]), + [showAddTagButton, tags?.[tagType]] + ); + + const getUpdatedTags = (selectedTag: string[]): EntityTags[] => { + const updatedTags = selectedTag.map((t) => ({ + tagFQN: t, + source: isGlossaryType ? TagSource.Glossary : TagSource.Classification, + })); + + return updatedTags; + }; + + const handleSave = useCallback( + (selectedTag: string[]) => { + const updatedTags = getUpdatedTags(selectedTag); + onSelectionChange([ + ...updatedTags, + ...((isGlossaryType + ? tags?.[TagSource.Classification] + : tags?.[TagSource.Glossary]) ?? []), + ]); + }, + [isGlossaryType, tags, getUpdatedTags] + ); + + const editTagButton = useMemo( + () => + !isEmpty(tags?.[tagType]) && showEditButton ? ( +