This is an automated email from the ASF dual-hosted git repository.

ppawar pushed a commit to branch atlas-2.5
in repository https://gitbox.apache.org/repos/asf/atlas.git


The following commit(s) were added to refs/heads/atlas-2.5 by this push:
     new bfde94988 ATLAS-5243: Atlas UI: Fix relationship tab UX: navigation, 
graph popup, and skeleton handling in classic and React UIs (#572) ( cherry 
picked from 1c548f9a697e35b07d6f6b1a0b28f75c28f33ca8)
bfde94988 is described below

commit bfde94988f24a13688cda923f8465ff8fe6b733c
Author: Prasad Pawar <[email protected]>
AuthorDate: Tue Mar 17 20:19:35 2026 +0530

    ATLAS-5243: Atlas UI: Fix relationship tab UX: navigation, graph popup, and 
skeleton handling in classic and React UIs (#572)
    ( cherry picked from 1c548f9a697e35b07d6f6b1a0b28f75c28f33ca8)
---
 dashboard/src/styles/detailPage.scss               |   9 ++
 .../EntityDetailTabs/RelationshipCard.tsx          |  14 ++-
 .../EntityDetailTabs/RelationshipLineage.tsx       | 107 +++++++++------------
 .../EntityDetailTabs/RelationshipsTab.tsx          |  13 ++-
 dashboardv2/public/css/scss/relationship.scss      |   8 ++
 dashboardv2/public/js/router/Router.js             |  18 ++--
 .../js/views/detail_page/DetailPageLayoutView.js   |   5 +-
 .../detail_page/RelationshipCardsLayoutView.js     |  15 ++-
 .../js/views/graph/RelationshipLayoutView.js       |  17 ++--
 9 files changed, 117 insertions(+), 89 deletions(-)

diff --git a/dashboard/src/styles/detailPage.scss 
b/dashboard/src/styles/detailPage.scss
index 0322027e3..360927694 100644
--- a/dashboard/src/styles/detailPage.scss
+++ b/dashboard/src/styles/detailPage.scss
@@ -345,6 +345,15 @@ pre.code-block .json-string {
   text-decoration: underline;
 }
 
+.relationship-card__link--deleted,
+.relationship-card__text--deleted {
+  color: #bb5838;
+}
+
+.relationship-card__link--deleted:hover {
+  color: #bb5838;
+}
+
 .relationship-card__text {
   color: #334155;
 }
diff --git 
a/dashboard/src/views/DetailPage/EntityDetailTabs/RelationshipCard.tsx 
b/dashboard/src/views/DetailPage/EntityDetailTabs/RelationshipCard.tsx
index dc7ea748e..c9bcebb0e 100644
--- a/dashboard/src/views/DetailPage/EntityDetailTabs/RelationshipCard.tsx
+++ b/dashboard/src/views/DetailPage/EntityDetailTabs/RelationshipCard.tsx
@@ -336,6 +336,16 @@ function RelationshipCard({
                                                                const itemGuid 
= item?.guid
                                                                const href = 
itemGuid ? `/detailPage/${itemGuid}` : ''
                                                                const 
displayText = getDisplayText(item)
+                                                               const ref = 
itemGuid ? referredEntities?.[itemGuid] : null
+                                                               const status =
+                                                                       
ref?.status || item?.status || item?.attributes?.status || ''
+                                                               const isDeleted 
= status === 'DELETED'
+                                                               const linkClass 
= isDeleted
+                                                                       ? 
'relationship-card__link relationship-card__link--deleted'
+                                                                       : 
'relationship-card__link'
+                                                               const textClass 
= isDeleted
+                                                                       ? 
'relationship-card__text relationship-card__text--deleted'
+                                                                       : 
'relationship-card__text'
                                                                return (
                                                                        <li
                                                                                
key={`${attributeName}-${index}`}
@@ -350,13 +360,13 @@ function RelationshipCard({
                                                                                
                        onKeyDown={(event) =>
                                                                                
                                handleKeyDown(event, href)
                                                                                
                        }
-                                                                               
                        className='relationship-card__link'
+                                                                               
                        className={linkClass}
                                                                                
                >
                                                                                
                        {displayText}
                                                                                
                </Link>
                                                                                
        </LightTooltip>
                                                                                
) : (
-                                                                               
        <span className='relationship-card__text'>
+                                                                               
        <span className={textClass}>
                                                                                
                {displayText}
                                                                                
        </span>
                                                                                
)}
diff --git 
a/dashboard/src/views/DetailPage/EntityDetailTabs/RelationshipLineage.tsx 
b/dashboard/src/views/DetailPage/EntityDetailTabs/RelationshipLineage.tsx
index 61a86a8f3..c5fb1f653 100644
--- a/dashboard/src/views/DetailPage/EntityDetailTabs/RelationshipLineage.tsx
+++ b/dashboard/src/views/DetailPage/EntityDetailTabs/RelationshipLineage.tsx
@@ -47,7 +47,6 @@ import ArrowRightAltIcon from 
"@mui/icons-material/ArrowRightAlt";
 import { CloseIcon, LightTooltip } from "@components/muiComponents";
 import { useAppSelector } from "@hooks/reducerHook";
 import { Link as MUILink } from "@mui/material";
-import DeleteOutlineOutlinedIcon from 
"@mui/icons-material/DeleteOutlineOutlined";
 
 const CustomLink = ({
   href,
@@ -59,7 +58,7 @@ const CustomLink = ({
   params
 }: any): any => {
   return (
-    <li className={status} style={{ listStyle: "numeric" }}>
+    <li className={status}>
       <MUILink
         component={RouterLink}
         to={{
@@ -133,9 +132,10 @@ const RelationshipLineage = ({
   const createGraph = (data) => {
     // Use getBoundingClientRect to get the actual width and height
     const svgElement = svgRef?.current;
-    const { width, height } = svgElement
-      ? svgElement.getBoundingClientRect()
-      : { width: 0, height: 0 };
+    const rect = svgElement ? svgElement.getBoundingClientRect() : null;
+    const width = rect ? rect.width : 0;
+    const height = rect ? rect.height : 0;
+    const padding = 60;
 
     let nodes = d3.values(data.nodes);
     let links = data.links;
@@ -147,8 +147,8 @@ const RelationshipLineage = ({
 
     var svg = d3
         .select(svgElement)
-        .attr("viewBox", `0 0 ${width} ${height}`)
-        .attr("enable-background", `new 0 0 ${width} ${height}`),
+        .attr("viewBox", `${-padding} ${-padding} ${width + padding * 2} 
${height + padding * 2}`)
+        .attr("enable-background", `new ${-padding} ${-padding} ${width + 
padding * 2} ${height + padding * 2}`),
       node,
       path;
 
@@ -295,24 +295,30 @@ const RelationshipLineage = ({
           return "#fff";
         });
 
-      var countBox = circleContainer.append("g");
+      var countBox = circleContainer.append("g").attr("class", 
"relationship-node-count");
 
       countBox
         .append("circle")
-        .attr("cx", 18)
-        .attr("cy", -20)
+        .attr("cx", 22)
+        .attr("cy", 32)
         .attr("r", function (d) {
           if (isArray(d.value) && d.value.length > 1) {
-            return 10;
+            return 12;
           }
-        });
+        })
+        .attr("fill", "#333")
+        .attr("stroke", "#fff")
+        .attr("stroke-width", "1.5px");
 
       countBox
         .append("text")
-        .attr("dx", 18)
-        .attr("dy", -16)
+        .attr("x", 22)
+        .attr("y", 32)
         .attr("text-anchor", "middle")
-        .attr("fill", defaultEntityColor)
+        .attr("dominant-baseline", "middle")
+        .attr("fill", "#fff")
+        .style("font-size", "11px")
+        .style("font-weight", "600")
         .text(function (d) {
           if (isArray(d.value) && d.value.length > 1) {
             return d.value.length;
@@ -452,11 +458,13 @@ const RelationshipLineage = ({
     let searchString = options.searchString;
     let listString = [];
     const getEntityTypelist = (options) => {
-      let activeEntityColor = "#4a90e2";
+      let activeEntityColor = "#1976d2";
       let deletedEntityColor = "#BB5838";
+      const entityStatus = options.entityStatus || options.status;
+      const relationshipStatus = options.relationshipStatus || options.status;
       const getdefault = (obj) => {
         let options = obj.options;
-        let status = entityStateReadOnly[options.entityStatus || 
options.status]
+        let status = entityStateReadOnly[entityStatus]
           ? " deleted-relation"
           : "";
         let nodeGuid = options.guid;
@@ -504,61 +512,26 @@ const RelationshipLineage = ({
       };
       const getWithButton = (obj) => {
         let options = obj.options;
-        let status = entityStateReadOnly[options.entityStatus || 
options.status]
+        let status = entityStateReadOnly[entityStatus]
           ? " deleted-relation"
           : "";
         let entityColor = obj.color;
         let name = obj.name;
-        let typeName = options.typeName;
 
-        let relationship = obj.relationship || false;
-        let entity = obj.entity || false;
-        let icon = '<i class="fa fa-trash"></i>';
-        let title = "Deleted";
-        if (relationship) {
-          icon = '<i class="fa fa-long-arrow-right"></i>';
-          status = entityStateReadOnly[
-            options.relationshipStatus || options.status
-          ]
+        if (obj.relationship) {
+          status = entityStateReadOnly[relationshipStatus]
             ? "deleted-relation"
             : "";
-          title = "Relationship Deleted";
         }
         return (
-          <li className={status} style={{ listStyle: "numeric" }}>
+          <li className={status}>
             <MUILink
               component={RouterLink}
-              to={`#!/detailPage/${options.guid}?tabActive=relationship`}
+              to={`/detailPage/${options.guid}?tabActive=relationship`}
               style={{ color: entityColor }}
             >
               {name} ({options.typeName})
             </MUILink>
-
-            <Button
-              type="button"
-              title={title}
-              className="btn btn-sm deleteBtn deletedTableBtn btn-action"
-              startIcon={
-                relationship ? (
-                  <ArrowRightAltIcon sx={{ fontSize: "1.25rem" }} />
-                ) : (
-                  <LightTooltip title="Deleted">
-                    <IconButton
-                      aria-label="back"
-                      sx={{
-                        display: "inline-flex",
-                        position: "relative",
-                        padding: "4px",
-                        marginLeft: "4px",
-                        color: (theme) => theme.palette.grey[500]
-                      }}
-                    >
-                      <DeleteOutlineOutlinedIcon sx={{ fontSize: "1.25rem" }} 
/>
-                    </IconButton>
-                  </LightTooltip>
-                )
-              }
-            ></Button>
           </li>
         );
       };
@@ -566,22 +539,22 @@ const RelationshipLineage = ({
       const name = options.entityName
         ? options.entityName
         : extractKeyValueFromEntity(options, "displayText").name;
-      if (options.entityStatus == "ACTIVE") {
-        if (options.relationshipStatus == "ACTIVE") {
+      if (entityStatus === "ACTIVE") {
+        if (relationshipStatus === "ACTIVE") {
           return getdefault({
             color: activeEntityColor,
             options: options,
             name: name
           });
-        } else if (options.relationshipStatus == "DELETED") {
+        } else if (relationshipStatus === "DELETED") {
           return getWithButton({
-            color: activeEntityColor,
+            color: deletedEntityColor,
             options: options,
             name: name,
             relationship: true
           });
         }
-      } else if (options.entityStatus == "DELETED") {
+      } else if (entityStatus === "DELETED") {
         return getWithButton({
           color: deletedEntityColor,
           options: options,
@@ -655,10 +628,16 @@ const RelationshipLineage = ({
         {/* )} */}
 
         <Stack
+          component="ol"
           gap={"0.875rem"}
-          paddingLeft="0.5rem"
           maxHeight="220px"
-          sx={{ overflowY: "auto" }}
+          sx={{
+            overflowY: "auto",
+            listStyleType: "decimal",
+            listStylePosition: "outside",
+            paddingLeft: "1.75em",
+            margin: 0
+          }}
         >
           {listString}
         </Stack>
diff --git 
a/dashboard/src/views/DetailPage/EntityDetailTabs/RelationshipsTab.tsx 
b/dashboard/src/views/DetailPage/EntityDetailTabs/RelationshipsTab.tsx
index c6e07e8e2..9f26786f8 100644
--- a/dashboard/src/views/DetailPage/EntityDetailTabs/RelationshipsTab.tsx
+++ b/dashboard/src/views/DetailPage/EntityDetailTabs/RelationshipsTab.tsx
@@ -70,6 +70,8 @@ const RelationshipsTab: React.FC<EntityDetailTabProps> = ({
     Record<string, boolean>
   >({});
   const [showInitialSkeletons, setShowInitialSkeletons] = 
useState<boolean>(true);
+  const [hasRelationshipApiError, setHasRelationshipApiError] =
+    useState<boolean>(false);
   const initialSkeletonTimerRef = useRef<ReturnType<typeof setTimeout> | null>(
     null
   );
@@ -79,6 +81,7 @@ const RelationshipsTab: React.FC<EntityDetailTabProps> = ({
     setCardData({});
     setCardTotalCounts({});
     setShowInitialSkeletons(true);
+    setHasRelationshipApiError(false);
     fetchStartedRef.current = false;
 
     if (initialSkeletonTimerRef.current) {
@@ -239,6 +242,7 @@ const RelationshipsTab: React.FC<EntityDetailTabProps> = ({
         })
         .catch((err) => {
           console.error(`Error fetching relationship ${relationName}:`, err);
+          setHasRelationshipApiError(true);
           setCardData((prev) => ({ ...prev, [relationName]: [] }));
           setSortByNameByAttr((prev) => ({
             ...prev,
@@ -254,6 +258,11 @@ const RelationshipsTab: React.FC<EntityDetailTabProps> = ({
           if (completedCount >= totalCount) {
             setInitialLoadDone(true);
             setCardLoading(false);
+            setShowInitialSkeletons(false);
+            if (initialSkeletonTimerRef.current) {
+              clearTimeout(initialSkeletonTimerRef.current);
+              initialSkeletonTimerRef.current = null;
+            }
           }
         });
     });
@@ -555,7 +564,9 @@ const RelationshipsTab: React.FC<EntityDetailTabProps> = ({
                   Object.values(cardData).every((arr) => isEmpty(arr)) &&
                   !checked) ? (
                 <div className="relationship-cards-empty">
-                  No relationship data available
+                  {hasRelationshipApiError
+                    ? "Failed to load relationship data"
+                    : "No relationship data available"}
                 </div>
               ) : (
                 <div className="relationship-cards-grid 
relationship-cards-grid--custom">
diff --git a/dashboardv2/public/css/scss/relationship.scss 
b/dashboardv2/public/css/scss/relationship.scss
index 98e22a8fc..133cabcae 100644
--- a/dashboardv2/public/css/scss/relationship.scss
+++ b/dashboardv2/public/css/scss/relationship.scss
@@ -295,6 +295,14 @@
                     &:hover {
                         text-decoration: underline;
                     }
+
+                    &.relationship-card-link--deleted {
+                        color: $delete_link;
+
+                        &:hover {
+                            color: $delete_link;
+                        }
+                    }
                 }
 
                 .value-text {
diff --git a/dashboardv2/public/js/router/Router.js 
b/dashboardv2/public/js/router/Router.js
index 7af2ca1c4..3f89f2d2a 100644
--- a/dashboardv2/public/js/router/Router.js
+++ b/dashboardv2/public/js/router/Router.js
@@ -181,16 +181,16 @@ define([
                     });
 
                     var dOptions = _.extend({ id: id, value: paramObj }, 
options);
-                    that.renderViewIfNotExists({
-                        view: App.rNContent,
-                        viewName: "DetailPageLayoutView",
-                        manualRender: function() {
-                            this.view.currentView.manualRender(dOptions);
-                        },
-                        render: function() {
-                            return new DetailPageLayoutView(dOptions);
+                    var currentView = App.rNContent.currentView;
+                    var isSameEntity = currentView && currentView._viewName 
=== "DetailPageLayoutView" && currentView.id === id;
+                    if (isSameEntity) {
+                        currentView.manualRender(dOptions);
+                    } else {
+                        if (currentView && currentView.destroy) {
+                            currentView.destroy();
                         }
-                    });
+                        App.rNContent.show(new DetailPageLayoutView(dOptions));
+                    }
                 });
             }
         },
diff --git a/dashboardv2/public/js/views/detail_page/DetailPageLayoutView.js 
b/dashboardv2/public/js/views/detail_page/DetailPageLayoutView.js
index 0c4328f54..59006946f 100644
--- a/dashboardv2/public/js/views/detail_page/DetailPageLayoutView.js
+++ b/dashboardv2/public/js/views/detail_page/DetailPageLayoutView.js
@@ -294,9 +294,9 @@ define(['require',
                                 this.editEntity = true;
                             }
                         }
-                        if (collectionJSON.attributes && 
collectionJSON.attributes.columns) {
+                        if (collectionJSON.attributes && 
_.isArray(collectionJSON.attributes.columns)) {
                             var valueSorted = 
_.sortBy(collectionJSON.attributes.columns, function(val) {
-                                return val.attributes && 
val.attributes.position
+                                return val && val.attributes && 
val.attributes.position;
                             });
                             collectionJSON.attributes.columns = valueSorted;
                         }
@@ -449,6 +449,7 @@ define(['require',
                     var oldId = this.id;
                     _.extend(this, _.pick(options, 'value', 'id'));
                     if (this.id !== oldId) {
+                        this.detailPageObj = null;
                         this.collection.url = UrlLinks.entitiesApiUrl({ guid: 
this.id, minExtInfo: true });
                         this.fetchCollection();
                     }
diff --git 
a/dashboardv2/public/js/views/detail_page/RelationshipCardsLayoutView.js 
b/dashboardv2/public/js/views/detail_page/RelationshipCardsLayoutView.js
index b0444cc40..6c60e36bc 100644
--- a/dashboardv2/public/js/views/detail_page/RelationshipCardsLayoutView.js
+++ b/dashboardv2/public/js/views/detail_page/RelationshipCardsLayoutView.js
@@ -46,6 +46,7 @@ define([
             this.showDeletedByName = {};
             this.pageLimitByName = {};
             this.isInitialLoading = false;
+            this.hasApiError = false;
         },
         onRender: function() {
             this.fetchInitialCards();
@@ -179,6 +180,7 @@ define([
                 return;
             }
 
+            this.hasApiError = false;
             if (_.isFunction(this.onDataLoading)) {
                 this.onDataLoading(true);
             }
@@ -233,6 +235,7 @@ define([
                         },
                         function(error) {
                             console.error("[RelationshipCardsLayoutView] API 
error for", name, ":", error);
+                            that.hasApiError = true;
                             processResponse(name, null);
                             that.renderCards();
                             return null;
@@ -390,13 +393,16 @@ define([
                 var displayLabel = showTypeNameInDisplay && typeName
                     ? displayText + " (" + typeName + ")"
                     : displayText;
-                var href = item && item.guid ? "#/detailPage/" + item.guid : 
"";
+                var status = (ref && ref.status) || item.status || 
(item.attributes && item.attributes.status) || "";
+                var isDeleted = status === "DELETED";
+                var deletedClass = isDeleted ? " 
relationship-card-link--deleted" : "";
+                var href = item && item.guid ? "#!/detailPage/" + item.guid + 
"?tabActive=relationship" : "";
                 var qualifiedName = item && item.qualifiedName ? 
item.qualifiedName : "";
                 var nameValue = item && item.attributes && 
item.attributes.name ? item.attributes.name : "";
                 var searchText = _.escape((displayText + " " + qualifiedName + 
" " + nameValue).toLowerCase());
                 return "<li class='relationship-card-item' data-search='" + 
searchText + "'>" +
-                    (href ? "<a class='relationship-card-link' href='" + href 
+ "'>" + _.escape(displayLabel) + "</a>" :
-                        "<span class='relationship-card-link'>" + 
_.escape(displayLabel) + "</span>") +
+                    (href ? "<a class='relationship-card-link" + deletedClass 
+ "' href='" + href + "'>" + _.escape(displayLabel) + "</a>" :
+                        "<span class='relationship-card-link" + deletedClass + 
"'>" + _.escape(displayLabel) + "</span>") +
                     "</li>";
             }).join("");
 
@@ -585,7 +591,8 @@ define([
                 return _.has(that.cardData, n) && _.isEmpty(that.cardData[n]);
             });
             if (cardCount === 0 && allLoadedAndEmpty && !this.showEmptyValues) 
{
-                html = "<div class='relationship-cards-empty'>No relationship 
data available</div>";
+                var emptyMsg = this.hasApiError ? "Failed to load relationship 
data" : "No relationship data available";
+                html = "<div class='relationship-cards-empty'>" + emptyMsg + 
"</div>";
             }
             
             if (this.$el && this.$el.length) {
diff --git a/dashboardv2/public/js/views/graph/RelationshipLayoutView.js 
b/dashboardv2/public/js/views/graph/RelationshipLayoutView.js
index 35d04261a..e47cb9bd2 100644
--- a/dashboardv2/public/js/views/graph/RelationshipLayoutView.js
+++ b/dashboardv2/public/js/views/graph/RelationshipLayoutView.js
@@ -253,7 +253,7 @@ define([
                     listString = "",
                     data = this.selectedNodeData,
                     typeName = this.selectedNodeType,
-                    activeEntityColor = "#00b98b",
+                    activeEntityColor = "#1976d2",
                     deletedEntityColor = "#BB5838",
                     defaultEntityColor = "#e0e0e0",
                     normalizeEntity = function(entity) {
@@ -277,11 +277,11 @@ define([
                             entityTypeButton = "";
                         if (guid) {
                             if (options.entity) {
-                                entityTypeButton = "<a href='#/detailPage/" + 
guid + "' class='entity-type-name' style='color:" + options.color + "'>" + name 
+ "</a>";
+                                entityTypeButton = "<a href='#!/detailPage/" + 
guid + "' class='entity-type-name' style='color:" + options.color + "'>" + name 
+ "</a>";
                             } else if (options.relationship) {
                                 entityTypeButton = "<a 
href='#/relationshipDetailPage/" + guid + "' class='entity-type-name' 
style='color:" + options.color + "'>" + name + "</a>";
                             } else {
-                                entityTypeButton = "<a href='#/detailPage/" + 
guid + "' class='entity-type-name' style='color:" + options.color + "'>" + name 
+ "</a>";
+                                entityTypeButton = "<a href='#!/detailPage/" + 
guid + "' class='entity-type-name' style='color:" + options.color + "'>" + name 
+ "</a>";
                             }
                         } else {
                             entityTypeButton = "<pre class='entity-type-name' 
style='color:" + options.color + "'>" + name + "</pre>";
@@ -346,12 +346,14 @@ define([
                 }.bind(this);
                 var buildListItem = function(item) {
                     var name = item.entityName || Utils.getName(item, 
"displayText");
-                    var href = item.guid ? "#/detailPage/" + item.guid : "";
+                    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 content = href
-                        ? "<a href='" + href + "' class='entity-type-name' 
style='color:" + color + "'>" + _.escape(name) + "</a>"
-                        : "<span class='entity-type-name' style='color:" + 
color + "'>" + _.escape(name) + "</span>";
+                        ? "<a href='" + href + "' class='entity-type-name' 
style='color:" + color + "'>" + _.escape(displayLabel) + "</a>"
+                        : "<span class='entity-type-name' style='color:" + 
color + "'>" + _.escape(displayLabel) + "</span>";
                     return "<li class='entity-list-item'>" + content + "</li>";
                 };
                 if (_.isArray(data)) {
@@ -363,8 +365,9 @@ define([
                     }
                     _.each(_.sortBy(data, "entityName"), function(val) {
                         var name = val.entityName || Utils.getName(val, 
"displayText");
+                        var searchTarget = (name + " " + (val.typeName || 
"")).toLowerCase();
                         if (searchString) {
-                            if 
(name.toLowerCase().includes(searchString.toLowerCase())) {
+                            if 
(searchTarget.includes(searchString.toLowerCase())) {
                                 listString += buildListItem(val);
                             } else {
                                 return;

Reply via email to