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 (