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!,
+ ),
+ }))}
+ />
+
+ )}
);
}