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: {