fix: lineage full screen view (#12473)

* fix: remove redirection to separate page

* fix: remove unused lineage page component

* fix: position full screen container

* fix: add unit tests

* fix: add cypress for full screen toggle

* fix: lineage style issue

* fix: address comments
This commit is contained in:
karanh37 2023-07-19 14:07:45 +05:30 committed by GitHub
parent 940ab3d183
commit 6b46f4e1cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 510 additions and 566 deletions

View File

@ -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'
);
});
});

View File

@ -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: <TabsLabel id={EntityTabs.LINEAGE} name={t('label.lineage')} />,
key: EntityTabs.LINEAGE,
children: (
<Card className="lineage-card card-body-full w-auto border-none">
<EntityLineageComponent
entityType={EntityType.DASHBOARD}
hasEditAccess={
dashboardPermissions.EditAll || dashboardPermissions.EditLineage
}
/>
</Card>
<EntityLineageComponent
entity={dashboardDetails}
entityType={EntityType.DASHBOARD}
hasEditAccess={
dashboardPermissions.EditAll || dashboardPermissions.EditLineage
}
/>
),
},
{

View File

@ -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: (
<Card
className="card-body-full m-md w-auto h-60vh"
data-testid="lineage-details">
<EntityLineageComponent
deleted={deleted}
entityType={EntityType.DASHBOARD_DATA_MODEL}
hasEditAccess={hasEditLineagePermission}
/>
</Card>
<EntityLineageComponent
deleted={deleted}
entity={dataModelData as SourceType}
entityType={EntityType.DASHBOARD_DATA_MODEL}
hasEditAccess={hasEditLineagePermission}
/>
),
},
];

View File

