From bcbd3aef221d61a9781c06053383c4bd16d1c4ed Mon Sep 17 00:00:00 2001 From: Sachin Chaurasiya Date: Tue, 4 Jan 2022 15:10:57 +0530 Subject: [PATCH] Moving EntityLineage Utils to separate utils file. (#2014) --- .../EntityLineage/EntityLineage.component.tsx | 363 +---------------- .../resources/ui/src/constants/constants.ts | 4 + .../ui/src/utils/EntityLineageUtils.tsx | 364 ++++++++++++++++++ 3 files changed, 380 insertions(+), 351 deletions(-) create mode 100644 openmetadata-ui/src/main/resources/ui/src/utils/EntityLineageUtils.tsx diff --git a/openmetadata-ui/src/main/resources/ui/src/components/EntityLineage/EntityLineage.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/EntityLineage/EntityLineage.component.tsx index 77b1f6df3f1..b12c7802cc9 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/EntityLineage/EntityLineage.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/EntityLineage/EntityLineage.component.tsx @@ -13,376 +13,37 @@ import { AxiosResponse } from 'axios'; import { isEmpty } from 'lodash'; -import { LeafNodes, LineagePos, LoadingNodeState } from 'Models'; -import React, { - FunctionComponent, - MouseEvent as ReactMouseEvent, - useEffect, - useRef, - useState, -} from 'react'; +import React, { FunctionComponent, useEffect, useRef, useState } from 'react'; import ReactFlow, { addEdge, - ArrowHeadType, Connection, Controls, Edge, Elements, FlowElement, - Node, - OnLoadParams, - Position, ReactFlowProvider, removeElements, } from 'react-flow-renderer'; -import { Link } from 'react-router-dom'; import { getTableDetails } from '../../axiosAPIs/tableAPI'; import { Column } from '../../generated/entity/data/table'; -import { - Edge as LineageEdge, - EntityLineage, -} from '../../generated/type/entityLineage'; import { EntityReference } from '../../generated/type/entityReference'; import useToastContext from '../../hooks/useToastContext'; -import { isLeafNode } from '../../utils/EntityUtils'; +import { + dragHandle, + getDataLabel, + getLineageData, + getNoLineageDataPlaceholder, + onLoad, + onNodeContextMenu, + onNodeMouseEnter, + onNodeMouseLeave, + onNodeMouseMove, +} from '../../utils/EntityLineageUtils'; import SVGIcons from '../../utils/SvgUtils'; import { getEntityIcon } from '../../utils/TableUtils'; import EntityInfoDrawer from '../EntityInfoDrawer/EntityInfoDrawer.component'; -import Loader from '../Loader/Loader'; import CustomNode from './CustomNode.component'; import { EntityLineageProp, SelectedNode } from './EntityLineage.interface'; -const onLoad = (reactFlowInstance: OnLoadParams) => { - reactFlowInstance.fitView(); - reactFlowInstance.zoomTo(1); -}; -/* eslint-disable-next-line */ -const onNodeMouseEnter = (_event: ReactMouseEvent, _node: Node | Edge) => { - return; -}; -/* eslint-disable-next-line */ -const onNodeMouseMove = (_event: ReactMouseEvent, _node: Node | Edge) => { - return; -}; -/* eslint-disable-next-line */ -const onNodeMouseLeave = (_event: ReactMouseEvent, _node: Node | Edge) => { - return; -}; -/* eslint-disable-next-line */ -const onNodeContextMenu = (_event: ReactMouseEvent, _node: Node | Edge) => { - _event.preventDefault(); -}; - -const dragHandle = (event: ReactMouseEvent) => { - event.stopPropagation(); -}; - -const getDataLabel = (v = '', separator = '.', isTextOnly = false) => { - const length = v.split(separator).length; - if (isTextOnly) { - return v.split(separator)[length - 1]; - } - - return ( - - {v.split(separator)[length - 1]} - - ); -}; - -const getNoLineageDataPlaceholder = () => { - return ( -
- - Lineage is currently supported for Airflow. To enable lineage collection - from Airflow, please follow the documentation - - - here. - -
- ); -}; - -const positionX = 150; -const positionY = 60; - -const getLineageData = ( - entityLineage: EntityLineage, - onSelect: (state: boolean, value: SelectedNode) => void, - loadNodeHandler: (node: EntityReference, pos: LineagePos) => void, - lineageLeafNodes: LeafNodes, - isNodeLoading: LoadingNodeState, - getNodeLable: (node: EntityReference) => React.ReactNode -) => { - const [x, y] = [0, 0]; - const nodes = entityLineage['nodes']; - let upstreamEdges: Array = - entityLineage['upstreamEdges']?.map((up) => ({ isMapped: false, ...up })) || - []; - let downstreamEdges: Array = - entityLineage['downstreamEdges']?.map((down) => ({ - isMapped: false, - ...down, - })) || []; - const mainNode = entityLineage['entity']; - - const UPStreamNodes: Elements = []; - const DOWNStreamNodes: Elements = []; - const lineageEdges: Elements = []; - - const makeNode = ( - node: EntityReference, - pos: LineagePos, - depth: number, - posDepth: number - ) => { - const [xVal, yVal] = [positionX * 2 * depth, y + positionY * posDepth]; - - return { - id: `node-${node.id}-${depth}`, - sourcePosition: Position.Right, - targetPosition: Position.Left, - type: 'default', - className: 'leaf-node', - data: { - label: getNodeLable(node), - }, - position: { - x: pos === 'from' ? -xVal : xVal, - y: yVal, - }, - }; - }; - - const getNodes = ( - id: string, - pos: LineagePos, - depth: number, - NodesArr: Array = [] - ): Array => { - if (pos === 'to') { - let upDepth = NodesArr.filter((nd) => nd.lDepth === depth).length; - const UPNodes: Array = []; - const updatedUpStreamEdge = upstreamEdges.map((up) => { - if (up.toEntity === id) { - const edg = UPStreamNodes.find((up) => up.id.includes(`node-${id}`)); - const node = nodes?.find((nd) => nd.id === up.fromEntity); - if (node) { - UPNodes.push(node); - UPStreamNodes.push(makeNode(node, 'from', depth, upDepth)); - lineageEdges.push({ - id: `edge-${up.fromEntity}-${id}-${depth}`, - source: `node-${node.id}-${depth}`, - target: edg ? edg.id : `node-${id}-${depth}`, - type: 'custom', - arrowHeadType: ArrowHeadType.ArrowClosed, - }); - } - upDepth += 1; - - return { - ...up, - isMapped: true, - }; - } else { - return up; - } - }); - - upstreamEdges = updatedUpStreamEdge; - - return UPNodes?.map((upNd) => ({ lDepth: depth, ...upNd })) || []; - } else { - let downDepth = NodesArr.filter((nd) => nd.lDepth === depth).length; - const DOWNNodes: Array = []; - const updatedDownStreamEdge = downstreamEdges.map((down) => { - if (down.fromEntity === id) { - const edg = DOWNStreamNodes.find((down) => - down.id.includes(`node-${id}`) - ); - const node = nodes?.find((nd) => nd.id === down.toEntity); - if (node) { - DOWNNodes.push(node); - DOWNStreamNodes.push(makeNode(node, 'to', depth, downDepth)); - lineageEdges.push({ - id: `edge-${id}-${down.toEntity}`, - source: edg ? edg.id : `node-${id}-${depth}`, - target: `node-${node.id}-${depth}`, - type: 'custom', - arrowHeadType: ArrowHeadType.ArrowClosed, - }); - } - downDepth += 1; - - return { - ...down, - isMapped: true, - }; - } else { - return down; - } - }); - - downstreamEdges = updatedDownStreamEdge; - - return DOWNNodes?.map((downNd) => ({ lDepth: depth, ...downNd })) || []; - } - }; - - const getUpStreamData = ( - Entity: EntityReference, - depth = 1, - upNodesArr: Array = [] - ) => { - const upNodes = getNodes(Entity.id, 'to', depth, upNodesArr); - upNodesArr.push(...upNodes); - upNodes.forEach((up) => { - if ( - upstreamEdges.some((upE) => upE.toEntity === up.id && !upE.isMapped) - ) { - getUpStreamData(up, depth + 1, upNodesArr); - } - }); - - return upNodesArr; - }; - - const getDownStreamData = ( - Entity: EntityReference, - depth = 1, - downNodesArr: Array = [] - ) => { - const downNodes = getNodes(Entity.id, 'from', depth, downNodesArr); - downNodesArr.push(...downNodes); - downNodes.forEach((down) => { - if ( - downstreamEdges.some( - (downE) => downE.fromEntity === down.id && !downE.isMapped - ) - ) { - getDownStreamData(down, depth + 1, downNodesArr); - } - }); - - return downNodesArr; - }; - - getUpStreamData(mainNode); - - getDownStreamData(mainNode); - - const lineageData = [ - { - id: `node-${mainNode.id}-1`, - sourcePosition: 'right', - targetPosition: 'left', - type: lineageEdges.find((ed: FlowElement) => - (ed as Edge).target.includes(mainNode.id) - ) - ? lineageEdges.find((ed: FlowElement) => - (ed as Edge).source.includes(mainNode.id) - ) - ? 'default' - : 'output' - : 'input', - className: 'leaf-node core', - data: { - label: getNodeLable(mainNode), - }, - position: { x: x, y: y }, - }, - ...UPStreamNodes.map((up) => { - const node = entityLineage?.nodes?.find((d) => up.id.includes(d.id)); - - return lineageEdges.find( - (ed: FlowElement) => (ed as Edge).target === up.id - ) - ? up - : { - ...up, - type: 'input', - data: { - label: ( -
-
{ - e.stopPropagation(); - onSelect(false, {} as SelectedNode); - if (node) { - loadNodeHandler(node, 'from'); - } - }}> - {!isLeafNode( - lineageLeafNodes, - node?.id as string, - 'from' - ) && !up.id.includes(isNodeLoading.id as string) ? ( - - ) : null} - {isNodeLoading.state && - up.id.includes(isNodeLoading.id as string) ? ( - - ) : null} -
- -
{up?.data?.label}
-
- ), - }, - }; - }), - ...DOWNStreamNodes.map((down) => { - const node = entityLineage?.nodes?.find((d) => down.id.includes(d.id)); - - return lineageEdges.find((ed: FlowElement) => - (ed as Edge).source.includes(down.id) - ) - ? down - : { - ...down, - type: 'output', - data: { - label: ( -
-
{down?.data?.label}
- -
{ - e.stopPropagation(); - onSelect(false, {} as SelectedNode); - if (node) { - loadNodeHandler(node, 'to'); - } - }}> - {!isLeafNode(lineageLeafNodes, node?.id as string, 'to') && - !down.id.includes(isNodeLoading.id as string) ? ( - - ) : null} - {isNodeLoading.state && - down.id.includes(isNodeLoading.id as string) ? ( - - ) : null} -
-
- ), - }, - }; - }), - ...lineageEdges, - ]; - - return lineageData; -}; const Entitylineage: FunctionComponent = ({ entityLineage, diff --git a/openmetadata-ui/src/main/resources/ui/src/constants/constants.ts b/openmetadata-ui/src/main/resources/ui/src/constants/constants.ts index 1ad4e52c0cd..ae5d3a5e214 100644 --- a/openmetadata-ui/src/main/resources/ui/src/constants/constants.ts +++ b/openmetadata-ui/src/main/resources/ui/src/constants/constants.ts @@ -286,3 +286,7 @@ export const TITLE_FOR_NON_OWNER_ACTION = export const TITLE_FOR_NON_ADMIN_ACTION = 'Only Admin is allowed for the action'; + +// Entity Lineage Constant +export const positionX = 150; +export const positionY = 60; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/EntityLineageUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/EntityLineageUtils.tsx new file mode 100644 index 00000000000..82256215a1d --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/utils/EntityLineageUtils.tsx @@ -0,0 +1,364 @@ +/* + * Copyright 2021 Collate + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { LeafNodes, LineagePos, LoadingNodeState } from 'Models'; +import React, { MouseEvent as ReactMouseEvent } from 'react'; +import { + OnLoadParams, + Edge, + Elements, + Position, + FlowElement, + ArrowHeadType, + Node, +} from 'react-flow-renderer'; +import { Link } from 'react-router-dom'; +import { SelectedNode } from '../components/EntityLineage/EntityLineage.interface'; +import Loader from '../components/Loader/Loader'; +import { positionX, positionY } from '../constants/constants'; +import { + EntityLineage, + Edge as LineageEdge, +} from '../generated/type/entityLineage'; +import { EntityReference } from '../generated/type/entityReference'; +import { isLeafNode } from './EntityUtils'; + +export const onLoad = (reactFlowInstance: OnLoadParams) => { + reactFlowInstance.fitView(); + reactFlowInstance.zoomTo(1); +}; +/* eslint-disable-next-line */ +export const onNodeMouseEnter = (_event: ReactMouseEvent, _node: Node) => { + return; +}; +/* eslint-disable-next-line */ +export const onNodeMouseMove = (_event: ReactMouseEvent, _node: Node) => { + return; +}; +/* eslint-disable-next-line */ +export const onNodeMouseLeave = (_event: ReactMouseEvent, _node: Node) => { + return; +}; +/* eslint-disable-next-line */ +export const onNodeContextMenu = (event: ReactMouseEvent, _node: Node) => { + event.preventDefault(); +}; + +export const dragHandle = (event: ReactMouseEvent) => { + event.stopPropagation(); +}; + +export const getLineageData = ( + entityLineage: EntityLineage, + onSelect: (state: boolean, value: SelectedNode) => void, + loadNodeHandler: (node: EntityReference, pos: LineagePos) => void, + lineageLeafNodes: LeafNodes, + isNodeLoading: LoadingNodeState, + getNodeLable: (node: EntityReference) => React.ReactNode +) => { + const [x, y] = [0, 0]; + const nodes = entityLineage['nodes']; + let upstreamEdges: Array = + entityLineage['upstreamEdges']?.map((up) => ({ isMapped: false, ...up })) || + []; + let downstreamEdges: Array = + entityLineage['downstreamEdges']?.map((down) => ({ + isMapped: false, + ...down, + })) || []; + const mainNode = entityLineage['entity']; + + const UPStreamNodes: Elements = []; + const DOWNStreamNodes: Elements = []; + const lineageEdges: Elements = []; + + const makeNode = ( + node: EntityReference, + pos: LineagePos, + depth: number, + posDepth: number + ) => { + const [xVal, yVal] = [positionX * 2 * depth, y + positionY * posDepth]; + + return { + id: `node-${node.id}-${depth}`, + sourcePosition: Position.Right, + targetPosition: Position.Left, + type: 'default', + className: 'leaf-node', + data: { + label: getNodeLable(node), + }, + position: { + x: pos === 'from' ? -xVal : xVal, + y: yVal, + }, + }; + }; + + const getNodes = ( + id: string, + pos: LineagePos, + depth: number, + NodesArr: Array = [] + ): Array => { + if (pos === 'to') { + let upDepth = NodesArr.filter((nd) => nd.lDepth === depth).length; + const UPNodes: Array = []; + const updatedUpStreamEdge = upstreamEdges.map((up) => { + if (up.toEntity === id) { + const edg = UPStreamNodes.find((up) => up.id.includes(`node-${id}`)); + const node = nodes?.find((nd) => nd.id === up.fromEntity); + if (node) { + UPNodes.push(node); + UPStreamNodes.push(makeNode(node, 'from', depth, upDepth)); + lineageEdges.push({ + id: `edge-${up.fromEntity}-${id}-${depth}`, + source: `node-${node.id}-${depth}`, + target: edg ? edg.id : `node-${id}-${depth}`, + type: 'custom', + arrowHeadType: ArrowHeadType.ArrowClosed, + }); + } + upDepth += 1; + + return { + ...up, + isMapped: true, + }; + } else { + return up; + } + }); + + upstreamEdges = updatedUpStreamEdge; + + return UPNodes?.map((upNd) => ({ lDepth: depth, ...upNd })) || []; + } else { + let downDepth = NodesArr.filter((nd) => nd.lDepth === depth).length; + const DOWNNodes: Array = []; + const updatedDownStreamEdge = downstreamEdges.map((down) => { + if (down.fromEntity === id) { + const edg = DOWNStreamNodes.find((down) => + down.id.includes(`node-${id}`) + ); + const node = nodes?.find((nd) => nd.id === down.toEntity); + if (node) { + DOWNNodes.push(node); + DOWNStreamNodes.push(makeNode(node, 'to', depth, downDepth)); + lineageEdges.push({ + id: `edge-${id}-${down.toEntity}`, + source: edg ? edg.id : `node-${id}-${depth}`, + target: `node-${node.id}-${depth}`, + type: 'custom', + arrowHeadType: ArrowHeadType.ArrowClosed, + }); + } + downDepth += 1; + + return { + ...down, + isMapped: true, + }; + } else { + return down; + } + }); + + downstreamEdges = updatedDownStreamEdge; + + return DOWNNodes?.map((downNd) => ({ lDepth: depth, ...downNd })) || []; + } + }; + + const getUpStreamData = ( + Entity: EntityReference, + depth = 1, + upNodesArr: Array = [] + ) => { + const upNodes = getNodes(Entity.id, 'to', depth, upNodesArr); + upNodesArr.push(...upNodes); + upNodes.forEach((up) => { + if ( + upstreamEdges.some((upE) => upE.toEntity === up.id && !upE.isMapped) + ) { + getUpStreamData(up, depth + 1, upNodesArr); + } + }); + + return upNodesArr; + }; + + const getDownStreamData = ( + Entity: EntityReference, + depth = 1, + downNodesArr: Array = [] + ) => { + const downNodes = getNodes(Entity.id, 'from', depth, downNodesArr); + downNodesArr.push(...downNodes); + downNodes.forEach((down) => { + if ( + downstreamEdges.some( + (downE) => downE.fromEntity === down.id && !downE.isMapped + ) + ) { + getDownStreamData(down, depth + 1, downNodesArr); + } + }); + + return downNodesArr; + }; + + getUpStreamData(mainNode); + + getDownStreamData(mainNode); + + const lineageData = [ + { + id: `node-${mainNode.id}-1`, + sourcePosition: 'right', + targetPosition: 'left', + type: lineageEdges.find((ed: FlowElement) => + (ed as Edge).target.includes(mainNode.id) + ) + ? lineageEdges.find((ed: FlowElement) => + (ed as Edge).source.includes(mainNode.id) + ) + ? 'default' + : 'output' + : 'input', + className: 'leaf-node core', + data: { + label: getNodeLable(mainNode), + }, + position: { x: x, y: y }, + }, + ...UPStreamNodes.map((up) => { + const node = entityLineage?.nodes?.find((d) => up.id.includes(d.id)); + + return lineageEdges.find( + (ed: FlowElement) => (ed as Edge).target === up.id + ) + ? up + : { + ...up, + type: 'input', + data: { + label: ( +
+
{ + e.stopPropagation(); + onSelect(false, {} as SelectedNode); + if (node) { + loadNodeHandler(node, 'from'); + } + }}> + {!isLeafNode( + lineageLeafNodes, + node?.id as string, + 'from' + ) && !up.id.includes(isNodeLoading.id as string) ? ( + + ) : null} + {isNodeLoading.state && + up.id.includes(isNodeLoading.id as string) ? ( + + ) : null} +
+ +
{up?.data?.label}
+
+ ), + }, + }; + }), + ...DOWNStreamNodes.map((down) => { + const node = entityLineage?.nodes?.find((d) => down.id.includes(d.id)); + + return lineageEdges.find((ed: FlowElement) => + (ed as Edge).source.includes(down.id) + ) + ? down + : { + ...down, + type: 'output', + data: { + label: ( +
+
{down?.data?.label}
+ +
{ + e.stopPropagation(); + onSelect(false, {} as SelectedNode); + if (node) { + loadNodeHandler(node, 'to'); + } + }}> + {!isLeafNode(lineageLeafNodes, node?.id as string, 'to') && + !down.id.includes(isNodeLoading.id as string) ? ( + + ) : null} + {isNodeLoading.state && + down.id.includes(isNodeLoading.id as string) ? ( + + ) : null} +
+
+ ), + }, + }; + }), + ...lineageEdges, + ]; + + return lineageData; +}; + +export const getDataLabel = (v = '', separator = '.', isTextOnly = false) => { + const length = v.split(separator).length; + if (isTextOnly) { + return v.split(separator)[length - 1]; + } + + return ( + + {v.split(separator)[length - 1]} + + ); +}; + +export const getNoLineageDataPlaceholder = () => { + return ( +
+ + Lineage is currently supported for Airflow. To enable lineage collection + from Airflow, please follow the documentation + + + here. + +
+ ); +};