From 5dd9089b7f5764a7f3b29d333d681c2711b0970e Mon Sep 17 00:00:00 2001 From: Sachin Chaurasiya Date: Wed, 5 Jan 2022 14:49:54 +0530 Subject: [PATCH] UI : Fixed Lineage Nodes positioning (#2020) * UI : Fixed Lineage Nodes positioning * Moved utils to utils file * Addressing review comment --- .../src/main/resources/ui/package.json | 1 + .../EntityLineage/EntityLineage.component.tsx | 9 ++-- .../resources/ui/src/constants/constants.ts | 3 ++ .../resources/ui/src/enums/entity.enum.ts | 5 ++ .../main/resources/ui/src/react-app-env.d.ts | 1 + .../ui/src/utils/EntityLineageUtils.tsx | 51 ++++++++++++++++++- .../src/main/resources/ui/yarn.lock | 15 ++++++ 7 files changed, 81 insertions(+), 4 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/package.json b/openmetadata-ui/src/main/resources/ui/package.json index 55d26f42ad4..6a59214cac5 100644 --- a/openmetadata-ui/src/main/resources/ui/package.json +++ b/openmetadata-ui/src/main/resources/ui/package.json @@ -24,6 +24,7 @@ "cookie-storage": "^6.1.0", "core-js": "^3.10.1", "cronstrue": "^1.122.0", + "dagre": "^0.8.5", "diff": "^5.0.0", "draft-js": "^0.11.7", "fast-json-patch": "^3.0.0-1", 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 b12c7802cc9..67b779af80b 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 @@ -31,6 +31,7 @@ import useToastContext from '../../hooks/useToastContext'; import { dragHandle, getDataLabel, + getLayoutedElements, getLineageData, getNoLineageDataPlaceholder, onLoad, @@ -107,7 +108,9 @@ const Entitylineage: FunctionComponent = ({ ) as Elements; }; - const [elements, setElements] = useState(setElementsHandle()); + const [elements, setElements] = useState( + getLayoutedElements(setElementsHandle()) + ); const closeDrawer = (value: boolean) => { setIsDrawerOpen(value); @@ -152,7 +155,7 @@ const Entitylineage: FunctionComponent = ({ }; const onNodeExpand = (tableColumns?: Column[]) => { - const elements = setElementsHandle(); + const elements = getLayoutedElements(setElementsHandle()); setElements( elements.map((preEl) => { if (preEl.id.includes(expandNode?.id as string)) { @@ -189,7 +192,7 @@ const Entitylineage: FunctionComponent = ({ }; useEffect(() => { - setElements(setElementsHandle()); + setElements(getLayoutedElements(setElementsHandle())); setExpandNode(undefined); setTableColumns([]); }, [entityLineage, isNodeLoading]); 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 ae5d3a5e214..6e0c73731c4 100644 --- a/openmetadata-ui/src/main/resources/ui/src/constants/constants.ts +++ b/openmetadata-ui/src/main/resources/ui/src/constants/constants.ts @@ -290,3 +290,6 @@ export const TITLE_FOR_NON_ADMIN_ACTION = // Entity Lineage Constant export const positionX = 150; export const positionY = 60; + +export const nodeWidth = 172; +export const nodeHeight = 36; diff --git a/openmetadata-ui/src/main/resources/ui/src/enums/entity.enum.ts b/openmetadata-ui/src/main/resources/ui/src/enums/entity.enum.ts index 22d7c4b4890..335756540a5 100644 --- a/openmetadata-ui/src/main/resources/ui/src/enums/entity.enum.ts +++ b/openmetadata-ui/src/main/resources/ui/src/enums/entity.enum.ts @@ -24,3 +24,8 @@ export enum ChangeType { UPDATED = 'Updated', REMOVED = 'Removed', } + +export enum EntityLineageDirection { + TOP_BOTTOM = 'TB', + LEFT_RIGHT = 'LR', +} diff --git a/openmetadata-ui/src/main/resources/ui/src/react-app-env.d.ts b/openmetadata-ui/src/main/resources/ui/src/react-app-env.d.ts index 2378ff5165f..fbdeb8e7cbb 100644 --- a/openmetadata-ui/src/main/resources/ui/src/react-app-env.d.ts +++ b/openmetadata-ui/src/main/resources/ui/src/react-app-env.d.ts @@ -27,3 +27,4 @@ declare module 'react-table'; declare module 'recharts'; declare module 'diff'; declare module '@deuex-solutions/react-tutorial'; +declare module 'dagre'; 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 82256215a1d..0c22c5fd02e 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/EntityLineageUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/EntityLineageUtils.tsx @@ -21,17 +21,25 @@ import { FlowElement, ArrowHeadType, Node, + isNode, } 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 { + nodeHeight, + nodeWidth, + positionX, + positionY, +} from '../constants/constants'; import { EntityLineage, Edge as LineageEdge, } from '../generated/type/entityLineage'; import { EntityReference } from '../generated/type/entityReference'; import { isLeafNode } from './EntityUtils'; +import dagre from 'dagre'; +import { EntityLineageDirection } from '../enums/entity.enum'; export const onLoad = (reactFlowInstance: OnLoadParams) => { reactFlowInstance.fitView(); @@ -362,3 +370,44 @@ export const getNoLineageDataPlaceholder = () => { ); }; + +const dagreGraph = new dagre.graphlib.Graph(); +dagreGraph.setDefaultEdgeLabel(() => ({})); + +export const getLayoutedElements = ( + elements: Elements, + direction = EntityLineageDirection.LEFT_RIGHT +) => { + const isHorizontal = direction === EntityLineageDirection.LEFT_RIGHT; + dagreGraph.setGraph({ rankdir: direction }); + + elements.forEach((el) => { + if (isNode(el)) { + dagreGraph.setNode(el.id, { + width: el?.__rf?.width ?? nodeWidth, + height: el?.__rf?.height ?? nodeHeight, + }); + } else { + dagreGraph.setEdge(el.source, el.target); + } + }); + + dagre.layout(dagreGraph); + + return elements.map((el) => { + if (isNode(el)) { + const nodeWithPosition = dagreGraph.node(el.id); + el.targetPosition = isHorizontal ? Position.Left : Position.Top; + el.sourcePosition = isHorizontal ? Position.Right : Position.Bottom; + el.position = { + x: + nodeWithPosition.x - + (el?.__rf?.width ?? nodeWidth) / 2 + + Math.random() / 1000, + y: nodeWithPosition.y - (el?.__rf?.height ?? nodeHeight) / 2, + }; + } + + return el; + }); +}; diff --git a/openmetadata-ui/src/main/resources/ui/yarn.lock b/openmetadata-ui/src/main/resources/ui/yarn.lock index 0aedae08ea0..2c5e860c4ad 100644 --- a/openmetadata-ui/src/main/resources/ui/yarn.lock +++ b/openmetadata-ui/src/main/resources/ui/yarn.lock @@ -4300,6 +4300,14 @@ d@1, d@^1.0.1: es5-ext "^0.10.50" type "^1.0.1" +dagre@^0.8.5: + version "0.8.5" + resolved "https://registry.yarnpkg.com/dagre/-/dagre-0.8.5.tgz#ba30b0055dac12b6c1fcc247817442777d06afee" + integrity sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw== + dependencies: + graphlib "^2.1.8" + lodash "^4.17.15" + damerau-levenshtein@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.7.tgz#64368003512a1a6992593741a09a9d31a836f55d" @@ -5965,6 +5973,13 @@ grapheme-splitter@^1.0.4: resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e" integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== +graphlib@^2.1.8: + version "2.1.8" + resolved "https://registry.yarnpkg.com/graphlib/-/graphlib-2.1.8.tgz#5761d414737870084c92ec7b5dbcb0592c9d35da" + integrity sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A== + dependencies: + lodash "^4.17.15" + growly@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"