diff --git a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Flow/EditEntityLineage.spec.js b/openmetadata-ui/src/main/resources/ui/cypress/e2e/Flow/Lineage.spec.js similarity index 62% rename from openmetadata-ui/src/main/resources/ui/cypress/e2e/Flow/EditEntityLineage.spec.js rename to openmetadata-ui/src/main/resources/ui/cypress/e2e/Flow/Lineage.spec.js index 13a4c3d33d1..383c5afd75b 100644 --- a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Flow/EditEntityLineage.spec.js +++ b/openmetadata-ui/src/main/resources/ui/cypress/e2e/Flow/Lineage.spec.js @@ -48,3 +48,38 @@ describe('Entity Details Page', () => { }); }); }); + +describe('Lineage functionality', () => { + beforeEach(() => { + cy.login(); + }); + + it('toggle fullscreen mode', () => { + visitEntityDetailsPage( + tableEntity.term, + tableEntity.serviceName, + tableEntity.entity + ); + cy.get('[data-testid="lineage"]').click(); + + // Enable fullscreen + cy.get('[data-testid="full-screen"]').click(); + cy.url().should('include', 'fullscreen=true'); + cy.get('[data-testid="breadcrumb"]') + .should('be.visible') + .and('contain', 'Lineage'); + cy.get('[data-testid="lineage-details"]').should( + 'have.class', + 'full-screen-lineage' + ); + + // Exit fullscreen + cy.get('[data-testid="exit-full-screen"]').click(); + cy.url().should('not.include', 'fullscreen=true'); + cy.get('[data-testid="breadcrumb"]').should('not.contain', 'Lineage'); + cy.get('[data-testid="lineage-details"]').should( + 'not.have.class', + 'full-screen-lineage' + ); + }); +}); 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 31d0f76bf1c..c8ac7a691e2 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 @@ -11,7 +11,7 @@ * limitations under the License. */ -import { Card, Col, Row, Space, Table, Tabs, Typography } from 'antd'; +import { Col, Row, Space, Table, Tabs, Typography } from 'antd'; import { ColumnsType } from 'antd/lib/table'; import { AxiosError } from 'axios'; import ActivityFeedProvider, { @@ -661,14 +661,13 @@ const DashboardDetails = ({ label: , key: EntityTabs.LINEAGE, children: ( - - - + ), }, { 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 9724f06d89e..2222aef6e63 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 @@ -23,6 +23,7 @@ import { DataAssetsHeader } from 'components/DataAssets/DataAssetsHeader/DataAss import EntityLineageComponent from 'components/EntityLineage/EntityLineage.component'; import { EntityName } from 'components/Modals/EntityNameModal/EntityNameModal.interface'; import SchemaEditor from 'components/schema-editor/SchemaEditor'; +import { SourceType } from 'components/searched-data/SearchedData.interface'; import TabsLabel from 'components/TabsLabel/TabsLabel.component'; import TagsContainerV2 from 'components/Tag/TagsContainerV2/TagsContainerV2'; import { getDataModelDetailsPath, getVersionPath } from 'constants/constants'; @@ -313,15 +314,12 @@ const DataModelDetails = ({ ), key: EntityTabs.LINEAGE, children: ( - - - + ), }, ]; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/EntityLineage/CustomControls.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/EntityLineage/CustomControls.component.tsx index f9a37170d53..36e95d3f538 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/EntityLineage/CustomControls.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/EntityLineage/CustomControls.component.tsx @@ -175,6 +175,7 @@ const CustomControls: FC = ({ className={classNames('custom-control-search-box', { 'custom-control-search-box-edit-mode': isEditMode, })} + data-testid="lineage-search" filterOption={handleSearchFilterOption} options={nodeOptions} placeholder={t('label.search-entity', { @@ -200,6 +201,7 @@ const CustomControls: FC = ({
= ({ = ({ /> = ({ {showFitView && ( @@ -241,6 +246,7 @@ const CustomControls: FC = ({ {handleFullScreenViewClick && ( @@ -249,6 +255,7 @@ const CustomControls: FC = ({ {onExitFullScreenViewClick && ( @@ -257,6 +264,7 @@ const CustomControls: FC = ({ setDialogVisible(true)}> @@ -270,7 +278,7 @@ const CustomControls: FC = ({ className={classNames( 'custom-control-edit-button h-8 w-8 rounded-full p-x-xss', { - 'bg-primary': isEditMode, + active: isEditMode, } )} data-testid="edit-lineage" diff --git a/openmetadata-ui/src/main/resources/ui/src/components/EntityLineage/CustomControls.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/EntityLineage/CustomControls.test.tsx new file mode 100644 index 00000000000..80161929217 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/EntityLineage/CustomControls.test.tsx @@ -0,0 +1,163 @@ +/* + * Copyright 2023 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 { fireEvent, render } from '@testing-library/react'; +import { LOADING_STATE } from 'enums/common.enum'; +import { MOCK_LINEAGE_DATA } from 'mocks/Lineage.mock'; +import React from 'react'; +import CustomControlsComponent from './CustomControls.component'; + +const mockFitView = jest.fn(); +const mockZoomTo = jest.fn(); +const mockOnOptionSelect = jest.fn(); +const mockOnLineageConfigUpdate = jest.fn(); +const mockOnEditLinageClick = jest.fn(); +const mockOnExpandColumnClick = jest.fn(); +const mockHandleFullScreenViewClick = jest.fn(); +const mockOnExitFullScreenViewClick = jest.fn(); +const mockOnZoomHandler = jest.fn(); +const mockZoomValue = 1; + +jest.mock('reactflow', () => ({ + useReactFlow: () => ({ + fitView: mockFitView, + zoomTo: mockZoomTo, + }), + Position: () => ({ + Left: 'left', + Top: 'top', + Right: 'right', + Bottom: 'bottom', + }), + MarkerType: () => ({ + Arrow: 'arrow', + ArrowClosed: 'arrowclosed', + }), +})); + +const customProps = { + fitView: mockFitView, + zoomTo: mockZoomTo, + onOptionSelect: mockOnOptionSelect, + onLineageConfigUpdate: mockOnLineageConfigUpdate, + onEditLinageClick: mockOnEditLinageClick, + onExpandColumnClick: mockOnExpandColumnClick, + handleFullScreenViewClick: mockHandleFullScreenViewClick, + onExitFullScreenViewClick: mockOnExitFullScreenViewClick, + onZoomHandler: mockOnZoomHandler, + zoomValue: mockZoomValue, + deleted: false, + hasEditAccess: true, + isEditMode: false, + lineageData: MOCK_LINEAGE_DATA, + isColumnsExpanded: false, + loading: false, + status: LOADING_STATE.INITIAL, + lineageConfig: { + upstreamDepth: 1, + downstreamDepth: 1, + nodesPerLayer: 50, + }, +}; + +describe('CustomControls', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + it('calls fitView on Fit View button click', () => { + const { getByTestId } = render( + + ); + const fitViewButton = getByTestId('fit-to-screen'); + fireEvent.click(fitViewButton); + + expect(mockFitView).toHaveBeenCalled(); + }); + + it('calls zoomTo with zoomInValue on Zoom In button click', () => { + const { getByTestId } = render( + + ); + const zoomInButton = getByTestId('zoom-in-button'); + fireEvent.click(zoomInButton); + const zoomRangeInput = getByTestId( + 'lineage-zoom-slider' + ) as HTMLInputElement; + + expect(zoomRangeInput.value).toBe('0.75'); + }); + + it('calls zoomTo with zoomOutValue on Zoom Out button click', () => { + const { getByTestId } = render( + + ); + const zoomOutButton = getByTestId('zoom-out-button'); + fireEvent.click(zoomOutButton); + const zoomRangeInput = getByTestId( + 'lineage-zoom-slider' + ) as HTMLInputElement; + + expect(zoomRangeInput.value).toBe('1.25'); + }); + + it('calls onEditLinageClick on Edit Lineage button click', () => { + const { getByTestId } = render( + + ); + const editLineageButton = getByTestId('edit-lineage'); + fireEvent.click(editLineageButton); + + expect(mockOnEditLinageClick).toHaveBeenCalled(); + }); + + it('calls onExpandColumnClick on Expand Column button click', () => { + const { getByTestId } = render( + + ); + const expandColumnButton = getByTestId('expand-column'); + fireEvent.click(expandColumnButton); + + expect(mockOnExpandColumnClick).toHaveBeenCalled(); + }); + + it('calls mockHandleFullScreenViewClick on Full Screen button click', () => { + const { getByTestId } = render( + + ); + const fullScreenButton = getByTestId('full-screen'); + fireEvent.click(fullScreenButton); + + expect(mockHandleFullScreenViewClick).toHaveBeenCalled(); + }); + + it('calls mockOnExitFullScreenViewClick on Exit Full Screen button click', () => { + const { getByTestId } = render( + + ); + const exitFullScreenButton = getByTestId('exit-full-screen'); + fireEvent.click(exitFullScreenButton); + + expect(mockOnExitFullScreenViewClick).toHaveBeenCalled(); + }); + + it('should show lineage config dialog on setting button click', () => { + const { getByTestId, getByRole } = render( + + ); + const settingButton = getByTestId('lineage-config'); + fireEvent.click(settingButton); + const dialog = getByRole('dialog'); + + expect(dialog).toBeInTheDocument(); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/EntityLineage/EntityLineage.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/EntityLineage/EntityLineage.component.tsx index 5d117deacbe..14eb738ff42 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/EntityLineage/EntityLineage.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/EntityLineage/EntityLineage.component.tsx @@ -11,8 +11,10 @@ * limitations under the License. */ -import { Modal, Space } from 'antd'; +import { Card, Modal, Space } from 'antd'; import { AxiosError } from 'axios'; +import classNames from 'classnames'; +import TitleBreadcrumb from 'components/common/title-breadcrumb/title-breadcrumb.component'; import { useTourProvider } from 'components/TourProvider/TourProvider'; import { mockDatasetData } from 'constants/mockTourData.constants'; import { @@ -25,16 +27,18 @@ import { upperCase, } from 'lodash'; import { LoadingState } from 'Models'; +import Qs from 'qs'; import React, { DragEvent, FunctionComponent, useCallback, useEffect, + useMemo, useRef, useState, } from 'react'; import { useTranslation } from 'react-i18next'; -import { useHistory, useParams } from 'react-router-dom'; +import { useHistory, useLocation, useParams } from 'react-router-dom'; import ReactFlow, { addEdge, Background, @@ -52,8 +56,11 @@ import { getDataModelDetails } from 'rest/dataModelsAPI'; import { getLineageByFQN } from 'rest/lineageAPI'; import { searchData } from 'rest/miscAPI'; import { getTableDetails } from 'rest/tableAPI'; -import { getEntityLineage, getEntityName } from 'utils/EntityUtils'; -import { getLineageViewPath } from 'utils/RouterUtils'; +import { + getEntityBreadcrumbs, + getEntityLineage, + getEntityName, +} from 'utils/EntityUtils'; import { PAGE_SIZE } from '../../constants/constants'; import { ELEMENT_DELETE_STATE, @@ -90,7 +97,6 @@ import { getDeletedLineagePlaceholder, getEdgeStyle, getEdgeType, - getEntityLineagePath, getEntityNodeIcon, getLayoutedElements, getLineageData, @@ -150,9 +156,10 @@ const EntityLineageComponent: FunctionComponent = ({ deleted, hasEditAccess, entityType, - isFullScreen = false, + entity, }: EntityLineageProp) => { const { t } = useTranslation(); + const location = useLocation(); const { isTourOpen } = useTourProvider(); const reactFlowWrapper = useRef(null); const [reactFlowInstance, setReactFlowInstance] = @@ -213,14 +220,34 @@ const EntityLineageComponent: FunctionComponent = ({ }); const params = useParams>(); + const queryParams = new URLSearchParams(location.search); + + const isFullScreen = queryParams.get('fullscreen') === 'true'; const entityFQN = params[getParamByEntityType(entityType)] ?? params['entityFQN']; const history = useHistory(); const onFullScreenClick = useCallback(() => { - history.push(getLineageViewPath(entityType, entityFQN)); + history.push({ + search: Qs.stringify({ fullscreen: true }), + }); }, [entityType, entityFQN]); + const breadcrumbs = useMemo( + () => + entity + ? [ + ...getEntityBreadcrumbs(entity, entityType), + { + name: t('label.lineage'), + url: '', + activeTitle: true, + }, + ] + : [], + [entity] + ); + const fetchLineageData = useCallback( async (config: LineageConfig) => { if (isTourOpen) { @@ -311,10 +338,9 @@ const EntityLineageComponent: FunctionComponent = ({ ); const onExitFullScreenViewClick = useCallback(() => { - const path = getEntityLineagePath(entityType, entityFQN); - if (path !== '') { - history.push(path); - } + history.push({ + search: '', + }); }, [entityType, entityFQN, history]); /** @@ -1587,128 +1613,140 @@ const EntityLineageComponent: FunctionComponent = ({ } return ( -
-
- - { - onLoad(reactFlowInstance); - setReactFlowInstance(reactFlowInstance); - }} - onMove={(_e, viewPort) => handleZoomLevel(viewPort.zoom)} - onNodeClick={(_e, node) => { - onNodeClick(node); - _e.stopPropagation(); - }} - onNodeContextMenu={onNodeContextMenu} - onNodeDrag={dragHandle} - onNodeDragStart={dragHandle} - onNodeDragStop={dragHandle} - onNodeMouseEnter={onNodeMouseEnter} - onNodeMouseLeave={onNodeMouseLeave} - onNodeMouseMove={onNodeMouseMove} - onNodesChange={onNodesChange} - onPaneClick={onPaneClick}> - {updatedLineageData && ( - - )} - - - -
- {isDrawerOpen && - !isEditMode && - (selectedEdgeInfo ? ( - { - setIsDrawerOpen(false); - setSelectedEdgeInfo(undefined); - }} - /> - ) : ( - - ))} - - {showDeleteModal && ( - { - setShowDeleteModal(false); - }} - onOk={onRemove}> - {getModalBodyText(selectedEdge)} - + + {isFullScreen && ( + )} +
+
+ + { + onLoad(reactFlowInstance); + setReactFlowInstance(reactFlowInstance); + }} + onMove={(_e, viewPort) => handleZoomLevel(viewPort.zoom)} + onNodeClick={(_e, node) => { + onNodeClick(node); + _e.stopPropagation(); + }} + onNodeContextMenu={onNodeContextMenu} + onNodeDrag={dragHandle} + onNodeDragStart={dragHandle} + onNodeDragStop={dragHandle} + onNodeMouseEnter={onNodeMouseEnter} + onNodeMouseLeave={onNodeMouseLeave} + onNodeMouseMove={onNodeMouseMove} + onNodesChange={onNodesChange} + onPaneClick={onPaneClick}> + {updatedLineageData && ( + + )} + + + +
+ {isDrawerOpen && + !isEditMode && + (selectedEdgeInfo ? ( + { + setIsDrawerOpen(false); + setSelectedEdgeInfo(undefined); + }} + /> + ) : ( + + ))} + + {showDeleteModal && ( + { + setShowDeleteModal(false); + }} + onOk={onRemove}> + {getModalBodyText(selectedEdge)} + + )} - setPipelineSearchValue(value)} - onSelect={handlePipelineSelection} - /> -
+ setPipelineSearchValue(value)} + onSelect={handlePipelineSelection} + /> +
+ ); }; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/EntityLineage/EntityLineage.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/EntityLineage/EntityLineage.interface.ts index 6a276e871d6..ec596d786a6 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/EntityLineage/EntityLineage.interface.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/EntityLineage/EntityLineage.interface.ts @@ -11,6 +11,7 @@ * limitations under the License. */ +import { SourceType } from 'components/searched-data/SearchedData.interface'; import { LoadingState } from 'Models'; import { HTMLAttributes } from 'react'; import { Edge as FlowEdge, FitViewOptions, Node } from 'reactflow'; @@ -33,6 +34,7 @@ export interface EntityLineageProp { deleted?: boolean; hasEditAccess?: boolean; isFullScreen?: boolean; + entity?: SourceType; } export interface Edge { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/EntityLineage/Entitylineage.component.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/EntityLineage/Entitylineage.component.test.tsx index 4381d44ab46..202265c6f33 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/EntityLineage/Entitylineage.component.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/EntityLineage/Entitylineage.component.test.tsx @@ -11,7 +11,7 @@ * limitations under the License. */ -import { render, screen } from '@testing-library/react'; +import { fireEvent, render, screen } from '@testing-library/react'; import { MOCK_CHILD_MAP, MOCK_LINEAGE_DATA } from 'mocks/Lineage.mock'; import React from 'react'; import { act } from 'react-dom/test-utils'; @@ -107,6 +107,15 @@ jest.mock('../EntityInfoDrawer/EntityInfoDrawer.component', () => { return jest.fn().mockReturnValue(

EntityInfoDrawerComponent

); }); +const mockPush = jest.fn(); + +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useHistory: () => ({ + push: mockPush, + }), +})); + describe('Test EntityLineage Component', () => { it('Check if EntityLineage is rendering all the nodes', async () => { act(() => { @@ -141,4 +150,52 @@ describe('Test EntityLineage Component', () => { expect(lineageContainer).not.toBeInTheDocument(); }); + + it('should add fullscreen true in url on fullscreen button click', async () => { + render(, { + wrapper: MemoryRouter, + }); + + const lineageContainer = await screen.findByTestId('lineage-container'); + const reactFlowElement = await screen.findByTestId('rf__wrapper'); + const fullscreenButton = await screen.getByTestId('full-screen'); + + expect(lineageContainer).toBeInTheDocument(); + expect(reactFlowElement).toBeInTheDocument(); + + act(() => { + fireEvent.click(fullscreenButton); + }); + + expect(mockPush).toHaveBeenCalledTimes(1); + expect(mockPush).toHaveBeenCalledWith({ + search: 'fullscreen=true', + }); + }); + + it('should show breadcrumbs when URL has fullscreen=true', async () => { + act(() => { + render( + + + + ); + }); + const lineageContainer = await screen.findByTestId('lineage-container'); + const reactFlowElement = await screen.findByTestId('rf__wrapper'); + + expect(lineageContainer).toBeInTheDocument(); + expect(reactFlowElement).toBeInTheDocument(); + + const breadcrumbs = await screen.getByTestId('breadcrumb'); + + expect(breadcrumbs).toBeInTheDocument(); + + const mainRootElement = await screen.getByTestId('lineage-details'); + + expect(mainRootElement).toHaveClass('full-screen-lineage'); + }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/EntityLineage/entityLineage.style.less b/openmetadata-ui/src/main/resources/ui/src/components/EntityLineage/entityLineage.style.less index 4c9d37f3141..aa82cc616a0 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/EntityLineage/entityLineage.style.less +++ b/openmetadata-ui/src/main/resources/ui/src/components/EntityLineage/entityLineage.style.less @@ -13,7 +13,7 @@ @import url('../../styles/variables.less'); -@sidebar-width: 100px; +@lineage-sidebar-width: 100px; .custom-react-flow { .react-flow__node-input.selectable.selected { @@ -87,7 +87,7 @@ height: 32px; } .ant-select.custom-control-search-box-edit-mode { - margin-left: @sidebar-width; + margin-left: @lineage-sidebar-width; } .custom-control-basic-button { padding-left: 4px; @@ -134,6 +134,9 @@ display: block !important; border: 1px solid @border-color; background-color: @body-bg-color; + &.active { + background-color: @primary-color; + } } .custom-lineage-heading { @@ -160,7 +163,7 @@ position: absolute; top: 0; left: 0; - width: @sidebar-width; + width: @lineage-sidebar-width; z-index: 200; overflow-y: auto; padding: 8px; @@ -174,3 +177,11 @@ transform: translateX(0); display: block; } + +.lineage-card.full-screen-lineage { + top: @navbar-height; + left: @sidebar-width; + position: fixed !important; + width: calc(100vw - @sidebar-width); + height: calc(100vh - @navbar-height); +} 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 f0b1c6002fb..ad349aa9f2a 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/MlModelDetail/MlModelDetail.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/MlModelDetail/MlModelDetail.component.tsx @@ -11,7 +11,7 @@ * limitations under the License. */ -import { Card, Col, Row, Space, Table, Tabs, Typography } from 'antd'; +import { Col, Row, Space, Table, Tabs, Typography } from 'antd'; import { ColumnsType } from 'antd/lib/table'; import { AxiosError } from 'axios'; import ActivityFeedProvider, { @@ -495,16 +495,13 @@ const MlModelDetail: FC = ({ label: , key: EntityTabs.LINEAGE, children: ( - - - + ), }, { 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 0f12fc62558..10a43b4a6f5 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 @@ -672,18 +672,14 @@ const PipelineDetails = ({ label: , key: EntityTabs.LINEAGE, children: ( - - - + ), }, { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/TopicDetails/TopicDetails.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/TopicDetails/TopicDetails.component.tsx index e883fd75fcd..422a49c928f 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/TopicDetails/TopicDetails.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/TopicDetails/TopicDetails.component.tsx @@ -11,7 +11,7 @@ * limitations under the License. */ -import { Card, Col, Row, Space, Tabs } from 'antd'; +import { Col, Row, Space, Tabs } from 'antd'; import { AxiosError } from 'axios'; import ActivityFeedProvider, { useActivityFeedProvider, @@ -397,17 +397,13 @@ const TopicDetails: React.FC = ({ label: , key: EntityTabs.LINEAGE, children: ( - - - + ), }, { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/router/AuthenticatedAppRouter.tsx b/openmetadata-ui/src/main/resources/ui/src/components/router/AuthenticatedAppRouter.tsx index b5b81f15798..f50e497cbd3 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/router/AuthenticatedAppRouter.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/router/AuthenticatedAppRouter.tsx @@ -12,7 +12,6 @@ */ import DataQualityPage from 'pages/DataQuality/DataQualityPage'; -import LineagePage from 'pages/LineagePage/LineagePage'; import React, { FunctionComponent, useMemo } from 'react'; import { Redirect, Route, Switch } from 'react-router-dom'; import { ROUTES } from '../../constants/constants'; @@ -562,11 +561,6 @@ const AuthenticatedAppRouter: FunctionComponent = () => { - {/* keep these route above the setting route always */} { const [isChildrenLoading, setIsChildrenLoading] = useState(false); const [hasError, setHasError] = useState(false); const [isEditDescription, setIsEditDescription] = useState(false); - const [isLineageLoading, setIsLineageLoading] = useState(false); const [containerData, setContainerData] = useState(); const [containerChildrenData, setContainerChildrenData] = useState< @@ -111,14 +94,6 @@ const ContainerPage = () => { >([]); const [containerPermissions, setContainerPermissions] = useState(DEFAULT_ENTITY_PERMISSION); - const [entityLineage, setEntityLineage] = useState( - {} as EntityLineage - ); - const [leafNodes, setLeafNodes] = useState({} as LeafNodes); - const [isNodeLoading, setNodeLoading] = useState({ - id: undefined, - state: false, - }); const [feedCount, setFeedCount] = useState(0); const [entityFieldThreadCount, setEntityFieldThreadCount] = useState< @@ -170,22 +145,6 @@ const ContainerPage = () => { } }; - const fetchLineageData = async (containerFQN: string) => { - setIsLineageLoading(true); - try { - const response = await getLineageByFQN( - containerFQN, - EntityType.CONTAINER - ); - - setEntityLineage(response); - } catch (error) { - showErrorToast(error as AxiosError); - } finally { - setIsLineageLoading(false); - } - }; - const fetchResourcePermission = async (containerFQN: string) => { setIsLoading(true); try { @@ -431,67 +390,6 @@ const ContainerPage = () => { } }; - // Lineage handlers - const handleAddLineage = async (edge: Edge) => { - try { - await addLineage(edge); - } catch (error) { - showErrorToast(error as AxiosError); - } - }; - - const handleRemoveLineage = async (data: EdgeData) => { - try { - await deleteLineageEdge( - data.fromEntity, - data.fromId, - data.toEntity, - data.toId - ); - } catch (error) { - showErrorToast(error as AxiosError); - } - }; - - const handleSetLeafNode = (val: EntityLineage, pos: LineagePos) => { - if (pos === 'to' && val.downstreamEdges?.length === 0) { - setLeafNodes((prev) => ({ - ...prev, - downStreamNode: [...(prev.downStreamNode ?? []), val.entity.id], - })); - } - if (pos === 'from' && val.upstreamEdges?.length === 0) { - setLeafNodes((prev) => ({ - ...prev, - upStreamNode: [...(prev.upStreamNode ?? []), val.entity.id], - })); - } - }; - - const handleLoadLineageNode = async ( - node: EntityReference, - pos: LineagePos - ) => { - setNodeLoading({ id: node.id, state: true }); - - try { - const response = await getLineageByFQN( - node.fullyQualifiedName ?? '', - node.type - ); - handleSetLeafNode(response, pos); - setEntityLineage(getEntityLineage(entityLineage, response, pos)); - setTimeout(() => { - setNodeLoading((prev) => ({ ...prev, state: false })); - }, 500); - } catch (error) { - showErrorToast(error as AxiosError); - } - }; - - const handleFullScreenClick = () => - history.push(getLineageViewPath(EntityType.CONTAINER, containerName)); - const handleExtensionUpdate = async (updatedContainer: Container) => { try { const response = await handleUpdateContainerData(updatedContainer); @@ -691,24 +589,11 @@ const ContainerPage = () => { label: , key: EntityTabs.LINEAGE, children: ( - - - setEntityLineage(lineage) - } - entityType={EntityType.CONTAINER} - hasEditAccess={hasEditLineagePermission} - isLoading={isLineageLoading} - isNodeLoading={isNodeLoading} - lineageLeafNodes={leafNodes} - loadNodeHandler={handleLoadLineageNode} - removeLineageHandler={handleRemoveLineage} - onFullScreenClick={handleFullScreenClick} - /> - + ), }, { @@ -745,24 +630,16 @@ const ContainerPage = () => { hasEditCustomFieldsPermission, deleted, owner, - isNodeLoading, - leafNodes, - isLineageLoading, isChildrenLoading, entityFieldThreadCount, tags, - entityLineage, feedCount, containerChildrenData, - handleAddLineage, handleUpdateDataModel, handleUpdateDescription, getEntityFieldThreadCounts, handleTagSelection, onThreadLinkSelect, - handleLoadLineageNode, - handleRemoveLineage, - handleFullScreenClick, handleExtensionUpdate, ] ); @@ -779,9 +656,6 @@ const ContainerPage = () => { }, [containerName]); useEffect(() => { - if (tab === EntityTabs.LINEAGE) { - fetchLineageData(containerName); - } if (tab === EntityTabs.CHILDREN) { fetchContainerChildren(containerName); } diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/LineagePage/LineagePage.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/LineagePage/LineagePage.tsx deleted file mode 100644 index 655ea340e0f..00000000000 --- a/openmetadata-ui/src/main/resources/ui/src/pages/LineagePage/LineagePage.tsx +++ /dev/null @@ -1,189 +0,0 @@ -/* - * 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 { AxiosError } from 'axios'; -import TitleBreadcrumb from 'components/common/title-breadcrumb/title-breadcrumb.component'; -import { TitleBreadcrumbProps } from 'components/common/title-breadcrumb/title-breadcrumb.interface'; -import PageLayoutV1 from 'components/containers/PageLayoutV1'; -import EntityLineageComponent from 'components/EntityLineage/EntityLineage.component'; -import { Container } from 'generated/entity/data/container'; -import React, { useEffect, useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { useParams } from 'react-router-dom'; -import { getDashboardByFqn } from 'rest/dashboardAPI'; -import { getMlModelByFQN } from 'rest/mlModelAPI'; -import { getPipelineByFqn } from 'rest/pipelineAPI'; -import { getContainerByName } from 'rest/storageAPI'; -import { getTableDetailsByFQN } from 'rest/tableAPI'; -import { getTopicByFqn } from 'rest/topicsAPI'; -import { - getContainerDetailPath, - getDashboardDetailsPath, - getMlModelPath, - getPipelineDetailsPath, - getTableTabPath, - getTopicDetailsPath, -} from '../../constants/constants'; -import { EntityTabs, EntityType } from '../../enums/entity.enum'; -import { Dashboard } from '../../generated/entity/data/dashboard'; -import { Mlmodel } from '../../generated/entity/data/mlmodel'; -import { Pipeline } from '../../generated/entity/data/pipeline'; -import { Topic } from '../../generated/entity/data/topic'; -import { getEntityBreadcrumbs, getEntityName } from '../../utils/EntityUtils'; -import { showErrorToast } from '../../utils/ToastUtils'; -import './lineagePage.style.less'; - -const LineagePage = () => { - const { t } = useTranslation(); - const { entityType, entityFQN } = - useParams<{ entityType: EntityType; entityFQN: string }>(); - - const [titleBreadcrumb, setTitleBreadcrumb] = useState< - TitleBreadcrumbProps['titleLinks'] - >([]); - - const updateBreadcrumb = ( - apiRes: Topic | Dashboard | Pipeline | Mlmodel | Container, - currentEntityPath: string, - entityType: EntityType - ) => { - setTitleBreadcrumb([ - ...getEntityBreadcrumbs(apiRes, entityType), - { - name: getEntityName(apiRes), - url: currentEntityPath, - }, - { - name: t('label.lineage'), - url: '', - activeTitle: true, - }, - ]); - }; - - const fetchEntityDetails = async () => { - try { - switch (entityType) { - case EntityType.TABLE: - { - const tableRes = await getTableDetailsByFQN(entityFQN, ''); - setTitleBreadcrumb([ - ...getEntityBreadcrumbs(tableRes, EntityType.TABLE), - { - name: getEntityName(tableRes), - url: getTableTabPath(entityFQN, EntityTabs.LINEAGE), - }, - { - name: t('label.lineage'), - url: '', - activeTitle: true, - }, - ]); - } - - break; - - case EntityType.TOPIC: - { - const topicRes = await getTopicByFqn(entityFQN, ''); - updateBreadcrumb( - topicRes, - getTopicDetailsPath(entityFQN, EntityTabs.LINEAGE), - EntityType.TOPIC - ); - } - - break; - - case EntityType.DASHBOARD: - { - const dashboardRes = await getDashboardByFqn(entityFQN, ''); - updateBreadcrumb( - dashboardRes, - getDashboardDetailsPath(entityFQN, EntityTabs.LINEAGE), - EntityType.DASHBOARD - ); - } - - break; - - case EntityType.PIPELINE: - { - const pipelineRes = await getPipelineByFqn(entityFQN, ''); - updateBreadcrumb( - pipelineRes, - getPipelineDetailsPath(entityFQN, EntityTabs.LINEAGE), - EntityType.PIPELINE - ); - } - - break; - - case EntityType.MLMODEL: - { - const mlmodelRes = await getMlModelByFQN(entityFQN, ''); - updateBreadcrumb( - mlmodelRes, - getMlModelPath(entityFQN, EntityTabs.LINEAGE), - EntityType.MLMODEL - ); - } - - break; - - case EntityType.CONTAINER: - { - const containerRes = await getContainerByName(entityFQN, ''); - updateBreadcrumb( - containerRes, - getContainerDetailPath(entityFQN, EntityTabs.LINEAGE), - EntityType.CONTAINER - ); - } - - break; - - default: - break; - } - } catch (error) { - showErrorToast( - error as AxiosError, - t('server.entity-fetch-error', { - entity: entityType, - }) - ); - } - }; - - useEffect(() => { - if (entityFQN && entityType) { - fetchEntityDetails(); - } - }, [entityFQN, entityType]); - - return ( - -
- - -
-
- ); -}; - -export default LineagePage; diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/LineagePage/lineagePage.style.less b/openmetadata-ui/src/main/resources/ui/src/pages/LineagePage/lineagePage.style.less deleted file mode 100644 index 26e86bfb0ec..00000000000 --- a/openmetadata-ui/src/main/resources/ui/src/pages/LineagePage/lineagePage.style.less +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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. - */ - -.lineage-page-container { - display: flex; - flex-direction: column; - gap: 16px; - height: 100%; - - .ant-card-body { - height: 100%; - } -} diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/TableDetailsPageV1/TableDetailsPageV1.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/TableDetailsPageV1/TableDetailsPageV1.tsx index 3150f511ed9..11d9f542b1b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/TableDetailsPageV1/TableDetailsPageV1.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/TableDetailsPageV1/TableDetailsPageV1.tsx @@ -10,7 +10,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Card, Col, Row, Space, Tabs, Typography } from 'antd'; +import { Col, Row, Space, Tabs, Typography } from 'antd'; import { AxiosError } from 'axios'; import classNames from 'classnames'; import ActivityFeedProvider, { @@ -35,6 +35,7 @@ import { } from 'components/PermissionProvider/PermissionProvider.interface'; import SampleDataTableComponent from 'components/SampleDataTable/SampleDataTable.component'; import SchemaTab from 'components/SchemaTab/SchemaTab.component'; +import { SourceType } from 'components/searched-data/SearchedData.interface'; import TableProfilerV1 from 'components/TableProfiler/TableProfilerV1'; import TableQueries from 'components/TableQueries/TableQueries'; import TabsLabel from 'components/TabsLabel/TabsLabel.component'; @@ -624,17 +625,14 @@ const TableDetailsPageV1 = () => { label: , key: EntityTabs.LINEAGE, children: ( - - - + ), }, diff --git a/openmetadata-ui/src/main/resources/ui/src/styles/variables.less b/openmetadata-ui/src/main/resources/ui/src/styles/variables.less index ba35c198162..518f6d77a3a 100644 --- a/openmetadata-ui/src/main/resources/ui/src/styles/variables.less +++ b/openmetadata-ui/src/main/resources/ui/src/styles/variables.less @@ -78,6 +78,9 @@ @grey-bg-with-alpha: #7575751a; @btn-shadow: none; +@navbar-height: 64px; +@sidebar-width: 60px; + // Sizing @page-height: calc(100vh - 64px); @left-side-panel-width: 230px; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/RouterUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/RouterUtils.ts index a57c47b7952..00a2225bb78 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/RouterUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/RouterUtils.ts @@ -25,8 +25,6 @@ import { PLACEHOLDER_ENTITY_TYPE_FQN, PLACEHOLDER_GLOSSARY_NAME, PLACEHOLDER_GLOSSARY_TERMS_FQN, - PLACEHOLDER_ROUTE_ENTITY_FQN, - PLACEHOLDER_ROUTE_ENTITY_TYPE, PLACEHOLDER_ROUTE_FQN, PLACEHOLDER_ROUTE_INGESTION_FQN, PLACEHOLDER_ROUTE_INGESTION_TYPE, @@ -48,7 +46,7 @@ import { GlobalSettingsMenuCategory, } from '../constants/GlobalSettings.constants'; import { arrServiceTypes } from '../constants/Services.constant'; -import { EntityAction, EntityType } from '../enums/entity.enum'; +import { EntityAction } from '../enums/entity.enum'; import { PipelineType } from '../generated/api/services/ingestionPipelines/createIngestionPipeline'; import { getServiceRouteFromServiceType } from './ServiceUtils'; import { getEncodedFqn } from './StringsUtils'; @@ -395,16 +393,6 @@ export const getLogEntityPath = ( ); }; -export const getLineageViewPath = (entity: EntityType, fqn: string) => { - let path = ROUTES.LINEAGE_FULL_SCREEN_VIEW; - - path = path - .replace(PLACEHOLDER_ROUTE_ENTITY_TYPE, entity) - .replace(PLACEHOLDER_ROUTE_ENTITY_FQN, fqn); - - return path; -}; - export const getGlossaryPathWithAction = ( fqn: string, action: EntityAction