diff --git a/.changeset/pretty-cameras-give.md b/.changeset/pretty-cameras-give.md new file mode 100644 index 0000000000..8f9f4a83fa --- /dev/null +++ b/.changeset/pretty-cameras-give.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-catalog': patch +--- + +Update the `AboutCard` to properly support non-standard entity types and rework the defaults for the build-in kinds. + +This change also uses `useElementFilter(...)` instead of `React.children.count(...)` in `AboutField` to properly recognize whether children are available. diff --git a/.changeset/thick-trees-pay.md b/.changeset/thick-trees-pay.md new file mode 100644 index 0000000000..6ee7b514ca --- /dev/null +++ b/.changeset/thick-trees-pay.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog': patch +--- + +Use a `Link` for the edit button on the `AboutCard` instead of doing `window.open(...)` diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx index 539cce7da3..6c85ebcab7 100644 --- a/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx @@ -15,21 +15,20 @@ */ import { RELATION_OWNED_BY } from '@backstage/catalog-model'; +import { + ApiProvider, + ApiRegistry, + ConfigReader, +} from '@backstage/core-app-api'; import { ScmIntegrationsApi, scmIntegrationsApiRef, } from '@backstage/integration-react'; import { EntityProvider } from '@backstage/plugin-catalog-react'; import { renderInTestApp } from '@backstage/test-utils'; -import { act, fireEvent } from '@testing-library/react'; import React from 'react'; -import { AboutCard } from './AboutCard'; -import { - ApiProvider, - ApiRegistry, - ConfigReader, -} from '@backstage/core-app-api'; import { viewTechDocRouteRef } from '../../routes'; +import { AboutCard } from './AboutCard'; describe('', () => { it('renders info', async () => { @@ -166,14 +165,10 @@ describe('', () => { , ); - const editButton = getByTitle('Edit Metadata'); - window.open = jest.fn(); - await act(async () => { - fireEvent.click(editButton); - }); - expect(window.open).toHaveBeenCalledWith( - `https://github.com/backstage/backstage/edit/master/software.yaml`, - '_blank', + const editLink = getByTitle('Edit Metadata').closest('a'); + expect(editLink).toHaveAttribute( + 'href', + 'https://github.com/backstage/backstage/edit/master/software.yaml', ); }); diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.tsx index 113ebe47bd..582b464afa 100644 --- a/plugins/catalog/src/components/AboutCard/AboutCard.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutCard.tsx @@ -20,6 +20,13 @@ import { RELATION_CONSUMES_API, RELATION_PROVIDES_API, } from '@backstage/catalog-model'; +import { + HeaderIconLinkRow, + IconLinkVerticalProps, + InfoCardVariants, + Link, +} from '@backstage/core-components'; +import { useApi, useRouteRef } from '@backstage/core-plugin-api'; import { ScmIntegrationIcon, scmIntegrationsApiRef, @@ -42,15 +49,8 @@ import DocsIcon from '@material-ui/icons/Description'; import EditIcon from '@material-ui/icons/Edit'; import ExtensionIcon from '@material-ui/icons/Extension'; import React from 'react'; -import { AboutContent } from './AboutContent'; - -import { - HeaderIconLinkRow, - IconLinkVerticalProps, - InfoCardVariants, -} from '@backstage/core-components'; -import { useApi, useRouteRef } from '@backstage/core-plugin-api'; import { viewTechDocRouteRef } from '../../routes'; +import { AboutContent } from './AboutContent'; const useStyles = makeStyles({ gridItemCard: { @@ -148,12 +148,11 @@ export function AboutCard({ variant }: AboutCardProps) { title="About" action={ { - window.open(entityMetadataEditUrl ?? '#', '_blank'); - }} + to={entityMetadataEditUrl ?? '#'} > diff --git a/plugins/catalog/src/components/AboutCard/AboutContent.test.tsx b/plugins/catalog/src/components/AboutCard/AboutContent.test.tsx new file mode 100644 index 0000000000..a1d16363a5 --- /dev/null +++ b/plugins/catalog/src/components/AboutCard/AboutContent.test.tsx @@ -0,0 +1,631 @@ +/* + * Copyright 2021 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, + RELATION_OWNED_BY, + RELATION_PART_OF, +} from '@backstage/catalog-model'; +import { renderInTestApp } from '@backstage/test-utils'; +import React from 'react'; +import { AboutContent } from './AboutContent'; + +describe('', () => { + describe('An unknown entity', () => { + let entity: Entity; + + beforeEach(() => { + entity = { + apiVersion: 'custom.company/v1', + kind: 'Unknown', + metadata: { + name: 'software', + description: 'This is the description', + tags: ['tag-1'], + }, + spec: { + owner: 'o', + domain: 'd', + system: 's', + type: 't', + lifecycle: 'l', + }, + relations: [ + { + type: RELATION_OWNED_BY, + target: { + kind: 'user', + name: 'o', + namespace: 'default', + }, + }, + { + type: RELATION_PART_OF, + target: { + kind: 'system', + name: 's', + namespace: 'default', + }, + }, + { + type: RELATION_PART_OF, + target: { + kind: 'domain', + name: 'd', + namespace: 'default', + }, + }, + ], + }; + }); + + it('renders info', async () => { + const { getByText, queryByText } = await renderInTestApp( + , + ); + + expect(getByText('Description')).toBeInTheDocument(); + expect(getByText('Description').nextSibling).toHaveTextContent( + 'This is the description', + ); + expect(getByText('Owner')).toBeInTheDocument(); + expect(getByText('Owner').nextSibling).toHaveTextContent('user:o'); + expect(getByText('Domain')).toBeInTheDocument(); + expect(getByText('Domain').nextSibling).toHaveTextContent('d'); + expect(getByText('System')).toBeInTheDocument(); + expect(getByText('System').nextSibling).toHaveTextContent('s'); + expect(queryByText('Parent Component')).not.toBeInTheDocument(); + expect(getByText('Type')).toBeInTheDocument(); + expect(getByText('Type').nextSibling).toHaveTextContent('t'); + expect(getByText('Lifecycle')).toBeInTheDocument(); + expect(getByText('Lifecycle').nextSibling).toHaveTextContent('l'); + expect(getByText('Tags')).toBeInTheDocument(); + expect(getByText('Tags').nextSibling).toHaveTextContent('tag-1'); + }); + + it('highlights missing required fields', async () => { + delete entity.metadata.tags; + entity.spec = {}; + entity.relations = []; + + const { getByText, queryByText } = await renderInTestApp( + , + ); + + expect(getByText('Description')).toBeInTheDocument(); + expect(getByText('Description').nextSibling).toHaveTextContent( + 'This is the description', + ); + expect(getByText('Owner')).toBeInTheDocument(); + expect(getByText('Owner').nextSibling).toHaveTextContent('No Owner'); + expect(queryByText('Domain')).not.toBeInTheDocument(); + expect(queryByText('System')).not.toBeInTheDocument(); + expect(queryByText('Parent Component')).not.toBeInTheDocument(); + expect(queryByText('Type')).not.toBeInTheDocument(); + expect(queryByText('Lifecycle')).not.toBeInTheDocument(); + }); + }); + + describe('API entity', () => { + let entity: Entity; + + beforeEach(() => { + entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'API', + metadata: { + name: 'software', + description: 'This is the description', + tags: ['tag-1'], + }, + spec: { + type: 'openapi', + lifecycle: 'production', + owner: 'guest', + definition: '...', + system: 'system', + }, + relations: [ + { + type: RELATION_OWNED_BY, + target: { + kind: 'user', + name: 'guest', + namespace: 'default', + }, + }, + { + type: RELATION_PART_OF, + target: { + kind: 'system', + name: 'system', + namespace: 'default', + }, + }, + ], + }; + }); + + it('renders info', async () => { + const { getByText, queryByText } = await renderInTestApp( + , + ); + + expect(getByText('Description')).toBeInTheDocument(); + expect(getByText('Description').nextSibling).toHaveTextContent( + 'This is the description', + ); + expect(getByText('Owner')).toBeInTheDocument(); + expect(getByText('Owner').nextSibling).toHaveTextContent('user:guest'); + expect(queryByText('Domain')).not.toBeInTheDocument(); + expect(getByText('System')).toBeInTheDocument(); + expect(getByText('System').nextSibling).toHaveTextContent('system'); + expect(queryByText('Parent Component')).not.toBeInTheDocument(); + expect(getByText('Type')).toBeInTheDocument(); + expect(getByText('Type').nextSibling).toHaveTextContent('openapi'); + expect(getByText('Lifecycle')).toBeInTheDocument(); + expect(getByText('Lifecycle').nextSibling).toHaveTextContent( + 'production', + ); + expect(getByText('Tags')).toBeInTheDocument(); + expect(getByText('Tags').nextSibling).toHaveTextContent('tag-1'); + }); + + it('highlights missing required fields', async () => { + delete entity.metadata.tags; + delete entity.spec!.type; + delete entity.spec!.lifecycle; + delete entity.spec!.system; + entity.relations = []; + + const { getByText, queryByText } = await renderInTestApp( + , + ); + + expect(getByText('Description')).toBeInTheDocument(); + expect(getByText('Description').nextSibling).toHaveTextContent( + 'This is the description', + ); + expect(getByText('Owner')).toBeInTheDocument(); + expect(getByText('Owner').nextSibling).toHaveTextContent('No Owner'); + expect(queryByText('Domain')).not.toBeInTheDocument(); + expect(getByText('System')).toBeInTheDocument(); + expect(getByText('System').nextSibling).toHaveTextContent('No System'); + expect(queryByText('Parent Component')).not.toBeInTheDocument(); + expect(getByText('Type')).toBeInTheDocument(); + expect(getByText('Type').nextSibling).toHaveTextContent('unknown'); + expect(getByText('Lifecycle')).toBeInTheDocument(); + expect(getByText('Lifecycle').nextSibling).toHaveTextContent('unknown'); + expect(getByText('Tags')).toBeInTheDocument(); + expect(getByText('Tags').nextSibling).toHaveTextContent('No Tags'); + }); + }); + + describe('Component entity', () => { + let entity: Entity; + + beforeEach(() => { + entity = { + apiVersion: 'v1', + kind: 'Component', + metadata: { + name: 'software', + description: 'This is the description', + tags: ['tag-1'], + }, + spec: { + owner: 'guest', + type: 'service', + lifecycle: 'production', + system: 'system', + subcomponentOf: ['parent-software'], + }, + relations: [ + { + type: RELATION_OWNED_BY, + target: { + kind: 'user', + name: 'guest', + namespace: 'default', + }, + }, + { + type: RELATION_PART_OF, + target: { + kind: 'system', + name: 'system', + namespace: 'default', + }, + }, + { + type: RELATION_PART_OF, + target: { + kind: 'component', + name: 'parent-software', + namespace: 'default', + }, + }, + ], + }; + }); + + it('renders info', async () => { + const { getByText, queryByText } = await renderInTestApp( + , + ); + + expect(getByText('Description')).toBeInTheDocument(); + expect(getByText('Description').nextSibling).toHaveTextContent( + 'This is the description', + ); + expect(getByText('Owner')).toBeInTheDocument(); + expect(getByText('Owner').nextSibling).toHaveTextContent('user:guest'); + expect(queryByText('Domain')).not.toBeInTheDocument(); + expect(getByText('System')).toBeInTheDocument(); + expect(getByText('System').nextSibling).toHaveTextContent('system'); + expect(getByText('Parent Component')).toBeInTheDocument(); + expect(getByText('Parent Component').nextSibling).toHaveTextContent( + 'parent-software', + ); + expect(getByText('Type')).toBeInTheDocument(); + expect(getByText('Type').nextSibling).toHaveTextContent('service'); + expect(getByText('Lifecycle')).toBeInTheDocument(); + expect(getByText('Lifecycle').nextSibling).toHaveTextContent( + 'production', + ); + expect(getByText('Tags')).toBeInTheDocument(); + expect(getByText('Tags').nextSibling).toHaveTextContent('tag-1'); + }); + + it('highlights missing required fields', async () => { + delete entity.metadata.tags; + delete entity.spec!.type; + delete entity.spec!.lifecycle; + delete entity.spec!.system; + entity.relations = []; + + const { getByText, queryByText } = await renderInTestApp( + , + ); + + expect(getByText('Description')).toBeInTheDocument(); + expect(getByText('Description').nextSibling).toHaveTextContent( + 'This is the description', + ); + expect(getByText('Owner')).toBeInTheDocument(); + expect(getByText('Owner').nextSibling).toHaveTextContent('No Owner'); + expect(queryByText('Domain')).not.toBeInTheDocument(); + expect(getByText('System')).toBeInTheDocument(); + expect(getByText('System').nextSibling).toHaveTextContent('No System'); + expect(queryByText('Parent Component')).not.toBeInTheDocument(); + expect(getByText('Type')).toBeInTheDocument(); + expect(getByText('Type').nextSibling).toHaveTextContent('unknown'); + expect(getByText('Lifecycle')).toBeInTheDocument(); + expect(getByText('Lifecycle').nextSibling).toHaveTextContent('unknown'); + expect(getByText('Tags')).toBeInTheDocument(); + expect(getByText('Tags').nextSibling).toHaveTextContent('No Tags'); + }); + }); + + describe('Domain entity', () => { + let entity: Entity; + + beforeEach(() => { + entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Domain', + metadata: { + name: 'software', + description: 'This is the description', + tags: ['tag-1'], + }, + spec: { + owner: 'guest', + }, + relations: [ + { + type: RELATION_OWNED_BY, + target: { + kind: 'user', + name: 'guest', + namespace: 'default', + }, + }, + ], + }; + }); + + it('renders info', async () => { + const { getByText, queryByText } = await renderInTestApp( + , + ); + + expect(getByText('Description')).toBeInTheDocument(); + expect(getByText('Description').nextSibling).toHaveTextContent( + 'This is the description', + ); + expect(getByText('Owner')).toBeInTheDocument(); + expect(getByText('Owner').nextSibling).toHaveTextContent('user:guest'); + expect(queryByText('Domain')).not.toBeInTheDocument(); + expect(queryByText('System')).not.toBeInTheDocument(); + expect(queryByText('Parent Component')).not.toBeInTheDocument(); + expect(queryByText('Type')).not.toBeInTheDocument(); + expect(queryByText('Lifecycle')).not.toBeInTheDocument(); + expect(getByText('Tags')).toBeInTheDocument(); + expect(getByText('Tags').nextSibling).toHaveTextContent('tag-1'); + }); + + it('highlights missing required fields', async () => { + delete entity.metadata.tags; + entity.relations = []; + + const { getByText, queryByText } = await renderInTestApp( + , + ); + + expect(getByText('Description')).toBeInTheDocument(); + expect(getByText('Description').nextSibling).toHaveTextContent( + 'This is the description', + ); + expect(getByText('Owner')).toBeInTheDocument(); + expect(getByText('Owner').nextSibling).toHaveTextContent('No Owner'); + expect(queryByText('Domain')).not.toBeInTheDocument(); + expect(queryByText('System')).not.toBeInTheDocument(); + expect(queryByText('Parent Component')).not.toBeInTheDocument(); + expect(queryByText('Type')).not.toBeInTheDocument(); + expect(queryByText('Lifecycle')).not.toBeInTheDocument(); + expect(getByText('Tags')).toBeInTheDocument(); + expect(getByText('Tags').nextSibling).toHaveTextContent('No Tags'); + }); + }); + + describe('Location entity', () => { + let entity: Entity; + + beforeEach(() => { + entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Location', + metadata: { + name: 'software', + description: 'This is the description', + tags: ['tag-1'], + }, + spec: { + type: 'root', + }, + relations: [], + }; + }); + + it('renders info', async () => { + const { getByText, queryByText } = await renderInTestApp( + , + ); + + expect(getByText('Description')).toBeInTheDocument(); + expect(getByText('Description').nextSibling).toHaveTextContent( + 'This is the description', + ); + expect(getByText('Owner')).toBeInTheDocument(); + expect(getByText('Owner').nextSibling).toHaveTextContent('No Owner'); + expect(queryByText('Domain')).not.toBeInTheDocument(); + expect(queryByText('System')).not.toBeInTheDocument(); + expect(queryByText('Parent Component')).not.toBeInTheDocument(); + expect(getByText('Type')).toBeInTheDocument(); + expect(getByText('Type').nextSibling).toHaveTextContent('root'); + expect(queryByText('Lifecycle')).not.toBeInTheDocument(); + expect(getByText('Tags')).toBeInTheDocument(); + expect(getByText('Tags').nextSibling).toHaveTextContent('tag-1'); + }); + + it('highlights missing required fields', async () => { + delete entity.metadata.tags; + delete entity.spec!.type; + + const { getByText, queryByText } = await renderInTestApp( + , + ); + + expect(getByText('Description')).toBeInTheDocument(); + expect(getByText('Description').nextSibling).toHaveTextContent( + 'This is the description', + ); + expect(getByText('Owner')).toBeInTheDocument(); + expect(getByText('Owner').nextSibling).toHaveTextContent('No Owner'); + expect(queryByText('Domain')).not.toBeInTheDocument(); + expect(queryByText('System')).not.toBeInTheDocument(); + expect(queryByText('Parent Component')).not.toBeInTheDocument(); + expect(getByText('Type')).toBeInTheDocument(); + expect(getByText('Type').nextSibling).toHaveTextContent('unknown'); + expect(queryByText('Lifecycle')).not.toBeInTheDocument(); + expect(getByText('Tags')).toBeInTheDocument(); + expect(getByText('Tags').nextSibling).toHaveTextContent('No Tags'); + }); + }); + + describe('Resource entity', () => { + let entity: Entity; + + beforeEach(() => { + entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Resource', + metadata: { + name: 'software', + description: 'This is the description', + tags: ['tag-1'], + }, + spec: { + type: 's3', + owner: 'guest', + system: 'system', + }, + relations: [ + { + type: RELATION_OWNED_BY, + target: { + kind: 'user', + name: 'guest', + namespace: 'default', + }, + }, + { + type: RELATION_PART_OF, + target: { + kind: 'system', + name: 'system', + namespace: 'default', + }, + }, + ], + }; + }); + + it('renders info', async () => { + const { getByText, queryByText } = await renderInTestApp( + , + ); + + expect(getByText('Description')).toBeInTheDocument(); + expect(getByText('Description').nextSibling).toHaveTextContent( + 'This is the description', + ); + expect(getByText('Owner')).toBeInTheDocument(); + expect(getByText('Owner').nextSibling).toHaveTextContent('user:guest'); + expect(queryByText('Domain')).not.toBeInTheDocument(); + expect(getByText('System')).toBeInTheDocument(); + expect(getByText('System').nextSibling).toHaveTextContent('system'); + expect(queryByText('Parent Component')).not.toBeInTheDocument(); + expect(getByText('Type')).toBeInTheDocument(); + expect(getByText('Type').nextSibling).toHaveTextContent('s3'); + expect(queryByText('Lifecycle')).not.toBeInTheDocument(); + expect(getByText('Tags')).toBeInTheDocument(); + expect(getByText('Tags').nextSibling).toHaveTextContent('tag-1'); + }); + + it('highlights missing required fields', async () => { + delete entity.metadata.tags; + delete entity.spec!.type; + delete entity.spec!.system; + entity.relations = []; + + const { getByText, queryByText } = await renderInTestApp( + , + ); + + expect(getByText('Description')).toBeInTheDocument(); + expect(getByText('Description').nextSibling).toHaveTextContent( + 'This is the description', + ); + expect(getByText('Owner')).toBeInTheDocument(); + expect(getByText('Owner').nextSibling).toHaveTextContent('No Owner'); + expect(queryByText('Domain')).not.toBeInTheDocument(); + expect(getByText('System')).toBeInTheDocument(); + expect(getByText('System').nextSibling).toHaveTextContent('No System'); + expect(queryByText('Parent Component')).not.toBeInTheDocument(); + expect(getByText('Type')).toBeInTheDocument(); + expect(getByText('Type').nextSibling).toHaveTextContent('unknown'); + expect(queryByText('Lifecycle')).not.toBeInTheDocument(); + expect(getByText('Tags')).toBeInTheDocument(); + expect(getByText('Tags').nextSibling).toHaveTextContent('No Tags'); + }); + }); + + describe('System entity', () => { + let entity: Entity; + + beforeEach(() => { + entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'System', + metadata: { + name: 'software', + description: 'This is the description', + tags: ['tag-1'], + }, + spec: { + owner: 'guest', + domain: 'domain', + }, + relations: [ + { + type: RELATION_OWNED_BY, + target: { + kind: 'user', + name: 'guest', + namespace: 'default', + }, + }, + { + type: RELATION_PART_OF, + target: { + kind: 'domain', + name: 'domain', + namespace: 'default', + }, + }, + ], + }; + }); + + it('renders info', async () => { + const { getByText, queryByText } = await renderInTestApp( + , + ); + + expect(getByText('Description')).toBeInTheDocument(); + expect(getByText('Description').nextSibling).toHaveTextContent( + 'This is the description', + ); + expect(getByText('Owner')).toBeInTheDocument(); + expect(getByText('Owner').nextSibling).toHaveTextContent('user:guest'); + expect(getByText('Domain')).toBeInTheDocument(); + expect(getByText('Domain').nextSibling).toHaveTextContent('domain'); + expect(queryByText('System')).not.toBeInTheDocument(); + expect(queryByText('Parent Component')).not.toBeInTheDocument(); + expect(queryByText('Type')).not.toBeInTheDocument(); + expect(queryByText('Lifecycle')).not.toBeInTheDocument(); + expect(getByText('Tags')).toBeInTheDocument(); + expect(getByText('Tags').nextSibling).toHaveTextContent('tag-1'); + }); + + it('highlights missing required fields', async () => { + delete entity.metadata.tags; + delete entity.spec!.domain; + entity.relations = []; + + const { getByText, queryByText } = await renderInTestApp( + , + ); + + expect(getByText('Description')).toBeInTheDocument(); + expect(getByText('Description').nextSibling).toHaveTextContent( + 'This is the description', + ); + expect(getByText('Owner')).toBeInTheDocument(); + expect(getByText('Owner').nextSibling).toHaveTextContent('No Owner'); + expect(getByText('Domain')).toBeInTheDocument(); + expect(getByText('Domain').nextSibling).toHaveTextContent('No Domain'); + expect(queryByText('System')).not.toBeInTheDocument(); + expect(queryByText('Parent Component')).not.toBeInTheDocument(); + expect(queryByText('Type')).not.toBeInTheDocument(); + expect(queryByText('Lifecycle')).not.toBeInTheDocument(); + expect(getByText('Tags')).toBeInTheDocument(); + expect(getByText('Tags').nextSibling).toHaveTextContent('No Tags'); + }); + }); +}); diff --git a/plugins/catalog/src/components/AboutCard/AboutContent.tsx b/plugins/catalog/src/components/AboutCard/AboutContent.tsx index 968ed3de2a..9c8260c14d 100644 --- a/plugins/catalog/src/components/AboutCard/AboutContent.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutContent.tsx @@ -40,9 +40,13 @@ type Props = { export const AboutContent = ({ entity }: Props) => { const classes = useStyles(); const isSystem = entity.kind.toLocaleLowerCase('en-US') === 'system'; - const isDomain = entity.kind.toLocaleLowerCase('en-US') === 'domain'; const isResource = entity.kind.toLocaleLowerCase('en-US') === 'resource'; const isComponent = entity.kind.toLocaleLowerCase('en-US') === 'component'; + const isAPI = entity.kind.toLocaleLowerCase('en-US') === 'api'; + const isTemplate = entity.kind.toLocaleLowerCase('en-US') === 'template'; + const isLocation = entity.kind.toLocaleLowerCase('en-US') === 'location'; + const isGroup = entity.kind.toLocaleLowerCase('en-US') === 'group'; + const partOfSystemRelations = getEntityRelations(entity, RELATION_PART_OF, { kind: 'system', }); @@ -65,31 +69,44 @@ export const AboutContent = ({ entity }: Props) => { {entity?.metadata?.description || 'No description'} - - + + {ownedByRelations.length > 0 && ( + + )} - {isSystem && ( + {(isSystem || partOfDomainRelations.length > 0) && ( - + {partOfDomainRelations.length > 0 && ( + + )} )} - {!isSystem && !isDomain && ( + {(isAPI || + isComponent || + isResource || + partOfSystemRelations.length > 0) && ( - + {partOfSystemRelations.length > 0 && ( + + )} )} {isComponent && partOfComponentRelations.length > 0 && ( @@ -104,14 +121,22 @@ export const AboutContent = ({ entity }: Props) => { /> )} - {!isSystem && !isDomain && ( + {(isAPI || + isComponent || + isResource || + isTemplate || + isGroup || + isLocation || + typeof entity?.spec?.type === 'string') && ( )} - {!isSystem && !isDomain && !isResource && ( + {(isAPI || + isComponent || + typeof entity?.spec?.lifecycle === 'string') && ( ({ value: { @@ -45,14 +46,17 @@ type Props = { export const AboutField = ({ label, value, gridSizes, children }: Props) => { const classes = useStyles(); + const childElements = useElementFilter(children, c => c.getElements()); + // Content is either children or a string prop `value` - const content = React.Children.count(children) ? ( - children - ) : ( - - {value || `unknown`} - - ); + const content = + childElements.length > 0 ? ( + childElements + ) : ( + + {value || `unknown`} + + ); return (