diff --git a/.changeset/quiet-hounds-drive.md b/.changeset/quiet-hounds-drive.md new file mode 100644 index 0000000000..ca1b1c6dbd --- /dev/null +++ b/.changeset/quiet-hounds-drive.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog': minor +--- + +Add `Location` target(s) to `AboutCard`. diff --git a/plugins/catalog/src/components/AboutCard/AboutContent.test.tsx b/plugins/catalog/src/components/AboutCard/AboutContent.test.tsx index 87ee8cc6ec..f4dbb73b45 100644 --- a/plugins/catalog/src/components/AboutCard/AboutContent.test.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutContent.test.tsx @@ -411,6 +411,7 @@ describe('', () => { }, spec: { type: 'root', + target: 'https://backstage.io', }, relations: [], }; @@ -440,6 +441,10 @@ describe('', () => { expect(queryByText('Lifecycle')).not.toBeInTheDocument(); expect(getByText('Tags')).toBeInTheDocument(); expect(getByText('Tags').nextSibling).toHaveTextContent('tag-1'); + expect(getByText('Targets')).toBeInTheDocument(); + expect(getByText('Targets').nextSibling).toHaveTextContent( + 'https://backstage.io', + ); }); it('highlights missing required fields', async () => { diff --git a/plugins/catalog/src/components/AboutCard/AboutContent.tsx b/plugins/catalog/src/components/AboutCard/AboutContent.tsx index ee1dad5d39..260a0eccbf 100644 --- a/plugins/catalog/src/components/AboutCard/AboutContent.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutContent.tsx @@ -16,6 +16,7 @@ import { Entity, + getEntitySourceLocation, RELATION_OWNED_BY, RELATION_PART_OF, } from '@backstage/catalog-model'; @@ -23,9 +24,11 @@ import { EntityRefLinks, getEntityRelations, } from '@backstage/plugin-catalog-react'; +import { JsonArray } from '@backstage/types'; import { Chip, Grid, makeStyles, Typography } from '@material-ui/core'; import React from 'react'; import { AboutField } from './AboutField'; +import { LinksGridList } from '../EntityLinksCard/LinksGridList'; const useStyles = makeStyles({ description: { @@ -42,6 +45,30 @@ export interface AboutContentProps { entity: Entity; } +function getLocationTargetHref( + target: string, + type: string, + entitySourceLocation: { + type: string; + target: string; + }, +): string { + if (type === 'url' || target.includes('://')) { + return target; + } + + const srcLocationUrl = + entitySourceLocation.type === 'file' + ? `file://${entitySourceLocation.target}` + : entitySourceLocation.target; + + if (type === 'file' || entitySourceLocation.type === 'file') { + return new URL(target, srcLocationUrl).href; + } + + return srcLocationUrl; +} + /** @public */ export function AboutContent(props: AboutContentProps) { const { entity } = props; @@ -69,6 +96,18 @@ export function AboutContent(props: AboutContentProps) { }); const ownedByRelations = getEntityRelations(entity, RELATION_OWNED_BY); + let entitySourceLocation: + | { + type: string; + target: string; + } + | undefined; + try { + entitySourceLocation = getEntitySourceLocation(entity); + } catch (e) { + entitySourceLocation = undefined; + } + return ( @@ -159,6 +198,23 @@ export function AboutContent(props: AboutContentProps) { ))} + {isLocation && (entity?.spec?.targets || entity?.spec?.target) && ( + + target as string) + .map(target => ({ + text: target, + href: getLocationTargetHref( + target, + (entity?.spec?.type || 'unknown') as string, + entitySourceLocation!, + ), + }))} + /> + + )} ); }