From e7422b0e4322749d57d10d46ca84b4fb46f24d44 Mon Sep 17 00:00:00 2001 From: Satender Date: Fri, 24 Apr 2026 17:11:56 +0530 Subject: [PATCH 01/11] 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 02/11] 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 03/11] 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 04/11] 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 05/11] 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 06/11] 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 07/11] 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; From ad6cfae931b33325ba6d43cafefbe3140a9974ff Mon Sep 17 00:00:00 2001 From: Satender Date: Thu, 30 Apr 2026 14:35:55 +0530 Subject: [PATCH 08/11] added E2E test case to check if temp lineage table nodes are rendered --- .../playwright/e2e/Flow/GlobalSearch.spec.ts | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/GlobalSearch.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/GlobalSearch.spec.ts index 81428859bcec..ec4bbdbb15d5 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/GlobalSearch.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/GlobalSearch.spec.ts @@ -13,11 +13,16 @@ import test, { expect } from '@playwright/test'; import { SidebarItem } from '../../constant/sidebar'; import { redirectToHomePage } from '../../utils/common'; +import { waitForAllLoadersToDisappear } from '../../utils/entity'; import { sidebarClick } from '../../utils/sidebar'; const DESCRIPTION_SEARCH = 'The dimension table contains data about your customers. The customers table contains one row per customer. It includes historical metrics (such as the total amount that each customer has spent in your store) as well as forward-looking metrics (such as the predicted number of days between future orders and the expected order value in the next 30 days). This table also includes columns that segment customers into various categories (such as new, returning, promising, at risk, dormant, and loyal), which you can use to target marketing activities.The dimension table contains data about your customers. The customers table contains one row per customer. It includes historical metrics (such as the total amount that each customer has spent in your store) as well as forward-looking metrics (such as the predicted number of days between future orders and the expected order value in the next 30 days). This table also includes columns that segment customers into various categories (such as new, returning, promising, at risk, dormant, and loyal), which you can use to target marketing activities.'; +const RAW_ORDER = 'raw_order'; +const RAW_ORDER_FQN = 'sample_data.ecommerce_db.shopify.raw_order'; +const TEMP_TABLE_NAMES = ['tmp_order_staging', 'tmp_order_enriched']; + // use the admin user to login test.use({ storageState: 'playwright/.auth/admin.json' }); @@ -63,3 +68,47 @@ test('searching for longer description should work', async ({ page }) => { await expect(page.getByTestId('alert-bar')).not.toBeVisible(); }); + +test('check if temp lineage table nodes are rendered on canvas', async ({ page }) => { + await redirectToHomePage(page); + + await sidebarClick(page, SidebarItem.EXPLORE); + + await page.getByTestId('global-search-selector').click(); + await page.getByTestId('global-search-select-option-Table').click(); + + await page + .getByTestId('navbar-search-container') + .getByTestId('searchBox') + .fill(RAW_ORDER); + + await page.keyboard.press('Enter'); + + await page.getByTestId('search-container').getByTestId('loader').waitFor({ + state: 'detached', + }); + + await page.getByTestId('search-results').waitFor({ state: 'visible' }); + + await page + .getByTestId('search-results') + .getByTestId(`table-data-card_${RAW_ORDER_FQN}`) + .getByTestId('entity-link') + .click(); + + await page.waitForURL(`**/${RAW_ORDER_FQN}**`, { + waitUntil: 'domcontentloaded', + }); + await waitForAllLoadersToDisappear(page); + + const lineageRes = page.waitForResponse('/api/v1/lineage/getLineage?*'); + await page.getByTestId('lineage').click(); + await lineageRes; + await waitForAllLoadersToDisappear(page); + + for (const tempTableName of TEMP_TABLE_NAMES) { + await expect( + page.getByTestId(`lineage-node-${tempTableName}`) + ).toBeVisible(); + } +}); From b741b648701638a10d8e1ca59a81ef55fe18db49 Mon Sep 17 00:00:00 2001 From: Satender Date: Thu, 30 Apr 2026 15:42:04 +0530 Subject: [PATCH 09/11] updated code as per comments --- .../playwright/e2e/Flow/GlobalSearch.spec.ts | 48 ------------------- .../Pages/Lineage/DataAssetLineage.spec.ts | 44 +++++++++++++++++ 2 files changed, 44 insertions(+), 48 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/GlobalSearch.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/GlobalSearch.spec.ts index ec4bbdbb15d5..8c0cc7a7ee7f 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/GlobalSearch.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/GlobalSearch.spec.ts @@ -13,16 +13,11 @@ import test, { expect } from '@playwright/test'; import { SidebarItem } from '../../constant/sidebar'; import { redirectToHomePage } from '../../utils/common'; -import { waitForAllLoadersToDisappear } from '../../utils/entity'; import { sidebarClick } from '../../utils/sidebar'; const DESCRIPTION_SEARCH = 'The dimension table contains data about your customers. The customers table contains one row per customer. It includes historical metrics (such as the total amount that each customer has spent in your store) as well as forward-looking metrics (such as the predicted number of days between future orders and the expected order value in the next 30 days). This table also includes columns that segment customers into various categories (such as new, returning, promising, at risk, dormant, and loyal), which you can use to target marketing activities.The dimension table contains data about your customers. The customers table contains one row per customer. It includes historical metrics (such as the total amount that each customer has spent in your store) as well as forward-looking metrics (such as the predicted number of days between future orders and the expected order value in the next 30 days). This table also includes columns that segment customers into various categories (such as new, returning, promising, at risk, dormant, and loyal), which you can use to target marketing activities.'; -const RAW_ORDER = 'raw_order'; -const RAW_ORDER_FQN = 'sample_data.ecommerce_db.shopify.raw_order'; -const TEMP_TABLE_NAMES = ['tmp_order_staging', 'tmp_order_enriched']; - // use the admin user to login test.use({ storageState: 'playwright/.auth/admin.json' }); @@ -69,46 +64,3 @@ test('searching for longer description should work', async ({ page }) => { await expect(page.getByTestId('alert-bar')).not.toBeVisible(); }); -test('check if temp lineage table nodes are rendered on canvas', async ({ page }) => { - await redirectToHomePage(page); - - await sidebarClick(page, SidebarItem.EXPLORE); - - await page.getByTestId('global-search-selector').click(); - await page.getByTestId('global-search-select-option-Table').click(); - - await page - .getByTestId('navbar-search-container') - .getByTestId('searchBox') - .fill(RAW_ORDER); - - await page.keyboard.press('Enter'); - - await page.getByTestId('search-container').getByTestId('loader').waitFor({ - state: 'detached', - }); - - await page.getByTestId('search-results').waitFor({ state: 'visible' }); - - await page - .getByTestId('search-results') - .getByTestId(`table-data-card_${RAW_ORDER_FQN}`) - .getByTestId('entity-link') - .click(); - - await page.waitForURL(`**/${RAW_ORDER_FQN}**`, { - waitUntil: 'domcontentloaded', - }); - await waitForAllLoadersToDisappear(page); - - const lineageRes = page.waitForResponse('/api/v1/lineage/getLineage?*'); - await page.getByTestId('lineage').click(); - await lineageRes; - await waitForAllLoadersToDisappear(page); - - for (const tempTableName of TEMP_TABLE_NAMES) { - await expect( - page.getByTestId(`lineage-node-${tempTableName}`) - ).toBeVisible(); - } -}); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Lineage/DataAssetLineage.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Lineage/DataAssetLineage.spec.ts index 766ad1cd0232..8f8d5fbdd32f 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Lineage/DataAssetLineage.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Lineage/DataAssetLineage.spec.ts @@ -448,6 +448,50 @@ test.describe('Column Level Lineage', () => { }); }); +test.describe('Temp lineage table nodes', () => { + const RAW_ORDER_FQN = 'sample_data.ecommerce_db.shopify.raw_order'; + const TEMP_TABLE_NAMES = ['tmp_order_staging', 'tmp_order_enriched']; + + test.beforeAll('verify sample data entity exists', async ({ browser }) => { + const { apiContext, afterAction } = await getDefaultAdminAPIContext(browser); + + try { + const response = await apiContext.get( + `/api/v1/tables/name/${encodeURIComponent(RAW_ORDER_FQN)}` + ); + + if (!response.ok()) { + throw new Error( + `Sample entity '${RAW_ORDER_FQN}' not found. Ensure sample data is loaded before running temp lineage tests.` + ); + } + } finally { + await afterAction(); + } + }); + + test.beforeEach(async ({ page }) => { + await redirectToHomePage(page); + }); + + test('should render temp lineage table nodes on canvas', async ({ page }) => { + await page.goto(`/table/${encodeURIComponent(RAW_ORDER_FQN)}`); + await waitForAllLoadersToDisappear(page); + + await visitLineageTab(page); + await waitForAllLoadersToDisappear(page); + + await page.getByTestId('fit-screen').click(); + await page.getByRole('menuitem', { name: 'Fit to screen' }).click(); + + for (const tempTableName of TEMP_TABLE_NAMES) { + await expect( + page.getByTestId(`lineage-node-${tempTableName}`) + ).toBeVisible(); + } + }); +}); + test.describe('Lineage Settings modal', () => { const table = new TableClass(); From 409fbc16cea97b1af626cda5bca479435304ac69 Mon Sep 17 00:00:00 2001 From: Satender Date: Thu, 30 Apr 2026 19:16:18 +0530 Subject: [PATCH 10/11] fixed linting issue --- .../ui/playwright/e2e/Pages/Lineage/DataAssetLineage.spec.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Lineage/DataAssetLineage.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Lineage/DataAssetLineage.spec.ts index 8f8d5fbdd32f..10a2e5c89648 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Lineage/DataAssetLineage.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Lineage/DataAssetLineage.spec.ts @@ -453,7 +453,9 @@ test.describe('Temp lineage table nodes', () => { const TEMP_TABLE_NAMES = ['tmp_order_staging', 'tmp_order_enriched']; test.beforeAll('verify sample data entity exists', async ({ browser }) => { - const { apiContext, afterAction } = await getDefaultAdminAPIContext(browser); + const { apiContext, afterAction } = await getDefaultAdminAPIContext( + browser + ); try { const response = await apiContext.get( From 68e138a047968846a755b35159621456b6ee2c31 Mon Sep 17 00:00:00 2001 From: Satender Date: Mon, 4 May 2026 10:17:50 +0530 Subject: [PATCH 11/11] removed extra line, failing UI checks --- .../main/resources/ui/playwright/e2e/Flow/GlobalSearch.spec.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/GlobalSearch.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/GlobalSearch.spec.ts index 8c0cc7a7ee7f..81428859bcec 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/GlobalSearch.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/GlobalSearch.spec.ts @@ -63,4 +63,3 @@ test('searching for longer description should work', async ({ page }) => { await expect(page.getByTestId('alert-bar')).not.toBeVisible(); }); -