diff --git a/.changeset/spicy-pants-teach.md b/.changeset/spicy-pants-teach.md new file mode 100644 index 0000000000..8e16ae089e --- /dev/null +++ b/.changeset/spicy-pants-teach.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-catalog-react': patch +--- + +Expose `useRelatedEntities` from `@backstage/plugin-catalog-react` to retrieve +entities references via relations from the API. diff --git a/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.tsx b/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.tsx index 8a808a6488..6124ea979a 100644 --- a/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.tsx +++ b/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.tsx @@ -19,12 +19,11 @@ import { Entity, RELATION_CONSUMES_API, } from '@backstage/catalog-model'; -import { useEntity } from '@backstage/plugin-catalog-react'; import { EmptyState, InfoCard, Progress } from '@backstage/core'; +import { useEntity, useRelatedEntities } from '@backstage/plugin-catalog-react'; import React, { PropsWithChildren } from 'react'; -import { ApisTable } from './ApisTable'; import { MissingConsumesApisEmptyState } from '../EmptyState'; -import { useRelatedEntities } from '../useRelatedEntities'; +import { ApisTable } from './ApisTable'; const ApisCard = ({ children, @@ -45,10 +44,9 @@ type Props = { export const ConsumedApisCard = ({ variant = 'gridItem' }: Props) => { const { entity } = useEntity(); - const { entities, loading, error } = useRelatedEntities( - entity, - RELATION_CONSUMES_API, - ); + const { entities, loading, error } = useRelatedEntities(entity, { + type: RELATION_CONSUMES_API, + }); if (loading) { return ( diff --git a/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.tsx b/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.tsx index ed6e893a03..92155e65f5 100644 --- a/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.tsx +++ b/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.tsx @@ -19,12 +19,11 @@ import { Entity, RELATION_PROVIDES_API, } from '@backstage/catalog-model'; -import { useEntity } from '@backstage/plugin-catalog-react'; import { EmptyState, InfoCard, Progress } from '@backstage/core'; +import { useEntity, useRelatedEntities } from '@backstage/plugin-catalog-react'; import React, { PropsWithChildren } from 'react'; -import { ApisTable } from './ApisTable'; import { MissingProvidesApisEmptyState } from '../EmptyState'; -import { useRelatedEntities } from '../useRelatedEntities'; +import { ApisTable } from './ApisTable'; const ApisCard = ({ children, @@ -45,10 +44,9 @@ type Props = { export const ProvidedApisCard = ({ variant = 'gridItem' }: Props) => { const { entity } = useEntity(); - const { entities, loading, error } = useRelatedEntities( - entity, - RELATION_PROVIDES_API, - ); + const { entities, loading, error } = useRelatedEntities(entity, { + type: RELATION_PROVIDES_API, + }); if (loading) { return ( diff --git a/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.tsx b/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.tsx index 56d2fb977d..9ed5bf6d2a 100644 --- a/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.tsx +++ b/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.tsx @@ -19,11 +19,10 @@ import { Entity, RELATION_API_CONSUMED_BY, } from '@backstage/catalog-model'; -import { useEntity } from '@backstage/plugin-catalog-react'; import { EmptyState, InfoCard, Progress } from '@backstage/core'; +import { useEntity, useRelatedEntities } from '@backstage/plugin-catalog-react'; import React, { PropsWithChildren } from 'react'; import { MissingConsumesApisEmptyState } from '../EmptyState'; -import { useRelatedEntities } from '../useRelatedEntities'; import { ComponentsTable } from './ComponentsTable'; const ComponentsCard = ({ @@ -45,10 +44,9 @@ type Props = { export const ConsumingComponentsCard = ({ variant = 'gridItem' }: Props) => { const { entity } = useEntity(); - const { entities, loading, error } = useRelatedEntities( - entity, - RELATION_API_CONSUMED_BY, - ); + const { entities, loading, error } = useRelatedEntities(entity, { + type: RELATION_API_CONSUMED_BY, + }); if (loading) { return ( diff --git a/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.tsx b/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.tsx index 959ff3feed..06377ca0d9 100644 --- a/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.tsx +++ b/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.tsx @@ -19,11 +19,10 @@ import { Entity, RELATION_API_PROVIDED_BY, } from '@backstage/catalog-model'; -import { useEntity } from '@backstage/plugin-catalog-react'; import { EmptyState, InfoCard, Progress } from '@backstage/core'; +import { useEntity, useRelatedEntities } from '@backstage/plugin-catalog-react'; import React, { PropsWithChildren } from 'react'; import { MissingProvidesApisEmptyState } from '../EmptyState'; -import { useRelatedEntities } from '../useRelatedEntities'; import { ComponentsTable } from './ComponentsTable'; const ComponentsCard = ({ @@ -45,10 +44,9 @@ type Props = { export const ProvidingComponentsCard = ({ variant = 'gridItem' }: Props) => { const { entity } = useEntity(); - const { entities, loading, error } = useRelatedEntities( - entity, - RELATION_API_PROVIDED_BY, - ); + const { entities, loading, error } = useRelatedEntities(entity, { + type: RELATION_API_PROVIDED_BY, + }); if (loading) { return ( diff --git a/plugins/catalog-react/src/hooks/index.ts b/plugins/catalog-react/src/hooks/index.ts index 77de70b8d4..2ecf6e9a90 100644 --- a/plugins/catalog-react/src/hooks/index.ts +++ b/plugins/catalog-react/src/hooks/index.ts @@ -15,3 +15,4 @@ */ export { EntityContext, useEntity, useEntityFromUrl } from './useEntity'; export { useEntityCompoundName } from './useEntityCompoundName'; +export { useRelatedEntities } from './useRelatedEntities'; diff --git a/plugins/api-docs/src/components/useRelatedEntities.ts b/plugins/catalog-react/src/hooks/useRelatedEntities.ts similarity index 53% rename from plugins/api-docs/src/components/useRelatedEntities.ts rename to plugins/catalog-react/src/hooks/useRelatedEntities.ts index 5c01d38189..281ef217c1 100644 --- a/plugins/api-docs/src/components/useRelatedEntities.ts +++ b/plugins/catalog-react/src/hooks/useRelatedEntities.ts @@ -15,36 +15,48 @@ */ import { Entity } from '@backstage/catalog-model'; import { useApi } from '@backstage/core'; -import { catalogApiRef } from '@backstage/plugin-catalog-react'; import { useAsyncRetry } from 'react-use'; +import { catalogApiRef } from '../api'; -// TODO: Maybe this hook is interesting for others too? export function useRelatedEntities( entity: Entity, - type: string, + { type, kind }: { type?: string; kind?: string }, ): { - entities: (Entity | undefined)[] | undefined; + entities: Entity[] | undefined; loading: boolean; error: Error | undefined; } { const catalogApi = useApi(catalogApiRef); - const { loading, value, error } = useAsyncRetry< - (Entity | undefined)[] + const { loading, value: entities, error } = useAsyncRetry< + Entity[] >(async () => { const relations = - entity.relations && entity.relations.filter(r => r.type === type); + entity.relations && + entity.relations.filter( + r => + (!type || r.type === type) && + (!kind || + r.target.kind.toLocaleLowerCase() === kind.toLocaleLowerCase()), + ); if (!relations) { return []; } - return await Promise.all( + // TODO: This code could be more efficient if there was an endpoint in the + // backend that either returns the relations of entity (filtered by type) + // or if there is a way to perform a batch request by entity name. However, + // such an implementation would probably be better placed in the graphql API. + const results = await Promise.all( relations?.map(r => catalogApi.getEntityByName(r.target)), ); + // Skip entities that where not found, for example if a relation references + // an entity that doesn't exist. + return results.filter(e => e) as Entity[]; }, [entity, type]); return { - entities: value, + entities, loading, error, };