mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-09-11 09:59:04 +00:00
Change lineage positioning algorithm (#18897)
* use elk algorithm to position nodes * change positioning * fix spacing * do not reset zoom value * minor pw fix * force click on lineage edge
This commit is contained in:
parent
e2789da9dc
commit
fe661a2f49
@ -85,6 +85,7 @@
|
|||||||
"eventemitter3": "^5.0.1",
|
"eventemitter3": "^5.0.1",
|
||||||
"fast-json-patch": "^3.1.1",
|
"fast-json-patch": "^3.1.1",
|
||||||
"history": "4.5.1",
|
"history": "4.5.1",
|
||||||
|
"elkjs": "^0.9.3",
|
||||||
"html-react-parser": "^1.4.14",
|
"html-react-parser": "^1.4.14",
|
||||||
"https-browserify": "^1.0.0",
|
"https-browserify": "^1.0.0",
|
||||||
"i18next": "^21.10.0",
|
"i18next": "^21.10.0",
|
||||||
|
@ -92,58 +92,70 @@ for (const EntityClass of entities) {
|
|||||||
defaultEntity
|
defaultEntity
|
||||||
);
|
);
|
||||||
|
|
||||||
await test.step('Should create lineage for the entity', async () => {
|
try {
|
||||||
await redirectToHomePage(page);
|
await test.step('Should create lineage for the entity', async () => {
|
||||||
await currentEntity.visitEntityPage(page);
|
await redirectToHomePage(page);
|
||||||
await visitLineageTab(page);
|
await currentEntity.visitEntityPage(page);
|
||||||
await verifyColumnLayerInactive(page);
|
await visitLineageTab(page);
|
||||||
await editLineage(page);
|
await verifyColumnLayerInactive(page);
|
||||||
await performZoomOut(page);
|
await editLineage(page);
|
||||||
for (const entity of entities) {
|
await performZoomOut(page);
|
||||||
await connectEdgeBetweenNodes(page, currentEntity, entity);
|
for (const entity of entities) {
|
||||||
}
|
await connectEdgeBetweenNodes(page, currentEntity, entity);
|
||||||
|
}
|
||||||
|
|
||||||
await redirectToHomePage(page);
|
await redirectToHomePage(page);
|
||||||
await currentEntity.visitEntityPage(page);
|
await currentEntity.visitEntityPage(page);
|
||||||
await visitLineageTab(page);
|
await visitLineageTab(page);
|
||||||
await page
|
await page.click('[data-testid="edit-lineage"]');
|
||||||
.locator('.react-flow__controls-fitview')
|
await page
|
||||||
.dispatchEvent('click');
|
.locator('.react-flow__controls-fitview')
|
||||||
|
.dispatchEvent('click');
|
||||||
|
|
||||||
for (const entity of entities) {
|
for (const entity of entities) {
|
||||||
await verifyNodePresent(page, entity);
|
await verifyNodePresent(page, entity);
|
||||||
}
|
}
|
||||||
});
|
await page.click('[data-testid="edit-lineage"]');
|
||||||
|
});
|
||||||
|
|
||||||
await test.step('Should create pipeline between entities', async () => {
|
await test.step('Should create pipeline between entities', async () => {
|
||||||
await editLineage(page);
|
await redirectToHomePage(page);
|
||||||
await performZoomOut(page);
|
await currentEntity.visitEntityPage(page);
|
||||||
|
await visitLineageTab(page);
|
||||||
|
await editLineage(page);
|
||||||
|
await page
|
||||||
|
.locator('.react-flow__controls-fitview')
|
||||||
|
.dispatchEvent('click');
|
||||||
|
|
||||||
for (const entity of entities) {
|
for (const entity of entities) {
|
||||||
await applyPipelineFromModal(page, currentEntity, entity, pipeline);
|
await applyPipelineFromModal(page, currentEntity, entity, pipeline);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await test.step('Verify Lineage Export CSV', async () => {
|
await test.step('Verify Lineage Export CSV', async () => {
|
||||||
await redirectToHomePage(page);
|
await redirectToHomePage(page);
|
||||||
await currentEntity.visitEntityPage(page);
|
await currentEntity.visitEntityPage(page);
|
||||||
await visitLineageTab(page);
|
await visitLineageTab(page);
|
||||||
await verifyExportLineageCSV(page, currentEntity, entities, pipeline);
|
await verifyExportLineageCSV(page, currentEntity, entities, pipeline);
|
||||||
});
|
});
|
||||||
|
|
||||||
await test.step('Remove lineage between nodes for the entity', async () => {
|
await test.step(
|
||||||
await redirectToHomePage(page);
|
'Remove lineage between nodes for the entity',
|
||||||
await currentEntity.visitEntityPage(page);
|
async () => {
|
||||||
await visitLineageTab(page);
|
await redirectToHomePage(page);
|
||||||
await editLineage(page);
|
await currentEntity.visitEntityPage(page);
|
||||||
await performZoomOut(page);
|
await visitLineageTab(page);
|
||||||
|
await editLineage(page);
|
||||||
|
await performZoomOut(page);
|
||||||
|
|
||||||
for (const entity of entities) {
|
for (const entity of entities) {
|
||||||
await deleteEdge(page, currentEntity, entity);
|
await deleteEdge(page, currentEntity, entity);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
);
|
||||||
await cleanup();
|
} finally {
|
||||||
|
await cleanup();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,7 +132,9 @@ export const dragAndDropNode = async (
|
|||||||
await page.hover(originSelector);
|
await page.hover(originSelector);
|
||||||
await page.mouse.down();
|
await page.mouse.down();
|
||||||
const box = (await destinationElement.boundingBox())!;
|
const box = (await destinationElement.boundingBox())!;
|
||||||
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
|
const x = (box.x + box.width / 2) * 0.25; // 0.25 as zoom factor
|
||||||
|
const y = (box.y + box.height / 2) * 0.25; // 0.25 as zoom factor
|
||||||
|
await page.mouse.move(x, y);
|
||||||
await destinationElement.hover();
|
await destinationElement.hover();
|
||||||
await page.mouse.up();
|
await page.mouse.up();
|
||||||
};
|
};
|
||||||
@ -348,7 +350,8 @@ export const applyPipelineFromModal = async (
|
|||||||
|
|
||||||
await page
|
await page
|
||||||
.locator(`[data-testid="edge-${fromNodeFqn}-${toNodeFqn}"]`)
|
.locator(`[data-testid="edge-${fromNodeFqn}-${toNodeFqn}"]`)
|
||||||
.dispatchEvent('click');
|
.click({ force: true });
|
||||||
|
|
||||||
await page.locator('[data-testid="add-pipeline"]').dispatchEvent('click');
|
await page.locator('[data-testid="add-pipeline"]').dispatchEvent('click');
|
||||||
|
|
||||||
const waitForSearchResponse = page.waitForResponse(
|
const waitForSearchResponse = page.waitForResponse(
|
||||||
|
@ -85,12 +85,8 @@ const LineageNodeLabelV1 = ({ node }: Pick<LineageNodeLabelProps, 'node'>) => {
|
|||||||
return (
|
return (
|
||||||
<div className="w-76">
|
<div className="w-76">
|
||||||
<div className="m-0 p-x-md p-y-xs">
|
<div className="m-0 p-x-md p-y-xs">
|
||||||
<div className="d-flex gap-2 items-center m-b-xs">
|
{breadcrumbs.length > 0 && (
|
||||||
<Space
|
<div className="d-flex gap-2 items-center m-b-xs lineage-breadcrumb">
|
||||||
wrap
|
|
||||||
align="start"
|
|
||||||
className="lineage-breadcrumb w-full"
|
|
||||||
size={4}>
|
|
||||||
{breadcrumbs.map((breadcrumb, index) => (
|
{breadcrumbs.map((breadcrumb, index) => (
|
||||||
<React.Fragment key={breadcrumb.name}>
|
<React.Fragment key={breadcrumb.name}>
|
||||||
<Typography.Text
|
<Typography.Text
|
||||||
@ -105,8 +101,9 @@ const LineageNodeLabelV1 = ({ node }: Pick<LineageNodeLabelProps, 'node'>) => {
|
|||||||
)}
|
)}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
))}
|
))}
|
||||||
</Space>
|
</div>
|
||||||
</div>
|
)}
|
||||||
|
|
||||||
<EntityLabel node={node} />
|
<EntityLabel node={node} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -113,6 +113,10 @@ const NodeChildren = ({ node, isConnectable }: NodeChildrenProps) => {
|
|||||||
}
|
}
|
||||||
}, [children]);
|
}, [children]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setShowAllColumns(expandAllColumns);
|
||||||
|
}, [expandAllColumns]);
|
||||||
|
|
||||||
const renderRecord = useCallback(
|
const renderRecord = useCallback(
|
||||||
(record: Column) => {
|
(record: Column) => {
|
||||||
const isColumnTraced = tracedColumns.includes(
|
const isColumnTraced = tracedColumns.includes(
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
.ant-btn.ant-btn-background-ghost.expand-btn {
|
.ant-btn.ant-btn-background-ghost.expand-btn {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
}
|
}
|
||||||
@ -43,6 +44,7 @@
|
|||||||
border: 1px solid @lineage-border;
|
border: 1px solid @lineage-border;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
.profiler-item {
|
.profiler-item {
|
||||||
width: 36px;
|
width: 36px;
|
||||||
height: 36px;
|
height: 36px;
|
||||||
@ -50,16 +52,20 @@
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
line-height: 36px;
|
line-height: 36px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
|
||||||
&.green {
|
&.green {
|
||||||
border: 1px solid @green-5;
|
border: 1px solid @green-5;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.amber {
|
&.amber {
|
||||||
border: 1px solid @yellow-4;
|
border: 1px solid @yellow-4;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.red {
|
&.red {
|
||||||
border: 1px solid @red-5;
|
border: 1px solid @red-5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-container {
|
.column-container {
|
||||||
min-height: 48px;
|
min-height: 48px;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
@ -68,19 +74,24 @@
|
|||||||
.lineage-collapse-column.ant-collapse {
|
.lineage-collapse-column.ant-collapse {
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
|
|
||||||
.ant-collapse-header {
|
.ant-collapse-header {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
|
||||||
.custom-node-column-container {
|
.custom-node-column-container {
|
||||||
background-color: @lineage-collapse-header;
|
background-color: @lineage-collapse-header;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lineage-column-node-handle {
|
.lineage-column-node-handle {
|
||||||
background-color: @lineage-collapse-header;
|
background-color: @lineage-collapse-header;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-collapse-content-box {
|
.ant-collapse-content-box {
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-collapse-item {
|
.ant-collapse-item {
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
@ -114,6 +125,7 @@
|
|||||||
.lineage-node-handle {
|
.lineage-node-handle {
|
||||||
border-color: @primary-color;
|
border-color: @primary-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lineage-node {
|
.lineage-node {
|
||||||
border-color: @primary-color !important;
|
border-color: @primary-color !important;
|
||||||
}
|
}
|
||||||
@ -137,15 +149,19 @@
|
|||||||
.lineage-node {
|
.lineage-node {
|
||||||
border-color: @primary-color !important;
|
border-color: @primary-color !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lineage-node-handle {
|
.lineage-node-handle {
|
||||||
border-color: @primary-color;
|
border-color: @primary-color;
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
color: @primary-color;
|
color: @primary-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.label-container {
|
.label-container {
|
||||||
background: @primary-1;
|
background: @primary-1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-container {
|
.column-container {
|
||||||
background: @primary-1;
|
background: @primary-1;
|
||||||
border-top: 1px solid @border-color;
|
border-top: 1px solid @border-color;
|
||||||
@ -171,15 +187,19 @@
|
|||||||
&.lineage-node {
|
&.lineage-node {
|
||||||
border-color: @red-3 !important;
|
border-color: @red-3 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lineage-node-handle {
|
.lineage-node-handle {
|
||||||
border-color: @red-3;
|
border-color: @red-3;
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
color: @red-3;
|
color: @red-3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.label-container {
|
.label-container {
|
||||||
background: fade(@red-3, 10%);
|
background: fade(@red-3, 10%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-container {
|
.column-container {
|
||||||
background: fade(@red-3, 10%);
|
background: fade(@red-3, 10%);
|
||||||
border-top: 1px solid @border-color;
|
border-top: 1px solid @border-color;
|
||||||
@ -191,15 +211,18 @@
|
|||||||
.label-container {
|
.label-container {
|
||||||
background: @primary-1;
|
background: @primary-1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-container {
|
.column-container {
|
||||||
background: @primary-1;
|
background: @primary-1;
|
||||||
border-top: 1px solid @border-color;
|
border-top: 1px solid @border-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.data-quality-failed-custom-node-header.custom-node-header-active {
|
.data-quality-failed-custom-node-header.custom-node-header-active {
|
||||||
.label-container {
|
.label-container {
|
||||||
background: fade(@red-3, 10%);
|
background: fade(@red-3, 10%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-container {
|
.column-container {
|
||||||
background: fade(@red-3, 10%);
|
background: fade(@red-3, 10%);
|
||||||
border-top: 1px solid @red-3;
|
border-top: 1px solid @red-3;
|
||||||
@ -214,6 +237,7 @@
|
|||||||
.lineage-node-handle.react-flow__handle-left {
|
.lineage-node-handle.react-flow__handle-left {
|
||||||
left: -22px;
|
left: -22px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lineage-node-handle.react-flow__handle-right {
|
.lineage-node-handle.react-flow__handle-right {
|
||||||
right: -22px;
|
right: -22px;
|
||||||
}
|
}
|
||||||
@ -227,6 +251,7 @@
|
|||||||
border-color: @lineage-border !important;
|
border-color: @lineage-border !important;
|
||||||
background: @white !important;
|
background: @white !important;
|
||||||
top: 43px !important; // Need to show handles on top half
|
top: 43px !important; // Need to show handles on top half
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
color: @text-grey-muted;
|
color: @text-grey-muted;
|
||||||
}
|
}
|
||||||
@ -241,9 +266,11 @@
|
|||||||
height: 25px;
|
height: 25px;
|
||||||
transform: none;
|
transform: none;
|
||||||
border: none;
|
border: none;
|
||||||
|
|
||||||
&.react-flow__handle-left {
|
&.react-flow__handle-left {
|
||||||
left: 0;
|
left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.react-flow__handle-right {
|
&.react-flow__handle-right {
|
||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
@ -266,6 +293,7 @@
|
|||||||
|
|
||||||
.custom-node-name-icon {
|
.custom-node-name-icon {
|
||||||
width: 14px;
|
width: 14px;
|
||||||
|
display: flex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,9 +15,9 @@
|
|||||||
height: 28px;
|
height: 28px;
|
||||||
width: 28px;
|
width: 28px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lineage-breadcrumb {
|
.lineage-breadcrumb {
|
||||||
.lineage-breadcrumb-item {
|
.lineage-breadcrumb-item {
|
||||||
max-width: 140px;
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
@ -63,11 +63,7 @@ import {
|
|||||||
ZOOM_VALUE,
|
ZOOM_VALUE,
|
||||||
} from '../../constants/Lineage.constants';
|
} from '../../constants/Lineage.constants';
|
||||||
import { mockDatasetData } from '../../constants/mockTourData.constants';
|
import { mockDatasetData } from '../../constants/mockTourData.constants';
|
||||||
import {
|
import { EntityLineageNodeType, EntityType } from '../../enums/entity.enum';
|
||||||
EntityLineageDirection,
|
|
||||||
EntityLineageNodeType,
|
|
||||||
EntityType,
|
|
||||||
} from '../../enums/entity.enum';
|
|
||||||
import { AddLineage } from '../../generated/api/lineage/addLineage';
|
import { AddLineage } from '../../generated/api/lineage/addLineage';
|
||||||
import { LineageSettings } from '../../generated/configuration/lineageSettings';
|
import { LineageSettings } from '../../generated/configuration/lineageSettings';
|
||||||
import { LineageLayer } from '../../generated/settings/settings';
|
import { LineageLayer } from '../../generated/settings/settings';
|
||||||
@ -97,7 +93,7 @@ import {
|
|||||||
getChildMap,
|
getChildMap,
|
||||||
getClassifiedEdge,
|
getClassifiedEdge,
|
||||||
getConnectedNodesEdges,
|
getConnectedNodesEdges,
|
||||||
getLayoutedElements,
|
getELKLayoutedElements,
|
||||||
getLineageEdge,
|
getLineageEdge,
|
||||||
getLineageEdgeForAPI,
|
getLineageEdgeForAPI,
|
||||||
getLoadingStatusValue,
|
getLoadingStatusValue,
|
||||||
@ -107,6 +103,7 @@ import {
|
|||||||
getUpdatedColumnsFromEdge,
|
getUpdatedColumnsFromEdge,
|
||||||
getUpstreamDownstreamNodesEdges,
|
getUpstreamDownstreamNodesEdges,
|
||||||
onLoad,
|
onLoad,
|
||||||
|
positionNodesUsingElk,
|
||||||
removeLineageHandler,
|
removeLineageHandler,
|
||||||
} from '../../utils/EntityLineageUtils';
|
} from '../../utils/EntityLineageUtils';
|
||||||
import { getEntityReferenceFromEntity } from '../../utils/EntityUtils';
|
import { getEntityReferenceFromEntity } from '../../utils/EntityUtils';
|
||||||
@ -1067,27 +1064,29 @@ const LineageProvider = ({ children }: LineageProviderProps) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const selectNode = (node: Node) => {
|
const selectNode = (node: Node) => {
|
||||||
centerNodePosition(node, reactFlowInstance);
|
centerNodePosition(node, reactFlowInstance, zoomValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
const repositionLayout = useCallback(
|
const repositionLayout = useCallback(
|
||||||
(activateNode = false) => {
|
async (activateNode = false) => {
|
||||||
|
if (nodes.length === 0 || !reactFlowInstance) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const isColView = activeLayer.includes(LineageLayer.ColumnLevelLineage);
|
const isColView = activeLayer.includes(LineageLayer.ColumnLevelLineage);
|
||||||
const { node, edge } = getLayoutedElements(
|
const { nodes: layoutedNodes, edges: layoutedEdges } =
|
||||||
{
|
await getELKLayoutedElements(
|
||||||
node: nodes,
|
nodes,
|
||||||
edge: edges,
|
edges,
|
||||||
},
|
isColView,
|
||||||
EntityLineageDirection.LEFT_RIGHT,
|
isEditMode || expandAllColumns,
|
||||||
isColView,
|
columnsHavingLineage
|
||||||
isEditMode || expandAllColumns,
|
);
|
||||||
columnsHavingLineage
|
|
||||||
);
|
|
||||||
|
|
||||||
setNodes(node);
|
setNodes(layoutedNodes);
|
||||||
setEdges(edge);
|
setEdges(layoutedEdges);
|
||||||
|
|
||||||
const rootNode = node.find((n) => n.data.isRootNode);
|
const rootNode = layoutedNodes.find((n) => n.data.isRootNode);
|
||||||
if (!rootNode) {
|
if (!rootNode) {
|
||||||
if (activateNode && reactFlowInstance) {
|
if (activateNode && reactFlowInstance) {
|
||||||
onLoad(reactFlowInstance); // Call fitview in case of pipeline
|
onLoad(reactFlowInstance); // Call fitview in case of pipeline
|
||||||
@ -1097,12 +1096,13 @@ const LineageProvider = ({ children }: LineageProviderProps) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Center the root node in the view
|
// Center the root node in the view
|
||||||
centerNodePosition(rootNode, reactFlowInstance);
|
centerNodePosition(rootNode, reactFlowInstance, zoomValue);
|
||||||
if (activateNode) {
|
if (activateNode) {
|
||||||
onNodeClick(rootNode);
|
onNodeClick(rootNode);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
|
zoomValue,
|
||||||
reactFlowInstance,
|
reactFlowInstance,
|
||||||
activeLayer,
|
activeLayer,
|
||||||
nodes,
|
nodes,
|
||||||
@ -1115,7 +1115,7 @@ const LineageProvider = ({ children }: LineageProviderProps) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const redrawLineage = useCallback(
|
const redrawLineage = useCallback(
|
||||||
(lineageData: EntityLineageResponse) => {
|
async (lineageData: EntityLineageResponse) => {
|
||||||
const allNodes = uniqWith(
|
const allNodes = uniqWith(
|
||||||
[
|
[
|
||||||
...(lineageData.nodes ?? []),
|
...(lineageData.nodes ?? []),
|
||||||
@ -1135,8 +1135,28 @@ const LineageProvider = ({ children }: LineageProviderProps) => {
|
|||||||
lineageData.edges ?? [],
|
lineageData.edges ?? [],
|
||||||
decodedFqn
|
decodedFqn
|
||||||
);
|
);
|
||||||
setNodes(updatedNodes);
|
|
||||||
setEdges(updatedEdges);
|
if (reactFlowInstance && reactFlowInstance.viewportInitialized) {
|
||||||
|
const positionedNodesEdges = await positionNodesUsingElk(
|
||||||
|
updatedNodes,
|
||||||
|
updatedEdges,
|
||||||
|
activeLayer.includes(LineageLayer.ColumnLevelLineage),
|
||||||
|
isEditMode || expandAllColumns,
|
||||||
|
columnsHavingLineage
|
||||||
|
);
|
||||||
|
setNodes(positionedNodesEdges.nodes);
|
||||||
|
setEdges(positionedNodesEdges.edges);
|
||||||
|
const rootNode = positionedNodesEdges.nodes.find(
|
||||||
|
(n) => n.data.isRootNode
|
||||||
|
);
|
||||||
|
if (rootNode) {
|
||||||
|
centerNodePosition(rootNode, reactFlowInstance, zoomValue);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setNodes(updatedNodes);
|
||||||
|
setEdges(updatedEdges);
|
||||||
|
}
|
||||||
|
|
||||||
setColumnsHavingLineage(columnsHavingLineage);
|
setColumnsHavingLineage(columnsHavingLineage);
|
||||||
|
|
||||||
// Get upstream downstream nodes and edges data
|
// Get upstream downstream nodes and edges data
|
||||||
@ -1151,7 +1171,14 @@ const LineageProvider = ({ children }: LineageProviderProps) => {
|
|||||||
selectNode(activeNode);
|
selectNode(activeNode);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[decodedFqn, activeNode, activeLayer, isEditMode]
|
[
|
||||||
|
decodedFqn,
|
||||||
|
activeNode,
|
||||||
|
activeLayer,
|
||||||
|
isEditMode,
|
||||||
|
reactFlowInstance,
|
||||||
|
zoomValue,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
import { CheckOutlined, SearchOutlined } from '@ant-design/icons';
|
import { CheckOutlined, SearchOutlined } from '@ant-design/icons';
|
||||||
import { graphlib, layout } from '@dagrejs/dagre';
|
import { graphlib, layout } from '@dagrejs/dagre';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
|
import ELK, { ElkExtendedEdge, ElkNode } from 'elkjs/lib/elk.bundled.js';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import {
|
import {
|
||||||
cloneDeep,
|
cloneDeep,
|
||||||
@ -125,14 +126,15 @@ export const onLoad = (reactFlowInstance: ReactFlowInstance) => {
|
|||||||
|
|
||||||
export const centerNodePosition = (
|
export const centerNodePosition = (
|
||||||
node: Node,
|
node: Node,
|
||||||
reactFlowInstance?: ReactFlowInstance
|
reactFlowInstance?: ReactFlowInstance,
|
||||||
|
zoomValue?: number
|
||||||
) => {
|
) => {
|
||||||
const { position, width } = node;
|
const { position, width } = node;
|
||||||
reactFlowInstance?.setCenter(
|
reactFlowInstance?.setCenter(
|
||||||
position.x + (width ?? 1 / 2),
|
position.x + (width ?? 1 / 2),
|
||||||
position.y + NODE_HEIGHT / 2,
|
position.y + NODE_HEIGHT / 2,
|
||||||
{
|
{
|
||||||
zoom: ZOOM_VALUE,
|
zoom: zoomValue ?? ZOOM_VALUE,
|
||||||
duration: ZOOM_TRANSITION_DURATION,
|
duration: ZOOM_TRANSITION_DURATION,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -218,6 +220,73 @@ export const getLayoutedElements = (
|
|||||||
return { node: uNode, edge: edgesRequired };
|
return { node: uNode, edge: edgesRequired };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const layoutOptions = {
|
||||||
|
'elk.algorithm': 'layered',
|
||||||
|
'elk.direction': 'RIGHT',
|
||||||
|
'elk.layered.spacing.edgeNodeBetweenLayers': '50',
|
||||||
|
'elk.spacing.nodeNode': '60',
|
||||||
|
'elk.layered.nodePlacement.strategy': 'SIMPLE',
|
||||||
|
};
|
||||||
|
|
||||||
|
const elk = new ELK();
|
||||||
|
|
||||||
|
export const getELKLayoutedElements = async (
|
||||||
|
nodes: Node[],
|
||||||
|
edges: Edge[],
|
||||||
|
isExpanded = true,
|
||||||
|
expandAllColumns = false,
|
||||||
|
columnsHavingLineage: string[] = []
|
||||||
|
) => {
|
||||||
|
const elkNodes: ElkNode[] = nodes.map((node) => {
|
||||||
|
const { childrenHeight } = getEntityChildrenAndLabel(
|
||||||
|
node.data.node,
|
||||||
|
expandAllColumns,
|
||||||
|
columnsHavingLineage
|
||||||
|
);
|
||||||
|
const nodeHeight = isExpanded ? childrenHeight + 220 : NODE_HEIGHT;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...node,
|
||||||
|
targetPosition: 'left',
|
||||||
|
sourcePosition: 'right',
|
||||||
|
width: NODE_WIDTH,
|
||||||
|
height: nodeHeight,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const elkEdges: ElkExtendedEdge[] = edges.map((edge) => ({
|
||||||
|
id: edge.id,
|
||||||
|
sources: [edge.source],
|
||||||
|
targets: [edge.target],
|
||||||
|
}));
|
||||||
|
|
||||||
|
const graph = {
|
||||||
|
id: 'root',
|
||||||
|
layoutOptions: layoutOptions,
|
||||||
|
children: elkNodes,
|
||||||
|
edges: elkEdges,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const layoutedGraph = await elk.layout(graph);
|
||||||
|
const updatedNodes: Node[] = nodes.map((node) => {
|
||||||
|
const layoutedNode = (layoutedGraph?.children ?? []).find(
|
||||||
|
(elkNode) => elkNode.id === node.id
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...node,
|
||||||
|
position: { x: layoutedNode?.x ?? 0, y: layoutedNode?.y ?? 0 },
|
||||||
|
hidden: false,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return { nodes: updatedNodes, edges: edges ?? [] };
|
||||||
|
} catch (error) {
|
||||||
|
return { nodes: [], edges: [] };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const getModalBodyText = (selectedEdge: Edge) => {
|
export const getModalBodyText = (selectedEdge: Edge) => {
|
||||||
const { data } = selectedEdge;
|
const { data } = selectedEdge;
|
||||||
const { fromEntity, toEntity } = data.edge as EdgeDetails;
|
const { fromEntity, toEntity } = data.edge as EdgeDetails;
|
||||||
@ -508,7 +577,7 @@ const calculateHeightAndFlattenNode = (
|
|||||||
expandAllColumns ||
|
expandAllColumns ||
|
||||||
columnsHavingLineage.indexOf(child.fullyQualifiedName ?? '') !== -1
|
columnsHavingLineage.indexOf(child.fullyQualifiedName ?? '') !== -1
|
||||||
) {
|
) {
|
||||||
totalHeight += 27; // Add height for the current child
|
totalHeight += 31; // Add height for the current child
|
||||||
}
|
}
|
||||||
flattened.push(child);
|
flattened.push(child);
|
||||||
|
|
||||||
@ -682,6 +751,24 @@ const getNodeType = (
|
|||||||
return EntityLineageNodeType.DEFAULT;
|
return EntityLineageNodeType.DEFAULT;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const positionNodesUsingElk = async (
|
||||||
|
nodes: Node[],
|
||||||
|
edges: Edge[],
|
||||||
|
isColView: boolean,
|
||||||
|
expandAllColumns = false,
|
||||||
|
columnsHavingLineage: string[] = []
|
||||||
|
) => {
|
||||||
|
const obj = await getELKLayoutedElements(
|
||||||
|
nodes,
|
||||||
|
edges,
|
||||||
|
isColView,
|
||||||
|
expandAllColumns,
|
||||||
|
columnsHavingLineage
|
||||||
|
);
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
};
|
||||||
|
|
||||||
export const createNodes = (
|
export const createNodes = (
|
||||||
nodesData: EntityReference[],
|
nodesData: EntityReference[],
|
||||||
edgesData: EdgeDetails[],
|
edgesData: EdgeDetails[],
|
||||||
@ -692,37 +779,8 @@ export const createNodes = (
|
|||||||
getEntityName(a).localeCompare(getEntityName(b))
|
getEntityName(a).localeCompare(getEntityName(b))
|
||||||
);
|
);
|
||||||
|
|
||||||
const GraphInstance = graphlib.Graph;
|
return uniqueNodesData.map((node) => {
|
||||||
const graph = new GraphInstance();
|
|
||||||
|
|
||||||
// Set an object for the graph label
|
|
||||||
graph.setGraph({
|
|
||||||
rankdir: EntityLineageDirection.LEFT_RIGHT,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Default to assigning a new object as a label for each new edge.
|
|
||||||
graph.setDefaultEdgeLabel(() => ({}));
|
|
||||||
|
|
||||||
// Add nodes to the graph
|
|
||||||
uniqueNodesData.forEach((node) => {
|
|
||||||
const { childrenHeight } = getEntityChildrenAndLabel(node as SourceType);
|
const { childrenHeight } = getEntityChildrenAndLabel(node as SourceType);
|
||||||
const nodeHeight = isExpanded ? childrenHeight + 220 : NODE_HEIGHT;
|
|
||||||
graph.setNode(node.id, { width: NODE_WIDTH, height: nodeHeight });
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add edges to the graph (if you have edge information)
|
|
||||||
edgesData.forEach((edge) => {
|
|
||||||
graph.setEdge(edge.fromEntity.id, edge.toEntity.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Perform the layout
|
|
||||||
layout(graph);
|
|
||||||
|
|
||||||
// Get the layout positions
|
|
||||||
const layoutPositions = graph.nodes().map((nodeId) => graph.node(nodeId));
|
|
||||||
|
|
||||||
return uniqueNodesData.map((node, index) => {
|
|
||||||
const position = layoutPositions[index];
|
|
||||||
const type =
|
const type =
|
||||||
node.type === EntityLineageNodeType.LOAD_MORE
|
node.type === EntityLineageNodeType.LOAD_MORE
|
||||||
? node.type
|
? node.type
|
||||||
@ -741,9 +799,11 @@ export const createNodes = (
|
|||||||
node,
|
node,
|
||||||
isRootNode: entityFqn === node.fullyQualifiedName,
|
isRootNode: entityFqn === node.fullyQualifiedName,
|
||||||
},
|
},
|
||||||
|
width: NODE_WIDTH,
|
||||||
|
height: isExpanded ? childrenHeight + 220 : NODE_HEIGHT,
|
||||||
position: {
|
position: {
|
||||||
x: position.x - NODE_WIDTH / 2,
|
x: 0,
|
||||||
y: position.y - position.height / 2,
|
y: 0,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -7448,6 +7448,11 @@ electron-to-chromium@^1.4.535:
|
|||||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.537.tgz#aac4101db53066be1e49baedd000a26bc754adc9"
|
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.537.tgz#aac4101db53066be1e49baedd000a26bc754adc9"
|
||||||
integrity sha512-W1+g9qs9hviII0HAwOdehGYkr+zt7KKdmCcJcjH0mYg6oL8+ioT3Skjmt7BLoAQqXhjf40AXd+HlR4oAWMlXjA==
|
integrity sha512-W1+g9qs9hviII0HAwOdehGYkr+zt7KKdmCcJcjH0mYg6oL8+ioT3Skjmt7BLoAQqXhjf40AXd+HlR4oAWMlXjA==
|
||||||
|
|
||||||
|
elkjs@^0.9.3:
|
||||||
|
version "0.9.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/elkjs/-/elkjs-0.9.3.tgz#16711f8ceb09f1b12b99e971b138a8384a529161"
|
||||||
|
integrity sha512-f/ZeWvW/BCXbhGEf1Ujp29EASo/lk1FDnETgNKwJrsVvGZhUWCZyg3xLJjAsxfOmt8KjswHmI5EwCQcPMpOYhQ==
|
||||||
|
|
||||||
emittery@^0.7.1:
|
emittery@^0.7.1:
|
||||||
version "0.7.2"
|
version "0.7.2"
|
||||||
resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.7.2.tgz#25595908e13af0f5674ab419396e2fb394cdfa82"
|
resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.7.2.tgz#25595908e13af0f5674ab419396e2fb394cdfa82"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user