From 33846acfcb4f085d8bd05f372220248a61e56f06 Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Fri, 15 Jan 2021 11:13:58 +0100 Subject: [PATCH 1/3] Display the owner and system as links to the entity pages in the about card --- .changeset/healthy-comics-drive.md | 5 ++ .../src/components/AboutCard/AboutContent.tsx | 43 ++++++---- .../EntityRefLink/EntityRefLink.test.tsx | 86 +++++++++++++++++++ .../EntityRefLink/EntityRefLink.tsx | 75 ++++++++++++++++ .../src/components/EntityRefLink/index.ts | 16 ++++ 5 files changed, 206 insertions(+), 19 deletions(-) create mode 100644 .changeset/healthy-comics-drive.md create mode 100644 plugins/catalog/src/components/EntityRefLink/EntityRefLink.test.tsx create mode 100644 plugins/catalog/src/components/EntityRefLink/EntityRefLink.tsx create mode 100644 plugins/catalog/src/components/EntityRefLink/index.ts diff --git a/.changeset/healthy-comics-drive.md b/.changeset/healthy-comics-drive.md new file mode 100644 index 0000000000..3847274eda --- /dev/null +++ b/.changeset/healthy-comics-drive.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog': patch +--- + +Display the owner and system as links to the entity pages in the about card. diff --git a/plugins/catalog/src/components/AboutCard/AboutContent.tsx b/plugins/catalog/src/components/AboutCard/AboutContent.tsx index b8d5d84316..725217db9a 100644 --- a/plugins/catalog/src/components/AboutCard/AboutContent.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutContent.tsx @@ -14,15 +14,16 @@ * limitations under the License. */ -import React from 'react'; -import { Grid, Typography, Chip, makeStyles } from '@material-ui/core'; -import { AboutField } from './AboutField'; import { Entity, - ENTITY_DEFAULT_NAMESPACE, RELATION_OWNED_BY, - serializeEntityRef, + RELATION_PART_OF, } from '@backstage/catalog-model'; +import { Chip, Grid, makeStyles, Typography } from '@material-ui/core'; +import React from 'react'; +import { EntityRefLink } from '../EntityRefLink'; +import { getEntityRelations } from '../getEntityRelations'; +import { AboutField } from './AboutField'; const useStyles = makeStyles({ description: { @@ -36,6 +37,11 @@ type Props = { export const AboutContent = ({ entity }: Props) => { const classes = useStyles(); + const [partOfSystemRelation] = getEntityRelations(entity, RELATION_PART_OF, { + kind: 'system', + }); + const ownedByRelations = getEntityRelations(entity, RELATION_OWNED_BY); + return ( @@ -43,22 +49,21 @@ export const AboutContent = ({ entity }: Props) => { {entity?.metadata?.description || 'No description'} + + {ownedByRelations.map((t, i) => [ + i > 0 && ', ', + , + ])} + r.type === RELATION_OWNED_BY) - .map(({ target: { kind, name, namespace } }) => - // TODO(Rugvip): we want to provide some utils for this - serializeEntityRef({ - kind, - name, - namespace: - namespace === ENTITY_DEFAULT_NAMESPACE ? undefined : namespace, - }), - ) - .join(', ')} + label="System" + value="No System" gridSizes={{ xs: 12, sm: 6, lg: 4 }} - /> + > + {partOfSystemRelation && ( + + )} + ', () => { + it('renders link for entity in default namespace', () => { + const entity = { + apiVersion: 'v1', + kind: 'Component', + metadata: { + name: 'software', + }, + spec: { + owner: 'guest', + type: 'service', + lifecycle: 'production', + }, + }; + const { getByText } = render(, { + wrapper: MemoryRouter, + }); + + expect(getByText('component:software')).toBeInTheDocument(); + }); + + it('renders link for entity in other namespace', () => { + const entity = { + apiVersion: 'v1', + kind: 'Component', + metadata: { + name: 'software', + namespace: 'test', + }, + spec: { + owner: 'guest', + type: 'service', + lifecycle: 'production', + }, + }; + const { getByText } = render(, { + wrapper: MemoryRouter, + }); + expect(getByText('component:test/software')).toBeInTheDocument(); + }); + + it('renders link for entity name in default namespace', () => { + const entityName = { + kind: 'Component', + namespace: 'default', + name: 'software', + }; + const { getByText } = render(, { + wrapper: MemoryRouter, + }); + expect(getByText('component:software')).toBeInTheDocument(); + }); + + it('renders link for entity name in other namespace', () => { + const entityName = { + kind: 'Component', + namespace: 'test', + name: 'software', + }; + const { getByText } = render(, { + wrapper: MemoryRouter, + }); + expect(getByText('component:test/software')).toBeInTheDocument(); + }); +}); diff --git a/plugins/catalog/src/components/EntityRefLink/EntityRefLink.tsx b/plugins/catalog/src/components/EntityRefLink/EntityRefLink.tsx new file mode 100644 index 0000000000..1084fbd37e --- /dev/null +++ b/plugins/catalog/src/components/EntityRefLink/EntityRefLink.tsx @@ -0,0 +1,75 @@ +/* + * Copyright 2020 Spotify AB + * + * 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, + EntityName, + ENTITY_DEFAULT_NAMESPACE, + serializeEntityRef, +} from '@backstage/catalog-model'; +import { Link } from '@material-ui/core'; +import React from 'react'; +import { generatePath } from 'react-router'; +import { Link as RouterLink } from 'react-router-dom'; +import { entityRoute } from '../../routes'; + +type EntityRefLinkProps = { + entityRef: Entity | EntityName; +}; + +// TODO: This component is private for now, as it should probably belong into +// some kind of helper module for the catalog plugin to avoid a dependency on +// the catalog plugin itself. +export const EntityRefLink = ({ entityRef }: EntityRefLinkProps) => { + let kind; + let namespace; + let name; + + if ('metadata' in entityRef) { + kind = entityRef.kind; + namespace = entityRef.metadata.namespace; + name = entityRef.metadata.name; + } else { + kind = entityRef.kind; + namespace = entityRef.namespace; + name = entityRef.name; + } + + if (namespace === ENTITY_DEFAULT_NAMESPACE) { + namespace = undefined; + } + + kind = kind.toLowerCase(); + + const title = `${serializeEntityRef({ + kind, + name, + namespace, + })}`; + const routeParams = { + kind, + namespace: namespace?.toLowerCase() ?? ENTITY_DEFAULT_NAMESPACE, + name, + }; + + return ( + + {title} + + ); +}; diff --git a/plugins/catalog/src/components/EntityRefLink/index.ts b/plugins/catalog/src/components/EntityRefLink/index.ts new file mode 100644 index 0000000000..aa2c6641ef --- /dev/null +++ b/plugins/catalog/src/components/EntityRefLink/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2020 Spotify AB + * + * 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. + */ +export { EntityRefLink } from './EntityRefLink'; From 6507f83070bbd58b52bc3da4a8a4b45e7865f54b Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Fri, 15 Jan 2021 12:34:47 +0100 Subject: [PATCH 2/3] Hide the entity kind for systems --- .../src/components/AboutCard/AboutContent.tsx | 15 +++++--- .../EntityRefLink/EntityRefLink.test.tsx | 38 +++++++++++++++++++ .../EntityRefLink/EntityRefLink.tsx | 9 ++++- 3 files changed, 55 insertions(+), 7 deletions(-) diff --git a/plugins/catalog/src/components/AboutCard/AboutContent.tsx b/plugins/catalog/src/components/AboutCard/AboutContent.tsx index 725217db9a..d0c25c69b6 100644 --- a/plugins/catalog/src/components/AboutCard/AboutContent.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutContent.tsx @@ -50,10 +50,12 @@ export const AboutContent = ({ entity }: Props) => { - {ownedByRelations.map((t, i) => [ - i > 0 && ', ', - , - ])} + {ownedByRelations.map((t, i) => ( + + {i > 0 && ', '} + + + ))} { gridSizes={{ xs: 12, sm: 6, lg: 4 }} > {partOfSystemRelation && ( - + )} ', () => { expect(getByText('component:test/software')).toBeInTheDocument(); }); + it('renders link for entity and hides default kind', () => { + const entity = { + apiVersion: 'v1', + kind: 'Component', + metadata: { + name: 'software', + namespace: 'test', + }, + spec: { + owner: 'guest', + type: 'service', + lifecycle: 'production', + }, + }; + const { getByText } = render( + , + { + wrapper: MemoryRouter, + }, + ); + expect(getByText('test/software')).toBeInTheDocument(); + }); + it('renders link for entity name in default namespace', () => { const entityName = { kind: 'Component', @@ -83,4 +106,19 @@ describe('', () => { }); expect(getByText('component:test/software')).toBeInTheDocument(); }); + + it('renders link for entity name and hides default kind', () => { + const entityName = { + kind: 'Component', + namespace: 'test', + name: 'software', + }; + const { getByText } = render( + , + { + wrapper: MemoryRouter, + }, + ); + expect(getByText('test/software')).toBeInTheDocument(); + }); }); diff --git a/plugins/catalog/src/components/EntityRefLink/EntityRefLink.tsx b/plugins/catalog/src/components/EntityRefLink/EntityRefLink.tsx index 1084fbd37e..a06ba69fbe 100644 --- a/plugins/catalog/src/components/EntityRefLink/EntityRefLink.tsx +++ b/plugins/catalog/src/components/EntityRefLink/EntityRefLink.tsx @@ -27,12 +27,16 @@ import { entityRoute } from '../../routes'; type EntityRefLinkProps = { entityRef: Entity | EntityName; + defaultKind?: string; }; // TODO: This component is private for now, as it should probably belong into // some kind of helper module for the catalog plugin to avoid a dependency on // the catalog plugin itself. -export const EntityRefLink = ({ entityRef }: EntityRefLinkProps) => { +export const EntityRefLink = ({ + entityRef, + defaultKind, +}: EntityRefLinkProps) => { let kind; let namespace; let name; @@ -54,7 +58,7 @@ export const EntityRefLink = ({ entityRef }: EntityRefLinkProps) => { kind = kind.toLowerCase(); const title = `${serializeEntityRef({ - kind, + kind: defaultKind && defaultKind.toLowerCase() === kind ? undefined : kind, name, namespace, })}`; @@ -64,6 +68,7 @@ export const EntityRefLink = ({ entityRef }: EntityRefLinkProps) => { name, }; + // TODO: Use useRouteRef here to generate the path return ( Date: Mon, 18 Jan 2021 09:41:36 +0100 Subject: [PATCH 3/3] Show domain for systems and hide other fields if the don't apply for the kind --- .changeset/healthy-comics-drive.md | 3 +- .../src/components/AboutCard/AboutContent.tsx | 70 +++++++++++++------ 2 files changed, 50 insertions(+), 23 deletions(-) diff --git a/.changeset/healthy-comics-drive.md b/.changeset/healthy-comics-drive.md index 3847274eda..4b532dc853 100644 --- a/.changeset/healthy-comics-drive.md +++ b/.changeset/healthy-comics-drive.md @@ -2,4 +2,5 @@ '@backstage/plugin-catalog': patch --- -Display the owner and system as links to the entity pages in the about card. +Display the owner, system, and domain as links to the entity pages in the about card. +Only display fields in the about card that are applicable to the entity kind. diff --git a/plugins/catalog/src/components/AboutCard/AboutContent.tsx b/plugins/catalog/src/components/AboutCard/AboutContent.tsx index d0c25c69b6..178debb42a 100644 --- a/plugins/catalog/src/components/AboutCard/AboutContent.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutContent.tsx @@ -37,9 +37,15 @@ type Props = { export const AboutContent = ({ entity }: Props) => { const classes = useStyles(); + const isSystem = entity.kind.toLowerCase() === 'system'; + const isDomain = entity.kind.toLowerCase() === 'domain'; + const isResource = entity.kind.toLowerCase() === 'resource'; const [partOfSystemRelation] = getEntityRelations(entity, RELATION_PART_OF, { kind: 'system', }); + const [partOfDomainRelation] = getEntityRelations(entity, RELATION_PART_OF, { + kind: 'domain', + }); const ownedByRelations = getEntityRelations(entity, RELATION_OWNED_BY); return ( @@ -57,28 +63,48 @@ export const AboutContent = ({ entity }: Props) => { ))} - - {partOfSystemRelation && ( - - )} - - - + {isSystem && ( + + {partOfDomainRelation && ( + + )} + + )} + {!isSystem && !isDomain && ( + + {partOfSystemRelation && ( + + )} + + )} + {!isSystem && !isDomain && ( + + )} + {!isSystem && !isDomain && !isResource && ( + + )}