From 95f81d56a887a588a2780d9c11f23232d46bbaac Mon Sep 17 00:00:00 2001 From: bnechyporenko Date: Mon, 19 Sep 2022 16:31:54 +0200 Subject: [PATCH 01/17] Provide custom links in EntityRefLinks Signed-off-by: bnechyporenko --- .../src/components/EntityRefLink/EntityRefLinks.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.tsx b/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.tsx index 30dda011d4..054dd0350c 100644 --- a/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.tsx +++ b/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Backstage Authors + * 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. @@ -13,7 +13,6 @@ * 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'; @@ -27,6 +26,7 @@ import { LinkProps } from '@backstage/core-components'; export type EntityRefLinksProps = { entityRefs: (string | Entity | CompoundEntityRef)[]; defaultKind?: string; + titleFn?: (r: string | Entity | CompoundEntityRef) => string; } & Omit; /** @@ -35,7 +35,7 @@ export type EntityRefLinksProps = { * @public */ export function EntityRefLinks(props: EntityRefLinksProps) { - const { entityRefs, defaultKind, ...linkProps } = props; + const { entityRefs, defaultKind, titleFn, ...linkProps } = props; return ( <> {entityRefs.map((r, i) => ( @@ -45,6 +45,7 @@ export function EntityRefLinks(props: EntityRefLinksProps) { {...linkProps} entityRef={r} defaultKind={defaultKind} + title={titleFn ? titleFn(r) : undefined} /> ))} From aeb0d0227696147887f2b10cdee181cba319a5ee Mon Sep 17 00:00:00 2001 From: bnechyporenko Date: Mon, 19 Sep 2022 16:43:46 +0200 Subject: [PATCH 02/17] Updated api-report.md Signed-off-by: bnechyporenko --- plugins/catalog-react/api-report.md | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/catalog-react/api-report.md b/plugins/catalog-react/api-report.md index ee43a0a5c1..49dd7843c0 100644 --- a/plugins/catalog-react/api-report.md +++ b/plugins/catalog-react/api-report.md @@ -285,6 +285,7 @@ export function EntityRefLinks(props: EntityRefLinksProps): JSX.Element; export type EntityRefLinksProps = { entityRefs: (string | Entity | CompoundEntityRef)[]; defaultKind?: string; + titleFn?: (r: string | Entity | CompoundEntityRef) => string; } & Omit; // @public From 7939e743f58e2485b789ee382b5777aed0f46a0f Mon Sep 17 00:00:00 2001 From: bnechyporenko Date: Mon, 19 Sep 2022 16:56:49 +0200 Subject: [PATCH 03/17] Added a release note Signed-off-by: bnechyporenko --- .changeset/calm-pianos-burn.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/calm-pianos-burn.md diff --git a/.changeset/calm-pianos-burn.md b/.changeset/calm-pianos-burn.md new file mode 100644 index 0000000000..a7860ca4b7 --- /dev/null +++ b/.changeset/calm-pianos-burn.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-react': patch +--- + +Provide custom displayed text in EntityRefLinks From f4c67c3eca7c78addcb2c96dd3566f09dedd5679 Mon Sep 17 00:00:00 2001 From: bnechyporenko Date: Mon, 19 Sep 2022 18:19:22 +0200 Subject: [PATCH 04/17] Incorporated the feedback Signed-off-by: bnechyporenko --- plugins/catalog-react/api-report.md | 2 +- .../EntityRefLink/EntityRefLinks.tsx | 35 ++++++++++++------- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/plugins/catalog-react/api-report.md b/plugins/catalog-react/api-report.md index 49dd7843c0..711e1935c1 100644 --- a/plugins/catalog-react/api-report.md +++ b/plugins/catalog-react/api-report.md @@ -285,7 +285,7 @@ export function EntityRefLinks(props: EntityRefLinksProps): JSX.Element; export type EntityRefLinksProps = { entityRefs: (string | Entity | CompoundEntityRef)[]; defaultKind?: string; - titleFn?: (r: string | Entity | CompoundEntityRef) => string; + getTitle?: (cer: CompoundEntityRef) => string; } & Omit; // @public diff --git a/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.tsx b/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.tsx index 054dd0350c..0434dfb525 100644 --- a/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.tsx +++ b/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.tsx @@ -26,7 +26,7 @@ import { LinkProps } from '@backstage/core-components'; export type EntityRefLinksProps = { entityRefs: (string | Entity | CompoundEntityRef)[]; defaultKind?: string; - titleFn?: (r: string | Entity | CompoundEntityRef) => string; + getTitle?: (cer: CompoundEntityRef) => string; } & Omit; /** @@ -35,20 +35,29 @@ export type EntityRefLinksProps = { * @public */ export function EntityRefLinks(props: EntityRefLinksProps) { - const { entityRefs, defaultKind, titleFn, ...linkProps } = props; + const { entityRefs, defaultKind, getTitle, ...linkProps } = props; return ( <> - {entityRefs.map((r, i) => ( - - {i > 0 && ', '} - - - ))} + {entityRefs.map((r, i) => { + const isCompoundEntityRef = + getTitle && typeof r !== 'string' && !('metadata' in r && getTitle); + + const title = isCompoundEntityRef + ? getTitle(r as CompoundEntityRef) + : undefined; + + return ( + + {i > 0 && ', '} + + + ); + })} ); } From aa6372c0bb2ddb716cb2b9c04cd6fede7ca88848 Mon Sep 17 00:00:00 2001 From: bnechyporenko Date: Mon, 19 Sep 2022 18:21:23 +0200 Subject: [PATCH 05/17] Incorporated the feedback Signed-off-by: bnechyporenko --- .../src/components/EntityRefLink/EntityRefLinks.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.tsx b/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.tsx index 0434dfb525..b1a0c39558 100644 --- a/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.tsx +++ b/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.tsx @@ -40,7 +40,7 @@ export function EntityRefLinks(props: EntityRefLinksProps) { <> {entityRefs.map((r, i) => { const isCompoundEntityRef = - getTitle && typeof r !== 'string' && !('metadata' in r && getTitle); + getTitle && typeof r !== 'string' && !('metadata' in r); const title = isCompoundEntityRef ? getTitle(r as CompoundEntityRef) From 4b4e8975aa85140c2cd147f1292b445be12fdb4a Mon Sep 17 00:00:00 2001 From: bnechyporenko Date: Mon, 19 Sep 2022 18:39:54 +0200 Subject: [PATCH 06/17] Incorporated the feedback Signed-off-by: bnechyporenko --- plugins/catalog-react/api-report.md | 2 +- .../src/components/EntityRefLink/EntityRefLinks.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/catalog-react/api-report.md b/plugins/catalog-react/api-report.md index 711e1935c1..bb19e2a5b8 100644 --- a/plugins/catalog-react/api-report.md +++ b/plugins/catalog-react/api-report.md @@ -285,7 +285,7 @@ export function EntityRefLinks(props: EntityRefLinksProps): JSX.Element; export type EntityRefLinksProps = { entityRefs: (string | Entity | CompoundEntityRef)[]; defaultKind?: string; - getTitle?: (cer: CompoundEntityRef) => string; + getTitle?: (cer: CompoundEntityRef) => string | undefined; } & Omit; // @public diff --git a/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.tsx b/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.tsx index b1a0c39558..cf18a2d643 100644 --- a/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.tsx +++ b/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.tsx @@ -26,7 +26,7 @@ import { LinkProps } from '@backstage/core-components'; export type EntityRefLinksProps = { entityRefs: (string | Entity | CompoundEntityRef)[]; defaultKind?: string; - getTitle?: (cer: CompoundEntityRef) => string; + getTitle?: (cer: CompoundEntityRef) => string | undefined; } & Omit; /** From e19bcd165361260ebd716a55afdaf813613dcce3 Mon Sep 17 00:00:00 2001 From: bnechyporenko Date: Mon, 26 Sep 2022 17:19:29 +0200 Subject: [PATCH 07/17] Incorporated the feedback Signed-off-by: bnechyporenko --- .../EntityRefLink/EntityRefLinks.tsx | 51 +++++--- .../EntityRefLink/FetchedEntityRefLinks.tsx | 115 ++++++++++++++++++ 2 files changed, 149 insertions(+), 17 deletions(-) create mode 100644 plugins/catalog-react/src/components/EntityRefLink/FetchedEntityRefLinks.tsx 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 && ', '} + + + ); + })} + + ); +} From bc3f2c1f20fdbaf7ce3fbdbcf21f4a02db181c2e Mon Sep 17 00:00:00 2001 From: bnechyporenko Date: Tue, 27 Sep 2022 09:11:30 +0200 Subject: [PATCH 08/17] Incorporated partially the feedback Signed-off-by: bnechyporenko --- .../EntityRefLink/EntityRefLinks.tsx | 4 +- .../EntityRefLink/FetchedEntityRefLinks.tsx | 60 ++++++------------- 2 files changed, 22 insertions(+), 42 deletions(-) diff --git a/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.tsx b/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.tsx index 9f66fffd7a..bb8950041a 100644 --- a/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.tsx +++ b/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.tsx @@ -54,8 +54,10 @@ export function EntityRefLinks< if (fetchEntities) { return ( ); } diff --git a/plugins/catalog-react/src/components/EntityRefLink/FetchedEntityRefLinks.tsx b/plugins/catalog-react/src/components/EntityRefLink/FetchedEntityRefLinks.tsx index 5dbe429496..6b6ead7872 100644 --- a/plugins/catalog-react/src/components/EntityRefLink/FetchedEntityRefLinks.tsx +++ b/plugins/catalog-react/src/components/EntityRefLink/FetchedEntityRefLinks.tsx @@ -13,7 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Entity, CompoundEntityRef } from '@backstage/catalog-model'; +import { + Entity, + CompoundEntityRef, + parseEntityRef, +} from '@backstage/catalog-model'; import React from 'react'; import { EntityRefLink } from './EntityRefLink'; import { ErrorPanel, LinkProps, Progress } from '@backstage/core-components'; @@ -31,7 +35,6 @@ export type FetchedEntityRefLinksProps< > = { defaultKind?: string; entityRefs: TRef[]; - fetchEntities: true; getTitle?(entity: Entity): string | undefined; } & Omit; @@ -44,60 +47,35 @@ export type FetchedEntityRefLinksProps< export function FetchedEntityRefLinks< TRef extends string | CompoundEntityRef | Entity, >(props: FetchedEntityRefLinksProps) { - const { entityRefs, defaultKind, fetchEntities, getTitle, ...linkProps } = - props; + const { entityRefs, defaultKind, getTitle, ...linkProps } = props; const catalogApi = useApi(catalogApiRef); const { - value: refToEntity = new Map(), + value: entities = new Array(), 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], - ); + } = useAsync(async () => { + const refs = entityRefs.reduce((acc, current) => { + return 'metadata' in current ? acc : [...acc, parseEntityRef(current)]; + }, new Array()); + + return refs + ? (await catalogApi.getEntities({ filter: refs })).items + : (entityRefs as Array); + }, [entityRefs]); if (loading) { return ; } if (error) { - return ( - <> - - - ); + 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; - } - + {entities.map((r: Entity, i) => { return ( {i > 0 && ', '} @@ -105,7 +83,7 @@ export function FetchedEntityRefLinks< {...linkProps} defaultKind={defaultKind} entityRef={r} - title={title} + title={getTitle ? getTitle(r as Entity) : undefined} /> ); From 02ba465d072e9437f9703375da667a370a4cdf19 Mon Sep 17 00:00:00 2001 From: bnechyporenko Date: Tue, 27 Sep 2022 15:23:53 +0200 Subject: [PATCH 09/17] Added a test for FetchedEntityRefLinks.tsx Signed-off-by: bnechyporenko --- .../EntityRefLink/EntityRefLinks.tsx | 2 +- .../FetchedEntityRefLinks.test.tsx | 84 +++++++++++++++++++ .../EntityRefLink/FetchedEntityRefLinks.tsx | 4 +- 3 files changed, 87 insertions(+), 3 deletions(-) create mode 100644 plugins/catalog-react/src/components/EntityRefLink/FetchedEntityRefLinks.test.tsx diff --git a/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.tsx b/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.tsx index bb8950041a..71a60f3a15 100644 --- a/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.tsx +++ b/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.tsx @@ -37,7 +37,7 @@ export type EntityRefLinksProps< defaultKind?: string; entityRefs: TRef[]; fetchEntities: true; - getTitle?(entity: Entity): string | undefined; + getTitle(entity: Entity): string | undefined; } & Omit); /** diff --git a/plugins/catalog-react/src/components/EntityRefLink/FetchedEntityRefLinks.test.tsx b/plugins/catalog-react/src/components/EntityRefLink/FetchedEntityRefLinks.test.tsx new file mode 100644 index 0000000000..0a3ea1003e --- /dev/null +++ b/plugins/catalog-react/src/components/EntityRefLink/FetchedEntityRefLinks.test.tsx @@ -0,0 +1,84 @@ +/* + * 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 { FetchedEntityRefLinks } from './FetchedEntityRefLinks'; +import { entityRouteRef } from '../../routes'; +import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; +import { Entity } from '@backstage/catalog-model'; +import React from 'react'; +import { JsonObject } from '@backstage/types'; +import { catalogApiRef } from '../../api'; +import { CatalogApi } from '@backstage/catalog-client'; + +describe('', () => { + it('should fetch entities and render the custom display text', async () => { + const entityRefs = [ + { + kind: 'Component', + namespace: 'default', + name: 'software', + }, + { + kind: 'API', + namespace: 'default', + name: 'interface', + }, + ]; + + const catalogApi: Partial = { + getEntities: () => + Promise.resolve({ + items: entityRefs.map(ref => ({ + apiVersion: 'backstage.io/v1alpha1', + kind: ref.kind, + metadata: { + name: ref.name, + namespace: ref.namespace, + }, + spec: { + profile: { + displayName: ref.name.toLocaleUpperCase('en-US'), + }, + type: 'organization', + }, + })), + }), + }; + + const getTitle = (e: Entity): string => + (e.spec?.profile!! as JsonObject).displayName!!.toString()!!; + + const rendered = await renderInTestApp( + + + , + { + mountedRoutes: { + '/catalog/:namespace/:kind/:name/*': entityRouteRef, + }, + }, + ); + + expect(rendered.getByText('SOFTWARE')).toHaveAttribute( + 'href', + '/catalog/default/component/software', + ); + + expect(rendered.getByText('INTERFACE')).toHaveAttribute( + 'href', + '/catalog/default/api/interface', + ); + }); +}); diff --git a/plugins/catalog-react/src/components/EntityRefLink/FetchedEntityRefLinks.tsx b/plugins/catalog-react/src/components/EntityRefLink/FetchedEntityRefLinks.tsx index 6b6ead7872..629306014f 100644 --- a/plugins/catalog-react/src/components/EntityRefLink/FetchedEntityRefLinks.tsx +++ b/plugins/catalog-react/src/components/EntityRefLink/FetchedEntityRefLinks.tsx @@ -35,7 +35,7 @@ export type FetchedEntityRefLinksProps< > = { defaultKind?: string; entityRefs: TRef[]; - getTitle?(entity: Entity): string | undefined; + getTitle(entity: Entity): string | undefined; } & Omit; /** @@ -83,7 +83,7 @@ export function FetchedEntityRefLinks< {...linkProps} defaultKind={defaultKind} entityRef={r} - title={getTitle ? getTitle(r as Entity) : undefined} + title={getTitle(r as Entity)} /> ); From 2bc50cff236ed9ee2fc00377813d6241efd467ec Mon Sep 17 00:00:00 2001 From: bnechyporenko Date: Tue, 27 Sep 2022 15:26:05 +0200 Subject: [PATCH 10/17] Fixed a wrong annotation Signed-off-by: bnechyporenko --- .../src/components/EntityRefLink/FetchedEntityRefLinks.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/catalog-react/src/components/EntityRefLink/FetchedEntityRefLinks.tsx b/plugins/catalog-react/src/components/EntityRefLink/FetchedEntityRefLinks.tsx index 629306014f..17c0a9de37 100644 --- a/plugins/catalog-react/src/components/EntityRefLink/FetchedEntityRefLinks.tsx +++ b/plugins/catalog-react/src/components/EntityRefLink/FetchedEntityRefLinks.tsx @@ -26,7 +26,7 @@ import { catalogApiRef } from '../../api'; import { useApi } from '@backstage/core-plugin-api'; /** - * Props for {@link EntityRefLink}. + * Props for {@link FetchedEntityRefLinks}. * * @public */ From 8bb14a6ae6818a3f6334d78d9b3117653600e52e Mon Sep 17 00:00:00 2001 From: bnechyporenko Date: Tue, 27 Sep 2022 15:57:53 +0200 Subject: [PATCH 11/17] Fixed a problem in definition of EntityRefLinksProps Signed-off-by: bnechyporenko --- .../src/components/EntityRefLink/EntityRefLinks.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.tsx b/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.tsx index 71a60f3a15..2502fbbb6e 100644 --- a/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.tsx +++ b/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.tsx @@ -26,19 +26,21 @@ import { FetchedEntityRefLinks } from './FetchedEntityRefLinks'; */ 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); + } +) & + Omit; /** * Shows a list of clickable links to entities. From d3ae8eed67461d617fc6f2ff49b9bb074072fb7e Mon Sep 17 00:00:00 2001 From: bnechyporenko Date: Tue, 27 Sep 2022 16:28:14 +0200 Subject: [PATCH 12/17] Fixed a problem in definition of EntityRefLinksProps Signed-off-by: bnechyporenko --- plugins/catalog-react/api-report.md | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/plugins/catalog-react/api-report.md b/plugins/catalog-react/api-report.md index bb19e2a5b8..9e7a07c08b 100644 --- a/plugins/catalog-react/api-report.md +++ b/plugins/catalog-react/api-report.md @@ -279,14 +279,28 @@ export type EntityRefLinkProps = { } & Omit; // @public -export function EntityRefLinks(props: EntityRefLinksProps): JSX.Element; +export function EntityRefLinks< + TRef extends string | CompoundEntityRef | Entity, +>(props: EntityRefLinksProps): JSX.Element; // @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; // @public export function entityRouteParams(entity: Entity): { From 9e6b3c3adc52426230bda742877996b71c21fe2d Mon Sep 17 00:00:00 2001 From: bnechyporenko Date: Tue, 27 Sep 2022 17:44:53 +0200 Subject: [PATCH 13/17] Fixed an issue in filter in FetchedEntityRefLinks.tsx Signed-off-by: bnechyporenko --- .../components/EntityRefLink/FetchedEntityRefLinks.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/plugins/catalog-react/src/components/EntityRefLink/FetchedEntityRefLinks.tsx b/plugins/catalog-react/src/components/EntityRefLink/FetchedEntityRefLinks.tsx index 17c0a9de37..72c9aa5125 100644 --- a/plugins/catalog-react/src/components/EntityRefLink/FetchedEntityRefLinks.tsx +++ b/plugins/catalog-react/src/components/EntityRefLink/FetchedEntityRefLinks.tsx @@ -61,7 +61,15 @@ export function FetchedEntityRefLinks< }, new Array()); return refs - ? (await catalogApi.getEntities({ filter: refs })).items + ? ( + await catalogApi.getEntities({ + filter: refs.map(ref => ({ + kind: ref.kind, + 'metadata.namespace': ref.namespace, + 'metadata.name': ref.name, + })), + }) + ).items : (entityRefs as Array); }, [entityRefs]); From c59e05111bfec4fcf31273a339f15f028cafb774 Mon Sep 17 00:00:00 2001 From: bnechyporenko Date: Thu, 29 Sep 2022 14:39:06 +0200 Subject: [PATCH 14/17] Fixed an incorrect condition for an empty array Signed-off-by: bnechyporenko --- .../src/components/EntityRefLink/FetchedEntityRefLinks.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/catalog-react/src/components/EntityRefLink/FetchedEntityRefLinks.tsx b/plugins/catalog-react/src/components/EntityRefLink/FetchedEntityRefLinks.tsx index 72c9aa5125..88b92b4689 100644 --- a/plugins/catalog-react/src/components/EntityRefLink/FetchedEntityRefLinks.tsx +++ b/plugins/catalog-react/src/components/EntityRefLink/FetchedEntityRefLinks.tsx @@ -60,7 +60,7 @@ export function FetchedEntityRefLinks< return 'metadata' in current ? acc : [...acc, parseEntityRef(current)]; }, new Array()); - return refs + return refs.length > 0 ? ( await catalogApi.getEntities({ filter: refs.map(ref => ({ From 94431456d46d93896407806d79f235abc7a108a9 Mon Sep 17 00:00:00 2001 From: Bogdan Nechyporenko Date: Thu, 29 Sep 2022 14:39:48 +0200 Subject: [PATCH 15/17] Update .changeset/calm-pianos-burn.md Co-authored-by: Patrik Oldsberg Signed-off-by: Bogdan Nechyporenko --- .changeset/calm-pianos-burn.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/calm-pianos-burn.md b/.changeset/calm-pianos-burn.md index a7860ca4b7..6a44d42cfe 100644 --- a/.changeset/calm-pianos-burn.md +++ b/.changeset/calm-pianos-burn.md @@ -2,4 +2,4 @@ '@backstage/plugin-catalog-react': patch --- -Provide custom displayed text in EntityRefLinks +Added two new `EntityRefLinks` props, the first being `getTitle` that allows for customization of the title used for each link. The second one is `fetchEntities`, which triggers a fetching of all entities so that the full entity definition is available in the `getTitle` callback. From 4a7dd7865e924e68beb3e531c15b54b8e4045748 Mon Sep 17 00:00:00 2001 From: bnechyporenko Date: Thu, 29 Sep 2022 15:22:37 +0200 Subject: [PATCH 16/17] Added a test case which covers the case when the array of entities was provided to entityRefs Signed-off-by: bnechyporenko --- .../FetchedEntityRefLinks.test.tsx | 57 ++++++++++++++++++- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/plugins/catalog-react/src/components/EntityRefLink/FetchedEntityRefLinks.test.tsx b/plugins/catalog-react/src/components/EntityRefLink/FetchedEntityRefLinks.test.tsx index 0a3ea1003e..c416adcfbd 100644 --- a/plugins/catalog-react/src/components/EntityRefLink/FetchedEntityRefLinks.test.tsx +++ b/plugins/catalog-react/src/components/EntityRefLink/FetchedEntityRefLinks.test.tsx @@ -23,6 +23,9 @@ import { catalogApiRef } from '../../api'; import { CatalogApi } from '@backstage/catalog-client'; describe('', () => { + const getTitle = (e: Entity): string => + (e.spec?.profile!! as JsonObject).displayName!!.toString()!!; + it('should fetch entities and render the custom display text', async () => { const entityRefs = [ { @@ -57,9 +60,6 @@ describe('', () => { }), }; - const getTitle = (e: Entity): string => - (e.spec?.profile!! as JsonObject).displayName!!.toString()!!; - const rendered = await renderInTestApp( @@ -81,4 +81,55 @@ describe('', () => { '/catalog/default/api/interface', ); }); + + it('should use entities as they are provided and render the custom display text', async () => { + const entityRefs = [ + { + kind: 'Component', + namespace: 'default', + name: 'tool', + }, + { + kind: 'API', + namespace: 'default', + name: 'implementation', + }, + ].map(ref => ({ + apiVersion: 'backstage.io/v1alpha1', + kind: ref.kind, + metadata: { + name: ref.name, + namespace: ref.namespace, + }, + spec: { + profile: { + displayName: ref.name.toLocaleUpperCase('en-US'), + }, + type: 'organization', + }, + })); + + const catalogApi: Partial = {}; + + const rendered = await renderInTestApp( + + + , + { + mountedRoutes: { + '/catalog/:namespace/:kind/:name/*': entityRouteRef, + }, + }, + ); + + expect(rendered.getByText('TOOL')).toHaveAttribute( + 'href', + '/catalog/default/component/tool', + ); + + expect(rendered.getByText('IMPLEMENTATION')).toHaveAttribute( + 'href', + '/catalog/default/api/implementation', + ); + }); }); From 359efe7db9fd5416d7cb6c745e309d2096877e46 Mon Sep 17 00:00:00 2001 From: bnechyporenko Date: Thu, 29 Sep 2022 15:32:26 +0200 Subject: [PATCH 17/17] Added a test case which covers the case when the array of entities was provided to entityRefs Signed-off-by: bnechyporenko --- .../FetchedEntityRefLinks.test.tsx | 83 +++++++++++++++++++ .../EntityRefLink/FetchedEntityRefLinks.tsx | 27 +++--- 2 files changed, 100 insertions(+), 10 deletions(-) diff --git a/plugins/catalog-react/src/components/EntityRefLink/FetchedEntityRefLinks.test.tsx b/plugins/catalog-react/src/components/EntityRefLink/FetchedEntityRefLinks.test.tsx index c416adcfbd..36e2c0450d 100644 --- a/plugins/catalog-react/src/components/EntityRefLink/FetchedEntityRefLinks.test.tsx +++ b/plugins/catalog-react/src/components/EntityRefLink/FetchedEntityRefLinks.test.tsx @@ -132,4 +132,87 @@ describe('', () => { '/catalog/default/api/implementation', ); }); + + it('should handle heterogeneous array of values to render the custom display text', async () => { + const entityRefs = [ + ...[ + { + kind: 'Component', + namespace: 'default', + name: 'tool', + }, + { + kind: 'API', + namespace: 'default', + name: 'implementation', + }, + ].map(ref => ({ + apiVersion: 'backstage.io/v1alpha1', + kind: ref.kind, + metadata: { + name: ref.name, + namespace: ref.namespace, + }, + spec: { + profile: { + displayName: ref.name.toLocaleUpperCase('en-US'), + }, + type: 'organization', + }, + })), + { + kind: 'Component', + namespace: 'default', + name: 'interface', + }, + ]; + + const catalogApi: Partial = { + getEntities: () => + Promise.resolve({ + items: [ + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'interface', + namespace: 'default', + }, + spec: { + profile: { + displayName: 'INTERFACE', + }, + type: 'organization', + }, + }, + ], + }), + }; + + const rendered = await renderInTestApp( + + + , + { + mountedRoutes: { + '/catalog/:namespace/:kind/:name/*': entityRouteRef, + }, + }, + ); + + expect(rendered.getByText('TOOL')).toHaveAttribute( + 'href', + '/catalog/default/component/tool', + ); + + expect(rendered.getByText('IMPLEMENTATION')).toHaveAttribute( + 'href', + '/catalog/default/api/implementation', + ); + + expect(rendered.getByText('INTERFACE')).toHaveAttribute( + 'href', + '/catalog/default/component/interface', + ); + }); }); diff --git a/plugins/catalog-react/src/components/EntityRefLink/FetchedEntityRefLinks.tsx b/plugins/catalog-react/src/components/EntityRefLink/FetchedEntityRefLinks.tsx index 88b92b4689..ea4a32b511 100644 --- a/plugins/catalog-react/src/components/EntityRefLink/FetchedEntityRefLinks.tsx +++ b/plugins/catalog-react/src/components/EntityRefLink/FetchedEntityRefLinks.tsx @@ -60,17 +60,24 @@ export function FetchedEntityRefLinks< return 'metadata' in current ? acc : [...acc, parseEntityRef(current)]; }, new Array()); + const pureEntities = entityRefs.filter( + ref => 'metadata' in ref, + ) as Array; + return refs.length > 0 - ? ( - await catalogApi.getEntities({ - filter: refs.map(ref => ({ - kind: ref.kind, - 'metadata.namespace': ref.namespace, - 'metadata.name': ref.name, - })), - }) - ).items - : (entityRefs as Array); + ? [ + ...( + await catalogApi.getEntities({ + filter: refs.map(ref => ({ + kind: ref.kind, + 'metadata.namespace': ref.namespace, + 'metadata.name': ref.name, + })), + }) + ).items, + ...pureEntities, + ] + : pureEntities; }, [entityRefs]); if (loading) {