fix: Missing edges in the catalog-graph plugin

Signed-off-by: Gustaf Räntilä <g.rantila@gmail.com>
This commit is contained in:
Gustaf Räntilä
2025-05-09 17:12:03 +02:00
parent b89550526e
commit 1307f007f5
3 changed files with 89 additions and 10 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-graph': patch
---
Fix rendering of missing relations
@@ -34,6 +34,17 @@ const useEntityRelationGraph = useEntityRelationGraphMocked as jest.Mock<
ReturnType<typeof useEntityRelationGraphMocked>
>;
/*
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',
},
]);
});
@@ -96,6 +96,21 @@ export function useEntityRelationNodesAndEdges({
const visitedNodes = new Set<string>();
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,