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..0a1e120eff
--- /dev/null
+++ b/plugins/catalog/src/components/DependsOnComponentsCard/DependsOnComponentsCard.test.tsx
@@ -0,0 +1,117 @@
+/*
+ * 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.getEntityByName.mockResolvedValue({
+ 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..18c6173e17
--- /dev/null
+++ b/plugins/catalog/src/components/DependsOnComponentsCard/DependsOnComponentsCard.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 { ComponentEntity, RELATION_DEPENDS_ON } 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 React from 'react';
+
+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 DependsOnComponentsCard = ({ variant = 'gridItem' }: Props) => {
+ const { entity } = useEntity();
+ const { entities, loading, error } = useRelatedEntities(entity, {
+ type: RELATION_DEPENDS_ON,
+ kind: 'Component',
+ });
+
+ if (loading) {
+ return (
+
+
+
+ );
+ }
+
+ if (error || !entities) {
+ return (
+
+ }
+ />
+
+ );
+ }
+
+ return (
+
+
+ No component is a dependency of this component.
+
+
+
+ Learn how to change this.
+
+
+
+ }
+ columns={columns}
+ entities={entities as ComponentEntity[]}
+ />
+ );
+};
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..52ccfe7862
--- /dev/null
+++ b/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.test.tsx
@@ -0,0 +1,117 @@
+/*
+ * 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.getEntityByName.mockResolvedValue({
+ 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..798a579b9a
--- /dev/null
+++ b/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.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 { ComponentEntity, RELATION_DEPENDS_ON } 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 React from 'react';
+
+type Props = {
+ variant?: 'gridItem';
+};
+
+const columns = [
+ EntityTable.columns.createEntityRefColumn({ defaultKind: 'resource' }),
+ EntityTable.columns.createOwnerColumn(),
+ EntityTable.columns.createSpecTypeColumn(),
+ EntityTable.columns.createSpecLifecycleColumn(),
+ EntityTable.columns.createMetadataDescriptionColumn(),
+];
+
+export const DependsOnResourcesCard = ({ variant = 'gridItem' }: Props) => {
+ const { entity } = useEntity();
+ const { entities, loading, error } = useRelatedEntities(entity, {
+ type: RELATION_DEPENDS_ON,
+ kind: 'Resource',
+ });
+
+ if (loading) {
+ return (
+
+
+
+ );
+ }
+
+ if (error || !entities) {
+ return (
+
+ }
+ />
+
+ );
+ }
+
+ return (
+
+
+ No resource is a dependency of this component.
+
+
+
+ Learn how to change this.
+
+
+
+ }
+ columns={columns}
+ entities={entities as ComponentEntity[]}
+ />
+ );
+};
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/HasResourcesCard/HasResourcesCard.test.tsx b/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.test.tsx
new file mode 100644
index 0000000000..5b89dbbd6f
--- /dev/null
+++ b/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.test.tsx
@@ -0,0 +1,117 @@
+/*
+ * 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.getEntityByName.mockResolvedValue({
+ 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..7da7fc4465
--- /dev/null
+++ b/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.tsx
@@ -0,0 +1,94 @@
+/*
+ * 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 { ResourceEntity, 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 React from 'react';
+
+type Props = {
+ variant?: 'gridItem';
+};
+
+const columns = [
+ EntityTable.columns.createEntityRefColumn({ defaultKind: 'resource' }),
+ EntityTable.columns.createOwnerColumn(),
+ EntityTable.columns.createSpecTypeColumn(),
+ EntityTable.columns.createSpecLifecycleColumn(),
+ EntityTable.columns.createMetadataDescriptionColumn(),
+];
+
+export const HasResourcesCard = ({
+ variant = 'gridItem',
+}: Props) => {
+ const { entity } = useEntity();
+ const { entities, loading, error } = useRelatedEntities(entity, {
+ type: RELATION_HAS_PART,
+ kind: 'Resource',
+ });
+
+ if (loading) {
+ return (
+
+
+
+ );
+ }
+
+ if (error || !entities) {
+ return (
+
+ }
+ />
+
+ );
+ }
+
+ return (
+
+
+ No resource is part of this system.
+
+
+
+ Learn how to change this.
+
+
+
+ }
+ columns={columns}
+ entities={entities as ResourceEntity[]}
+ />
+ );
+};
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/plugin.ts b/plugins/catalog/src/plugin.ts
index 20c7a865db..ba6cc3a19d 100644
--- a/plugins/catalog/src/plugin.ts
+++ b/plugins/catalog/src/plugin.ts
@@ -115,6 +115,39 @@ 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: {