@ -175,6 +175,7 @@ const CustomControls: FC<ControlProps> = ({
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<ControlProps> = ({
<div className="flow-control custom-control-fit-screen-button custom-control-zoom-slide">
<ControlButton
className="custom-control-basic-button"
data-testid="zoom-in-button"
onClick={onZoomOutHandler}>
<SVGIcons
alt="minus-icon"
@ -211,6 +213,7 @@ const CustomControls: FC<ControlProps> = ({
<input
className="tw-bg-body-hover"
data-testid="lineage-zoom-slider"
max={MAX_ZOOM_VALUE}
min={MIN_ZOOM_VALUE}
step={ZOOM_SLIDER_STEP}
@ -220,6 +223,7 @@ const CustomControls: FC<ControlProps> = ({
/>
<ControlButton
className="custom-control-basic-button"
data-testid="zoom-out-button"
onClick={onZoomInHandler}>
<SVGIcons
alt="plus-icon"
@ -233,6 +237,7 @@ const CustomControls: FC<ControlProps> = ({
{showFitView && (
<ControlButton
className="custom-control-basic-button custom-control-fit-screen-button"
data-testid="fit-to-screen"
title={t('label.fit-to-screen')}
onClick={onFitViewHandler}>
<SVGIcons alt="fit-view" icon={Icons.FITVEW} width="16" />
@ -241,6 +246,7 @@ const CustomControls: FC<ControlProps> = ({
{handleFullScreenViewClick && (
<ControlButton
className="custom-control-basic-button custom-control-fit-screen-button"
data-testid="full-screen"
title={t('label.full-screen')}
onClick={handleFullScreenViewClick}>
<FullScreen color={PRIMERY_COLOR} height={16} width={16} />
@ -249,6 +255,7 @@ const CustomControls: FC<ControlProps> = ({
{onExitFullScreenViewClick && (
<ControlButton
className="custom-control-basic-button custom-control-fit-screen-button"
data-testid="exit-full-screen"
title={t('label.exit-fit-to-screen')}
onClick={onExitFullScreenViewClick}>
<ExitFullScreen color={PRIMERY_COLOR} height={16} width={16} />
@ -257,6 +264,7 @@ const CustomControls: FC<ControlProps> = ({
<ControlButton
className="custom-control-basic-button custom-control-fit-screen-button"
data-testid="lineage-config"
disabled={isEditMode}
title={t('label.setting-plural')}
onClick={() => setDialogVisible(true)}>
@ -270,7 +278,7 @@ const CustomControls: FC<ControlProps> = ({
className={classNames(
'custom-control-edit-button h-8 w-8 rounded-full p-x-xss',
{
'bg-primary': isEditMode,
active: isEditMode,
}
)}
data-testid="edit-lineage"

View File

@ -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(
<CustomControlsComponent {...customProps} />
);
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(
<CustomControlsComponent {...customProps} />
);
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(
<CustomControlsComponent {...customProps} />
);
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(
<CustomControlsComponent {...customProps} />
);
const editLineageButton = getByTestId('edit-lineage');
fireEvent.click(editLineageButton);
expect(mockOnEditLinageClick).toHaveBeenCalled();
});
it('calls onExpandColumnClick on Expand Column button click', () => {
const { getByTestId } = render(
<CustomControlsComponent {...customProps} />
);
const expandColumnButton = getByTestId('expand-column');
fireEvent.click(expandColumnButton);
expect(mockOnExpandColumnClick).toHaveBeenCalled();
});
it('calls mockHandleFullScreenViewClick on Full Screen button click', () => {
const { getByTestId } = render(
<CustomControlsComponent {...customProps} />
);
const fullScreenButton = getByTestId('full-screen');
fireEvent.click(fullScreenButton);
expect(mockHandleFullScreenViewClick).toHaveBeenCalled();
});
it('calls mockOnExitFullScreenViewClick on Exit Full Screen button click', () => {
const { getByTestId } = render(
<CustomControlsComponent {...customProps} />
);
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(
<CustomControlsComponent {...customProps} />
);
const settingButton = getByTestId('lineage-config');
fireEvent.click(settingButton);
const dialog = getByRole('dialog');
expect(dialog).toBeInTheDocument();
});
});

View File

@ -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<EntityLineageProp> = ({
deleted,
hasEditAccess,
entityType,
isFullScreen = false,
entity,
}: EntityLineageProp) => {
const { t } = useTranslation();
const location = useLocation();
const { isTourOpen } = useTourProvider();
const reactFlowWrapper = useRef<HTMLDivElement>(null);
const [reactFlowInstance, setReactFlowInstance] =
@ -213,14 +220,34 @@ const EntityLineageComponent: FunctionComponent<EntityLineageProp> = ({
});
const params = useParams<Record<string, string>>();
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<EntityLineageProp> = ({
);
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<EntityLineageProp> = ({
}
return (
<div className="relative h-full" data-testid="lineage-container">
<div className="w-full h-full" ref={reactFlowWrapper}>
<ReactFlowProvider>
<ReactFlow
onlyRenderVisibleElements
className="custom-react-flow"
data-testid="react-flow-component"
edgeTypes={customEdges}
edges={edges}
maxZoom={MAX_ZOOM_VALUE}
minZoom={MIN_ZOOM_VALUE}
nodeTypes={nodeTypes}
nodes={nodes}
nodesConnectable={isEditMode}
selectNodesOnDrag={false}
onConnect={onConnect}
onDragOver={onDragOver}
onDrop={onDrop}
onEdgeClick={handleEdgeClick}
onEdgesChange={onEdgesChange}
onInit={(reactFlowInstance: ReactFlowInstance) => {
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 && (
<CustomControlsComponent
className="absolute top-1 right-1 bottom-full p-md"
deleted={deleted}
fitViewParams={{
minZoom: MIN_ZOOM_VALUE,
maxZoom: MAX_ZOOM_VALUE,
}}
handleFullScreenViewClick={
!isFullScreen ? onFullScreenClick : undefined
}
hasEditAccess={hasEditAccess}
isColumnsExpanded={expandAllColumns}
isEditMode={isEditMode}
lineageConfig={lineageConfig}
lineageData={updatedLineageData}
loading={loading}
status={status}
zoomValue={zoomValue}
onEditLinageClick={handleEditLineageClick}
onExitFullScreenViewClick={
isFullScreen ? onExitFullScreenViewClick : undefined
}
onExpandColumnClick={handleExpandColumnClick}
onLineageConfigUpdate={handleLineageConfigUpdate}
onOptionSelect={handleOptionSelect}
/>
)}
<Background gap={12} size={1} />
</ReactFlow>
</ReactFlowProvider>
</div>
{isDrawerOpen &&
!isEditMode &&
(selectedEdgeInfo ? (
<EdgeInfoDrawer
edge={selectedEdgeInfo}
nodes={nodes}
visible={isDrawerOpen}
onClose={() => {
setIsDrawerOpen(false);
setSelectedEdgeInfo(undefined);
}}
/>
) : (
<EntityInfoDrawer
isMainNode={selectedNode.name === updatedLineageData?.entity?.name}
selectedNode={selectedNode}
show={isDrawerOpen}
onCancel={closeDrawer}
/>
))}
<EntityLineageSidebar newAddedNode={newAddedNode} show={isEditMode} />
{showDeleteModal && (
<Modal
maskClosable={false}
okText={getLoadingStatusValue(
t('label.confirm'),
deletionState.loading,
deletionState.status
)}
open={showDeleteModal}
title={t('message.remove-lineage-edge')}
onCancel={() => {
setShowDeleteModal(false);
}}
onOk={onRemove}>
{getModalBodyText(selectedEdge)}
</Modal>
<Card
className={classNames('lineage-card card-body-full w-auto border-none', {
'full-screen-lineage': isFullScreen,
})}
data-testid="lineage-details"
id="lineageDetails">
{isFullScreen && (
<TitleBreadcrumb className="p-md" titleLinks={breadcrumbs} />
)}
<div className="relative h-full" data-testid="lineage-container">
<div className="w-full h-full" ref={reactFlowWrapper}>
<ReactFlowProvider>
<ReactFlow
onlyRenderVisibleElements
className="custom-react-flow"
data-testid="react-flow-component"
edgeTypes={customEdges}
edges={edges}
maxZoom={MAX_ZOOM_VALUE}
minZoom={MIN_ZOOM_VALUE}
nodeTypes={nodeTypes}
nodes={nodes}
nodesConnectable={isEditMode}
selectNodesOnDrag={false}
onConnect={onConnect}
onDragOver={onDragOver}
onDrop={onDrop}
onEdgeClick={handleEdgeClick}
onEdgesChange={onEdgesChange}
onInit={(reactFlowInstance: ReactFlowInstance) => {
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 && (
<CustomControlsComponent
className="absolute top-1 right-1 bottom-full p-md"
deleted={deleted}
fitViewParams={{
minZoom: MIN_ZOOM_VALUE,
maxZoom: MAX_ZOOM_VALUE,
}}
handleFullScreenViewClick={
!isFullScreen ? onFullScreenClick : undefined
}
hasEditAccess={hasEditAccess}
isColumnsExpanded={expandAllColumns}
isEditMode={isEditMode}
lineageConfig={lineageConfig}
lineageData={updatedLineageData}
loading={loading}
status={status}
zoomValue={zoomValue}
onEditLinageClick={handleEditLineageClick}
onExitFullScreenViewClick={
isFullScreen ? onExitFullScreenViewClick : undefined
}
onExpandColumnClick={handleExpandColumnClick}
onLineageConfigUpdate={handleLineageConfigUpdate}
onOptionSelect={handleOptionSelect}
/>
)}
<Background gap={12} size={1} />
</ReactFlow>
</ReactFlowProvider>
</div>
{isDrawerOpen &&
!isEditMode &&
(selectedEdgeInfo ? (
<EdgeInfoDrawer
edge={selectedEdgeInfo}
nodes={nodes}
visible={isDrawerOpen}
onClose={() => {
setIsDrawerOpen(false);
setSelectedEdgeInfo(undefined);
}}
/>
) : (
<EntityInfoDrawer
isMainNode={
selectedNode.name === updatedLineageData?.entity?.name
}
selectedNode={selectedNode}
show={isDrawerOpen}
onCancel={closeDrawer}
/>
))}
<EntityLineageSidebar newAddedNode={newAddedNode} show={isEditMode} />
{showDeleteModal && (
<Modal
maskClosable={false}
okText={getLoadingStatusValue(
t('label.confirm'),
deletionState.loading,
deletionState.status
)}
open={showDeleteModal}
title={t('message.remove-lineage-edge')}
onCancel={() => {
setShowDeleteModal(false);
}}
onOk={onRemove}>
{getModalBodyText(selectedEdge)}
</Modal>
)}
<AddPipeLineModal
pipelineOptions={pipelineOptions}
pipelineSearchValue={pipelineSearchValue}
selectedPipelineId={selectedPipelineId}
showAddPipelineModal={showAddPipelineModal}
onClear={onPipelineSelectionClear}
onModalCancel={handleModalCancel}
onRemoveEdgeClick={handleRemoveEdgeClick}
onSave={handleModalSave}
onSearch={(value) => setPipelineSearchValue(value)}
onSelect={handlePipelineSelection}
/>
</div>
<AddPipeLineModal
pipelineOptions={pipelineOptions}
pipelineSearchValue={pipelineSearchValue}
selectedPipelineId={selectedPipelineId}
showAddPipelineModal={showAddPipelineModal}
onClear={onPipelineSelectionClear}
onModalCancel={handleModalCancel}
onRemoveEdgeClick={handleRemoveEdgeClick}
onSave={handleModalSave}
onSearch={(value) => setPipelineSearchValue(value)}
onSelect={handlePipelineSelection}
/>
</div>
</Card>
);
};

View File

@ -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 {

View File

@ -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(<p>EntityInfoDrawerComponent</p>);
});
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(<EntityLineage {...mockEntityLineageProp} />, {
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(
<MemoryRouter
initialEntries={[
'/table/sample_data.ecommerce_db.shopify.raw_customer/lineage?fullscreen=true',
]}>
<EntityLineage {...mockEntityLineageProp} />
</MemoryRouter>
);
});
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');
});
});

View File

@ -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);
}

View File

@ -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<MlModelDetailProp> = ({
label: <TabsLabel id={EntityTabs.LINEAGE} name={t('label.lineage')} />,
key: EntityTabs.LINEAGE,
children: (
<Card
className="lineage-card card-body-full w-auto border-none"
data-testid="lineage-details">
<EntityLineageComponent
entityType={EntityType.MLMODEL}
hasEditAccess={
mlModelPermissions.EditAll || mlModelPermissions.EditLineage
}
/>
</Card>
<EntityLineageComponent
entity={mlModelDetail}
entityType={EntityType.MLMODEL}
hasEditAccess={
mlModelPermissions.EditAll || mlModelPermissions.EditLineage
}
/>
),
},
{

View File

@ -672,18 +672,14 @@ const PipelineDetails = ({
label: <TabsLabel id={EntityTabs.LINEAGE} name={t('label.lineage')} />,
key: EntityTabs.LINEAGE,
children: (
<Card
className="lineage-card card-body-full w-auto border-none"
data-testid="lineage-details"
id="lineageDetails">
<EntityLineageComponent
deleted={deleted}
entityType={EntityType.PIPELINE}
hasEditAccess={
pipelinePermissions.EditAll || pipelinePermissions.EditLineage
}
/>
</Card>
<EntityLineageComponent
deleted={deleted}
entity={pipelineDetails}
entityType={EntityType.PIPELINE}
hasEditAccess={
pipelinePermissions.EditAll || pipelinePermissions.EditLineage
}
/>
),
},
{

View File

@ -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<TopicDetailsProps> = ({
label: <TabsLabel id={EntityTabs.LINEAGE} name={t('label.lineage')} />,
key: EntityTabs.LINEAGE,
children: (
<Card
className="lineage-card card-body-full w-auto border-none"
data-testid="lineage-details"
id="lineageDetails">
<EntityLineageComponent
entityType={EntityType.TOPIC}
hasEditAccess={
topicPermissions.EditAll || topicPermissions.EditLineage
}
/>
</Card>
<EntityLineageComponent
entity={topicDetails}
entityType={EntityType.TOPIC}
hasEditAccess={
topicPermissions.EditAll || topicPermissions.EditLineage
}
/>
),
},
{

View File

@ -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 = () => {
<Route exact component={RequestTagsPage} path={ROUTES.REQUEST_TAGS} />
<Route exact component={UpdateTagsPage} path={ROUTES.UPDATE_TAGS} />
<Route
exact
component={LineagePage}
path={ROUTES.LINEAGE_FULL_SCREEN_VIEW}
/>
{/* keep these route above the setting route always */}
<AdminProtectedRoute

View File

@ -286,7 +286,6 @@ export const ROUTES = {
PROFILER_DASHBOARD: `/profiler-dashboard/${PLACEHOLDER_DASHBOARD_TYPE}/${PLACEHOLDER_ENTITY_TYPE_FQN}`,
PROFILER_DASHBOARD_WITH_TAB: `/profiler-dashboard/${PLACEHOLDER_DASHBOARD_TYPE}/${PLACEHOLDER_ENTITY_TYPE_FQN}/${PLACEHOLDER_ROUTE_TAB}`,
ADD_DATA_QUALITY_TEST_CASE: `/data-quality-test/${PLACEHOLDER_DASHBOARD_TYPE}/${PLACEHOLDER_ENTITY_TYPE_FQN}`,
LINEAGE_FULL_SCREEN_VIEW: `/lineage-view/${PLACEHOLDER_ROUTE_ENTITY_TYPE}/${PLACEHOLDER_ROUTE_ENTITY_FQN}`,
// Query Routes
QUERY_FULL_SCREEN_VIEW: `/query-view/${PLACEHOLDER_ROUTE_TABLE_FQN}/${PLACEHOLDER_ROUTE_QUERY_ID}`,

View File

@ -10,7 +10,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Card, Col, Row, Space, Tabs } from 'antd';
import { Col, Row, Space, Tabs } from 'antd';
import AppState from 'AppState';
import { AxiosError } from 'axios';
import ActivityFeedProvider, {
@ -27,13 +27,6 @@ import ContainerDataModel from 'components/ContainerDetail/ContainerDataModel/Co
import PageLayoutV1 from 'components/containers/PageLayoutV1';
import { DataAssetsHeader } from 'components/DataAssets/DataAssetsHeader/DataAssetsHeader.component';
import EntityLineageComponent from 'components/EntityLineage/EntityLineage.component';
import {
Edge,
EdgeData,
LeafNodes,
LineagePos,
LoadingNodeState,
} from 'components/EntityLineage/EntityLineage.interface';
import Loader from 'components/Loader/Loader';
import { EntityName } from 'components/Modals/EntityNameModal/EntityNameModal.interface';
import { usePermissionProvider } from 'components/PermissionProvider/PermissionProvider';
@ -50,8 +43,6 @@ import { EntityTabs, EntityType } from 'enums/entity.enum';
import { compare } from 'fast-json-patch';
import { CreateThread, ThreadType } from 'generated/api/feed/createThread';
import { Container } from 'generated/entity/data/container';
import { EntityLineage } from 'generated/type/entityLineage';
import { EntityReference } from 'generated/type/entityReference';
import { Include } from 'generated/type/include';
import { LabelType, State, TagLabel, TagSource } from 'generated/type/tagLabel';
import { EntityFieldThreadCount } from 'interface/feed.interface';
@ -62,8 +53,6 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useHistory, useParams } from 'react-router-dom';
import { postThread } from 'rest/feedsAPI';
import { getLineageByFQN } from 'rest/lineageAPI';
import { addLineage, deleteLineageEdge } from 'rest/miscAPI';
import {
addContainerFollower,
getContainerByName,
@ -79,14 +68,9 @@ import {
refreshPage,
sortTagsCaseInsensitive,
} from 'utils/CommonUtils';
import {
getEntityLineage,
getEntityName,
getEntityThreadLink,
} from 'utils/EntityUtils';
import { getEntityName, getEntityThreadLink } from 'utils/EntityUtils';
import { getEntityFieldThreadCounts } from 'utils/FeedUtils';
import { DEFAULT_ENTITY_PERMISSION } from 'utils/PermissionsUtils';
import { getLineageViewPath } from 'utils/RouterUtils';
import { getTagsWithoutTier, getTierTags } from 'utils/TableUtils';
import { showErrorToast, showSuccessToast } from 'utils/ToastUtils';
@ -103,7 +87,6 @@ const ContainerPage = () => {
const [isChildrenLoading, setIsChildrenLoading] = useState<boolean>(false);
const [hasError, setHasError] = useState<boolean>(false);
const [isEditDescription, setIsEditDescription] = useState<boolean>(false);
const [isLineageLoading, setIsLineageLoading] = useState<boolean>(false);
const [containerData, setContainerData] = useState<Container>();
const [containerChildrenData, setContainerChildrenData] = useState<
@ -111,14 +94,6 @@ const ContainerPage = () => {
>([]);
const [containerPermissions, setContainerPermissions] =
useState<OperationPermission>(DEFAULT_ENTITY_PERMISSION);
const [entityLineage, setEntityLineage] = useState<EntityLineage>(
{} as EntityLineage
);
const [leafNodes, setLeafNodes] = useState<LeafNodes>({} as LeafNodes);
const [isNodeLoading, setNodeLoading] = useState<LoadingNodeState>({
id: undefined,
state: false,
});
const [feedCount, setFeedCount] = useState<number>(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: <TabsLabel id={EntityTabs.LINEAGE} name={t('label.lineage')} />,
key: EntityTabs.LINEAGE,
children: (
<Card className="lineage-card card-body-full m-md w-auto">
<EntityLineageComponent
addLineageHandler={handleAddLineage}
deleted={deleted}
entityLineage={entityLineage}
entityLineageHandler={(lineage: EntityLineage) =>
setEntityLineage(lineage)
}
entityType={EntityType.CONTAINER}
hasEditAccess={hasEditLineagePermission}
isLoading={isLineageLoading}
isNodeLoading={isNodeLoading}
lineageLeafNodes={leafNodes}
loadNodeHandler={handleLoadLineageNode}
removeLineageHandler={handleRemoveLineage}
onFullScreenClick={handleFullScreenClick}
/>
</Card>
<EntityLineageComponent
entity={containerData}
entityType={EntityType.CONTAINER}
hasEditAccess={hasEditLineagePermission}
/>
),
},
{
@ -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);
}

View File

@ -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 (
<PageLayoutV1 className="p-x-lg" pageTitle={t('label.lineage')}>
<div className="lineage-page-container page-container">
<TitleBreadcrumb titleLinks={titleBreadcrumb} />
<EntityLineageComponent
hasEditAccess
isFullScreen
entityType={entityType}
/>
</div>
</PageLayoutV1>
);
};
export default LineagePage;

View File

@ -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%;
}
}

View File

@ -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: <TabsLabel id={EntityTabs.LINEAGE} name={t('label.lineage')} />,
key: EntityTabs.LINEAGE,
children: (
<Card
className="lineage-card card-body-full w-auto border-none"
id="lineageDetails">
<EntityLineageComponent
deleted={tableDetails?.deleted}
entityType={EntityType.TABLE}
hasEditAccess={
tablePermissions.EditAll || tablePermissions.EditLineage
}
/>
</Card>
<EntityLineageComponent
deleted={tableDetails?.deleted}
entity={tableDetails as SourceType}
entityType={EntityType.TABLE}
hasEditAccess={
tablePermissions.EditAll || tablePermissions.EditLineage
}
/>
),
},

View File

@ -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;

View File

@ -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