From 1307f007f53eac5d003fd13004c6fd3abbc48435 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20R=C3=A4ntil=C3=A4?= Date: Fri, 9 May 2025 17:12:03 +0200 Subject: [PATCH] fix: Missing edges in the catalog-graph plugin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Gustaf Räntilä --- .changeset/some-horses-ask.md | 5 ++ .../useEntityRelationNodesAndEdges.test.ts | 53 +++++++++++++++++++ .../useEntityRelationNodesAndEdges.ts | 41 ++++++++++---- 3 files changed, 89 insertions(+), 10 deletions(-) create mode 100644 .changeset/some-horses-ask.md diff --git a/.changeset/some-horses-ask.md b/.changeset/some-horses-ask.md new file mode 100644 index 0000000000..2eaabd49f5 --- /dev/null +++ b/.changeset/some-horses-ask.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-graph': patch +--- + +Fix rendering of missing relations diff --git a/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationNodesAndEdges.test.ts b/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationNodesAndEdges.test.ts index 0a5e43bf37..08b718d789 100644 --- a/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationNodesAndEdges.test.ts +++ b/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationNodesAndEdges.test.ts @@ -34,6 +34,17 @@ const useEntityRelationGraph = useEntityRelationGraphMocked as jest.Mock< ReturnType >; +/* + This is the full test graph: + - b:d/c -> k:d/a1 (ownerOf) + - b:d/c -> b:d/c1 (hasPart) + - k:d/a1 -> b:d/c (ownedBy) + - k:d/a1 -> b:d/c1 (ownedBy) + - b:d/c1 -> b:d/c (partOf) + - b:d/c1 -> k:d/a1 (ownerOf) + - b:d/c1 -> b:d/c2 (hasPart) + - b:d/c2 -> b:d/c1 (partOf) +*/ const entities: { [ref: string]: Entity } = { 'b:d/c': { apiVersion: 'a', @@ -233,6 +244,12 @@ describe('useEntityRelationNodesAndEdges', () => { relations: [RELATION_HAS_PART, RELATION_PART_OF], to: 'b:d/c1', }, + { + from: 'b:d/c1', + label: 'visible', + relations: [RELATION_OWNER_OF, RELATION_OWNED_BY], + to: 'k:d/a1', + }, { from: 'b:d/c1', label: 'visible', @@ -302,12 +319,42 @@ describe('useEntityRelationNodesAndEdges', () => { relations: [RELATION_HAS_PART], to: 'b:d/c1', }, + { + from: 'b:d/c1', + label: 'visible', + relations: [RELATION_PART_OF], + to: 'b:d/c', + }, + { + from: 'b:d/c1', + label: 'visible', + relations: [RELATION_OWNER_OF], + to: 'k:d/a1', + }, { from: 'b:d/c1', label: 'visible', relations: [RELATION_HAS_PART], to: 'b:d/c2', }, + { + from: 'b:d/c2', + label: 'visible', + relations: [RELATION_PART_OF], + to: 'b:d/c1', + }, + { + from: 'k:d/a1', + label: 'visible', + relations: [RELATION_OWNED_BY], + to: 'b:d/c', + }, + { + from: 'k:d/a1', + label: 'visible', + relations: [RELATION_OWNED_BY], + to: 'b:d/c1', + }, ]); }); @@ -549,6 +596,12 @@ describe('useEntityRelationNodesAndEdges', () => { relations: ['ownerOf', 'ownedBy'], to: 'k:d/a1', }, + { + from: 'b:d/c', + label: 'visible', + relations: ['ownerOf', 'ownedBy'], + to: 'k:d/a1', + }, ]); }); diff --git a/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationNodesAndEdges.ts b/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationNodesAndEdges.ts index 31398a137e..19a6dbe566 100644 --- a/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationNodesAndEdges.ts +++ b/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationNodesAndEdges.ts @@ -96,6 +96,21 @@ export function useEntityRelationNodesAndEdges({ const visitedNodes = new Set(); const nodeQueue = [...rootEntityRefs]; + const hasEdge = ( + fromRef: string, + toRef: string, + relation: [string, string?], + ): boolean => { + return edges.some( + edge => + fromRef === edge.from && + toRef === edge.to && + edge.relations.some( + rel => rel[0] === relation[0] && rel[1] === relation[1], + ), + ); + }; + while (nodeQueue.length > 0) { const entityRef = nodeQueue.pop()!; const entity = entities[entityRef]; @@ -122,20 +137,26 @@ export function useEntityRelationNodesAndEdges({ return; } - if (!unidirectional || !visitedNodes.has(rel.targetRef)) { - if (mergeRelations) { - const pair = relationPairs.find( - ([l, r]) => l === rel.type || r === rel.type, - ) ?? [rel.type]; - const [left] = pair; - + if (mergeRelations) { + const pair = relationPairs.find( + ([l, r]) => l === rel.type || r === rel.type, + ) ?? [rel.type]; + const [left] = pair; + const from = left === rel.type ? entityRef : rel.targetRef; + const to = left === rel.type ? rel.targetRef : entityRef; + if (!unidirectional || !hasEdge(from, to, pair)) { edges.push({ - from: left === rel.type ? entityRef : rel.targetRef, - to: left === rel.type ? rel.targetRef : entityRef, + from, + to, relations: pair, label: 'visible', }); - } else { + } + } else { + if ( + !unidirectional || + !hasEdge(entityRef, rel.targetRef, [rel.type]) + ) { edges.push({ from: entityRef, to: rel.targetRef,