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 10814c94a25..ca058f6a0e8 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 @@ -20,6 +20,7 @@ import { useAuthContext } from '../../authentication/auth-provider/AuthProvider' import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants'; import { getTeamAndUserDetailsPath } from '../../constants/constants'; import { observerOptions } from '../../constants/Mydata.constants'; +import { SettledStatus } from '../../enums/axios.enum'; import { EntityType } from '../../enums/entity.enum'; import { OwnerType } from '../../enums/user.enum'; import { Dashboard } from '../../generated/entity/data/dashboard'; @@ -358,24 +359,38 @@ const DashboardDetails = ({ const fetchTagsAndGlossaryTerms = () => { setIsTagLoading(true); - Promise.all([getTagCategories(), fetchGlossaryTerms()]) + Promise.allSettled([getTagCategories(), fetchGlossaryTerms()]) .then((values) => { let tagsAndTerms: TagOption[] = []; - if (values[0].data) { - tagsAndTerms = getTaglist(values[0].data).map((tag) => { + if ( + values[0].status === SettledStatus.FULFILLED && + values[0].value.data + ) { + tagsAndTerms = getTaglist(values[0].value.data).map((tag) => { return { fqn: tag, source: 'Tag' }; }); } - if (values[1] && values[1].length > 0) { - const glossaryTerms: TagOption[] = getGlossaryTermlist(values[1]).map( - (tag) => { - return { fqn: tag, source: 'Glossary' }; - } - ); + if ( + values[1].status === SettledStatus.FULFILLED && + values[1].value && + values[1].value.length > 0 + ) { + const glossaryTerms: TagOption[] = getGlossaryTermlist( + values[1].value + ).map((tag) => { + return { fqn: tag, source: 'Glossary' }; + }); tagsAndTerms = [...tagsAndTerms, ...glossaryTerms]; } setTagList(tagsAndTerms); - setTagFetchFailed(false); + if ( + values[0].status === SettledStatus.FULFILLED && + values[1].status === SettledStatus.FULFILLED + ) { + setTagFetchFailed(false); + } else { + setTagFetchFailed(true); + } }) .catch(() => { setTagList([]); @@ -577,6 +592,7 @@ const DashboardDetails = ({ { if (!editChartTags) { // Fetch tags and terms only once 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 a7cfd2f1785..93161b7a9a6 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 @@ -11,16 +11,31 @@ * limitations under the License. */ -import { findByTestId, findByText, render } from '@testing-library/react'; -import { LeafNodes, LoadingNodeState } from 'Models'; +import { + findByTestId, + findByText, + fireEvent, + render, +} from '@testing-library/react'; +import { flatten } from 'lodash'; +import { + FormattedGlossaryTermData, + LeafNodes, + LoadingNodeState, + TagOption, +} from 'Models'; import React from 'react'; import { MemoryRouter } from 'react-router-dom'; import { Dashboard } from '../../generated/entity/data/dashboard'; +import { TagCategory, TagClass } from '../../generated/entity/tags/tagCategory'; import { EntityLineage } from '../../generated/type/entityLineage'; import { EntityReference } from '../../generated/type/entityReference'; import { Paging } from '../../generated/type/paging'; import { TagLabel } from '../../generated/type/tagLabel'; +import { fetchGlossaryTerms } from '../../utils/GlossaryUtils'; +import { getTagCategories } from '../../utils/TagsUtils'; import DashboardDetails from './DashboardDetails.component'; +import { ChartType } from './DashboardDetails.interface'; jest.mock('../../authentication/auth-provider/AuthProvider', () => { return { @@ -54,7 +69,13 @@ const mockUserTeam = [ ]; const DashboardDetailsProps = { - charts: [], + charts: [ + { + chartUrl: 'http://localhost', + chartType: 'Area', + displayName: 'Test chart', + }, + ] as ChartType[], serviceType: '', dashboardUrl: '', tagList: [], @@ -100,6 +121,58 @@ const DashboardDetailsProps = { const mockObserve = jest.fn(); const mockunObserve = jest.fn(); +const mockTagList = [ + { + id: 'tagCatId1', + name: 'TagCat1', + description: '', + categoryType: 'Classification', + children: [ + { + id: 'tagId1', + name: 'Tag1', + fullyQualifiedName: 'TagCat1.Tag1', + description: '', + deprecated: false, + deleted: false, + }, + ], + }, + { + id: 'tagCatId2', + name: 'TagCat2', + description: '', + categoryType: 'Classification', + children: [ + { + id: 'tagId2', + name: 'Tag2', + fullyQualifiedName: 'TagCat2.Tag2', + description: '', + deprecated: false, + deleted: false, + }, + ], + }, +]; + +const mockGlossaryList = [ + { + name: 'Tag1', + displayName: 'Tag1', + fqdn: 'Glossary.Tag1', + type: 'glossaryTerm', + id: 'glossaryTagId1', + }, + { + name: 'Tag2', + displayName: 'Tag2', + fqdn: 'Glossary.Tag2', + type: 'glossaryTerm', + id: 'glossaryTagId2', + }, +]; + window.IntersectionObserver = jest.fn().mockImplementation(() => ({ observe: mockObserve, unobserve: mockunObserve, @@ -117,7 +190,15 @@ jest.mock('../common/rich-text-editor/RichTextEditorPreviewer', () => { }); jest.mock('../tags-container/tags-container', () => { - return jest.fn().mockReturnValue(

