diff --git a/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.tsx b/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.tsx index cf18a2d643..9f66fffd7a 100644 --- a/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.tsx +++ b/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.tsx @@ -17,43 +17,60 @@ import { Entity, CompoundEntityRef } from '@backstage/catalog-model'; import React from 'react'; import { EntityRefLink } from './EntityRefLink'; import { LinkProps } from '@backstage/core-components'; +import { FetchedEntityRefLinks } from './FetchedEntityRefLinks'; /** * Props for {@link EntityRefLink}. * * @public */ -export type EntityRefLinksProps = { - entityRefs: (string | Entity | CompoundEntityRef)[]; - defaultKind?: string; - getTitle?: (cer: CompoundEntityRef) => string | undefined; -} & Omit; +export type EntityRefLinksProps< + TRef extends string | CompoundEntityRef | Entity, +> = + | { + defaultKind?: string; + entityRefs: TRef[]; + fetchEntities?: false; + getTitle?(entity: TRef): string | undefined; + } + | ({ + defaultKind?: string; + entityRefs: TRef[]; + fetchEntities: true; + getTitle?(entity: Entity): string | undefined; + } & Omit); /** * Shows a list of clickable links to entities. * * @public */ -export function EntityRefLinks(props: EntityRefLinksProps) { - const { entityRefs, defaultKind, getTitle, ...linkProps } = props; +export function EntityRefLinks< + TRef extends string | CompoundEntityRef | Entity, +>(props: EntityRefLinksProps) { + const { entityRefs, defaultKind, fetchEntities, getTitle, ...linkProps } = + props; + + if (fetchEntities) { + return ( + + ); + } + return ( <> - {entityRefs.map((r, i) => { - const isCompoundEntityRef = - getTitle && typeof r !== 'string' && !('metadata' in r); - - const title = isCompoundEntityRef - ? getTitle(r as CompoundEntityRef) - : undefined; - + {entityRefs.map((r: TRef, i: number) => { return ( {i > 0 && ', '} ); diff --git a/plugins/catalog-react/src/components/EntityRefLink/FetchedEntityRefLinks.tsx b/plugins/catalog-react/src/components/EntityRefLink/FetchedEntityRefLinks.tsx new file mode 100644 index 0000000000..5dbe429496 --- /dev/null +++ b/plugins/catalog-react/src/components/EntityRefLink/FetchedEntityRefLinks.tsx @@ -0,0 +1,115 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Entity, CompoundEntityRef } from '@backstage/catalog-model'; +import React from 'react'; +import { EntityRefLink } from './EntityRefLink'; +import { ErrorPanel, LinkProps, Progress } from '@backstage/core-components'; +import useAsync from 'react-use/lib/useAsync'; +import { catalogApiRef } from '../../api'; +import { useApi } from '@backstage/core-plugin-api'; + +/** + * Props for {@link EntityRefLink}. + * + * @public + */ +export type FetchedEntityRefLinksProps< + TRef extends string | CompoundEntityRef | Entity, +> = { + defaultKind?: string; + entityRefs: TRef[]; + fetchEntities: true; + getTitle?(entity: Entity): string | undefined; +} & Omit; + +/** + * Shows a list of clickable links to entities with auto-fetching of entities + * for customising a displayed text via title attribute. + * + * @public + */ +export function FetchedEntityRefLinks< + TRef extends string | CompoundEntityRef | Entity, +>(props: FetchedEntityRefLinksProps) { + const { entityRefs, defaultKind, fetchEntities, getTitle, ...linkProps } = + props; + + const catalogApi = useApi(catalogApiRef); + + const { + value: refToEntity = new Map(), + loading, + error, + } = useAsync( + () => + entityRefs.reduce( + async (promisedAcc: Promise>, entityRef: TRef) => { + const acc = await promisedAcc; + const entity: Entity | undefined = + 'metadata' in entityRef + ? (entityRef as Entity) + : await catalogApi.getEntityByRef( + entityRef as string | CompoundEntityRef, + ); + if (entity) { + acc.set(entityRef, entity); + } + return acc; + }, + Promise.resolve(new Map()), + ), + [catalogApi, entityRefs], + ); + + if (loading) { + return ; + } + + if (error) { + return ( + <> + + + ); + } + + return ( + <> + {entityRefs.map((r: TRef, i) => { + let title: string | undefined; + + if (typeof r === 'string' || !('metadata' in r)) { + const entity = refToEntity.get(r); + title = getTitle && entity ? getTitle(entity) : undefined; + } else { + title = getTitle ? getTitle(r as Entity) : undefined; + } + + return ( + + {i > 0 && ', '} + + + ); + })} + + ); +}