From 9bb4b18628fa3b0ba5c98a93810f97639c522bc8 Mon Sep 17 00:00:00 2001 From: Shailesh Parmar Date: Wed, 12 Jul 2023 22:19:26 +0530 Subject: [PATCH] fix(ui): parent container is not populating in breadcrumb (#12294) * fix(ui): parent container is not populating in breadcrumb * fixed cypress for tagsAddRemove spec * updated parent hierarchy in breadcrumb for container * move fetch logic from container page to data header component * addressing comments * removed unwanted prop and added unit test for the change --- .../ui/cypress/e2e/Flow/TagsAddRemove.spec.js | 11 +- .../DataAssetsHeader.component.tsx | 49 +++++++- .../DataAssetsHeader.test.tsx | 117 ++++++++++++++++++ .../TitleBreadcrumbSkeleton.component.tsx | 35 +++--- .../Skeleton/Skeleton.interfaces.ts | 3 +- .../title-breadcrumb.component.tsx | 3 +- .../title-breadcrumb.interface.ts | 1 + .../src/pages/ContainerPage/ContainerPage.tsx | 24 ---- .../resources/ui/src/utils/EntityUtils.tsx | 101 ++++++++++----- 9 files changed, 261 insertions(+), 83 deletions(-) create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetsHeader/DataAssetsHeader.test.tsx 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 d961f4e2242..5d35d056362 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 @@ -19,13 +19,12 @@ import { import { TAGS_ADD_REMOVE_ENTITIES } from '../../constants/tagsAddRemove.constants'; const addTags = (tag) => { - cy.get('[data-testid="tag-selector"]') - .scrollIntoView() - .should('be.visible') - .click() - .type(tag.split('.')[1]); + const tagName = Cypress._.split(tag, '.')[1]; - cy.get(`[data-testid='tag-${tag}']`).should('be.visible').click(); + cy.get('[data-testid="tag-selector"]').scrollIntoView().should('be.visible'); + cy.get('[data-testid="tag-selector"]').click().type(tagName); + + cy.get(`[data-testid='tag-${tag}']`).click(); cy.get('[data-testid="tag-selector"] > .ant-select-selector').contains(tag); }; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetsHeader/DataAssetsHeader.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetsHeader/DataAssetsHeader.component.tsx index 23c7a7a8f8b..85b54da8718 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetsHeader/DataAssetsHeader.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetsHeader/DataAssetsHeader.component.tsx @@ -53,8 +53,10 @@ import React, { useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useHistory } from 'react-router-dom'; import { getActiveAnnouncement, getFeedCount } from 'rest/feedsAPI'; +import { getContainerByName } from 'rest/storageAPI'; import { getCurrentUserId, getEntityDetailLink } from 'utils/CommonUtils'; import { + getBreadcrumbForContainer, getBreadcrumbForEntitiesWithServiceOnly, getBreadcrumbForTable, getEntityBreadcrumbs, @@ -128,6 +130,8 @@ export const DataAssetsHeader = ({ const { isTourPage } = useTourProvider(); const { onCopyToClipBoard } = useClipboard(window.location.href); const [taskCount, setTaskCount] = useState(0); + const [parentContainers, setParentContainers] = useState([]); + const [isBreadcrumbLoading, setIsBreadcrumbLoading] = useState(false); const history = useHistory(); const icon = useMemo( () => @@ -196,12 +200,42 @@ export const DataAssetsHeader = ({ }); }; + const fetchContainerParent = async ( + parentName: string, + parents = [] as Container[] + ) => { + if (isEmpty(parentName)) { + return; + } + setIsBreadcrumbLoading(true); + try { + const response = await getContainerByName(parentName, 'parent'); + const updatedParent = [response, ...parents]; + if (response?.parent?.fullyQualifiedName) { + await fetchContainerParent( + response.parent.fullyQualifiedName, + updatedParent + ); + } else { + setParentContainers(updatedParent); + } + } catch (error) { + showErrorToast(error as AxiosError, t('server.unexpected-response')); + } finally { + setIsBreadcrumbLoading(false); + } + }; + useEffect(() => { if (dataAsset.fullyQualifiedName && !isTourPage) { fetchActiveAnnouncement(); fetchTaskCount(); } - }, [dataAsset.fullyQualifiedName]); + if (entityType === EntityType.CONTAINER) { + const asset = dataAsset as Container; + fetchContainerParent(asset.parent?.fullyQualifiedName ?? ''); + } + }, [dataAsset]); const { extraInfo, breadcrumbs }: DataAssetHeaderInfo = useMemo(() => { const returnData: DataAssetHeaderInfo = { @@ -354,8 +388,10 @@ export const DataAssetsHeader = ({ ); - returnData.breadcrumbs = - getBreadcrumbForEntitiesWithServiceOnly(containerDetails); + returnData.breadcrumbs = getBreadcrumbForContainer({ + entity: containerDetails, + parents: parentContainers, + }); break; @@ -430,7 +466,7 @@ export const DataAssetsHeader = ({ } return returnData; - }, [dataAsset, entityType]); + }, [dataAsset, entityType, parentContainers]); const handleOpenTaskClick = () => { if (!dataAsset.fullyQualifiedName) { @@ -460,7 +496,10 @@ export const DataAssetsHeader = ({ - + { + return jest + .fn() + .mockImplementation(() =>
TitleBreadcrumb.component
); + } +); +jest.mock( + 'components/Entity/EntityHeaderTitle/EntityHeaderTitle.component', + () => { + return jest + .fn() + .mockImplementation(() =>
EntityHeaderTitle.component
); + } +); +jest.mock('components/common/OwnerLabel/OwnerLabel.component', () => ({ + OwnerLabel: jest + .fn() + .mockImplementation(() =>
OwnerLabel.component
), +})); +jest.mock('components/common/TierCard/TierCard', () => + jest.fn().mockImplementation(({ children }) => ( +
+ TierCard.component +
{children}
+
+ )) +); +jest.mock('components/common/entityPageInfo/ManageButton/ManageButton', () => + jest.fn().mockImplementation(() =>
ManageButton.component
) +); +jest.mock( + 'components/common/entityPageInfo/AnnouncementCard/AnnouncementCard', + () => + jest.fn().mockImplementation(() =>
AnnouncementCard.component
) +); +jest.mock( + 'components/common/entityPageInfo/AnnouncementDrawer/AnnouncementDrawer', + () => + jest.fn().mockImplementation(() =>
AnnouncementDrawer.component
) +); +jest.mock('rest/storageAPI', () => ({ + getContainerByName: jest + .fn() + .mockImplementation(() => Promise.resolve({ name: 'test' })), +})); + +describe('DataAssetsHeader component', () => { + it('should call getContainerByName API on Page load for container assets', () => { + const mockGetContainerByName = getContainerByName as jest.Mock; + render(); + + expect(mockGetContainerByName).toHaveBeenCalledWith( + 'fullyQualifiedName', + 'parent' + ); + }); + + it('should not call getContainerByName API if parent is undefined', () => { + const mockGetContainerByName = getContainerByName as jest.Mock; + render( + + ); + + expect(mockGetContainerByName).not.toHaveBeenCalled(); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Skeleton/BreadCrumb/TitleBreadcrumbSkeleton.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Skeleton/BreadCrumb/TitleBreadcrumbSkeleton.component.tsx index 5ed00425e33..10fdedd8530 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Skeleton/BreadCrumb/TitleBreadcrumbSkeleton.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Skeleton/BreadCrumb/TitleBreadcrumbSkeleton.component.tsx @@ -10,29 +10,32 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Skeleton } from 'antd'; +import { Col, Row, Skeleton } from 'antd'; import { uniqueId } from 'lodash'; import React from 'react'; import { TitleBreadcrumbSkeletonProps } from '../Skeleton.interfaces'; const TitleBreadcrumbSkeleton = ({ - titleLinks, + loading, children, }: TitleBreadcrumbSkeletonProps) => - titleLinks.length === 0 ? ( -
- {titleLinks.map(() => ( - - ))} -
+ loading ? ( + + {Array(3) + .fill(null) + .map(() => ( + + + + ))} + ) : ( children ); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Skeleton/Skeleton.interfaces.ts b/openmetadata-ui/src/main/resources/ui/src/components/Skeleton/Skeleton.interfaces.ts index ea0853b0a64..76205b87aa5 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Skeleton/Skeleton.interfaces.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/Skeleton/Skeleton.interfaces.ts @@ -12,7 +12,6 @@ */ import { SkeletonProps } from 'antd'; import { SkeletonButtonProps } from 'antd/lib/skeleton/Button'; -import { TitleBreadcrumbProps } from '../common/title-breadcrumb/title-breadcrumb.interface'; export interface Key { key?: React.Key | null | undefined; @@ -26,7 +25,7 @@ export interface SkeletonInterface extends Children { dataLength?: number; } export interface TitleBreadcrumbSkeletonProps extends Children { - titleLinks: TitleBreadcrumbProps['titleLinks']; + loading: boolean; } export interface ButtonSkeletonProps diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/title-breadcrumb/title-breadcrumb.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/title-breadcrumb/title-breadcrumb.component.tsx index bab7b908593..ca3e61adfeb 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/title-breadcrumb/title-breadcrumb.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/title-breadcrumb/title-breadcrumb.component.tsx @@ -28,6 +28,7 @@ const TitleBreadcrumb: FunctionComponent = ({ titleLinks, className = '', noLink = false, + loading = false, widthDeductions, }: TitleBreadcrumbProps) => { const { t } = useTranslation(); @@ -59,7 +60,7 @@ const TitleBreadcrumb: FunctionComponent = ({ }, []); return ( - +