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',
+}
|