diff --git a/.changeset/weak-jeans-live.md b/.changeset/weak-jeans-live.md
new file mode 100644
index 0000000000..0d25f07cca
--- /dev/null
+++ b/.changeset/weak-jeans-live.md
@@ -0,0 +1,12 @@
+---
+'@backstage/plugin-catalog': patch
+---
+
+- Added `RelatedEntitesCard` as a base implementation of displaying entities that are related to another entity.
+- Added `HasResourcesCard` to display resources that are part of a system.
+- Added `DependsOnComponentsCard` to display components that are dependencies of a component.
+- Added `DependsOnResourcesCard` to display resources that are dependencies of a component.
+- Refactored `HasComponentsCard` to use base `RelatedEntitiesCard`. Card remains backwards compatible.
+- Refactored `HasSubcomponentsCard` to use base `RelatedEntitiesCard`. Card remains backwards compatible.
+- Refactored `HasSystemsCard` to use base `RelatedEntitiesCard`. Card remains backwards compatible.
+- Updated the example app to take advantage of these new components.
diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx
index 601134362d..c1d892e061 100644
--- a/packages/app/src/components/catalog/EntityPage.tsx
+++ b/packages/app/src/components/catalog/EntityPage.tsx
@@ -28,6 +28,8 @@ import {
import { EntityBadgesDialog } from '@backstage/plugin-badges';
import {
EntityAboutCard,
+ EntityDependsOnComponentsCard,
+ EntityDependsOnResourcesCard,
EntityHasComponentsCard,
EntityHasSubcomponentsCard,
EntityHasSystemsCard,
@@ -37,6 +39,7 @@ import {
EntitySwitch,
isComponentType,
isKind,
+ EntityHasResourcesCard,
} from '@backstage/plugin-catalog';
import {
EntityCircleCIContent,
@@ -288,6 +291,17 @@ const serviceEntityPage = (
+
+
+
+
+
+
+
+
+
+
+
@@ -336,6 +350,17 @@ const websiteEntityPage = (
{errorsContent}
+
+
+
+
+
+
+
+
+
+
+
@@ -466,6 +491,9 @@ const systemPage = (
+
+
+
diff --git a/plugins/catalog/src/components/DependsOnComponentsCard/DependsOnComponentsCard.test.tsx b/plugins/catalog/src/components/DependsOnComponentsCard/DependsOnComponentsCard.test.tsx
new file mode 100644
index 0000000000..8ea7638c8e
--- /dev/null
+++ b/plugins/catalog/src/components/DependsOnComponentsCard/DependsOnComponentsCard.test.tsx
@@ -0,0 +1,121 @@
+/*
+ * 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, RELATION_DEPENDS_ON } from '@backstage/catalog-model';
+import { ApiProvider, ApiRegistry } from '@backstage/core';
+import {
+ CatalogApi,
+ catalogApiRef,
+ EntityProvider,
+} from '@backstage/plugin-catalog-react';
+import { renderInTestApp } from '@backstage/test-utils';
+import { waitFor } from '@testing-library/react';
+import React from 'react';
+import { DependsOnComponentsCard } from './DependsOnComponentsCard';
+
+describe('', () => {
+ const catalogApi: jest.Mocked = {
+ getLocationById: jest.fn(),
+ getEntityByName: jest.fn(),
+ getEntities: jest.fn(),
+ addLocation: jest.fn(),
+ getLocationByEntity: jest.fn(),
+ removeEntityByUid: jest.fn(),
+ } as any;
+ let Wrapper: React.ComponentType;
+
+ beforeEach(() => {
+ const apis = ApiRegistry.with(catalogApiRef, catalogApi);
+
+ Wrapper = ({ children }: { children?: React.ReactNode }) => (
+ {children}
+ );
+ });
+
+ afterEach(() => jest.resetAllMocks());
+
+ it('shows empty list if no dependencies', async () => {
+ const entity: Entity = {
+ apiVersion: 'v1',
+ kind: 'Component',
+ metadata: {
+ name: 'my-component',
+ namespace: 'my-namespace',
+ },
+ relations: [],
+ };
+
+ const { getByText } = await renderInTestApp(
+
+
+
+
+ ,
+ );
+
+ expect(getByText('Components')).toBeInTheDocument();
+ expect(
+ getByText(/No component is a dependency of this component/i),
+ ).toBeInTheDocument();
+ });
+
+ it('shows dependency components', async () => {
+ const entity: Entity = {
+ apiVersion: 'v1',
+ kind: 'Component',
+ metadata: {
+ name: 'my-component',
+ namespace: 'my-namespace',
+ },
+ relations: [
+ {
+ target: {
+ kind: 'Component',
+ namespace: 'my-namespace',
+ name: 'target-name',
+ },
+ type: RELATION_DEPENDS_ON,
+ },
+ ],
+ };
+ catalogApi.getEntities.mockResolvedValue({
+ items: [
+ {
+ apiVersion: 'v1',
+ kind: 'Component',
+ metadata: {
+ namespace: 'my-namespace',
+ name: 'target-name',
+ },
+ spec: {},
+ },
+ ],
+ });
+
+ const { getByText } = await renderInTestApp(
+
+
+
+
+ ,
+ );
+
+ await waitFor(() => {
+ expect(getByText('Components')).toBeInTheDocument();
+ expect(getByText(/target-name/i)).toBeInTheDocument();
+ });
+ });
+});
diff --git a/plugins/catalog/src/components/DependsOnComponentsCard/DependsOnComponentsCard.tsx b/plugins/catalog/src/components/DependsOnComponentsCard/DependsOnComponentsCard.tsx
new file mode 100644
index 0000000000..f837bb02ff
--- /dev/null
+++ b/plugins/catalog/src/components/DependsOnComponentsCard/DependsOnComponentsCard.tsx
@@ -0,0 +1,43 @@
+/*
+ * 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 { RELATION_DEPENDS_ON } from '@backstage/catalog-model';
+import React from 'react';
+import {
+ asComponentEntities,
+ componentEntityColumns,
+ componentEntityHelpLink,
+ RelatedEntitiesCard,
+} from '../RelatedEntitiesCard';
+
+type Props = {
+ variant?: 'gridItem';
+};
+
+export const DependsOnComponentsCard = ({ variant = 'gridItem' }: Props) => {
+ return (
+
+ );
+};
diff --git a/plugins/catalog/src/components/DependsOnComponentsCard/index.ts b/plugins/catalog/src/components/DependsOnComponentsCard/index.ts
new file mode 100644
index 0000000000..f46faf8198
--- /dev/null
+++ b/plugins/catalog/src/components/DependsOnComponentsCard/index.ts
@@ -0,0 +1,17 @@
+/*
+ * 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 { DependsOnComponentsCard } from './DependsOnComponentsCard';
diff --git a/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.test.tsx b/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.test.tsx
new file mode 100644
index 0000000000..8f425ba6aa
--- /dev/null
+++ b/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.test.tsx
@@ -0,0 +1,121 @@
+/*
+ * 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, RELATION_DEPENDS_ON } from '@backstage/catalog-model';
+import { ApiProvider, ApiRegistry } from '@backstage/core';
+import {
+ CatalogApi,
+ catalogApiRef,
+ EntityProvider,
+} from '@backstage/plugin-catalog-react';
+import { renderInTestApp } from '@backstage/test-utils';
+import { waitFor } from '@testing-library/react';
+import React from 'react';
+import { DependsOnResourcesCard } from './DependsOnResourcesCard';
+
+describe('', () => {
+ const catalogApi: jest.Mocked = {
+ getLocationById: jest.fn(),
+ getEntityByName: jest.fn(),
+ getEntities: jest.fn(),
+ addLocation: jest.fn(),
+ getLocationByEntity: jest.fn(),
+ removeEntityByUid: jest.fn(),
+ } as any;
+ let Wrapper: React.ComponentType;
+
+ beforeEach(() => {
+ const apis = ApiRegistry.with(catalogApiRef, catalogApi);
+
+ Wrapper = ({ children }: { children?: React.ReactNode }) => (
+ {children}
+ );
+ });
+
+ afterEach(() => jest.resetAllMocks());
+
+ it('shows empty list if no dependencies', async () => {
+ const entity: Entity = {
+ apiVersion: 'v1',
+ kind: 'Component',
+ metadata: {
+ name: 'my-component',
+ namespace: 'my-namespace',
+ },
+ relations: [],
+ };
+
+ const { getByText } = await renderInTestApp(
+
+
+
+
+ ,
+ );
+
+ expect(getByText('Resources')).toBeInTheDocument();
+ expect(
+ getByText(/No resource is a dependency of this component/i),
+ ).toBeInTheDocument();
+ });
+
+ it('shows dependency resources', async () => {
+ const entity: Entity = {
+ apiVersion: 'v1',
+ kind: 'Component',
+ metadata: {
+ name: 'my-component',
+ namespace: 'my-namespace',
+ },
+ relations: [
+ {
+ target: {
+ kind: 'Resource',
+ namespace: 'my-namespace',
+ name: 'target-name',
+ },
+ type: RELATION_DEPENDS_ON,
+ },
+ ],
+ };
+ catalogApi.getEntities.mockResolvedValue({
+ items: [
+ {
+ apiVersion: 'v1',
+ kind: 'Resource',
+ metadata: {
+ namespace: 'my-namespace',
+ name: 'target-name',
+ },
+ spec: {},
+ },
+ ],
+ });
+
+ const { getByText } = await renderInTestApp(
+
+
+
+
+ ,
+ );
+
+ await waitFor(() => {
+ expect(getByText('Resources')).toBeInTheDocument();
+ expect(getByText(/target-name/i)).toBeInTheDocument();
+ });
+ });
+});
diff --git a/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.tsx b/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.tsx
new file mode 100644
index 0000000000..7b64654f20
--- /dev/null
+++ b/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.tsx
@@ -0,0 +1,43 @@
+/*
+ * 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 { RELATION_DEPENDS_ON } from '@backstage/catalog-model';
+import React from 'react';
+import {
+ asResourceEntities,
+ componentEntityHelpLink,
+ RelatedEntitiesCard,
+ resourceEntityColumns,
+} from '../RelatedEntitiesCard';
+
+type Props = {
+ variant?: 'gridItem';
+};
+
+export const DependsOnResourcesCard = ({ variant = 'gridItem' }: Props) => {
+ return (
+
+ );
+};
diff --git a/plugins/catalog/src/components/DependsOnResourcesCard/index.ts b/plugins/catalog/src/components/DependsOnResourcesCard/index.ts
new file mode 100644
index 0000000000..20062f51dd
--- /dev/null
+++ b/plugins/catalog/src/components/DependsOnResourcesCard/index.ts
@@ -0,0 +1,17 @@
+/*
+ * 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 { DependsOnResourcesCard } from './DependsOnResourcesCard';
diff --git a/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.tsx b/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.tsx
index b6eae81f30..e2fbb6ff70 100644
--- a/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.tsx
+++ b/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.tsx
@@ -14,79 +14,30 @@
* limitations under the License.
*/
-import { ComponentEntity, RELATION_HAS_PART } from '@backstage/catalog-model';
-import {
- CodeSnippet,
- InfoCard,
- Link,
- Progress,
- WarningPanel,
-} from '@backstage/core';
-import { Typography } from '@material-ui/core';
-import {
- EntityTable,
- useEntity,
- useRelatedEntities,
-} from '@backstage/plugin-catalog-react';
+import { RELATION_HAS_PART } from '@backstage/catalog-model';
import React from 'react';
+import {
+ asComponentEntities,
+ componentEntityColumns,
+ componentEntityHelpLink,
+ RelatedEntitiesCard,
+} from '../RelatedEntitiesCard';
type Props = {
variant?: 'gridItem';
};
-const columns = [
- EntityTable.columns.createEntityRefColumn({ defaultKind: 'component' }),
- EntityTable.columns.createOwnerColumn(),
- EntityTable.columns.createSpecTypeColumn(),
- EntityTable.columns.createSpecLifecycleColumn(),
- EntityTable.columns.createMetadataDescriptionColumn(),
-];
-
export const HasComponentsCard = ({ variant = 'gridItem' }: Props) => {
- const { entity } = useEntity();
- const { entities, loading, error } = useRelatedEntities(entity, {
- type: RELATION_HAS_PART,
- kind: 'Component',
- });
-
- if (loading) {
- return (
-
-
-
- );
- }
-
- if (error || !entities) {
- return (
-
- }
- />
-
- );
- }
-
return (
-
-
- No component is part of this system.
-
-
-
- Learn how to change this.
-
-
-
- }
- columns={columns}
- entities={entities as ComponentEntity[]}
+ title="Components"
+ entityKind="Component"
+ relationType={RELATION_HAS_PART}
+ columns={componentEntityColumns}
+ emptyMessage="No component is part of this system"
+ emptyHelpLink={componentEntityHelpLink}
+ asRenderableEntities={asComponentEntities}
/>
);
};
diff --git a/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.test.tsx b/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.test.tsx
new file mode 100644
index 0000000000..38c8a3b5f5
--- /dev/null
+++ b/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.test.tsx
@@ -0,0 +1,121 @@
+/*
+ * 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, RELATION_HAS_PART } from '@backstage/catalog-model';
+import { ApiProvider, ApiRegistry } from '@backstage/core';
+import {
+ CatalogApi,
+ catalogApiRef,
+ EntityProvider,
+} from '@backstage/plugin-catalog-react';
+import { renderInTestApp } from '@backstage/test-utils';
+import { waitFor } from '@testing-library/react';
+import React from 'react';
+import { HasResourcesCard } from './HasResourcesCard';
+
+describe('', () => {
+ const catalogApi: jest.Mocked = {
+ getLocationById: jest.fn(),
+ getEntityByName: jest.fn(),
+ getEntities: jest.fn(),
+ addLocation: jest.fn(),
+ getLocationByEntity: jest.fn(),
+ removeEntityByUid: jest.fn(),
+ } as any;
+ let Wrapper: React.ComponentType;
+
+ beforeEach(() => {
+ const apis = ApiRegistry.with(catalogApiRef, catalogApi);
+
+ Wrapper = ({ children }: { children?: React.ReactNode }) => (
+ {children}
+ );
+ });
+
+ afterEach(() => jest.resetAllMocks());
+
+ it('shows empty list if no relations', async () => {
+ const entity: Entity = {
+ apiVersion: 'v1',
+ kind: 'System',
+ metadata: {
+ name: 'my-system',
+ namespace: 'my-namespace',
+ },
+ relations: [],
+ };
+
+ const { getByText } = await renderInTestApp(
+
+
+
+
+ ,
+ );
+
+ expect(getByText('Resources')).toBeInTheDocument();
+ expect(
+ getByText(/No resource is part of this system/i),
+ ).toBeInTheDocument();
+ });
+
+ it('shows related resources', async () => {
+ const entity: Entity = {
+ apiVersion: 'v1',
+ kind: 'System',
+ metadata: {
+ name: 'my-system',
+ namespace: 'my-namespace',
+ },
+ relations: [
+ {
+ target: {
+ kind: 'Resource',
+ namespace: 'my-namespace',
+ name: 'target-name',
+ },
+ type: RELATION_HAS_PART,
+ },
+ ],
+ };
+ catalogApi.getEntities.mockResolvedValue({
+ items: [
+ {
+ apiVersion: 'v1',
+ kind: 'Resource',
+ metadata: {
+ name: 'target-name',
+ namespace: 'my-namespace',
+ },
+ spec: {},
+ },
+ ],
+ });
+
+ const { getByText } = await renderInTestApp(
+
+
+
+
+ ,
+ );
+
+ await waitFor(() => {
+ expect(getByText('Resources')).toBeInTheDocument();
+ expect(getByText(/target-name/i)).toBeInTheDocument();
+ });
+ });
+});
diff --git a/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.tsx b/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.tsx
new file mode 100644
index 0000000000..be923d30a1
--- /dev/null
+++ b/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.tsx
@@ -0,0 +1,43 @@
+/*
+ * 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 { RELATION_HAS_PART } from '@backstage/catalog-model';
+import React from 'react';
+import {
+ asResourceEntities,
+ RelatedEntitiesCard,
+ resourceEntityColumns,
+ resourceEntityHelpLink,
+} from '../RelatedEntitiesCard';
+
+type Props = {
+ variant?: 'gridItem';
+};
+
+export const HasResourcesCard = ({ variant = 'gridItem' }: Props) => {
+ return (
+
+ );
+};
diff --git a/plugins/catalog/src/components/HasResourcesCard/index.ts b/plugins/catalog/src/components/HasResourcesCard/index.ts
new file mode 100644
index 0000000000..48b1d419aa
--- /dev/null
+++ b/plugins/catalog/src/components/HasResourcesCard/index.ts
@@ -0,0 +1,17 @@
+/*
+ * 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 { HasResourcesCard } from './HasResourcesCard';
diff --git a/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx b/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx
index 41244ba3d4..bf4aeb18cc 100644
--- a/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx
+++ b/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx
@@ -14,79 +14,29 @@
* limitations under the License.
*/
-import { ComponentEntity, RELATION_HAS_PART } from '@backstage/catalog-model';
-import {
- CodeSnippet,
- InfoCard,
- Link,
- Progress,
- WarningPanel,
-} from '@backstage/core';
-import { Typography } from '@material-ui/core';
-import {
- EntityTable,
- useEntity,
- useRelatedEntities,
-} from '@backstage/plugin-catalog-react';
+import { RELATION_HAS_PART } from '@backstage/catalog-model';
import React from 'react';
+import {
+ asComponentEntities,
+ componentEntityColumns,
+ RelatedEntitiesCard,
+} from '../RelatedEntitiesCard';
type Props = {
variant?: 'gridItem';
};
-const columns = [
- EntityTable.columns.createEntityRefColumn({ defaultKind: 'component' }),
- EntityTable.columns.createOwnerColumn(),
- EntityTable.columns.createSpecTypeColumn(),
- EntityTable.columns.createSpecLifecycleColumn(),
- EntityTable.columns.createMetadataDescriptionColumn(),
-];
-
export const HasSubcomponentsCard = ({ variant = 'gridItem' }: Props) => {
- const { entity } = useEntity();
- const { entities, loading, error } = useRelatedEntities(entity, {
- type: RELATION_HAS_PART,
- kind: 'Component',
- });
-
- if (loading) {
- return (
-
-
-
- );
- }
-
- if (error || !entities) {
- return (
-
- }
- />
-
- );
- }
-
return (
-
-
- No subcomponent is part of this component.
-
-
-
- Learn how to change this.
-
-
-
- }
- columns={columns}
- entities={entities as ComponentEntity[]}
+ title="Subcomponents"
+ entityKind="Component"
+ relationType={RELATION_HAS_PART}
+ columns={componentEntityColumns}
+ asRenderableEntities={asComponentEntities}
+ emptyMessage="No subcomponent is part of this component"
+ emptyHelpLink="https://backstage.io/docs/features/software-catalog/descriptor-format#specsubcomponentof-optional"
/>
);
};
diff --git a/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx b/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx
index ea4de474de..b6c5bb7ca2 100644
--- a/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx
+++ b/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx
@@ -14,76 +14,30 @@
* limitations under the License.
*/
-import { RELATION_HAS_PART, SystemEntity } from '@backstage/catalog-model';
-import {
- CodeSnippet,
- InfoCard,
- Link,
- Progress,
- WarningPanel,
-} from '@backstage/core';
-import { Typography } from '@material-ui/core';
-import {
- EntityTable,
- useEntity,
- useRelatedEntities,
-} from '@backstage/plugin-catalog-react';
+import { RELATION_HAS_PART } from '@backstage/catalog-model';
import React from 'react';
+import {
+ asSystemEntities,
+ RelatedEntitiesCard,
+ systemEntityColumns,
+ systemEntityHelpLink,
+} from '../RelatedEntitiesCard';
type Props = {
variant?: 'gridItem';
};
-const columns = [
- EntityTable.columns.createEntityRefColumn({ defaultKind: 'system' }),
- EntityTable.columns.createOwnerColumn(),
- EntityTable.columns.createMetadataDescriptionColumn(),
-];
-
export const HasSystemsCard = ({ variant = 'gridItem' }: Props) => {
- const { entity } = useEntity();
- const { entities, loading, error } = useRelatedEntities(entity, {
- type: RELATION_HAS_PART,
- });
-
- if (loading) {
- return (
-
-
-
- );
- }
-
- if (error || !entities) {
- return (
-
- }
- />
-
- );
- }
-
return (
-
-
- No system is part of this domain.
-
-
-
- Learn how to change this.
-
-
-
- }
- columns={columns}
- entities={entities as SystemEntity[]}
+ title="Systems"
+ entityKind="System"
+ relationType={RELATION_HAS_PART}
+ columns={systemEntityColumns}
+ asRenderableEntities={asSystemEntities}
+ emptyMessage="No system is part of this domain"
+ emptyHelpLink={systemEntityHelpLink}
/>
);
};
diff --git a/plugins/catalog/src/components/RelatedEntitiesCard/RelatedEntitiesCard.tsx b/plugins/catalog/src/components/RelatedEntitiesCard/RelatedEntitiesCard.tsx
new file mode 100644
index 0000000000..f80f138515
--- /dev/null
+++ b/plugins/catalog/src/components/RelatedEntitiesCard/RelatedEntitiesCard.tsx
@@ -0,0 +1,92 @@
+/*
+ * 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 } from '@backstage/catalog-model';
+import {
+ InfoCard,
+ Link,
+ Progress,
+ ResponseErrorPanel,
+ TableColumn,
+} from '@backstage/core';
+import { Typography } from '@material-ui/core';
+import {
+ EntityTable,
+ useEntity,
+ useRelatedEntities,
+} from '@backstage/plugin-catalog-react';
+import React from 'react';
+
+type Props = {
+ variant?: 'gridItem';
+ title: string;
+ columns: TableColumn[];
+ entityKind: string;
+ relationType: string;
+ emptyMessage: string;
+ emptyHelpLink: string;
+ asRenderableEntities: (entities: Entity[]) => T[];
+};
+
+export function RelatedEntitiesCard({
+ variant = 'gridItem',
+ title,
+ columns,
+ entityKind,
+ relationType,
+ emptyMessage,
+ emptyHelpLink,
+ asRenderableEntities,
+}: Props) {
+ const { entity } = useEntity();
+ const { entities, loading, error } = useRelatedEntities(entity, {
+ type: relationType,
+ kind: entityKind,
+ });
+
+ if (loading) {
+ return (
+
+
+
+ );
+ }
+
+ if (error) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+ {emptyMessage}
+
+ Learn how to change this.
+
+
+ }
+ columns={columns}
+ entities={asRenderableEntities(entities || [])}
+ />
+ );
+}
diff --git a/plugins/catalog/src/components/RelatedEntitiesCard/index.ts b/plugins/catalog/src/components/RelatedEntitiesCard/index.ts
new file mode 100644
index 0000000000..3104004dd6
--- /dev/null
+++ b/plugins/catalog/src/components/RelatedEntitiesCard/index.ts
@@ -0,0 +1,18 @@
+/*
+ * 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 { RelatedEntitiesCard } from './RelatedEntitiesCard';
+export * from './presets';
diff --git a/plugins/catalog/src/components/RelatedEntitiesCard/presets.ts b/plugins/catalog/src/components/RelatedEntitiesCard/presets.ts
new file mode 100644
index 0000000000..9d3586a7e9
--- /dev/null
+++ b/plugins/catalog/src/components/RelatedEntitiesCard/presets.ts
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2021 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 {
+ ComponentEntity,
+ Entity,
+ ResourceEntity,
+ SystemEntity,
+} from '@backstage/catalog-model';
+import { TableColumn } from '@backstage/core';
+import { EntityTable } from '@backstage/plugin-catalog-react';
+
+export const componentEntityColumns: TableColumn[] = [
+ EntityTable.columns.createEntityRefColumn({ defaultKind: 'component' }),
+ EntityTable.columns.createOwnerColumn(),
+ EntityTable.columns.createSpecTypeColumn(),
+ EntityTable.columns.createSpecLifecycleColumn(),
+ EntityTable.columns.createMetadataDescriptionColumn(),
+];
+export const componentEntityHelpLink: string =
+ 'https://backstage.io/docs/features/software-catalog/descriptor-format#kind-component';
+export const asComponentEntities = (entities: Entity[]): ComponentEntity[] =>
+ entities as ComponentEntity[];
+
+export const resourceEntityColumns: TableColumn[] = [
+ EntityTable.columns.createEntityRefColumn({ defaultKind: 'resource' }),
+ EntityTable.columns.createOwnerColumn(),
+ EntityTable.columns.createSpecTypeColumn(),
+ EntityTable.columns.createSpecLifecycleColumn(),
+ EntityTable.columns.createMetadataDescriptionColumn(),
+];
+export const resourceEntityHelpLink: string =
+ 'https://backstage.io/docs/features/software-catalog/descriptor-format#kind-resource';
+export const asResourceEntities = (entities: Entity[]): ResourceEntity[] =>
+ entities as ResourceEntity[];
+
+export const systemEntityColumns: TableColumn[] = [
+ EntityTable.columns.createEntityRefColumn({ defaultKind: 'system' }),
+ EntityTable.columns.createOwnerColumn(),
+ EntityTable.columns.createMetadataDescriptionColumn(),
+];
+export const systemEntityHelpLink: string =
+ 'https://backstage.io/docs/features/software-catalog/descriptor-format#kind-system';
+export const asSystemEntities = (entities: Entity[]): SystemEntity[] =>
+ entities as SystemEntity[];
diff --git a/plugins/catalog/src/index.ts b/plugins/catalog/src/index.ts
index fb269fc484..2e74219718 100644
--- a/plugins/catalog/src/index.ts
+++ b/plugins/catalog/src/index.ts
@@ -26,7 +26,10 @@ export {
catalogPlugin,
catalogPlugin as plugin,
EntityAboutCard,
+ EntityDependsOnComponentsCard,
+ EntityDependsOnResourcesCard,
EntityHasComponentsCard,
+ EntityHasResourcesCard,
EntityHasSubcomponentsCard,
EntityHasSystemsCard,
EntityLinksCard,
diff --git a/plugins/catalog/src/plugin.ts b/plugins/catalog/src/plugin.ts
index 20c7a865db..3f4de6c67d 100644
--- a/plugins/catalog/src/plugin.ts
+++ b/plugins/catalog/src/plugin.ts
@@ -115,6 +115,37 @@ export const EntityHasSubcomponentsCard = catalogPlugin.provide(
}),
);
+export const EntityHasResourcesCard = catalogPlugin.provide(
+ createComponentExtension({
+ component: {
+ lazy: () =>
+ import('./components/HasResourcesCard').then(m => m.HasResourcesCard),
+ },
+ }),
+);
+
+export const EntityDependsOnComponentsCard = catalogPlugin.provide(
+ createComponentExtension({
+ component: {
+ lazy: () =>
+ import('./components/DependsOnComponentsCard').then(
+ m => m.DependsOnComponentsCard,
+ ),
+ },
+ }),
+);
+
+export const EntityDependsOnResourcesCard = catalogPlugin.provide(
+ createComponentExtension({
+ component: {
+ lazy: () =>
+ import('./components/DependsOnResourcesCard').then(
+ m => m.DependsOnResourcesCard,
+ ),
+ },
+ }),
+);
+
export const EntitySystemDiagramCard = catalogPlugin.provide(
createComponentExtension({
component: {