Tag Container

); + return jest.fn().mockImplementation(({ tagList }) => { + return ( + <> + {tagList.map((tag: TagOption, idx: number) => ( +

{tag.fqn}

+ ))} + + ); + }); }); jest.mock('../tags/tags', () => { @@ -144,6 +225,14 @@ jest.mock('../EntityLineage/EntityLineage.component', () => { return jest.fn().mockReturnValue(

Lineage

); }); +jest.mock('../common/non-admin-action/NonAdminAction', () => { + return jest + .fn() + .mockImplementation(({ children }) => ( +

{children}

+ )); +}); + jest.mock('../../utils/CommonUtils', () => ({ addToRecentViewed: jest.fn(), getCountBadge: jest.fn(), @@ -154,6 +243,30 @@ jest.mock('../../utils/CommonUtils', () => ({ getEntityPlaceHolder: jest.fn().mockReturnValue('value'), getEntityName: jest.fn().mockReturnValue('entityName'), pluralize: jest.fn().mockReturnValue('2 charts'), + isEven: jest.fn().mockReturnValue(true), + getEntityDeleteMessage: jest.fn(), +})); + +jest.mock('../../utils/GlossaryUtils', () => ({ + fetchGlossaryTerms: jest.fn(() => Promise.resolve(mockGlossaryList)), + getGlossaryTermlist: jest.fn((terms) => { + return terms.map((term: FormattedGlossaryTermData) => term?.fqdn); + }), +})); + +jest.mock('../../utils/TagsUtils', () => ({ + getTagCategories: jest.fn(() => Promise.resolve({ data: mockTagList })), + getTaglist: jest.fn((categories) => { + const children = categories.map((category: TagCategory) => { + return category.children || []; + }); + const allChildren = flatten(children); + const tagList = (allChildren as unknown as TagClass[]).map((tag) => { + return tag?.fullyQualifiedName || ''; + }); + + return tagList; + }), })); describe('Test DashboardDetails component', () => { @@ -243,4 +356,100 @@ describe('Test DashboardDetails component', () => { expect(mockObserve).toHaveBeenCalled(); }); + + it('Check if tags and glossary-terms are present', async () => { + const { getByTestId, findByText } = render( + , + { + wrapper: MemoryRouter, + } + ); + + const tagWrapper = getByTestId('tags-wrapper'); + fireEvent.click( + tagWrapper, + new MouseEvent('click', { bubbles: true, cancelable: true }) + ); + + const tag1 = await findByText('TagCat1.Tag1'); + const glossaryTerm1 = await findByText('Glossary.Tag1'); + + expect(tag1).toBeInTheDocument(); + expect(glossaryTerm1).toBeInTheDocument(); + }); + + it('Check if only tags are present', async () => { + (fetchGlossaryTerms as jest.Mock).mockImplementationOnce(() => + Promise.reject() + ); + const { getByTestId, findByText, queryByText } = render( + , + { + wrapper: MemoryRouter, + } + ); + + const tagWrapper = getByTestId('tags-wrapper'); + fireEvent.click( + tagWrapper, + new MouseEvent('click', { bubbles: true, cancelable: true }) + ); + + const tag1 = await findByText('TagCat1.Tag1'); + const glossaryTerm1 = queryByText('Glossary.Tag1'); + + expect(tag1).toBeInTheDocument(); + expect(glossaryTerm1).not.toBeInTheDocument(); + }); + + it('Check if only glossary terms are present', async () => { + (getTagCategories as jest.Mock).mockImplementationOnce(() => + Promise.reject() + ); + const { getByTestId, findByText, queryByText } = render( + , + { + wrapper: MemoryRouter, + } + ); + + const tagWrapper = getByTestId('tags-wrapper'); + fireEvent.click( + tagWrapper, + new MouseEvent('click', { bubbles: true, cancelable: true }) + ); + + const tag1 = queryByText('TagCat1.Tag1'); + const glossaryTerm1 = await findByText('Glossary.Tag1'); + + expect(tag1).not.toBeInTheDocument(); + expect(glossaryTerm1).toBeInTheDocument(); + }); + + it('Check that tags and glossary terms are not present', async () => { + (getTagCategories as jest.Mock).mockImplementationOnce(() => + Promise.reject() + ); + (fetchGlossaryTerms as jest.Mock).mockImplementationOnce(() => + Promise.reject() + ); + const { getByTestId, queryByText } = render( + , + { + wrapper: MemoryRouter, + } + ); + + const tagWrapper = getByTestId('tags-wrapper'); + fireEvent.click( + tagWrapper, + new MouseEvent('click', { bubbles: true, cancelable: true }) + ); + + const tag1 = queryByText('TagCat1.Tag1'); + const glossaryTerm1 = queryByText('Glossary.Tag1'); + + expect(tag1).not.toBeInTheDocument(); + expect(glossaryTerm1).not.toBeInTheDocument(); + }); }); 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 6ba07ca2bbc..e098abad60e 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 @@ -21,6 +21,7 @@ import { Link } from 'react-router-dom'; import { useExpanded, useTable } from 'react-table'; import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants'; import { getTableDetailsPath } from '../../constants/constants'; +import { SettledStatus } from '../../enums/axios.enum'; import { EntityType, FqnPart } from '../../enums/entity.enum'; import { Column, @@ -154,24 +155,38 @@ const EntityTable = ({ const fetchTagsAndGlossaryTerms = () => { setIsTagLoading(true); - Promise.all([getTagCategories(), fetchGlossaryTerms()]) + Promise.allSettled([getTagCategories(), fetchGlossaryTerms()]) .then((values) => { let tagsAndTerms: TagOption[] = []; - if (values[0].data) { - tagsAndTerms = getTaglist(values[0].data).map((tag) => { + if ( + values[0].status === SettledStatus.FULFILLED && + values[0].value.data + ) { + tagsAndTerms = getTaglist(values[0].value.data).map((tag) => { return { fqn: tag, source: 'Tag' }; }); } - if (values[1] && values[1].length > 0) { - const glossaryTerms: TagOption[] = getGlossaryTermlist(values[1]).map( - (tag) => { - return { fqn: tag, source: 'Glossary' }; - } - ); + if ( + values[1].status === SettledStatus.FULFILLED && + values[1].value && + values[1].value.length > 0 + ) { + const glossaryTerms: TagOption[] = getGlossaryTermlist( + values[1].value + ).map((tag) => { + return { fqn: tag, source: 'Glossary' }; + }); tagsAndTerms = [...tagsAndTerms, ...glossaryTerms]; } setAllTags(tagsAndTerms); - setTagFetchFailed(false); + if ( + values[0].status === SettledStatus.FULFILLED && + values[1].status === SettledStatus.FULFILLED + ) { + setTagFetchFailed(false); + } else { + setTagFetchFailed(true); + } }) .catch(() => { setAllTags([]); @@ -525,6 +540,7 @@ const EntityTable = ({ ) : (
{ if (!editColumnTag) { handleEditColumnTag(row.original, row.id); 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 a9522dfef36..65a6180db5e 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 @@ -18,10 +18,15 @@ import { queryByTestId, render, } from '@testing-library/react'; +import { flatten } from 'lodash'; +import { FormattedGlossaryTermData, TagOption } from 'Models'; import React from 'react'; import { MemoryRouter } from 'react-router-dom'; import { Table } from '../../generated/entity/data/table'; +import { TagCategory, TagClass } from '../../generated/entity/tags/tagCategory'; import { ModifiedTableColumn } from '../../interface/dataQuality.interface'; +import { fetchGlossaryTerms } from '../../utils/GlossaryUtils'; +import { getTagCategories } from '../../utils/TagsUtils'; import EntityTable from './EntityTable.component'; const mockTableheader = [ @@ -144,12 +149,68 @@ const mockEntityTableProp = { onThreadLinkSelect, }; +const mockTagList = [ + { + id: 'tagCatId1', + name: 'TagCat1', + description: '', + categoryType: 'Classification', + children: [ + { + id: 'tagId1', + name: 'Tag1', + fullyQualifiedName: 'TagCat1.Tag1', + description: '', + deprecated: false, + deleted: false, + }, + ], + }, + { + id: 'tagCatId2', + name: 'TagCat2', + description: '', + categoryType: 'Classification', + children: [ + { + id: 'tagId2', + name: 'Tag2', + fullyQualifiedName: 'TagCat2.Tag2', + description: '', + deprecated: false, + deleted: false, + }, + ], + }, +]; + +const mockGlossaryList = [ + { + name: 'Tag1', + displayName: 'Tag1', + fqdn: 'Glossary.Tag1', + type: 'glossaryTerm', + id: 'glossaryTagId1', + }, + { + name: 'Tag2', + displayName: 'Tag2', + fqdn: 'Glossary.Tag2', + type: 'glossaryTerm', + id: 'glossaryTagId2', + }, +]; + jest.mock('@fortawesome/react-fontawesome', () => ({ FontAwesomeIcon: jest.fn().mockReturnValue(Icon), })); jest.mock('../common/non-admin-action/NonAdminAction', () => { - return jest.fn().mockReturnValue(

NonAdminAction

); + return jest + .fn() + .mockImplementation(({ children }) => ( +

{children}

+ )); }); jest.mock('../common/rich-text-editor/RichTextEditorPreviewer', () => { @@ -159,7 +220,15 @@ jest.mock('../Modals/ModalWithMarkdownEditor/ModalWithMarkdownEditor', () => ({ ModalWithMarkdownEditor: jest.fn().mockReturnValue(

EditorModal

), })); jest.mock('../tags-container/tags-container', () => { - return jest.fn().mockReturnValue(

TagContainer

); + return jest.fn().mockImplementation(({ tagList }) => { + return ( + <> + {tagList.map((tag: TagOption, idx: number) => ( +

{tag.fqn}

+ ))} + + ); + }); }); jest.mock('../tags-viewer/tags-viewer', () => { return jest.fn().mockReturnValue(

TagViewer

); @@ -168,6 +237,28 @@ jest.mock('../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: FormattedGlossaryTermData) => term?.fqdn); + }), +})); + +jest.mock('../../utils/TagsUtils', () => ({ + getTagCategories: jest.fn(() => Promise.resolve({ data: mockTagList })), + getTaglist: jest.fn((categories) => { + const children = categories.map((category: TagCategory) => { + return category.children || []; + }); + const allChildren = flatten(children); + const tagList = (allChildren as unknown as TagClass[]).map((tag) => { + return tag?.fullyQualifiedName || ''; + }); + + return tagList; + }), +})); + describe('Test EntityTable Component', () => { it('Check if it has all child elements', async () => { const { container } = render(, { @@ -309,4 +400,97 @@ describe('Test EntityTable Component', () => { String(mockEntityFieldThreads[0].count) ); }); + + it('Check if tags and glossary-terms are present', async () => { + const { getAllByTestId, findAllByText } = render( + , + { + wrapper: MemoryRouter, + } + ); + + const tagWrapper = getAllByTestId('tags-wrapper')[0]; + fireEvent.click(tagWrapper); + + const tag1 = await findAllByText('TagCat1.Tag1'); + const glossaryTerm1 = await findAllByText('Glossary.Tag1'); + + expect(tag1).toHaveLength(mockEntityTableProp.tableColumns.length); + expect(glossaryTerm1).toHaveLength(mockEntityTableProp.tableColumns.length); + }); + + it('Check if only tags are present', async () => { + (fetchGlossaryTerms as jest.Mock).mockImplementationOnce(() => + Promise.reject() + ); + const { getAllByTestId, findAllByText, queryAllByText } = render( + , + { + wrapper: MemoryRouter, + } + ); + + const tagWrapper = getAllByTestId('tags-wrapper')[0]; + fireEvent.click( + tagWrapper, + new MouseEvent('click', { bubbles: true, cancelable: true }) + ); + + const tag1 = await findAllByText('TagCat1.Tag1'); + const glossaryTerm1 = queryAllByText('Glossary.Tag1'); + + expect(tag1).toHaveLength(mockEntityTableProp.tableColumns.length); + expect(glossaryTerm1).toHaveLength(0); + }); + + it('Check if only glossary terms are present', async () => { + (getTagCategories as jest.Mock).mockImplementationOnce(() => + Promise.reject() + ); + const { getAllByTestId, findAllByText, queryAllByText } = render( + , + { + wrapper: MemoryRouter, + } + ); + + const tagWrapper = getAllByTestId('tags-wrapper')[0]; + fireEvent.click( + tagWrapper, + new MouseEvent('click', { bubbles: true, cancelable: true }) + ); + + const tag1 = queryAllByText('TagCat1.Tag1'); + const glossaryTerm1 = await findAllByText('Glossary.Tag1'); + + expect(tag1).toHaveLength(0); + expect(glossaryTerm1).toHaveLength(mockEntityTableProp.tableColumns.length); + }); + + it('Check that tags and glossary terms are not present', async () => { + (getTagCategories as jest.Mock).mockImplementationOnce(() => + Promise.reject() + ); + (fetchGlossaryTerms as jest.Mock).mockImplementationOnce(() => + Promise.reject() + ); + const { getAllByTestId, queryAllByText } = render( + , + { + wrapper: MemoryRouter, + } + ); + + const tagWrapper = getAllByTestId('tags-wrapper')[0]; + fireEvent.click( + tagWrapper, + new MouseEvent('click', { bubbles: true, cancelable: true }) + ); + + const tag1 = queryAllByText('TagCat1.Tag1'); + const glossaryTerm1 = queryAllByText('Glossary.Tag1'); + + expect(tag1).toHaveLength(0); + expect(glossaryTerm1).toHaveLength(0); + }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/entityPageInfo/EntityPageInfo.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/entityPageInfo/EntityPageInfo.test.tsx index d6776f278ef..8abf959a2bb 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/entityPageInfo/EntityPageInfo.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/entityPageInfo/EntityPageInfo.test.tsx @@ -18,9 +18,17 @@ import { queryByTestId, render, } from '@testing-library/react'; +import { flatten } from 'lodash'; +import { FormattedGlossaryTermData, TagOption } from 'Models'; import React from 'react'; import { MemoryRouter } from 'react-router-dom'; +import { + TagCategory, + TagClass, +} from '../../../generated/entity/tags/tagCategory'; import { TagLabel } from '../../../generated/type/tagLabel'; +import { fetchGlossaryTerms } from '../../../utils/GlossaryUtils'; +import { getTagCategories } from '../../../utils/TagsUtils'; import EntityPageInfo from './EntityPageInfo'; const mockEntityFieldThreads = [ @@ -112,6 +120,58 @@ const mockEntityInfoProp = { onThreadLinkSelect, }; +const mockTagList = [ + { + id: 'tagCatId1', + name: 'TagCat1', + description: '', + categoryType: 'Classification', + children: [ + { + id: 'tagId1', + name: 'Tag1', + fullyQualifiedName: 'TagCat1.Tag1', + description: '', + deprecated: false, + deleted: false, + }, + ], + }, + { + id: 'tagCatId2', + name: 'TagCat2', + description: '', + categoryType: 'Classification', + children: [ + { + id: 'tagId2', + name: 'Tag2', + fullyQualifiedName: 'TagCat2.Tag2', + description: '', + deprecated: false, + deleted: false, + }, + ], + }, +]; + +const mockGlossaryList = [ + { + name: 'Tag1', + displayName: 'Tag1', + fqdn: 'Glossary.Tag1', + type: 'glossaryTerm', + id: 'glossaryTagId1', + }, + { + name: 'Tag2', + displayName: 'Tag2', + fqdn: 'Glossary.Tag2', + type: 'glossaryTerm', + id: 'glossaryTagId2', + }, +]; + jest.mock('../../../utils/CommonUtils', () => ({ getHtmlForNonAdminAction: jest.fn(), })); @@ -122,8 +182,10 @@ jest.mock('../../../utils/EntityUtils', () => ({ })); jest.mock('../../../utils/GlossaryUtils', () => ({ - fetchGlossaryTerms: jest.fn(), - getGlossaryTermlist: jest.fn(), + fetchGlossaryTerms: jest.fn(() => Promise.resolve(mockGlossaryList)), + getGlossaryTermlist: jest.fn((terms) => { + return terms.map((term: FormattedGlossaryTermData) => term?.fqdn); + }), })); jest.mock('../../../utils/TableUtils', () => ({ @@ -131,18 +193,38 @@ jest.mock('../../../utils/TableUtils', () => ({ })); jest.mock('../../../utils/TagsUtils', () => ({ - getTagCategories: jest.fn(), - getTaglist: jest.fn(), + getTagCategories: jest.fn(() => Promise.resolve({ data: mockTagList })), + getTaglist: jest.fn((categories) => { + const children = categories.map((category: TagCategory) => { + return category.children || []; + }); + const allChildren = flatten(children); + const tagList = (allChildren as unknown as TagClass[]).map((tag) => { + return tag?.fullyQualifiedName || ''; + }); + + return tagList; + }), })); jest.mock('../non-admin-action/NonAdminAction', () => { return jest .fn() - .mockReturnValue(

NonAdminAction

); + .mockImplementation(({ children }) => ( +

{children}

+ )); }); jest.mock('../../tags-container/tags-container', () => { - return jest.fn().mockReturnValue(

TagContainer

); + return jest.fn().mockImplementation(({ tagList }) => { + return ( + <> + {tagList.map((tag: TagOption, idx: number) => ( +

{tag.fqn}

+ ))} + + ); + }); }); jest.mock('../../tags-viewer/tags-viewer', () => { @@ -431,4 +513,100 @@ describe('Test EntityPageInfo component', () => { expect(onThreadLinkSelect).toBeCalled(); }); + + it('Check if tags and glossary-terms are present', async () => { + const { getByTestId, findByText } = render( + , + { + wrapper: MemoryRouter, + } + ); + + const tagWrapper = getByTestId('tags-wrapper'); + fireEvent.click( + tagWrapper, + new MouseEvent('click', { bubbles: true, cancelable: true }) + ); + + const tag1 = await findByText('TagCat1.Tag1'); + const glossaryTerm1 = await findByText('Glossary.Tag1'); + + expect(tag1).toBeInTheDocument(); + expect(glossaryTerm1).toBeInTheDocument(); + }); + + it('Check if only tags are present', async () => { + (fetchGlossaryTerms as jest.Mock).mockImplementationOnce(() => + Promise.reject() + ); + const { getByTestId, findByText, queryByText } = render( + , + { + wrapper: MemoryRouter, + } + ); + + const tagWrapper = getByTestId('tags-wrapper'); + fireEvent.click( + tagWrapper, + new MouseEvent('click', { bubbles: true, cancelable: true }) + ); + + const tag1 = await findByText('TagCat1.Tag1'); + const glossaryTerm1 = queryByText('Glossary.Tag1'); + + expect(tag1).toBeInTheDocument(); + expect(glossaryTerm1).not.toBeInTheDocument(); + }); + + it('Check if only glossary terms are present', async () => { + (getTagCategories as jest.Mock).mockImplementationOnce(() => + Promise.reject() + ); + const { getByTestId, findByText, queryByText } = render( + , + { + wrapper: MemoryRouter, + } + ); + + const tagWrapper = getByTestId('tags-wrapper'); + fireEvent.click( + tagWrapper, + new MouseEvent('click', { bubbles: true, cancelable: true }) + ); + + const tag1 = queryByText('TagCat1.Tag1'); + const glossaryTerm1 = await findByText('Glossary.Tag1'); + + expect(tag1).not.toBeInTheDocument(); + expect(glossaryTerm1).toBeInTheDocument(); + }); + + it('Check that tags and glossary terms are not present', async () => { + (getTagCategories as jest.Mock).mockImplementationOnce(() => + Promise.reject() + ); + (fetchGlossaryTerms as jest.Mock).mockImplementationOnce(() => + Promise.reject() + ); + const { getByTestId, queryByText } = render( + , + { + wrapper: MemoryRouter, + } + ); + + const tagWrapper = getByTestId('tags-wrapper'); + fireEvent.click( + tagWrapper, + new MouseEvent('click', { bubbles: true, cancelable: true }) + ); + + const tag1 = queryByText('TagCat1.Tag1'); + const glossaryTerm1 = queryByText('Glossary.Tag1'); + + expect(tag1).not.toBeInTheDocument(); + expect(glossaryTerm1).not.toBeInTheDocument(); + }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/entityPageInfo/EntityPageInfo.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/entityPageInfo/EntityPageInfo.tsx index cf7edd2d485..f78996846a2 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/entityPageInfo/EntityPageInfo.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/entityPageInfo/EntityPageInfo.tsx @@ -19,6 +19,7 @@ import { EntityFieldThreads, EntityTags, ExtraInfo, TagOption } from 'Models'; import React, { Fragment, useEffect, useState } from 'react'; import { FQN_SEPARATOR_CHAR } from '../../../constants/char.constants'; import { FOLLOWERS_VIEW_CAP } from '../../../constants/constants'; +import { SettledStatus } from '../../../enums/axios.enum'; import { Operation } from '../../../generated/entity/policies/accessControl/rule'; import { EntityReference } from '../../../generated/type/entityReference'; import { LabelType, State, TagLabel } from '../../../generated/type/tagLabel'; @@ -223,24 +224,38 @@ const EntityPageInfo = ({ const fetchTagsAndGlossaryTerms = () => { setIsTagLoading(true); - Promise.all([getTagCategories(), fetchGlossaryTerms()]) + Promise.allSettled([getTagCategories(), fetchGlossaryTerms()]) .then((values) => { let tagsAndTerms: TagOption[] = []; - if (values[0].data) { - tagsAndTerms = getTaglist(values[0].data).map((tag) => { + if ( + values[0].status === SettledStatus.FULFILLED && + values[0].value.data + ) { + tagsAndTerms = getTaglist(values[0].value.data).map((tag) => { return { fqn: tag, source: 'Tag' }; }); } - if (values[1] && values[1].length > 0) { - const glossaryTerms: TagOption[] = getGlossaryTermlist(values[1]).map( - (tag) => { - return { fqn: tag, source: 'Glossary' }; - } - ); + if ( + values[1].status === SettledStatus.FULFILLED && + values[1].value && + values[1].value.length > 0 + ) { + const glossaryTerms: TagOption[] = getGlossaryTermlist( + values[1].value + ).map((tag) => { + return { fqn: tag, source: 'Glossary' }; + }); tagsAndTerms = [...tagsAndTerms, ...glossaryTerms]; } setTagList(tagsAndTerms); - setTagFetchFailed(false); + if ( + values[0].status === SettledStatus.FULFILLED && + values[1].status === SettledStatus.FULFILLED + ) { + setTagFetchFailed(false); + } else { + setTagFetchFailed(true); + } }) .catch(() => { setTagList([]); @@ -395,6 +410,7 @@ const EntityPageInfo = ({ trigger="click">
{ // Fetch tags and terms only once if (tagList.length === 0 || tagFetchFailed) { diff --git a/openmetadata-ui/src/main/resources/ui/src/enums/axios.enum.ts b/openmetadata-ui/src/main/resources/ui/src/enums/axios.enum.ts index 82fea86110c..d14aecd4157 100644 --- a/openmetadata-ui/src/main/resources/ui/src/enums/axios.enum.ts +++ b/openmetadata-ui/src/main/resources/ui/src/enums/axios.enum.ts @@ -18,3 +18,8 @@ export enum ClientErrors { FORBIDDEN = 403, NOT_FOUND = 404, } + +export enum SettledStatus { + FULFILLED = 'fulfilled', + REJECTED = 'rejected', +}