From e39b89da0e9078b762c671cb87426bcb310b61c9 Mon Sep 17 00:00:00 2001 From: Brijesh Bhalala Date: Mon, 17 Aug 2026 15:59:39 +0530 Subject: [PATCH 1/2] ATLAS-5376: Atlas UI: Relationship cards layout breaking, overlapping, and tooltip placement issues with long entity names (React & Classic UI) --- dashboard/src/styles/detailPage.scss | 28 +- .../EntityDetailTabs/RelationshipLineage.tsx | 63 ++-- .../__tests__/RelationshipLineage.test.tsx | 35 ++ dashboardv2/public/css/scss/graph.scss | 7 + dashboardv2/public/css/scss/relationship.scss | 26 +- dashboardv2/public/css/scss/theme.scss | 4 +- .../RelationshipCardsLayoutView.js | 10 +- .../js/views/graph/RelationshipLayoutView.js | 330 +++++++++--------- 8 files changed, 309 insertions(+), 194 deletions(-) diff --git a/dashboard/src/styles/detailPage.scss b/dashboard/src/styles/detailPage.scss index c880159beb7..4f05362a2cc 100644 --- a/dashboard/src/styles/detailPage.scss +++ b/dashboard/src/styles/detailPage.scss @@ -189,7 +189,7 @@ pre.code-block .json-string { flex-direction: column; gap: 12px; min-width: 280px; - flex: 0 0 auto; + flex: 1 1 0; } @media (max-width: 599px) { @@ -216,6 +216,9 @@ pre.code-block .json-string { background-color: #ffffff; box-shadow: 0 1px 2px rgba(15, 23, 42, 0.08); min-height: 80px; + display: flex; + flex-direction: column; + overflow: hidden; } .relationship-card__header { @@ -296,6 +299,7 @@ pre.code-block .json-string { .relationship-card__content { display: flex; flex-direction: column; + min-width: 0; } .relationship-card__search { @@ -330,15 +334,18 @@ pre.code-block .json-string { .relationship-card__item { font-size: 0.875rem; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; line-height: 1.5; } .relationship-card__link { color: #2563eb; text-decoration: none; + display: inline-block; + max-width: 100%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + vertical-align: middle; } .relationship-card__link:hover { @@ -356,6 +363,12 @@ pre.code-block .json-string { .relationship-card__text { color: #334155; + display: inline-block; + max-width: 100%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + vertical-align: middle; } .relationship-card__empty { @@ -572,3 +585,10 @@ pre.code-block .json-string { cursor: pointer !important; margin-left: 6px !important; } + +.relationship-node-link { + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} diff --git a/dashboard/src/views/DetailPage/EntityDetailTabs/RelationshipLineage.tsx b/dashboard/src/views/DetailPage/EntityDetailTabs/RelationshipLineage.tsx index 090f550746e..c5e61d1094d 100644 --- a/dashboard/src/views/DetailPage/EntityDetailTabs/RelationshipLineage.tsx +++ b/dashboard/src/views/DetailPage/EntityDetailTabs/RelationshipLineage.tsx @@ -48,6 +48,16 @@ import { CloseIcon, LightTooltip } from "@components/muiComponents"; import { useAppSelector } from "@hooks/reducerHook"; import { Link as MUILink } from "@mui/material"; +interface CustomLinkProps { + href: string; + status: string; + entityColor: string; + guid: string; + name: string; + typeName: string; + params: URLSearchParams | string; +} + const CustomLink = ({ href, status, @@ -56,20 +66,24 @@ const CustomLink = ({ name, typeName, params -}: any): any => { +}: CustomLinkProps): JSX.Element => { + const displayLabel = `${name} (${typeName})`; return (
  • - - {name} ({typeName}) - + + + {displayLabel} + +
  • ); }; @@ -146,9 +160,9 @@ const RelationshipLineage = ({ selectedNodeColor = "#4a90e2"; var svg = d3 - .select(svgElement) - .attr("viewBox", `${-padding} ${-padding} ${width + padding * 2} ${height + padding * 2}`) - .attr("enable-background", `new ${-padding} ${-padding} ${width + padding * 2} ${height + padding * 2}`), + .select(svgElement) + .attr("viewBox", `${-padding} ${-padding} ${width + padding * 2} ${height + padding * 2}`) + .attr("enable-background", `new ${-padding} ${-padding} ${width + padding * 2} ${height + padding * 2}`), node, path; @@ -527,15 +541,18 @@ const RelationshipLineage = ({ ? "deleted-relation" : ""; } + const displayLabel = `${name} (${options.typeName})`; return (
  • - - {name} ({options.typeName}) - + + + {displayLabel} + +
  • ); }; @@ -617,7 +634,7 @@ const RelationshipLineage = ({ listString.push({getElement(data)}); } return ( - + {/* {listString?.length > 1 && ( */} { }); }); + describe('Tooltip Rendering', () => { + it('should render LightTooltip for relationship nodes in drawer', async () => { + render( + + + + ); + + const mockNode = { + name: 'Process', + value: [ + { + guid: 'proc-1', + typeName: 'Process', + displayText: 'Test Tooltip Name', + entityStatus: 'ACTIVE', + relationshipStatus: 'ACTIVE' + } + ] + }; + + act(() => { + if (mockEnterSelection.clickHandler) { + mockEnterSelection.clickHandler(mockNode); + } + }); + + await waitFor(() => { + const tooltips = screen.getAllByTestId('light-tooltip'); + const targetTooltip = tooltips.find(t => t.getAttribute('title') === 'Test Tooltip Name (Process)'); + expect(targetTooltip).toBeInTheDocument(); + }, { timeout: 3000 }); + }); + }); + describe('Edge Cases', () => { it('should handle entity without relationshipAttributes', () => { const entityWithoutAttributes = { diff --git a/dashboardv2/public/css/scss/graph.scss b/dashboardv2/public/css/scss/graph.scss index a369841fb51..1d0ae0bfc13 100644 --- a/dashboardv2/public/css/scss/graph.scss +++ b/dashboardv2/public/css/scss/graph.scss @@ -99,6 +99,13 @@ margin-bottom: 5px; text-align: left; + &.entity-list-item { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 100%; + } + &.deleted-relation { .deleteBtn { padding: 2px 8px !important; diff --git a/dashboardv2/public/css/scss/relationship.scss b/dashboardv2/public/css/scss/relationship.scss index 6ea9f83e124..cdbfb9b288f 100644 --- a/dashboardv2/public/css/scss/relationship.scss +++ b/dashboardv2/public/css/scss/relationship.scss @@ -121,7 +121,7 @@ flex-direction: column; gap: 12px; min-width: 280px; - flex: 0 0 auto; + flex: 1 1 0; } @media (max-width: 599px) { @@ -220,6 +220,7 @@ flex-direction: column; padding: 12px; min-height: 0; + min-width: 0; overflow: hidden; .relationship-card-search { @@ -241,6 +242,7 @@ overflow-y: auto; overflow-x: hidden; min-height: 0; + min-width: 0; position: relative; &::-webkit-scrollbar { @@ -267,10 +269,14 @@ padding: 0; margin: 0; text-align: left; + width: 100%; + overflow: hidden; .relationship-card-item { padding: 8px 0; border-bottom: 1px solid #f0f0f0; + width: 100%; + overflow: hidden; &:last-child { border-bottom: none; @@ -290,7 +296,12 @@ font-size: 13px; font-family: inherit; line-height: 1.5; - word-break: break-word; + display: inline-block; + max-width: 100%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + vertical-align: middle; &:hover { text-decoration: underline; @@ -547,4 +558,13 @@ 50% { background-position: -200% 0; } -} \ No newline at end of file +} +.entity-type-name { + &.active { + color: #1976d2; + } + &.deleted { + color: #BB5838; + } +} +.entity-type-name { &.active { color: #1976d2; } &.deleted { color: #BB5838; } } diff --git a/dashboardv2/public/css/scss/theme.scss b/dashboardv2/public/css/scss/theme.scss index 46abb127aed..9e25c63675e 100644 --- a/dashboardv2/public/css/scss/theme.scss +++ b/dashboardv2/public/css/scss/theme.scss @@ -88,6 +88,7 @@ header.atlas-header { overflow: auto; padding-top: 15px !important; padding-bottom: 10px !important; + box-sizing: border-box; &>div { height: 100%; @@ -536,7 +537,8 @@ hr[size="10"] { } .tooltip-inner { - max-width: none; + max-width: 300px; + word-break: break-all; color: #2c2c2c; background-color: #f9f9f9; box-shadow: 0px 0px 3px 0px #8080806b; diff --git a/dashboardv2/public/js/views/detail_page/RelationshipCardsLayoutView.js b/dashboardv2/public/js/views/detail_page/RelationshipCardsLayoutView.js index 5ded221b458..06655f03f93 100644 --- a/dashboardv2/public/js/views/detail_page/RelationshipCardsLayoutView.js +++ b/dashboardv2/public/js/views/detail_page/RelationshipCardsLayoutView.js @@ -489,8 +489,8 @@ define([ var nameValue = item && item.attributes && item.attributes.name ? item.attributes.name : ""; var searchText = _.escape((displayText + " " + qualifiedName + " " + nameValue).toLowerCase()); return "
  • " + - (href ? "" + _.escape(displayLabel) + "" : - "" + _.escape(displayLabel) + "") + + (href ? "" + _.escape(displayLabel) + "" : + "" + _.escape(displayLabel) + "") + "
  • "; }).join(""); @@ -693,6 +693,12 @@ define([ if (this.$el && this.$el.length) { this.$el.html(html); this.bindCardEvents(); + if ($.fn.tooltip) { + this.$el.find('[title]').tooltip({ + placement: 'bottom', + container: 'body' + }); + } } else { console.warn("[RelationshipCardsLayoutView] $el not available, cannot render cards"); } diff --git a/dashboardv2/public/js/views/graph/RelationshipLayoutView.js b/dashboardv2/public/js/views/graph/RelationshipLayoutView.js index 9c11c0017b1..bd3366adac3 100644 --- a/dashboardv2/public/js/views/graph/RelationshipLayoutView.js +++ b/dashboardv2/public/js/views/graph/RelationshipLayoutView.js @@ -29,7 +29,7 @@ define([ "utils/Enums", "utils/UrlLinks", "platform" -], function(require, Backbone, RelationshipLayoutViewtmpl, VLineageList, VEntity, Utils, CommonViewFunction, d3, d3Tip, Enums, UrlLinks, platform) { +], function (require, Backbone, RelationshipLayoutViewtmpl, VLineageList, VEntity, Utils, CommonViewFunction, d3, d3Tip, Enums, UrlLinks, platform) { "use strict"; var RelationshipLayoutView = Backbone.Marionette.LayoutView.extend( @@ -60,17 +60,17 @@ define([ }, /** ui events hash */ - events: function() { + events: function () { var events = {}; - events["click " + this.ui.relationshipDetailClose] = function() { + events["click " + this.ui.relationshipDetailClose] = function () { this.toggleInformationSlider({ close: true }); }; events["keyup " + this.ui.searchNode] = "searchNode"; events["click " + this.ui.boxClose] = "toggleBoxPanel"; - events["change " + this.ui.relationshipViewToggle] = function(e) { + events["change " + this.ui.relationshipViewToggle] = function (e) { this.relationshipViewToggle(e.currentTarget.checked); }; - events["click " + this.ui.noValueToggle] = function(e) { + events["click " + this.ui.noValueToggle] = function (e) { Utils.togglePropertyRelationshipTableEmptyValues({ inputType: this.ui.noValueToggle, tableEl: this.ui.relationshipDetailValue @@ -84,19 +84,19 @@ define([ * intialize a new RelationshipLayoutView Layout * @constructs */ - initialize: function(options) { + initialize: function (options) { _.extend(this, _.pick(options, "entity", "entityName", "guid", "actionCallBack", "attributeDefs", "referredEntities", "entityDefCollection")); this.graphData = this.createData(this.entity); this.relationshipCardCounts = {}; this.relationshipLoadedCounts = {}; this.cardsViewLoadInProgress = false; - - this.handleRelationshipDataUpdate = _.bind(function(payload) { + + this.handleRelationshipDataUpdate = _.bind(function (payload) { var relationshipAttributes = payload && payload.data ? payload.data : payload; var relationshipCounts = payload && payload.counts ? payload.counts : this.relationshipCardCounts; var loadedCounts = payload && payload.loadedCounts ? payload.loadedCounts : this.relationshipLoadedCounts; var referredEntities = payload && payload.referredEntities ? payload.referredEntities : null; - + if (!relationshipAttributes) { return; } @@ -116,7 +116,7 @@ define([ } } }, this); - this.handleRelationshipLoading = _.bind(function(isLoading) { + this.handleRelationshipLoading = _.bind(function (isLoading) { if (isLoading) { this.$(".fontLoader").show(); } else { @@ -124,12 +124,12 @@ define([ } }, this); }, - createData: function(entity) { + createData: function (entity) { var that = this, links = [], nodes = {}; if (entity && entity.relationshipAttributes) { - _.each(entity.relationshipAttributes, function(obj, key) { + _.each(entity.relationshipAttributes, function (obj, key) { var relationValue = obj; if (relationValue && relationValue.entities) { relationValue = relationValue.entities; @@ -152,14 +152,14 @@ define([ } return { nodes: nodes, links: links }; }, - onRender: function() { + onRender: function () { this.isRendered = true; // Initialize: show card view by default (checked = Card) this.ui.relationshipViewToggle.prop('checked', true); this.relationshipViewToggle(true); }, - onShow: function(argument) { + onShow: function (argument) { // Always create graph on show if graph view is active var isGraphView = !this.ui.relationshipViewToggle.is(':checked'); if (isGraphView) { @@ -170,12 +170,12 @@ define([ } } this.createTable(); - + this.ensureCardsView(); }, - ensureCardsView: function(forceRefresh) { + ensureCardsView: function (forceRefresh) { var that = this; - + if (this.relationshipCardsViewInstance) { if (forceRefresh) { this.relationshipCardsViewInstance.cardData = {}; @@ -198,7 +198,7 @@ define([ return; } this.cardsViewLoadInProgress = true; - require(['views/detail_page/RelationshipCardsLayoutView'], function(RelationshipCardsLayoutView) { + require(['views/detail_page/RelationshipCardsLayoutView'], function (RelationshipCardsLayoutView) { try { if (!that.relationshipCardsView) { console.warn("[RelationshipLayoutView] Region not available"); @@ -221,22 +221,22 @@ define([ } finally { that.cardsViewLoadInProgress = false; } - }, function(err) { + }, function (err) { console.error("[RelationshipLayoutView] Failed to load RelationshipCardsLayoutView:", err); that.cardsViewLoadInProgress = false; }); }, - updateCardsShowEmptyValues: function(showEmptyValues) { + updateCardsShowEmptyValues: function (showEmptyValues) { if (!this.relationshipCardsViewInstance) { return; } this.relationshipCardsViewInstance.showEmptyValues = !!showEmptyValues; this.relationshipCardsViewInstance.renderCards(); }, - noRelationship: function() { + noRelationship: function () { this.$("svg").html('No relationship data found'); }, - toggleInformationSlider: function(options) { + toggleInformationSlider: function (options) { var panel = this.$(".relationship-node-details"); if (options && options.close) { panel.removeClass("slide-to-left").addClass("slide-from-left"); @@ -248,10 +248,10 @@ define([ } } }, - toggleBoxPanel: function() { + toggleBoxPanel: function () { this.$(".relationship-node-details").removeClass("slide-to-left").addClass("slide-from-left"); }, - searchNode: function(e) { + searchNode: function (e) { var searchString = $(e.currentTarget).val(), listString = "", data = this.selectedNodeData, @@ -259,7 +259,7 @@ define([ activeEntityColor = "#1976d2", deletedEntityColor = "#BB5838", defaultEntityColor = "#e0e0e0", - normalizeEntity = function(entity) { + normalizeEntity = function (entity) { if (!entity) { return entity; } @@ -271,27 +271,29 @@ define([ } return entity; }.bind(this), - getdefault = function(options) { - return "
    " + options.name + "
    "; + getdefault = function (options) { + var colorClass = options.color === deletedEntityColor ? "deleted" : "active"; + return "
    " + options.name + "
    "; }, - getWithButton = function(options) { + getWithButton = function (options) { var name = options.name, guid = options.options.guid, - entityTypeButton = ""; + entityTypeButton = "", + colorClass = options.color === deletedEntityColor ? "deleted" : "active"; if (guid) { if (options.entity) { - entityTypeButton = "" + name + ""; + entityTypeButton = "" + name + ""; } else if (options.relationship) { - entityTypeButton = "" + name + ""; + entityTypeButton = "" + name + ""; } else { - entityTypeButton = "" + name + ""; + entityTypeButton = "" + name + ""; } } else { - entityTypeButton = "
    " + name + "
    "; + entityTypeButton = "
    " + name + "
    "; } return entityTypeButton; }, - getEntityTypelist = function(options) { + getEntityTypelist = function (options) { var name = options.entityName ? options.entityName : Utils.getName(options, "displayText"), entityTypeHtml = ""; if (options.entityStatus == "ACTIVE" || options.status == "ACTIVE") { @@ -327,12 +329,12 @@ define([ }; this.ui.searchNode.hide(); this.$("[data-id='typeName']").text(typeName); - var getElement = function(options) { + var getElement = function (options) { var name = options.entityName ? options.entityName : Utils.getName(options, "displayText"); var entityTypeButton = getEntityTypelist(options); return entityTypeButton; }; - var buildEntityObj = function(item) { + var buildEntityObj = function (item) { var normalized = normalizeEntity(item); var ref = normalized && normalized.guid ? this.referredEntities && this.referredEntities[normalized.guid] : null; var displayText = (ref && (ref.displayText || (ref.attributes && ref.attributes.name))) || @@ -347,26 +349,26 @@ define([ typeName: typeName }); }.bind(this); - var buildListItem = function(item) { + var buildListItem = function (item) { var name = item.entityName || Utils.getName(item, "displayText"); var typeName = item.typeName || ""; var displayLabel = typeName ? name + " (" + typeName + ")" : name; var href = item.guid ? "#!/detailPage/" + item.guid + "?tabActive=relationship" : ""; var isDeleted = (item.entityStatus || item.status) == "DELETED"; - var color = isDeleted ? deletedEntityColor : activeEntityColor; + var colorClass = isDeleted ? "deleted" : "active"; var content = href - ? "" + _.escape(displayLabel) + "" - : "" + _.escape(displayLabel) + ""; - return "
  • " + content + "
  • "; + ? "" + _.escape(displayLabel) + "" + : "" + _.escape(displayLabel) + ""; + return "
  • " + content + "
  • "; }; if (_.isArray(data)) { - data = _.map(data, function(item) { + data = _.map(data, function (item) { return buildEntityObj(item); }); if (data.length > 1) { this.ui.searchNode.show(); } - _.each(_.sortBy(data, "entityName"), function(val) { + _.each(_.sortBy(data, "entityName"), function (val) { var name = val.entityName || Utils.getName(val, "displayText"); var searchTarget = (name + " " + (val.typeName || "")).toLowerCase(); if (searchString) { @@ -384,8 +386,14 @@ define([ listString += buildListItem(data); } this.$("[data-id='entityList']").html(listString); + if ($.fn.tooltip) { + this.$("[data-id='entityList']").find('[title]').tooltip({ + placement: 'bottom', + container: 'body' + }); + } }, - createGraph: function(data) { + createGraph: function (data) { //Ref - http://bl.ocks.org/fancellu/2c782394602a93921faff74e594d1bb1 var that = this, @@ -398,7 +406,7 @@ define([ deletedEntityColor = "#BB5838", defaultEntityColor = "#e0e0e0", selectedNodeColor = "#4a90e2"; - var getNodeCount = function(node) { + var getNodeCount = function (node) { if (!node) { return 0; } @@ -434,7 +442,7 @@ define([ var zoom = d3 .zoom() .scaleExtent([0.1, 4]) - .on("zoom", function() { + .on("zoom", function () { container.attr("transform", d3.event.transform); }); @@ -456,7 +464,7 @@ define([ .attr("xoverflow", "visible") .append("svg:path") .attr("d", "M 0,-5 L 10,0 L 0,5") - .attr("fill", function(d) { + .attr("fill", function (d) { return d === "deletedLink" ? deletedEntityColor : activeEntityColor; }) .attr("stroke", "none"); @@ -467,7 +475,7 @@ define([ "link", d3 .forceLink(links) - .id(function(d) { + .id(function (d) { return d.name; }) .distance(150) @@ -476,45 +484,45 @@ define([ .force("center", d3.forceCenter(width / 2, height / 2)) .force("collision", d3.forceCollide().radius(50)); - path = container + path = container .append("g") - .selectAll("path") - .data(links) - .enter() + .selectAll("path") + .data(links) + .enter() .append("path") - .attr("class", "relatioship-link") - .attr("marker-end", function(d) { + .attr("class", "relatioship-link") + .attr("marker-end", function (d) { return isAllEntityRelationDeleted({ data: d, type: "link" }) ? "url(#deletedLink)" : "url(#activeLink)"; }) - .attr("stroke", function(d) { + .attr("stroke", function (d) { return isAllEntityRelationDeleted({ data: d, type: "link" }) ? deletedEntityColor : activeEntityColor; - }); + }); - node = container + node = container .append("g") - .selectAll(".node") - .data(nodes) - .enter() - .append("g") - .attr("class", "node") - .on("mousedown", function() { - d3.event.preventDefault(); - }) - .on("click", function(d) { - if (d3.event.defaultPrevented) return; // ignore drag - if (d && d.value && d.value.guid == that.guid) { - that.ui.boxClose.trigger("click"); - return; - } - that.toggleBoxPanel({ el: that.$(".relationship-node-details") }); - that.ui.searchNode.data({ obj: d }); - $(this) - .find("circle") - .addClass("node-detail-highlight"); - that.updateRelationshipDetails({ obj: d }); - }) - .call( - d3 + .selectAll(".node") + .data(nodes) + .enter() + .append("g") + .attr("class", "node") + .on("mousedown", function () { + d3.event.preventDefault(); + }) + .on("click", function (d) { + if (d3.event.defaultPrevented) return; // ignore drag + if (d && d.value && d.value.guid == that.guid) { + that.ui.boxClose.trigger("click"); + return; + } + that.toggleBoxPanel({ el: that.$(".relationship-node-details") }); + that.ui.searchNode.data({ obj: d }); + $(this) + .find("circle") + .addClass("node-detail-highlight"); + that.updateRelationshipDetails({ obj: d }); + }) + .call( + d3 .drag() .on("start", dragstarted) .on("drag", dragged) @@ -522,105 +530,105 @@ define([ ); node.append("circle") - .attr("r", function() { + .attr("r", function () { return 25; - }) - .attr("fill", function(d) { + }) + .attr("fill", function (d) { if (d.name === that.entity.typeName) { - return selectedNodeColor; - } else { + return selectedNodeColor; + } else { return isAllEntityRelationDeleted({ data: d, type: "node" }) ? deletedEntityColor : activeEntityColor; - } - }) + } + }) .attr("stroke", "#fff") .attr("stroke-width", "2px") .style("cursor", "pointer") - .on("click", function(d) { + .on("click", function (d) { if (d && d.value && d.value.guid == that.guid) { return; } that.selectedNodeData = d.value; that.selectedNodeType = d.name; - + // Show the panel var panel = that.$(".relationship-node-details"); panel.removeClass("slide-to-left").addClass("slide-from-left"); - + // Trigger after a small delay to ensure DOM is ready - setTimeout(function() { + setTimeout(function () { panel.removeClass("slide-from-left").addClass("slide-to-left"); that.searchNode({ currentTarget: that.ui.searchNode }); }, 10); - }); + }); - node.append("text") - .attr("x", 0) - .attr("y", 0) - .attr("dy", function() { - return 25 - 17; - }) - .attr("text-anchor", "middle") - .style("font-family", "FontAwesome") - .style("font-size", "25px") - .attr("class", "relationship-node-icon") - .text(function(d) { - var iconObj = Enums.graphIcon[d.name]; - if (iconObj && iconObj.textContent) { - return iconObj.textContent; - } - if (d && _.isArray(d.value) && d.value.length > 1) { - return "\uf0c5"; - } - return "\uf016"; - }) - .attr("fill", "#fff"); - - var countBox = node.append("g"); - - countBox - .append("circle") - .attr("cx", 18) - .attr("cy", -20) - .attr("class", "relationship-node-count") - .attr("r", function(d) { - var count = getNodeCount(d); - if (count > 1) { - return 9; - } - }); + node.append("text") + .attr("x", 0) + .attr("y", 0) + .attr("dy", function () { + return 25 - 17; + }) + .attr("text-anchor", "middle") + .style("font-family", "FontAwesome") + .style("font-size", "25px") + .attr("class", "relationship-node-icon") + .text(function (d) { + var iconObj = Enums.graphIcon[d.name]; + if (iconObj && iconObj.textContent) { + return iconObj.textContent; + } + if (d && _.isArray(d.value) && d.value.length > 1) { + return "\uf0c5"; + } + return "\uf016"; + }) + .attr("fill", "#fff"); + + var countBox = node.append("g"); + + countBox + .append("circle") + .attr("cx", 18) + .attr("cy", -20) + .attr("class", "relationship-node-count") + .attr("r", function (d) { + var count = getNodeCount(d); + if (count > 1) { + return 9; + } + }); - countBox - .append("text") - .attr("dx", 18) - .attr("dy", -16) - .attr("text-anchor", "middle") - .attr("fill", defaultEntityColor) - .attr("class", "relationship-node-count") - .text(function(d) { - var count = getNodeCount(d); - if (count > 1) { - return count; - } - }); + countBox + .append("text") + .attr("dx", 18) + .attr("dy", -16) + .attr("text-anchor", "middle") + .attr("fill", defaultEntityColor) + .attr("class", "relationship-node-count") + .text(function (d) { + var count = getNodeCount(d); + if (count > 1) { + return count; + } + }); - node - .append("text") - .attr("x", -15) - .attr("y", "35") - .attr("class", "relationship-node-label") - .text(function(d) { - return d.name; - }); + node + .append("text") + .attr("x", -15) + .attr("y", "35") + .attr("class", "relationship-node-label") + .text(function (d) { + return d.name; + }); - simulation.on("tick", function() { - path.attr("d", function(d) { + simulation.on("tick", function () { + path.attr("d", function (d) { var dx = d.target.x - d.source.x, dy = d.target.y - d.source.y, dr = Math.sqrt(dx * dx + dy * dy); return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y; }); - node.attr("transform", function(d) { + node.attr("transform", function (d) { return "translate(" + d.x + "," + d.y + ")"; }); }); @@ -659,7 +667,7 @@ define([ } return ( - _.findIndex(d.value, function(val) { + _.findIndex(d.value, function (val) { if (type == "node") { return (val.entityStatus || val.status) == "ACTIVE"; } else { @@ -668,7 +676,7 @@ define([ }) == -1 ); } - var zoomClick = function() { + var zoomClick = function () { var scaleFactor = 0.8; if (this.id === 'zoom_in') { scaleFactor = 1.3; @@ -678,7 +686,7 @@ define([ d3.selectAll(this.$('.lineageZoomButton')).on('click', zoomClick); }, - createTable: function() { + createTable: function () { this.entityModel = new VEntity({}); var table = CommonViewFunction.propertyTable({ scope: this, @@ -691,7 +699,7 @@ define([ tableEl: this.ui.relationshipDetailValue }); }, - relationshipViewToggle: function(checked) { + relationshipViewToggle: function (checked) { var that = this; // In the original code: checked = Table, unchecked = Graph @@ -705,12 +713,12 @@ define([ this.ui.relationshipDetailTable.hide(); this.ui.relationshipDetailValue.hide(); this.ui.relationshipCardsView.show(); - + // Render card view if not already rendered this.ensureCardsView(); - + // Force a re-render after a short delay to ensure DOM is ready - setTimeout(function() { + setTimeout(function () { if (that.relationshipCardsViewInstance) { that.relationshipCardsViewInstance.renderCards(); } @@ -724,7 +732,7 @@ define([ this.ui.relationshipDetailTable.show(); this.ui.relationshipDetailValue.show(); this.ui.relationshipCardsView.hide(); - + // Ensure graph is created if it hasn't been created yet if (this.graphData && !_.isEmpty(this.graphData.links)) { // Clear existing graph and recreate @@ -735,8 +743,8 @@ define([ } } }, - - onDestroy: function() { + + onDestroy: function () { this.cardsViewLoadInProgress = false; if (this.relationshipCardsViewInstance) { this.relationshipCardsViewInstance.destroy(); From 411af1045e77bc2df1998baee8a63110583241d1 Mon Sep 17 00:00:00 2001 From: Brijesh Bhalala Date: Wed, 26 Aug 2026 10:24:28 +0530 Subject: [PATCH 2/2] ATLAS-5376: Atlas UI: Relationship cards layout breaking, overlapping, and tooltip placement issues with long entity names (React & Classic UI) --- dashboard/src/styles/detailPage.scss | 8 ++++++ .../EntityDetailTabs/RelationshipLineage.tsx | 28 ++++++++----------- dashboardv2/public/css/scss/relationship.scss | 1 - dashboardv2/public/css/scss/theme.scss | 2 +- .../RelationshipCardsLayoutView.js | 9 ++++++ .../js/views/graph/RelationshipLayoutView.js | 8 +++++- 6 files changed, 36 insertions(+), 20 deletions(-) diff --git a/dashboard/src/styles/detailPage.scss b/dashboard/src/styles/detailPage.scss index 4f05362a2cc..fb715063c13 100644 --- a/dashboard/src/styles/detailPage.scss +++ b/dashboard/src/styles/detailPage.scss @@ -592,3 +592,11 @@ pre.code-block .json-string { text-overflow: ellipsis; white-space: nowrap; } + +.text-active { + color: #1976d2 !important; +} + +.text-deleted { + color: #BB5838 !important; +} diff --git a/dashboard/src/views/DetailPage/EntityDetailTabs/RelationshipLineage.tsx b/dashboard/src/views/DetailPage/EntityDetailTabs/RelationshipLineage.tsx index c5e61d1094d..974e5e5641c 100644 --- a/dashboard/src/views/DetailPage/EntityDetailTabs/RelationshipLineage.tsx +++ b/dashboard/src/views/DetailPage/EntityDetailTabs/RelationshipLineage.tsx @@ -51,7 +51,6 @@ import { Link as MUILink } from "@mui/material"; interface CustomLinkProps { href: string; status: string; - entityColor: string; guid: string; name: string; typeName: string; @@ -61,13 +60,12 @@ interface CustomLinkProps { const CustomLink = ({ href, status, - entityColor, guid, name, typeName, params }: CustomLinkProps): JSX.Element => { - const displayLabel = `${name} (${typeName})`; + const displayLabel = typeName ? `${name} (${typeName})` : name; return (
  • @@ -77,7 +75,7 @@ const CustomLink = ({ pathname: href, search: params.toString() ? params.toString() : "" }} - className={`relationship-node-link ${entityColor === "#1976d2" ? "text-blue" : "text-red"}`} + className={`relationship-node-link ${status.includes("deleted-relation") ? "text-deleted" : "text-active"}`} replace={true} underline="hover" > @@ -93,8 +91,8 @@ const RelationshipLineage = ({ relationshipAttributes, isLoading }: { - entity: Record; - relationshipAttributes?: Record; + entity: Record; + relationshipAttributes?: Record; isLoading?: boolean; }) => { const entityData = cloneDeep(entity); @@ -109,13 +107,13 @@ const RelationshipLineage = ({ const zoomOutButtonRef = useRef(null); const relationshipSVG = useRef(null); const [drawerOpen, setDrawerOpen] = useState(false); - const [nodeDetails, setNodeDetails] = useState({}); + const [nodeDetails, setNodeDetails] = useState>({}); const [searchTerm, setSearchTerm] = useState(""); const [zoomId, setZoomId] = useState(""); - const createData = (entityData: Record) => { + const createData = (entityData: Record) => { let links = []; - let nodes: Record = {}; + let nodes: Record = {}; if (entityData && entityData.relationshipAttributes) { for (const obj in entityData.relationshipAttributes) { if (!isEmpty(entityData.relationshipAttributes[obj])) { @@ -201,7 +199,7 @@ const RelationshipLineage = ({ var forceLink = d3 .forceLink() - .id(function (d: any) { + .id(function (d: Record) { return d.id; }) .distance(function (d) { @@ -266,7 +264,7 @@ const RelationshipLineage = ({ d.radius = 25; return d.radius; }) - .attr("fill", function (d: any) { + .attr("fill", function (d: Record) { if (d && d.value && d.value.guid == guid) { if (isAllEntityRelationDeleted({ data: d, type: "node" })) { return deletedEntityColor; @@ -486,7 +484,6 @@ const RelationshipLineage = ({ ? " deleted-relation" : ""; let nodeGuid = options.guid; - let entityColor = obj.color; let name = obj.name; let typeName = options.typeName; let keys = Array.from(searchParams.keys()); @@ -505,7 +502,6 @@ const RelationshipLineage = ({ {displayLabel} diff --git a/dashboardv2/public/css/scss/relationship.scss b/dashboardv2/public/css/scss/relationship.scss index cdbfb9b288f..ad08bb6f673 100644 --- a/dashboardv2/public/css/scss/relationship.scss +++ b/dashboardv2/public/css/scss/relationship.scss @@ -567,4 +567,3 @@ color: #BB5838; } } -.entity-type-name { &.active { color: #1976d2; } &.deleted { color: #BB5838; } } diff --git a/dashboardv2/public/css/scss/theme.scss b/dashboardv2/public/css/scss/theme.scss index 9e25c63675e..fa564471994 100644 --- a/dashboardv2/public/css/scss/theme.scss +++ b/dashboardv2/public/css/scss/theme.scss @@ -538,7 +538,7 @@ hr[size="10"] { .tooltip-inner { max-width: 300px; - word-break: break-all; + word-wrap: break-word; color: #2c2c2c; background-color: #f9f9f9; box-shadow: 0px 0px 3px 0px #8080806b; diff --git a/dashboardv2/public/js/views/detail_page/RelationshipCardsLayoutView.js b/dashboardv2/public/js/views/detail_page/RelationshipCardsLayoutView.js index 06655f03f93..66917fd894c 100644 --- a/dashboardv2/public/js/views/detail_page/RelationshipCardsLayoutView.js +++ b/dashboardv2/public/js/views/detail_page/RelationshipCardsLayoutView.js @@ -691,6 +691,9 @@ define([ } if (this.$el && this.$el.length) { + if ($.fn.tooltip) { + this.$el.find('[title]').tooltip('destroy'); + } this.$el.html(html); this.bindCardEvents(); if ($.fn.tooltip) { @@ -702,6 +705,12 @@ define([ } else { console.warn("[RelationshipCardsLayoutView] $el not available, cannot render cards"); } + }, + + onDestroy: function() { + if ($.fn.tooltip && this.$el) { + this.$el.find('[title]').tooltip('destroy'); + } } }); diff --git a/dashboardv2/public/js/views/graph/RelationshipLayoutView.js b/dashboardv2/public/js/views/graph/RelationshipLayoutView.js index bd3366adac3..d9e6ed8fe0e 100644 --- a/dashboardv2/public/js/views/graph/RelationshipLayoutView.js +++ b/dashboardv2/public/js/views/graph/RelationshipLayoutView.js @@ -359,7 +359,7 @@ define([ var content = href ? "" + _.escape(displayLabel) + "" : "" + _.escape(displayLabel) + ""; - return "
  • " + content + "
  • "; + return "
  • " + content + "
  • "; }; if (_.isArray(data)) { data = _.map(data, function (item) { @@ -385,6 +385,9 @@ define([ data = buildEntityObj(data); listString += buildListItem(data); } + if ($.fn.tooltip) { + this.$("[data-id='entityList']").find('[title]').tooltip('destroy'); + } this.$("[data-id='entityList']").html(listString); if ($.fn.tooltip) { this.$("[data-id='entityList']").find('[title]').tooltip({ @@ -745,6 +748,9 @@ define([ }, onDestroy: function () { + if ($.fn.tooltip && this.$el) { + this.$el.find('[title]').tooltip('destroy'); + } this.cardsViewLoadInProgress = false; if (this.relationshipCardsViewInstance) { this.relationshipCardsViewInstance.destroy();