diff --git a/.changeset/forty-experts-live.md b/.changeset/forty-experts-live.md new file mode 100644 index 0000000000..11fc3b688f --- /dev/null +++ b/.changeset/forty-experts-live.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-graph': patch +--- + +Add function to `EntityRelationsGraph` filter that excludes entities from graph diff --git a/plugins/catalog-graph/api-report.md b/plugins/catalog-graph/api-report.md index 8f75e52289..205ffed089 100644 --- a/plugins/catalog-graph/api-report.md +++ b/plugins/catalog-graph/api-report.md @@ -115,6 +115,7 @@ export type EntityRelationsGraphProps = { mergeRelations?: boolean; kinds?: string[]; relations?: string[]; + entityFilter?: (entity: Entity) => boolean; direction?: Direction; onNodeClick?: (value: EntityNode, event: MouseEvent_2) => void; relationPairs?: RelationPairs; diff --git a/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.tsx b/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.tsx index 1956a4bdae..7c7f4c6063 100644 --- a/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.tsx +++ b/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.tsx @@ -71,6 +71,7 @@ export const CatalogGraphCard = ( direction = Direction.LEFT_RIGHT, kinds, relations, + entityFilter, height, className, rootEntityNames, @@ -140,6 +141,7 @@ export const CatalogGraphCard = ( mergeRelations={mergeRelations} direction={direction} relationPairs={relationPairs} + entityFilter={entityFilter} zoom={zoom} /> diff --git a/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.tsx b/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.tsx index c7f9360589..ad673b192a 100644 --- a/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.tsx +++ b/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.tsx @@ -121,7 +121,11 @@ export const CatalogGraphPage = ( }; } & Partial, ) => { - const { relationPairs = ALL_RELATION_PAIRS, initialState } = props; + const { + relationPairs = ALL_RELATION_PAIRS, + initialState, + entityFilter, + } = props; const navigate = useNavigate(); const classes = useStyles(); @@ -256,6 +260,7 @@ export const CatalogGraphPage = ( onNodeClick={onNodeClick} direction={direction} relationPairs={relationPairs} + entityFilter={entityFilter} className={classes.graph} zoom="enabled" curve={curve} diff --git a/plugins/catalog-graph/src/components/EntityRelationsGraph/EntityRelationsGraph.tsx b/plugins/catalog-graph/src/components/EntityRelationsGraph/EntityRelationsGraph.tsx index 61e24ecac4..51de7cc47f 100644 --- a/plugins/catalog-graph/src/components/EntityRelationsGraph/EntityRelationsGraph.tsx +++ b/plugins/catalog-graph/src/components/EntityRelationsGraph/EntityRelationsGraph.tsx @@ -16,6 +16,7 @@ import { CompoundEntityRef, + Entity, stringifyEntityRef, } from '@backstage/catalog-model'; import { @@ -77,6 +78,7 @@ export type EntityRelationsGraphProps = { mergeRelations?: boolean; kinds?: string[]; relations?: string[]; + entityFilter?: (entity: Entity) => boolean; direction?: Direction; onNodeClick?: (value: EntityNode, event: MouseEvent) => void; relationPairs?: RelationPairs; @@ -101,6 +103,7 @@ export const EntityRelationsGraph = (props: EntityRelationsGraphProps) => { mergeRelations = true, kinds, relations, + entityFilter, direction = Direction.LEFT_RIGHT, onNodeClick, relationPairs = ALL_RELATION_PAIRS, @@ -130,6 +133,7 @@ export const EntityRelationsGraph = (props: EntityRelationsGraphProps) => { mergeRelations, kinds, relations, + entityFilter, onNodeClick, relationPairs, }); diff --git a/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationGraph.test.ts b/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationGraph.test.ts index 420af5345c..b2d9083180 100644 --- a/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationGraph.test.ts +++ b/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationGraph.test.ts @@ -353,6 +353,28 @@ describe('useEntityRelationGraph', () => { }); }); + test('should filter by func', async () => { + const { result, rerender } = renderHook(() => + useEntityRelationGraph({ + rootEntityRefs: ['b:d/c'], + filter: { + entityFilter: e => e.metadata.name !== 'c2', + }, + }), + ); + + // Simulate rerendering as this is triggered automatically due to the mock + for (let i = 0; i < 5; ++i) { + rerender(); + } + + expect(result.current.entities).toEqual({ + 'b:d/c': expect.anything(), + 'b:d/c1': expect.anything(), + 'k:d/a1': expect.anything(), + }); + }); + test('should support multiple roots by kind', async () => { const { result, rerender } = renderHook(() => useEntityRelationGraph({ diff --git a/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationGraph.ts b/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationGraph.ts index 957f35dc25..af2a491ada 100644 --- a/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationGraph.ts +++ b/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationGraph.ts @@ -16,6 +16,7 @@ import { Entity } from '@backstage/catalog-model'; import { useEffect } from 'react'; import { useEntityStore } from './useEntityStore'; +import { pickBy } from 'lodash'; /** * Discover the graph of entities connected by relations, starting from a set of @@ -24,13 +25,19 @@ import { useEntityStore } from './useEntityStore'; */ export function useEntityRelationGraph({ rootEntityRefs, - filter: { maxDepth = Number.POSITIVE_INFINITY, relations, kinds } = {}, + filter: { + maxDepth = Number.POSITIVE_INFINITY, + relations, + kinds, + entityFilter, + } = {}, }: { rootEntityRefs: string[]; filter?: { maxDepth?: number; relations?: string[]; kinds?: string[]; + entityFilter?: (entity: Entity) => boolean; }; }): { entities?: { [ref: string]: Entity }; @@ -60,6 +67,11 @@ export function useEntityRelationGraph({ processedEntityRefs.add(entityRef); if (entity && entity.relations) { + // If the entity is filtered out then no need to check any + // of its outgoing relationships to other entities + if (entityFilter && !entityFilter(entity)) { + continue; + } for (const rel of entity.relations) { if ( (!relations || relations.includes(rel.type)) && @@ -81,12 +93,23 @@ export function useEntityRelationGraph({ ++depth; } - requestEntities([...expectedEntities]); - }, [entities, rootEntityRefs, maxDepth, relations, kinds, requestEntities]); + }, [ + entities, + rootEntityRefs, + maxDepth, + relations, + kinds, + entityFilter, + requestEntities, + ]); + + const filteredEntities = entityFilter + ? pickBy(entities, (value, _key) => entityFilter(value)) + : entities; return { - entities, + entities: filteredEntities, loading, error, }; diff --git a/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationNodesAndEdges.ts b/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationNodesAndEdges.ts index 74aff1b3d0..31398a137e 100644 --- a/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationNodesAndEdges.ts +++ b/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationNodesAndEdges.ts @@ -18,7 +18,7 @@ import useDebounce from 'react-use/esm/useDebounce'; import { RelationPairs, ALL_RELATION_PAIRS } from './relations'; import { EntityEdge, EntityNode } from './types'; import { useEntityRelationGraph } from './useEntityRelationGraph'; -import { DEFAULT_NAMESPACE } from '@backstage/catalog-model'; +import { Entity, DEFAULT_NAMESPACE } from '@backstage/catalog-model'; /** * Generate nodes and edges to render the entity graph. @@ -30,6 +30,7 @@ export function useEntityRelationNodesAndEdges({ mergeRelations = true, kinds, relations, + entityFilter, onNodeClick, relationPairs = ALL_RELATION_PAIRS, }: { @@ -39,6 +40,7 @@ export function useEntityRelationNodesAndEdges({ mergeRelations?: boolean; kinds?: string[]; relations?: string[]; + entityFilter?: (entity: Entity) => boolean; onNodeClick?: (value: EntityNode, event: MouseEvent) => void; relationPairs?: RelationPairs; }): { @@ -57,6 +59,7 @@ export function useEntityRelationNodesAndEdges({ maxDepth, kinds, relations, + entityFilter, }, });