From e7422b0e4322749d57d10d46ca84b4fb46f24d44 Mon Sep 17 00:00:00 2001 From: Satender Date: Fri, 24 Apr 2026 17:11:56 +0530 Subject: [PATCH 1/7] added necessary changes to accomodate tempLineageTables nodes --- .../EntityLineage/LineageNodeLabelV1.tsx | 8 +- .../components/Lineage/Lineage.interface.ts | 7 +- .../LineageProvider/LineageProvider.tsx | 4 + .../ui/src/utils/CanvasUtils.test.ts | 62 ++++++++- .../resources/ui/src/utils/CanvasUtils.ts | 6 +- .../ui/src/utils/EntityLineageUtils.tsx | 123 +++++++++++++----- 6 files changed, 167 insertions(+), 43 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityLineage/LineageNodeLabelV1.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityLineage/LineageNodeLabelV1.tsx index 8982ce968f79..238d5ad4de3b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityLineage/LineageNodeLabelV1.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityLineage/LineageNodeLabelV1.tsx @@ -84,9 +84,11 @@ const EntityLabel = ({ node }: Pick) => { childrenCount > 0 ? 'with-footer' : '' )}> -
- {getServiceIcon(node)} -
+ {!node.isTempTable && ( +
+ {getServiceIcon(node)} +
+ )} { return; } + if (node.data?.node?.isTempTable) { + return; + } + if (node.type === EntityLineageNodeType.LOAD_MORE) { selectLoadMoreNode(node); } else { diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/CanvasUtils.test.ts b/openmetadata-ui/src/main/resources/ui/src/utils/CanvasUtils.test.ts index e04831a671ce..c3558572fa41 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/CanvasUtils.test.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/CanvasUtils.test.ts @@ -11,6 +11,7 @@ * limitations under the License. */ import { Edge, Node, Viewport } from 'reactflow'; +import { EntityType } from '../enums/entity.enum'; import { BoundingBox, boundsIntersect, @@ -218,11 +219,12 @@ describe('CanvasUtils', () => { const result = getEdgeCoordinates(edge, sourceNode, targetNode); + // Y uses node.height (100) from createMockNode, so midpoint = 50 expect(result).toEqual({ sourceX: 400, - sourceY: 33, + sourceY: 50, targetX: 490, - targetY: 33, + targetY: 50, }); }); @@ -237,9 +239,9 @@ describe('CanvasUtils', () => { expect(result).not.toBeNull(); expect(result?.sourceX).toBe(500); - expect(result?.sourceY).toBe(233); + expect(result?.sourceY).toBe(250); // 200 + 100/2 expect(result?.targetX).toBe(590); - expect(result?.targetY).toBe(333); + expect(result?.targetY).toBe(350); // 300 + 100/2 }); }); @@ -391,6 +393,55 @@ describe('CanvasUtils', () => { expect(result?.targetX).toBe(490); }); }); + + describe('temp lineage nodes', () => { + const createTempNode = (id: string, measuredHeight: number): Node => ({ + id, + position: { x: 0, y: 0 }, + data: { + node: { + id, + name: id, + entityType: EntityType.TABLE, + isTempTable: true, + columns: [], + }, + isRootNode: false, + }, + width: 400, + height: measuredHeight, + }); + + it('uses measured node.height for temp node entity-level edge', () => { + const edge = createMockEdge('edge1', 'temp_staging', 'node2', false); + const tempNode = createTempNode('temp_staging', 80); + const targetNode = createMockNode('node2'); + targetNode.position = { x: 500, y: 0 }; + + const result = getEdgeCoordinates(edge, tempNode, targetNode); + + expect(result).not.toBeNull(); + expect(result?.sourceY).toBe(40); // 0 + 80/2 (measured height), not 33 (getNodeHeight formula) + expect(result?.targetY).toBe(50); // 0 + 100/2 + }); + + it('centers edge at actual midpoint when measured height differs from computed height', () => { + const edge = createMockEdge('edge1', 'node1', 'node2', false); + const sourceNode = createMockNode('node1'); + sourceNode.height = 150; + sourceNode.position = { x: 0, y: 100 }; + const targetNode = createMockNode('node2'); + targetNode.height = 200; + targetNode.position = { x: 500, y: 100 }; + + const result = getEdgeCoordinates(edge, sourceNode, targetNode); + + expect(result).not.toBeNull(); + expect(result?.sourceY).toBe(175); // 100 + 150/2 + expect(result?.targetY).toBe(200); // 100 + 200/2 + }); + + }); }); describe('getEdgeBounds', () => { @@ -414,7 +465,8 @@ describe('CanvasUtils', () => { expect(result).not.toBeNull(); expect(result!.minX).toBeLessThan(351); expect(result!.maxX).toBeGreaterThan(500); - expect(result!.minY).toBeLessThan(0); + // sourceY = 50 (node.height=100 / 2), padding=50 → minY = 0 + expect(result!.minY).toBeLessThanOrEqual(0); expect(result!.maxY).toBeGreaterThan(100); }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/CanvasUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/CanvasUtils.ts index ffe7060f00e1..e5b077774b46 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/CanvasUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/CanvasUtils.ts @@ -254,8 +254,10 @@ function getEntityLineageCoordinates( targetNode: Node, isColumnLineage: boolean ): EdgeCoordinates { - const sourceHeight = getNodeHeight(sourceNode, isColumnLineage, 0); - const targetHeight = getNodeHeight(targetNode, isColumnLineage, 0); + const sourceHeight = + sourceNode.height ?? getNodeHeight(sourceNode, isColumnLineage, 0); + const targetHeight = + targetNode.height ?? getNodeHeight(targetNode, isColumnLineage, 0); return { sourceX: sourceNode.position.x + (sourceNode.width ?? 0), diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/EntityLineageUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/EntityLineageUtils.tsx index d405b497b710..6aaf627409b1 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/EntityLineageUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/EntityLineageUtils.tsx @@ -100,7 +100,11 @@ import { Pipeline } from '../generated/entity/data/pipeline'; import { SearchIndex } from '../generated/entity/data/searchIndex'; import { Table } from '../generated/entity/data/table'; import { Topic } from '../generated/entity/data/topic'; -import { ColumnLineage, LineageDetails } from '../generated/type/entityLineage'; +import { + ColumnLineage, + LineageDetails, + TempLineageTable, +} from '../generated/type/entityLineage'; import { EntityReference } from '../generated/type/entityReference'; import { TagSource } from '../generated/type/tagLabel'; import { useLineageStore } from '../hooks/useLineageStore'; @@ -117,12 +121,7 @@ import { jsonToCSV } from './StringsUtils'; import { showErrorToast } from './ToastUtils'; interface LayoutedElements { - node: Array< - Node & { - nodeHeight: number; - childrenHeight: number; - } - >; + node: Array; edge: Edge[]; } @@ -1520,31 +1519,21 @@ const processNodeArray = ( ); }; -const processPipelineEdge = (edge: EdgeDetails, pipelineNode: Pipeline) => { - const pipelineEntityType = get(pipelineNode, 'entityType'); - - // Create two edges: fromEntity -> pipeline and pipeline -> toEntity - const edgeFromToPipeline = { - fromEntity: edge.fromEntity, - toEntity: { - id: pipelineNode.id, - type: pipelineEntityType, - fullyQualifiedName: pipelineNode.fullyQualifiedName ?? '', - }, - extraInfo: edge, - }; - - const edgePipelineToTo = { - fromEntity: { - id: pipelineNode.id, - type: pipelineEntityType, - fullyQualifiedName: pipelineNode.fullyQualifiedName ?? '', - }, - toEntity: edge.toEntity, - extraInfo: edge, +const processPipelineEdge = ( + edge: EdgeDetails, + pipelineNode: Pipeline +): EdgeDetails[] => { + const pipelineEntityType = get(pipelineNode, 'entityType') as unknown as string; + const pipelineRef = { + id: pipelineNode.id, + type: pipelineEntityType, + fullyQualifiedName: pipelineNode.fullyQualifiedName ?? '', }; - return [edgeFromToPipeline, edgePipelineToTo]; + return [ + { fromEntity: edge.fromEntity, toEntity: pipelineRef, extraInfo: edge }, + { fromEntity: pipelineRef, toEntity: edge.toEntity, extraInfo: edge }, + ]; }; const processEdges = ( @@ -1615,6 +1604,71 @@ const processPagination = ( return { newNodes, newEdges }; }; +const extractTempLineageNodes = ( + edges: EdgeDetails[], + existingNodes: LineageNodeType[] +): { nodes: LineageNodeType[]; edges: EdgeDetails[] } => { + const newTempNodes = new Map(); + const newEdges: EdgeDetails[] = []; + + const existingByFqn = new Map( + existingNodes + .filter((n) => n.fullyQualifiedName) + .map((n) => [n.fullyQualifiedName!, n]) + ); + + const getOrCreateNode = (nameOrFqn: string): LineageNodeType => { + const existing = existingByFqn.get(nameOrFqn); + if (existing) { + return existing; + } + if (newTempNodes.has(nameOrFqn)) { + return newTempNodes.get(nameOrFqn) as LineageNodeType; + } + const tempNode: LineageNodeType = { + id: `temp_${nameOrFqn}`, + name: nameOrFqn, + displayName: nameOrFqn, + fullyQualifiedName: nameOrFqn, + type: EntityType.TABLE, + entityType: EntityType.TABLE, + isTempTable: true, + columns: [], + }; + newTempNodes.set(nameOrFqn, tempNode); + + return tempNode; + }; + + edges.forEach((edge) => { + if (!edge.tempLineageTables?.length) { + return; + } + edge.tempLineageTables.forEach((hop: TempLineageTable) => { + const fromNode = getOrCreateNode(hop.fromEntity); + const toNode = getOrCreateNode(hop.toEntity); + newEdges.push({ + fromEntity: { + id: fromNode.id, + type: fromNode.type ?? EntityType.TABLE, + fullyQualifiedName: fromNode.fullyQualifiedName, + }, + toEntity: { + id: toNode.id, + type: toNode.type ?? EntityType.TABLE, + fullyQualifiedName: toNode.fullyQualifiedName, + }, + }); + }); + }); + + const pureNewNodes = Array.from(newTempNodes.values()).filter( + (n) => !existingByFqn.has(n.fullyQualifiedName ?? '') + ); + + return { nodes: pureNewNodes, edges: newEdges }; +}; + export const parseLineageData = ( data: LineageData, entityFqn: string, // This contains fqn of node or entity that is being viewed in lineage page @@ -1656,14 +1710,19 @@ export const parseLineageData = ( ...newEdges, ]; + const { nodes: tempNodes, edges: tempEdges } = extractTempLineageNodes( + finalEdges, + finalNodes + ); + // Find the main entity const entity = nodesArray.find( (node) => node.fullyQualifiedName === entityFqn ) as LineageNodeType; return { - nodes: finalNodes, - edges: finalEdges, + nodes: [...finalNodes, ...tempNodes], + edges: [...finalEdges, ...tempEdges], entity, }; }; From db469d18259ca2d5fefbf4793925250331c95d98 Mon Sep 17 00:00:00 2001 From: Satender Date: Fri, 24 Apr 2026 17:56:48 +0530 Subject: [PATCH 2/7] updated code as per comments from Gitar --- .../ui/src/utils/CanvasUtils.test.ts | 1 - .../ui/src/utils/EntityLineageUtils.test.tsx | 135 ++++++++++++++++++ .../ui/src/utils/EntityLineageUtils.tsx | 8 +- 3 files changed, 141 insertions(+), 3 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/CanvasUtils.test.ts b/openmetadata-ui/src/main/resources/ui/src/utils/CanvasUtils.test.ts index c3558572fa41..b4339d0b5df6 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/CanvasUtils.test.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/CanvasUtils.test.ts @@ -440,7 +440,6 @@ describe('CanvasUtils', () => { expect(result?.sourceY).toBe(175); // 100 + 150/2 expect(result?.targetY).toBe(200); // 100 + 200/2 }); - }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/EntityLineageUtils.test.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/EntityLineageUtils.test.tsx index d782162cd683..815a7541838a 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/EntityLineageUtils.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/EntityLineageUtils.test.tsx @@ -1915,3 +1915,138 @@ describe('getLineageTableConfig', () => { }); }); }); + +describe('extractTempLineageNodes (via parseLineageData)', () => { + const actualLodash = jest.requireActual('lodash'); + + beforeEach(() => { + mockUniqWith.mockImplementation(actualLodash.uniqWith); + mockIsEqual.mockImplementation(actualLodash.isEqual); + mockGet.mockImplementation(actualLodash.get); + }); + + const makeNodeData = (id: string, fqn: string) => ({ + entity: { + id, + type: EntityType.TABLE, + fullyQualifiedName: fqn, + name: fqn, + columns: [], + }, + paging: { entityDownstreamCount: 0, entityUpstreamCount: 0 }, + }); + + const makeLineageData = ( + downstreamEdges: Record = {} + ): LineageData => ({ + nodes: { + 'id-a': makeNodeData('id-a', 'db.tableA'), + 'id-b': makeNodeData('id-b', 'db.tableB'), + }, + downstreamEdges, + upstreamEdges: {}, + }); + + it('creates a temp node with correct shape for an unknown FQN', () => { + const data = makeLineageData({ + 'e1': { + fromEntity: { id: 'id-a', type: EntityType.TABLE, fullyQualifiedName: 'db.tableA' }, + toEntity: { id: 'id-b', type: EntityType.TABLE, fullyQualifiedName: 'db.tableB' }, + tempLineageTables: [{ fromEntity: 'tmp_staging', toEntity: 'db.tableB' }], + }, + }); + + const { nodes } = parseLineageData(data, 'db.tableA', 'db.tableA'); + const tempNodes = nodes.filter((n) => n.isTempTable); + + expect(tempNodes).toHaveLength(1); + expect(tempNodes[0].id).toBe('temp_tmp_staging'); + expect(tempNodes[0].fullyQualifiedName).toBe('tmp_staging'); + expect(tempNodes[0].entityType).toBe(EntityType.TABLE); + expect(tempNodes[0].isTempTable).toBe(true); + }); + + it('does not create a temp node when the FQN already exists as a real node', () => { + const data = makeLineageData({ + 'e1': { + fromEntity: { id: 'id-a', type: EntityType.TABLE, fullyQualifiedName: 'db.tableA' }, + toEntity: { id: 'id-b', type: EntityType.TABLE, fullyQualifiedName: 'db.tableB' }, + tempLineageTables: [{ fromEntity: 'db.tableA', toEntity: 'tmp_staging' }], + }, + }); + + const { nodes } = parseLineageData(data, 'db.tableA', 'db.tableA'); + const tempNodes = nodes.filter((n) => n.isTempTable); + + expect(tempNodes).toHaveLength(1); + expect(tempNodes[0].fullyQualifiedName).toBe('tmp_staging'); + }); + + it('deduplicates temp nodes when the same FQN appears in multiple hops', () => { + const data = makeLineageData({ + 'e1': { + fromEntity: { id: 'id-a', type: EntityType.TABLE, fullyQualifiedName: 'db.tableA' }, + toEntity: { id: 'id-b', type: EntityType.TABLE, fullyQualifiedName: 'db.tableB' }, + tempLineageTables: [ + { fromEntity: 'tmp_staging', toEntity: 'tmp_mid' }, + { fromEntity: 'tmp_staging', toEntity: 'db.tableB' }, + ], + }, + }); + + const { nodes } = parseLineageData(data, 'db.tableA', 'db.tableA'); + const stagingNodes = nodes.filter((n) => n.fullyQualifiedName === 'tmp_staging'); + + expect(stagingNodes).toHaveLength(1); + }); + + it('creates one hop edge per tempLineageTables entry', () => { + const data = makeLineageData({ + 'e1': { + fromEntity: { id: 'id-a', type: EntityType.TABLE, fullyQualifiedName: 'db.tableA' }, + toEntity: { id: 'id-b', type: EntityType.TABLE, fullyQualifiedName: 'db.tableB' }, + tempLineageTables: [ + { fromEntity: 'db.tableA', toEntity: 'tmp_staging' }, + { fromEntity: 'tmp_staging', toEntity: 'db.tableB' }, + ], + }, + }); + + const { edges } = parseLineageData(data, 'db.tableA', 'db.tableA'); + + expect(edges).toHaveLength(2); + expect(edges[0].fromEntity.fullyQualifiedName).toBe('db.tableA'); + expect(edges[0].toEntity.fullyQualifiedName).toBe('tmp_staging'); + expect(edges[1].fromEntity.fullyQualifiedName).toBe('tmp_staging'); + expect(edges[1].toEntity.fullyQualifiedName).toBe('db.tableB'); + }); + + it('removes the original edge when it is expanded into temp hop edges', () => { + const data = makeLineageData({ + 'e1': { + fromEntity: { id: 'id-a', type: EntityType.TABLE, fullyQualifiedName: 'db.tableA' }, + toEntity: { id: 'id-b', type: EntityType.TABLE, fullyQualifiedName: 'db.tableB' }, + tempLineageTables: [{ fromEntity: 'db.tableA', toEntity: 'db.tableB' }], + }, + }); + + const { edges } = parseLineageData(data, 'db.tableA', 'db.tableA'); + + expect(edges.some((e) => e.tempLineageTables?.length)).toBe(false); + }); + + it('preserves regular edges that have no tempLineageTables', () => { + const data = makeLineageData({ + 'e1': { + fromEntity: { id: 'id-a', type: EntityType.TABLE, fullyQualifiedName: 'db.tableA' }, + toEntity: { id: 'id-b', type: EntityType.TABLE, fullyQualifiedName: 'db.tableB' }, + }, + }); + + const { edges, nodes } = parseLineageData(data, 'db.tableA', 'db.tableA'); + + expect(edges).toHaveLength(1); + expect(edges[0].fromEntity.fullyQualifiedName).toBe('db.tableA'); + expect(nodes.filter((n) => n.isTempTable)).toHaveLength(0); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/EntityLineageUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/EntityLineageUtils.tsx index 6aaf627409b1..bec353d5d649 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/EntityLineageUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/EntityLineageUtils.tsx @@ -1523,7 +1523,7 @@ const processPipelineEdge = ( edge: EdgeDetails, pipelineNode: Pipeline ): EdgeDetails[] => { - const pipelineEntityType = get(pipelineNode, 'entityType') as unknown as string; + const pipelineEntityType = String(get(pipelineNode, 'entityType')); const pipelineRef = { id: pipelineNode.id, type: pipelineEntityType, @@ -1720,9 +1720,13 @@ export const parseLineageData = ( (node) => node.fullyQualifiedName === entityFqn ) as LineageNodeType; + const baseEdges = tempEdges.length + ? finalEdges.filter((e) => !e.tempLineageTables?.length) + : finalEdges; + return { nodes: [...finalNodes, ...tempNodes], - edges: [...finalEdges, ...tempEdges], + edges: [...baseEdges, ...tempEdges], entity, }; }; From cf6f8309043d97836489ce08a8d3a6ec7feb2980 Mon Sep 17 00:00:00 2001 From: Satender Date: Mon, 27 Apr 2026 11:10:42 +0530 Subject: [PATCH 3/7] fixed lint issues --- .../resources/ui/src/utils/CanvasUtils.ts | 17 ++-- .../ui/src/utils/EntityLineageUtils.test.tsx | 92 ++++++++++--------- .../ui/src/utils/EntityLineageUtils.tsx | 10 +- 3 files changed, 64 insertions(+), 55 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/CanvasUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/CanvasUtils.ts index e5b077774b46..aa9f26526374 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/CanvasUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/CanvasUtils.ts @@ -93,6 +93,14 @@ const getBaseNodeHeightFromType = ( return isRootNode ? baseHeight + 10 : baseHeight; }; +const getNodeYPadding = (node: Node): number => { + const { children } = getEntityChildrenAndLabel(node.data.node); + + const sourceYPadding = children.length > 0 ? 48 : 0; + + return sourceYPadding; +}; + export function getNodeHeight( node: Node, isColumnLineage: boolean, @@ -124,15 +132,6 @@ export function getNodeHeight( return height; } -const getNodeYPadding = (node: Node): number => { - const { children } = getEntityChildrenAndLabel(node.data.node); - - const sourceYPadding = children.length > 0 ? 48 : 0; - - // Add padding for the node's border - return sourceYPadding; -}; - interface ColumnLineageData { columnIds: string[]; columnIndex: number; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/EntityLineageUtils.test.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/EntityLineageUtils.test.tsx index 815a7541838a..f6a37a39f59e 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/EntityLineageUtils.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/EntityLineageUtils.test.tsx @@ -15,6 +15,7 @@ import { Edge, Node } from 'reactflow'; import { EdgeDetails, LineageData, + LineageNodeType, } from '../components/Lineage/Lineage.interface'; import { SourceType } from '../components/SearchedData/SearchedData.interface'; import { EntityType } from '../enums/entity.enum'; @@ -620,7 +621,7 @@ describe('Test EntityLineageUtils utility', () => { describe('getEntityChildrenAndLabel', () => { it('should return empty values for null input', () => { - const result = getEntityChildrenAndLabel(null as any); + const result = getEntityChildrenAndLabel(null as unknown as LineageNodeType); expect(result).toEqual({ children: [], @@ -633,7 +634,7 @@ describe('Test EntityLineageUtils utility', () => { const node = { entityType: 'UNKNOWN', }; - const result = getEntityChildrenAndLabel(node as any); + const result = getEntityChildrenAndLabel(node as unknown as LineageNodeType); expect(result).toEqual({ children: [], @@ -652,7 +653,7 @@ describe('Test EntityLineageUtils utility', () => { columns, flattenColumns: columns, }; - const result = getEntityChildrenAndLabel(node as any); + const result = getEntityChildrenAndLabel(node as unknown as LineageNodeType); expect(result).toEqual({ children: columns, @@ -666,7 +667,7 @@ describe('Test EntityLineageUtils utility', () => { entityType: EntityType.TABLE, columns: [], }; - const result = getEntityChildrenAndLabel(node as any); + const result = getEntityChildrenAndLabel(node as unknown as LineageNodeType); expect(result).toEqual({ children: [], @@ -683,7 +684,7 @@ describe('Test EntityLineageUtils utility', () => { columns, flattenChildren, }; - const result = getEntityChildrenAndLabel(node as any); + const result = getEntityChildrenAndLabel(node as unknown as LineageNodeType); expect(result).toEqual({ children: flattenChildren, @@ -701,7 +702,7 @@ describe('Test EntityLineageUtils utility', () => { entityType: EntityType.DASHBOARD, charts, }; - const result = getEntityChildrenAndLabel(node as any); + const result = getEntityChildrenAndLabel(node as unknown as LineageNodeType); expect(result).toEqual({ children: charts, @@ -715,7 +716,7 @@ describe('Test EntityLineageUtils utility', () => { entityType: EntityType.DASHBOARD, charts: [], }; - const result = getEntityChildrenAndLabel(node as any); + const result = getEntityChildrenAndLabel(node as unknown as LineageNodeType); expect(result).toEqual({ children: [], @@ -733,7 +734,7 @@ describe('Test EntityLineageUtils utility', () => { entityType: EntityType.MLMODEL, mlFeatures, }; - const result = getEntityChildrenAndLabel(node as any); + const result = getEntityChildrenAndLabel(node as unknown as LineageNodeType); expect(result).toEqual({ children: mlFeatures, @@ -747,7 +748,7 @@ describe('Test EntityLineageUtils utility', () => { entityType: EntityType.MLMODEL, mlFeatures: [], }; - const result = getEntityChildrenAndLabel(node as any); + const result = getEntityChildrenAndLabel(node as unknown as LineageNodeType); expect(result).toEqual({ children: [], @@ -766,7 +767,7 @@ describe('Test EntityLineageUtils utility', () => { columns, flattenColumns: columns, }; - const result = getEntityChildrenAndLabel(node as any); + const result = getEntityChildrenAndLabel(node as unknown as LineageNodeType); expect(result).toEqual({ children: columns, @@ -786,7 +787,7 @@ describe('Test EntityLineageUtils utility', () => { columns, }, }; - const result = getEntityChildrenAndLabel(node as any); + const result = getEntityChildrenAndLabel(node as unknown as LineageNodeType); expect(result).toEqual({ children: columns, @@ -799,7 +800,7 @@ describe('Test EntityLineageUtils utility', () => { const node = { entityType: EntityType.CONTAINER, }; - const result = getEntityChildrenAndLabel(node as any); + const result = getEntityChildrenAndLabel(node as unknown as LineageNodeType); expect(result).toEqual({ children: [], @@ -819,7 +820,7 @@ describe('Test EntityLineageUtils utility', () => { schemaFields, }, }; - const result = getEntityChildrenAndLabel(node as any); + const result = getEntityChildrenAndLabel(node as unknown as LineageNodeType); expect(result).toEqual({ children: schemaFields, @@ -832,7 +833,7 @@ describe('Test EntityLineageUtils utility', () => { const node = { entityType: EntityType.TOPIC, }; - const result = getEntityChildrenAndLabel(node as any); + const result = getEntityChildrenAndLabel(node as unknown as LineageNodeType); expect(result).toEqual({ children: [], @@ -852,7 +853,7 @@ describe('Test EntityLineageUtils utility', () => { schemaFields, }, }; - const result = getEntityChildrenAndLabel(node as any); + const result = getEntityChildrenAndLabel(node as unknown as LineageNodeType); expect(result).toEqual({ children: schemaFields, @@ -872,7 +873,7 @@ describe('Test EntityLineageUtils utility', () => { schemaFields, }, }; - const result = getEntityChildrenAndLabel(node as any); + const result = getEntityChildrenAndLabel(node as unknown as LineageNodeType); expect(result).toEqual({ children: schemaFields, @@ -893,7 +894,7 @@ describe('Test EntityLineageUtils utility', () => { schemaFields: requestFields, }, }; - const result = getEntityChildrenAndLabel(node as any); + const result = getEntityChildrenAndLabel(node as unknown as LineageNodeType); expect(result).toEqual({ children: responseFields, @@ -906,7 +907,7 @@ describe('Test EntityLineageUtils utility', () => { const node = { entityType: EntityType.API_ENDPOINT, }; - const result = getEntityChildrenAndLabel(node as any); + const result = getEntityChildrenAndLabel(node as unknown as LineageNodeType); expect(result).toEqual({ children: [], @@ -924,7 +925,7 @@ describe('Test EntityLineageUtils utility', () => { entityType: EntityType.SEARCH_INDEX, fields, }; - const result = getEntityChildrenAndLabel(node as any); + const result = getEntityChildrenAndLabel(node as unknown as LineageNodeType); expect(result).toEqual({ children: fields, @@ -938,7 +939,7 @@ describe('Test EntityLineageUtils utility', () => { entityType: EntityType.SEARCH_INDEX, fields: [], }; - const result = getEntityChildrenAndLabel(node as any); + const result = getEntityChildrenAndLabel(node as unknown as LineageNodeType); expect(result).toEqual({ children: [], @@ -1370,17 +1371,19 @@ describe('parseLineageData', () => { jest.clearAllMocks(); // Setup default mock implementations - mockUniqWith.mockImplementation((array: any) => array || []); - mockIsEqual.mockImplementation((a: any, b: any) => a === b); - mockGet.mockImplementation((obj: any, path: any, defaultValue?: any) => { + mockUniqWith.mockImplementation((array) => array ?? []); + mockIsEqual.mockImplementation((a, b) => a === b); + mockGet.mockImplementation((obj, path, defaultValue?) => { if (!obj || !path) { return defaultValue; } - const pathStr = Array.isArray(path) ? path.join('.') : String(path); + const pathStr = Array.isArray(path) + ? (path as string[]).join('.') + : String(path); const keys = pathStr.split('.'); - let result = obj; + let result: unknown = obj; for (const key of keys) { - result = result?.[key]; + result = (result as Record)?.[key]; if (result === undefined) { return defaultValue; } @@ -1596,13 +1599,15 @@ describe('parseLineageData', () => { describe('Pagination handling', () => { it('should create load more nodes for entities with pagination', () => { // Mock get function to return pipeline type for filtering - mockGet.mockImplementation((obj: any, path: any) => { + mockGet.mockImplementation((obj, path) => { if (path === 'entityType') { - return obj?.entityType || EntityType.TABLE; + return (obj as Record)?.entityType || EntityType.TABLE; } - const pathStr = Array.isArray(path) ? path.join('.') : String(path); + const pathStr = Array.isArray(path) + ? (path as string[]).join('.') + : String(path); - return obj?.[pathStr]; + return (obj as Record)?.[pathStr]; }); const result = parseLineageData( @@ -1636,13 +1641,15 @@ describe('parseLineageData', () => { }, }; - mockGet.mockImplementation((obj: any, path: any) => { + mockGet.mockImplementation((obj, path) => { if (path === 'entityType') { - return obj?.entityType; + return (obj as Record)?.entityType; } - const pathStr = Array.isArray(path) ? path.join('.') : String(path); + const pathStr = Array.isArray(path) + ? (path as string[]).join('.') + : String(path); - return obj?.[pathStr]; + return (obj as Record)?.[pathStr]; }); const dataWithPipeline = { @@ -1742,11 +1749,11 @@ describe('parseLineageData', () => { }; // Mock uniqWith to actually remove duplicates - mockUniqWith.mockImplementation((array: any, compareFn?: any) => { + mockUniqWith.mockImplementation((array, compareFn?) => { if (!array) { return []; } - const unique: any[] = []; + const unique: unknown[] = []; for (const item of array) { if ( !unique.some((existing) => @@ -1757,12 +1764,15 @@ describe('parseLineageData', () => { } } - return unique; + return unique as ReturnType; }); - mockIsEqual.mockImplementation( - (a: any, b: any) => a.fullyQualifiedName === b.fullyQualifiedName - ); + mockIsEqual.mockImplementation((a, b) => { + const aObj = a as { fullyQualifiedName?: string }; + const bObj = b as { fullyQualifiedName?: string }; + + return aObj.fullyQualifiedName === bObj.fullyQualifiedName; + }); parseLineageData(dataWithDuplicates, mockEntityFqn, mockRootFqn); @@ -1780,7 +1790,7 @@ describe('getLineageTableConfig', () => { }); it('should return empty arrays for null CSV data', () => { - const result = getLineageTableConfig(null as any); + const result = getLineageTableConfig(null as unknown as string[][]); expect(result.columns).toEqual([]); expect(result.dataSource).toEqual([]); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/EntityLineageUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/EntityLineageUtils.tsx index bec353d5d649..23afba519a36 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/EntityLineageUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/EntityLineageUtils.tsx @@ -579,7 +579,7 @@ export const removeLineageHandler = async (data: EdgeData): Promise => { * @return {{ nodes: Node[]; edges: Edge[], nodeIds: string[], edgeIds: string[] }} - * An object containing the downstream nodes and edges. */ -export const getEntityChildrenAndLabel = (node: LineageNodeType) => { +export function getEntityChildrenAndLabel(node: LineageNodeType) { if (!node) { return { children: [], @@ -653,7 +653,7 @@ export const getEntityChildrenAndLabel = (node: LineageNodeType) => { childrenHeading: label, childrenCount, }; -}; +} // Nodes Icons export const getEntityNodeIcon = (label: string) => { @@ -1192,11 +1192,11 @@ export const createNewEdge = (edge: Edge) => { return selectedEdge; }; -export const getUpstreamDownstreamNodesEdges = ( +export function getUpstreamDownstreamNodesEdges( edges: EdgeDetails[], nodes: EntityReference[], currentNode: string -) => { +) { const downstreamEdges: EdgeDetails[] = []; const upstreamEdges: EdgeDetails[] = []; const downstreamNodes: EntityReference[] = []; @@ -1249,7 +1249,7 @@ export const getUpstreamDownstreamNodesEdges = ( findUpstream(activeNode); return { downstreamEdges, upstreamEdges, downstreamNodes, upstreamNodes }; -}; +} export const getExportEntity = (entity: LineageSourceType) => { const { From 11e7ce53018a2d305767572c435c4d8154237eb9 Mon Sep 17 00:00:00 2001 From: Satender Date: Mon, 27 Apr 2026 11:50:33 +0530 Subject: [PATCH 4/7] worked up the comments from Chirag --- .../resources/ui/src/utils/CanvasUtils.ts | 10 +- .../ui/src/utils/EntityLineageUtils.tsx | 342 +++++++++--------- 2 files changed, 179 insertions(+), 173 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/CanvasUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/CanvasUtils.ts index aa9f26526374..db54b4fb9653 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/CanvasUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/CanvasUtils.ts @@ -106,6 +106,10 @@ export function getNodeHeight( isColumnLineage: boolean, columnCount?: number ) { + if (node.height) { + return node.height; + } + const isRootNode = node.data?.isRootNode ?? false; const visibleColumnCount = isColumnLineage @@ -253,10 +257,8 @@ function getEntityLineageCoordinates( targetNode: Node, isColumnLineage: boolean ): EdgeCoordinates { - const sourceHeight = - sourceNode.height ?? getNodeHeight(sourceNode, isColumnLineage, 0); - const targetHeight = - targetNode.height ?? getNodeHeight(targetNode, isColumnLineage, 0); + const sourceHeight = getNodeHeight(sourceNode, isColumnLineage, 0); + const targetHeight = getNodeHeight(targetNode, isColumnLineage, 0); return { sourceX: sourceNode.position.x + (sourceNode.width ?? 0), diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/EntityLineageUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/EntityLineageUtils.tsx index 23afba519a36..95e272931862 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/EntityLineageUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/EntityLineageUtils.tsx @@ -206,6 +206,91 @@ export const getLayoutedElements = ( return { node: uNode, edge: edgesRequired }; }; +/** + * This function returns all the columns as children as well flattened children for subfield columns. + * It also returns the label for the children and the total height of the children. + * + * @param {Node} selectedNode - The node for which to retrieve the downstream nodes and edges. + * @param {string[]} columnsHavingLineage - All nodes in the lineage. + * @return {{ nodes: Node[]; edges: Edge[], nodeIds: string[], edgeIds: string[] }} - + * An object containing the downstream nodes and edges. + */ +export function getEntityChildrenAndLabel(node: LineageNodeType) { + if (!node) { + return { + children: [], + childrenHeading: '', + childrenCount: 0, + }; + } + const entityMappings: Record< + string, + { data: EntityChildren; label: string; childrenCount: number } + > = { + [EntityType.TABLE]: { + data: node.flattenChildren ?? node.columns ?? [], + label: t('label.column-plural'), + childrenCount: node.columns?.length ?? 0, + }, + [EntityType.DASHBOARD]: { + data: node.charts ?? [], + label: t('label.chart-plural'), + childrenCount: node.charts?.length ?? 0, + }, + [EntityType.MLMODEL]: { + data: node.mlFeatures ?? [], + label: t('label.feature-plural'), + childrenCount: node.mlFeatures?.length ?? 0, + }, + [EntityType.DASHBOARD_DATA_MODEL]: { + data: node.flattenChildren ?? node.columns ?? [], + label: t('label.column-plural'), + childrenCount: node.columns?.length ?? 0, + }, + [EntityType.CONTAINER]: { + data: node.flattenChildren ?? node.dataModel?.columns ?? [], + label: t('label.column-plural'), + childrenCount: node.dataModel?.columns?.length ?? 0, + }, + [EntityType.TOPIC]: { + data: node.flattenChildren ?? node.messageSchema?.schemaFields ?? [], + label: t('label.field-plural'), + childrenCount: node.messageSchema?.schemaFields?.length ?? 0, + }, + [EntityType.API_ENDPOINT]: { + data: + node.flattenChildren ?? + node?.responseSchema?.schemaFields ?? + node?.requestSchema?.schemaFields ?? + [], + label: t('label.field-plural'), + childrenCount: + node?.responseSchema?.schemaFields?.length ?? + node?.requestSchema?.schemaFields?.length ?? + 0, + }, + [EntityType.SEARCH_INDEX]: { + data: node.flattenChildren ?? node.fields ?? [], + label: t('label.field-plural'), + childrenCount: node.fields?.length ?? 0, + }, + }; + + const { data, label, childrenCount } = entityMappings[ + node.entityType as EntityType + ] || { + data: [], + label: '', + childrenCount: 0, + }; + + return { + children: data, + childrenHeading: label, + childrenCount, + }; +} + export const getELKLayoutedElements = async ( nodes: Node[], edges: Edge[], @@ -570,91 +655,6 @@ export const removeLineageHandler = async (data: EdgeData): Promise => { } }; -/** - * This function returns all the columns as children as well flattened children for subfield columns. - * It also returns the label for the children and the total height of the children. - * - * @param {Node} selectedNode - The node for which to retrieve the downstream nodes and edges. - * @param {string[]} columnsHavingLineage - All nodes in the lineage. - * @return {{ nodes: Node[]; edges: Edge[], nodeIds: string[], edgeIds: string[] }} - - * An object containing the downstream nodes and edges. - */ -export function getEntityChildrenAndLabel(node: LineageNodeType) { - if (!node) { - return { - children: [], - childrenHeading: '', - childrenCount: 0, - }; - } - const entityMappings: Record< - string, - { data: EntityChildren; label: string; childrenCount: number } - > = { - [EntityType.TABLE]: { - data: node.flattenChildren ?? node.columns ?? [], - label: t('label.column-plural'), - childrenCount: node.columns?.length ?? 0, - }, - [EntityType.DASHBOARD]: { - data: node.charts ?? [], - label: t('label.chart-plural'), - childrenCount: node.charts?.length ?? 0, - }, - [EntityType.MLMODEL]: { - data: node.mlFeatures ?? [], - label: t('label.feature-plural'), - childrenCount: node.mlFeatures?.length ?? 0, - }, - [EntityType.DASHBOARD_DATA_MODEL]: { - data: node.flattenChildren ?? node.columns ?? [], - label: t('label.column-plural'), - childrenCount: node.columns?.length ?? 0, - }, - [EntityType.CONTAINER]: { - data: node.flattenChildren ?? node.dataModel?.columns ?? [], - label: t('label.column-plural'), - childrenCount: node.dataModel?.columns?.length ?? 0, - }, - [EntityType.TOPIC]: { - data: node.flattenChildren ?? node.messageSchema?.schemaFields ?? [], - label: t('label.field-plural'), - childrenCount: node.messageSchema?.schemaFields?.length ?? 0, - }, - [EntityType.API_ENDPOINT]: { - data: - node.flattenChildren ?? - node?.responseSchema?.schemaFields ?? - node?.requestSchema?.schemaFields ?? - [], - label: t('label.field-plural'), - childrenCount: - node?.responseSchema?.schemaFields?.length ?? - node?.requestSchema?.schemaFields?.length ?? - 0, - }, - [EntityType.SEARCH_INDEX]: { - data: node.flattenChildren ?? node.fields ?? [], - label: t('label.field-plural'), - childrenCount: node.fields?.length ?? 0, - }, - }; - - const { data, label, childrenCount } = entityMappings[ - node.entityType as EntityType - ] || { - data: [], - label: '', - childrenCount: 0, - }; - - return { - children: data, - childrenHeading: label, - childrenCount, - }; -} - // Nodes Icons export const getEntityNodeIcon = (label: string) => { switch (label) { @@ -695,6 +695,65 @@ export const positionNodesUsingElk = async ( return obj; }; +export function getUpstreamDownstreamNodesEdges( + edges: EdgeDetails[], + nodes: EntityReference[], + currentNode: string +) { + const downstreamEdges: EdgeDetails[] = []; + const upstreamEdges: EdgeDetails[] = []; + const downstreamNodes: EntityReference[] = []; + const upstreamNodes: EntityReference[] = []; + const activeNode = nodes.find( + (node) => node.fullyQualifiedName === currentNode + ); + + if (!activeNode) { + return { downstreamEdges, upstreamEdges, downstreamNodes, upstreamNodes }; + } + + function findDownstream(node: EntityReference) { + const directDownstream = edges.filter( + (edge) => edge.fromEntity.fullyQualifiedName === node.fullyQualifiedName + ); + downstreamEdges.push(...directDownstream); + directDownstream.forEach((edge) => { + const toNode = nodes.find( + (item) => item.fullyQualifiedName === edge.toEntity.fullyQualifiedName + ); + if (!isUndefined(toNode)) { + if (!downstreamNodes.includes(toNode)) { + downstreamNodes.push(toNode); + findDownstream(toNode); + } + } + }); + } + + function findUpstream(node: EntityReference) { + const directUpstream = edges.filter( + (edge) => edge.toEntity.fullyQualifiedName === node.fullyQualifiedName + ); + upstreamEdges.push(...directUpstream); + directUpstream.forEach((edge) => { + const fromNode = nodes.find( + (item) => item.fullyQualifiedName === edge.fromEntity.fullyQualifiedName + ); + if (!isUndefined(fromNode)) { + if (!upstreamNodes.includes(fromNode)) { + upstreamNodes.push(fromNode); + findUpstream(fromNode); + } + } + }); + } + + findDownstream(activeNode); + findUpstream(activeNode); + + return { downstreamEdges, upstreamEdges, downstreamNodes, upstreamNodes }; +} + export const createNodes = ( nodesData: LineageNodeType[], edgesData: EdgeDetails[], @@ -1192,65 +1251,6 @@ export const createNewEdge = (edge: Edge) => { return selectedEdge; }; -export function getUpstreamDownstreamNodesEdges( - edges: EdgeDetails[], - nodes: EntityReference[], - currentNode: string -) { - const downstreamEdges: EdgeDetails[] = []; - const upstreamEdges: EdgeDetails[] = []; - const downstreamNodes: EntityReference[] = []; - const upstreamNodes: EntityReference[] = []; - const activeNode = nodes.find( - (node) => node.fullyQualifiedName === currentNode - ); - - if (!activeNode) { - return { downstreamEdges, upstreamEdges, downstreamNodes, upstreamNodes }; - } - - function findDownstream(node: EntityReference) { - const directDownstream = edges.filter( - (edge) => edge.fromEntity.fullyQualifiedName === node.fullyQualifiedName - ); - downstreamEdges.push(...directDownstream); - directDownstream.forEach((edge) => { - const toNode = nodes.find( - (item) => item.fullyQualifiedName === edge.toEntity.fullyQualifiedName - ); - if (!isUndefined(toNode)) { - if (!downstreamNodes.includes(toNode)) { - downstreamNodes.push(toNode); - findDownstream(toNode); - } - } - }); - } - - function findUpstream(node: EntityReference) { - const directUpstream = edges.filter( - (edge) => edge.toEntity.fullyQualifiedName === node.fullyQualifiedName - ); - upstreamEdges.push(...directUpstream); - directUpstream.forEach((edge) => { - const fromNode = nodes.find( - (item) => item.fullyQualifiedName === edge.fromEntity.fullyQualifiedName - ); - if (!isUndefined(fromNode)) { - if (!upstreamNodes.includes(fromNode)) { - upstreamNodes.push(fromNode); - findUpstream(fromNode); - } - } - }); - } - - findDownstream(activeNode); - findUpstream(activeNode); - - return { downstreamEdges, upstreamEdges, downstreamNodes, upstreamNodes }; -} - export const getExportEntity = (entity: LineageSourceType) => { const { name, @@ -1604,6 +1604,33 @@ const processPagination = ( return { newNodes, newEdges }; }; +const getOrCreateTempNode = ( + nameOrFqn: string, + existingByFqn: Map, + newTempNodes: Map +): LineageNodeType => { + const existing = existingByFqn.get(nameOrFqn); + if (existing) { + return existing; + } + if (newTempNodes.has(nameOrFqn)) { + return newTempNodes.get(nameOrFqn) as LineageNodeType; + } + const tempNode: LineageNodeType = { + id: `temp_${nameOrFqn}`, + name: nameOrFqn, + displayName: nameOrFqn, + fullyQualifiedName: nameOrFqn, + type: EntityType.TABLE, + entityType: EntityType.TABLE, + isTempTable: true, + columns: [], + }; + newTempNodes.set(nameOrFqn, tempNode); + + return tempNode; +}; + const extractTempLineageNodes = ( edges: EdgeDetails[], existingNodes: LineageNodeType[] @@ -1617,36 +1644,13 @@ const extractTempLineageNodes = ( .map((n) => [n.fullyQualifiedName!, n]) ); - const getOrCreateNode = (nameOrFqn: string): LineageNodeType => { - const existing = existingByFqn.get(nameOrFqn); - if (existing) { - return existing; - } - if (newTempNodes.has(nameOrFqn)) { - return newTempNodes.get(nameOrFqn) as LineageNodeType; - } - const tempNode: LineageNodeType = { - id: `temp_${nameOrFqn}`, - name: nameOrFqn, - displayName: nameOrFqn, - fullyQualifiedName: nameOrFqn, - type: EntityType.TABLE, - entityType: EntityType.TABLE, - isTempTable: true, - columns: [], - }; - newTempNodes.set(nameOrFqn, tempNode); - - return tempNode; - }; - edges.forEach((edge) => { if (!edge.tempLineageTables?.length) { return; } edge.tempLineageTables.forEach((hop: TempLineageTable) => { - const fromNode = getOrCreateNode(hop.fromEntity); - const toNode = getOrCreateNode(hop.toEntity); + const fromNode = getOrCreateTempNode(hop.fromEntity, existingByFqn, newTempNodes); + const toNode = getOrCreateTempNode(hop.toEntity, existingByFqn, newTempNodes); newEdges.push({ fromEntity: { id: fromNode.id, From 47f09abf832f6b33ccb58c6c89dc23a1b1343864 Mon Sep 17 00:00:00 2001 From: Satender Date: Mon, 27 Apr 2026 12:13:05 +0530 Subject: [PATCH 5/7] worked up linting issues --- .../ui/src/utils/EntityLineageUtils.test.tsx | 180 ++++++++++++++---- .../ui/src/utils/EntityLineageUtils.tsx | 12 +- 2 files changed, 148 insertions(+), 44 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/EntityLineageUtils.test.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/EntityLineageUtils.test.tsx index f6a37a39f59e..b73c3450bee4 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/EntityLineageUtils.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/EntityLineageUtils.test.tsx @@ -621,7 +621,9 @@ describe('Test EntityLineageUtils utility', () => { describe('getEntityChildrenAndLabel', () => { it('should return empty values for null input', () => { - const result = getEntityChildrenAndLabel(null as unknown as LineageNodeType); + const result = getEntityChildrenAndLabel( + null as unknown as LineageNodeType + ); expect(result).toEqual({ children: [], @@ -634,7 +636,9 @@ describe('Test EntityLineageUtils utility', () => { const node = { entityType: 'UNKNOWN', }; - const result = getEntityChildrenAndLabel(node as unknown as LineageNodeType); + const result = getEntityChildrenAndLabel( + node as unknown as LineageNodeType + ); expect(result).toEqual({ children: [], @@ -653,7 +657,9 @@ describe('Test EntityLineageUtils utility', () => { columns, flattenColumns: columns, }; - const result = getEntityChildrenAndLabel(node as unknown as LineageNodeType); + const result = getEntityChildrenAndLabel( + node as unknown as LineageNodeType + ); expect(result).toEqual({ children: columns, @@ -667,7 +673,9 @@ describe('Test EntityLineageUtils utility', () => { entityType: EntityType.TABLE, columns: [], }; - const result = getEntityChildrenAndLabel(node as unknown as LineageNodeType); + const result = getEntityChildrenAndLabel( + node as unknown as LineageNodeType + ); expect(result).toEqual({ children: [], @@ -684,7 +692,9 @@ describe('Test EntityLineageUtils utility', () => { columns, flattenChildren, }; - const result = getEntityChildrenAndLabel(node as unknown as LineageNodeType); + const result = getEntityChildrenAndLabel( + node as unknown as LineageNodeType + ); expect(result).toEqual({ children: flattenChildren, @@ -702,7 +712,9 @@ describe('Test EntityLineageUtils utility', () => { entityType: EntityType.DASHBOARD, charts, }; - const result = getEntityChildrenAndLabel(node as unknown as LineageNodeType); + const result = getEntityChildrenAndLabel( + node as unknown as LineageNodeType + ); expect(result).toEqual({ children: charts, @@ -716,7 +728,9 @@ describe('Test EntityLineageUtils utility', () => { entityType: EntityType.DASHBOARD, charts: [], }; - const result = getEntityChildrenAndLabel(node as unknown as LineageNodeType); + const result = getEntityChildrenAndLabel( + node as unknown as LineageNodeType + ); expect(result).toEqual({ children: [], @@ -734,7 +748,9 @@ describe('Test EntityLineageUtils utility', () => { entityType: EntityType.MLMODEL, mlFeatures, }; - const result = getEntityChildrenAndLabel(node as unknown as LineageNodeType); + const result = getEntityChildrenAndLabel( + node as unknown as LineageNodeType + ); expect(result).toEqual({ children: mlFeatures, @@ -748,7 +764,9 @@ describe('Test EntityLineageUtils utility', () => { entityType: EntityType.MLMODEL, mlFeatures: [], }; - const result = getEntityChildrenAndLabel(node as unknown as LineageNodeType); + const result = getEntityChildrenAndLabel( + node as unknown as LineageNodeType + ); expect(result).toEqual({ children: [], @@ -767,7 +785,9 @@ describe('Test EntityLineageUtils utility', () => { columns, flattenColumns: columns, }; - const result = getEntityChildrenAndLabel(node as unknown as LineageNodeType); + const result = getEntityChildrenAndLabel( + node as unknown as LineageNodeType + ); expect(result).toEqual({ children: columns, @@ -787,7 +807,9 @@ describe('Test EntityLineageUtils utility', () => { columns, }, }; - const result = getEntityChildrenAndLabel(node as unknown as LineageNodeType); + const result = getEntityChildrenAndLabel( + node as unknown as LineageNodeType + ); expect(result).toEqual({ children: columns, @@ -800,7 +822,9 @@ describe('Test EntityLineageUtils utility', () => { const node = { entityType: EntityType.CONTAINER, }; - const result = getEntityChildrenAndLabel(node as unknown as LineageNodeType); + const result = getEntityChildrenAndLabel( + node as unknown as LineageNodeType + ); expect(result).toEqual({ children: [], @@ -820,7 +844,9 @@ describe('Test EntityLineageUtils utility', () => { schemaFields, }, }; - const result = getEntityChildrenAndLabel(node as unknown as LineageNodeType); + const result = getEntityChildrenAndLabel( + node as unknown as LineageNodeType + ); expect(result).toEqual({ children: schemaFields, @@ -833,7 +859,9 @@ describe('Test EntityLineageUtils utility', () => { const node = { entityType: EntityType.TOPIC, }; - const result = getEntityChildrenAndLabel(node as unknown as LineageNodeType); + const result = getEntityChildrenAndLabel( + node as unknown as LineageNodeType + ); expect(result).toEqual({ children: [], @@ -853,7 +881,9 @@ describe('Test EntityLineageUtils utility', () => { schemaFields, }, }; - const result = getEntityChildrenAndLabel(node as unknown as LineageNodeType); + const result = getEntityChildrenAndLabel( + node as unknown as LineageNodeType + ); expect(result).toEqual({ children: schemaFields, @@ -873,7 +903,9 @@ describe('Test EntityLineageUtils utility', () => { schemaFields, }, }; - const result = getEntityChildrenAndLabel(node as unknown as LineageNodeType); + const result = getEntityChildrenAndLabel( + node as unknown as LineageNodeType + ); expect(result).toEqual({ children: schemaFields, @@ -894,7 +926,9 @@ describe('Test EntityLineageUtils utility', () => { schemaFields: requestFields, }, }; - const result = getEntityChildrenAndLabel(node as unknown as LineageNodeType); + const result = getEntityChildrenAndLabel( + node as unknown as LineageNodeType + ); expect(result).toEqual({ children: responseFields, @@ -907,7 +941,9 @@ describe('Test EntityLineageUtils utility', () => { const node = { entityType: EntityType.API_ENDPOINT, }; - const result = getEntityChildrenAndLabel(node as unknown as LineageNodeType); + const result = getEntityChildrenAndLabel( + node as unknown as LineageNodeType + ); expect(result).toEqual({ children: [], @@ -925,7 +961,9 @@ describe('Test EntityLineageUtils utility', () => { entityType: EntityType.SEARCH_INDEX, fields, }; - const result = getEntityChildrenAndLabel(node as unknown as LineageNodeType); + const result = getEntityChildrenAndLabel( + node as unknown as LineageNodeType + ); expect(result).toEqual({ children: fields, @@ -939,7 +977,9 @@ describe('Test EntityLineageUtils utility', () => { entityType: EntityType.SEARCH_INDEX, fields: [], }; - const result = getEntityChildrenAndLabel(node as unknown as LineageNodeType); + const result = getEntityChildrenAndLabel( + node as unknown as LineageNodeType + ); expect(result).toEqual({ children: [], @@ -1601,7 +1641,9 @@ describe('parseLineageData', () => { // Mock get function to return pipeline type for filtering mockGet.mockImplementation((obj, path) => { if (path === 'entityType') { - return (obj as Record)?.entityType || EntityType.TABLE; + return ( + (obj as Record)?.entityType || EntityType.TABLE + ); } const pathStr = Array.isArray(path) ? (path as string[]).join('.') @@ -1959,10 +2001,20 @@ describe('extractTempLineageNodes (via parseLineageData)', () => { it('creates a temp node with correct shape for an unknown FQN', () => { const data = makeLineageData({ - 'e1': { - fromEntity: { id: 'id-a', type: EntityType.TABLE, fullyQualifiedName: 'db.tableA' }, - toEntity: { id: 'id-b', type: EntityType.TABLE, fullyQualifiedName: 'db.tableB' }, - tempLineageTables: [{ fromEntity: 'tmp_staging', toEntity: 'db.tableB' }], + e1: { + fromEntity: { + id: 'id-a', + type: EntityType.TABLE, + fullyQualifiedName: 'db.tableA', + }, + toEntity: { + id: 'id-b', + type: EntityType.TABLE, + fullyQualifiedName: 'db.tableB', + }, + tempLineageTables: [ + { fromEntity: 'tmp_staging', toEntity: 'db.tableB' }, + ], }, }); @@ -1978,10 +2030,20 @@ describe('extractTempLineageNodes (via parseLineageData)', () => { it('does not create a temp node when the FQN already exists as a real node', () => { const data = makeLineageData({ - 'e1': { - fromEntity: { id: 'id-a', type: EntityType.TABLE, fullyQualifiedName: 'db.tableA' }, - toEntity: { id: 'id-b', type: EntityType.TABLE, fullyQualifiedName: 'db.tableB' }, - tempLineageTables: [{ fromEntity: 'db.tableA', toEntity: 'tmp_staging' }], + e1: { + fromEntity: { + id: 'id-a', + type: EntityType.TABLE, + fullyQualifiedName: 'db.tableA', + }, + toEntity: { + id: 'id-b', + type: EntityType.TABLE, + fullyQualifiedName: 'db.tableB', + }, + tempLineageTables: [ + { fromEntity: 'db.tableA', toEntity: 'tmp_staging' }, + ], }, }); @@ -1994,9 +2056,17 @@ describe('extractTempLineageNodes (via parseLineageData)', () => { it('deduplicates temp nodes when the same FQN appears in multiple hops', () => { const data = makeLineageData({ - 'e1': { - fromEntity: { id: 'id-a', type: EntityType.TABLE, fullyQualifiedName: 'db.tableA' }, - toEntity: { id: 'id-b', type: EntityType.TABLE, fullyQualifiedName: 'db.tableB' }, + e1: { + fromEntity: { + id: 'id-a', + type: EntityType.TABLE, + fullyQualifiedName: 'db.tableA', + }, + toEntity: { + id: 'id-b', + type: EntityType.TABLE, + fullyQualifiedName: 'db.tableB', + }, tempLineageTables: [ { fromEntity: 'tmp_staging', toEntity: 'tmp_mid' }, { fromEntity: 'tmp_staging', toEntity: 'db.tableB' }, @@ -2005,16 +2075,26 @@ describe('extractTempLineageNodes (via parseLineageData)', () => { }); const { nodes } = parseLineageData(data, 'db.tableA', 'db.tableA'); - const stagingNodes = nodes.filter((n) => n.fullyQualifiedName === 'tmp_staging'); + const stagingNodes = nodes.filter( + (n) => n.fullyQualifiedName === 'tmp_staging' + ); expect(stagingNodes).toHaveLength(1); }); it('creates one hop edge per tempLineageTables entry', () => { const data = makeLineageData({ - 'e1': { - fromEntity: { id: 'id-a', type: EntityType.TABLE, fullyQualifiedName: 'db.tableA' }, - toEntity: { id: 'id-b', type: EntityType.TABLE, fullyQualifiedName: 'db.tableB' }, + e1: { + fromEntity: { + id: 'id-a', + type: EntityType.TABLE, + fullyQualifiedName: 'db.tableA', + }, + toEntity: { + id: 'id-b', + type: EntityType.TABLE, + fullyQualifiedName: 'db.tableB', + }, tempLineageTables: [ { fromEntity: 'db.tableA', toEntity: 'tmp_staging' }, { fromEntity: 'tmp_staging', toEntity: 'db.tableB' }, @@ -2033,9 +2113,17 @@ describe('extractTempLineageNodes (via parseLineageData)', () => { it('removes the original edge when it is expanded into temp hop edges', () => { const data = makeLineageData({ - 'e1': { - fromEntity: { id: 'id-a', type: EntityType.TABLE, fullyQualifiedName: 'db.tableA' }, - toEntity: { id: 'id-b', type: EntityType.TABLE, fullyQualifiedName: 'db.tableB' }, + e1: { + fromEntity: { + id: 'id-a', + type: EntityType.TABLE, + fullyQualifiedName: 'db.tableA', + }, + toEntity: { + id: 'id-b', + type: EntityType.TABLE, + fullyQualifiedName: 'db.tableB', + }, tempLineageTables: [{ fromEntity: 'db.tableA', toEntity: 'db.tableB' }], }, }); @@ -2047,9 +2135,17 @@ describe('extractTempLineageNodes (via parseLineageData)', () => { it('preserves regular edges that have no tempLineageTables', () => { const data = makeLineageData({ - 'e1': { - fromEntity: { id: 'id-a', type: EntityType.TABLE, fullyQualifiedName: 'db.tableA' }, - toEntity: { id: 'id-b', type: EntityType.TABLE, fullyQualifiedName: 'db.tableB' }, + e1: { + fromEntity: { + id: 'id-a', + type: EntityType.TABLE, + fullyQualifiedName: 'db.tableA', + }, + toEntity: { + id: 'id-b', + type: EntityType.TABLE, + fullyQualifiedName: 'db.tableB', + }, }, }); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/EntityLineageUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/EntityLineageUtils.tsx index 95e272931862..bca9adb9cd99 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/EntityLineageUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/EntityLineageUtils.tsx @@ -1649,8 +1649,16 @@ const extractTempLineageNodes = ( return; } edge.tempLineageTables.forEach((hop: TempLineageTable) => { - const fromNode = getOrCreateTempNode(hop.fromEntity, existingByFqn, newTempNodes); - const toNode = getOrCreateTempNode(hop.toEntity, existingByFqn, newTempNodes); + const fromNode = getOrCreateTempNode( + hop.fromEntity, + existingByFqn, + newTempNodes + ); + const toNode = getOrCreateTempNode( + hop.toEntity, + existingByFqn, + newTempNodes + ); newEdges.push({ fromEntity: { id: fromNode.id, From 3bd28b67c1b4e07f47bc48ccba2be0208747cd44 Mon Sep 17 00:00:00 2001 From: Satender Date: Mon, 27 Apr 2026 18:09:54 +0530 Subject: [PATCH 6/7] fixed tc for nodeHeight --- .../src/main/resources/ui/src/utils/CanvasUtils.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/CanvasUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/CanvasUtils.ts index db54b4fb9653..0100db1c9f0a 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/CanvasUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/CanvasUtils.ts @@ -103,13 +103,9 @@ const getNodeYPadding = (node: Node): number => { export function getNodeHeight( node: Node, - isColumnLineage: boolean, + isColumnLineage?: boolean, columnCount?: number -) { - if (node.height) { - return node.height; - } - +): number { const isRootNode = node.data?.isRootNode ?? false; const visibleColumnCount = isColumnLineage @@ -257,8 +253,8 @@ function getEntityLineageCoordinates( targetNode: Node, isColumnLineage: boolean ): EdgeCoordinates { - const sourceHeight = getNodeHeight(sourceNode, isColumnLineage, 0); - const targetHeight = getNodeHeight(targetNode, isColumnLineage, 0); + const sourceHeight = sourceNode.height ?? 0; + const targetHeight = targetNode.height ?? 0; return { sourceX: sourceNode.position.x + (sourceNode.width ?? 0), From 0b3f388ccfca50bd282924cd6a6442cee0574a23 Mon Sep 17 00:00:00 2001 From: Satender Date: Mon, 27 Apr 2026 18:16:08 +0530 Subject: [PATCH 7/7] updated args for getEntityLineageCords --- openmetadata-ui/src/main/resources/ui/src/utils/CanvasUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/CanvasUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/CanvasUtils.ts index 0100db1c9f0a..232b80cc2ee2 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/CanvasUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/CanvasUtils.ts @@ -251,7 +251,7 @@ function getColumnLineageCoordinates( function getEntityLineageCoordinates( sourceNode: Node, targetNode: Node, - isColumnLineage: boolean + _isColumnLineage: boolean ): EdgeCoordinates { const sourceHeight = sourceNode.height ?? 0; const targetHeight = targetNode.height ?? 0;