From 7fc89bae29cb71ea937f0d76261231e23a792169 Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Fri, 29 Jan 2021 16:14:24 +0100 Subject: [PATCH] Display owner and system as entity page links in the tables of the `api-docs` plugin --- .changeset/empty-hairs-fetch.md | 13 +++ .../ApiExplorerTable/ApiExplorerTable.tsx | 82 ++++++++++++++++--- .../src/components/ApisCards/ApisTable.tsx | 81 +++++++++++++++--- .../ApisCards/ConsumedApisCard.test.tsx | 27 +++++- .../ApisCards/ProvidedApisCard.test.tsx | 27 +++++- .../ComponentsCards/ComponentsTable.tsx | 79 ++++++++++++++++-- .../ConsumingComponentsCard.test.tsx | 27 +++++- .../ProvidingComponentsCard.test.tsx | 27 +++++- .../components/EntityLink/EntityLink.test.tsx | 41 ---------- .../src/components/EntityLink/EntityLink.tsx | 43 ---------- plugins/catalog-react/src/index.ts | 1 + .../src/utils}/getEntityRelations.test.ts | 0 .../src/utils}/getEntityRelations.ts | 0 .../src/utils}/index.ts | 4 +- .../src/utils}/isOwnerOf.test.ts | 0 .../src/utils}/isOwnerOf.ts | 0 .../src/components/AboutCard/AboutContent.tsx | 6 +- .../components/CatalogPage/CatalogPage.tsx | 3 +- .../components/CatalogTable/CatalogTable.tsx | 2 +- .../Cards/OwnershipCard/OwnershipCard.tsx | 13 ++- .../org/src/components/getEntityRelations.ts | 42 ---------- plugins/org/src/components/isOwnerOf.ts | 55 ------------- 22 files changed, 339 insertions(+), 234 deletions(-) create mode 100644 .changeset/empty-hairs-fetch.md delete mode 100644 plugins/api-docs/src/components/EntityLink/EntityLink.test.tsx delete mode 100644 plugins/api-docs/src/components/EntityLink/EntityLink.tsx rename plugins/{catalog/src/components => catalog-react/src/utils}/getEntityRelations.test.ts (100%) rename plugins/{catalog/src/components => catalog-react/src/utils}/getEntityRelations.ts (100%) rename plugins/{api-docs/src/components/EntityLink => catalog-react/src/utils}/index.ts (85%) rename plugins/{catalog/src/components => catalog-react/src/utils}/isOwnerOf.test.ts (100%) rename plugins/{catalog/src/components => catalog-react/src/utils}/isOwnerOf.ts (100%) delete mode 100644 plugins/org/src/components/getEntityRelations.ts delete mode 100644 plugins/org/src/components/isOwnerOf.ts diff --git a/.changeset/empty-hairs-fetch.md b/.changeset/empty-hairs-fetch.md new file mode 100644 index 0000000000..aedeb8a853 --- /dev/null +++ b/.changeset/empty-hairs-fetch.md @@ -0,0 +1,13 @@ +--- +'@backstage/plugin-api-docs': patch +'@backstage/plugin-catalog': patch +'@backstage/plugin-catalog-react': patch +'@backstage/plugin-org': patch +--- + +Display owner and system as entity page links in the tables of the `api-docs` +plugin. + +Move `isOwnerOf` and `getEntityRelations` from `@backstage/plugin-catalog` to +`@backstage/plugin-catalog-react` and export it from there to use it by other +plugins. diff --git a/plugins/api-docs/src/components/ApiExplorerTable/ApiExplorerTable.tsx b/plugins/api-docs/src/components/ApiExplorerTable/ApiExplorerTable.tsx index 149f7470c3..83af0ef216 100644 --- a/plugins/api-docs/src/components/ApiExplorerTable/ApiExplorerTable.tsx +++ b/plugins/api-docs/src/components/ApiExplorerTable/ApiExplorerTable.tsx @@ -14,7 +14,13 @@ * limitations under the License. */ -import { ApiEntityV1alpha1, Entity } from '@backstage/catalog-model'; +import { + ApiEntityV1alpha1, + Entity, + EntityName, + RELATION_OWNED_BY, + RELATION_PART_OF, +} from '@backstage/catalog-model'; import { Table, TableColumn, @@ -22,24 +28,54 @@ import { TableState, useQueryParamState, } from '@backstage/core'; +import { + EntityRefLink, + EntityRefLinks, + formatEntityRefTitle, + getEntityRelations, +} from '@backstage/plugin-catalog-react'; import { Chip } from '@material-ui/core'; import { Alert } from '@material-ui/lab'; import React from 'react'; import { ApiTypeTitle } from '../ApiDefinitionCard'; -import { EntityLink } from '../EntityLink'; -const columns: TableColumn[] = [ +type EntityRow = ApiEntityV1alpha1 & { + row: { + partOfSystemRelationTitle?: string; + partOfSystemRelations: EntityName[]; + ownedByRelationsTitle?: string; + ownedByRelations: EntityName[]; + }; +}; + +const columns: TableColumn[] = [ { title: 'Name', field: 'metadata.name', highlight: true, - render: (entity: any) => ( - {entity.metadata.name} + render: entity => ( + {entity.metadata.name} + ), + }, + { + title: 'System', + field: 'row.partOfSystemRelationTitle', + render: entity => ( + ), }, { title: 'Owner', - field: 'spec.owner', + field: 'row.ownedByRelationsTitle', + render: entity => ( + + ), }, { title: 'Lifecycle', @@ -48,9 +84,7 @@ const columns: TableColumn[] = [ { title: 'Type', field: 'spec.type', - render: (entity: Entity) => ( - - ), + render: entity => , }, { title: 'Description', @@ -62,7 +96,7 @@ const columns: TableColumn[] = [ cellStyle: { padding: '0px 16px 0px 20px', }, - render: (entity: Entity) => ( + render: entity => ( <> {entity.metadata.tags && entity.metadata.tags.map(t => ( @@ -123,8 +157,32 @@ export const ApiExplorerTable = ({ ); } + const rows = entities.map(e => { + const partOfSystemRelations = getEntityRelations(e, RELATION_PART_OF, { + kind: 'system', + }); + const ownedByRelations = getEntityRelations(e, RELATION_OWNED_BY); + + return { + ...(e as ApiEntityV1alpha1), + row: { + ownedByRelationsTitle: ownedByRelations + .map(r => formatEntityRefTitle(r, { defaultKind: 'group' })) + .join(', '), + ownedByRelations, + partOfSystemRelationTitle: + partOfSystemRelations.length > 0 + ? formatEntityRefTitle(partOfSystemRelations[0], { + defaultKind: 'system', + }) + : undefined, + partOfSystemRelations, + }, + }; + }); + return ( - isLoading={loading} columns={columns} options={{ @@ -134,7 +192,7 @@ export const ApiExplorerTable = ({ padding: 'dense', showEmptyDataSourceMessage: !loading, }} - data={entities} + data={rows} filters={filters} initialState={queryParamState} onStateChange={setQueryParamState} diff --git a/plugins/api-docs/src/components/ApisCards/ApisTable.tsx b/plugins/api-docs/src/components/ApisCards/ApisTable.tsx index 7db62433eb..56ba697234 100644 --- a/plugins/api-docs/src/components/ApisCards/ApisTable.tsx +++ b/plugins/api-docs/src/components/ApisCards/ApisTable.tsx @@ -14,24 +14,59 @@ * limitations under the License. */ -import { ApiEntity } from '@backstage/catalog-model'; +import { + ApiEntity, + EntityName, + RELATION_OWNED_BY, + RELATION_PART_OF, +} from '@backstage/catalog-model'; import { Table, TableColumn } from '@backstage/core'; +import { + EntityRefLink, + EntityRefLinks, + formatEntityRefTitle, + getEntityRelations, +} from '@backstage/plugin-catalog-react'; import React from 'react'; import { ApiTypeTitle } from '../ApiDefinitionCard'; -import { EntityLink } from '../EntityLink'; -const columns: TableColumn[] = [ +type EntityRow = ApiEntity & { + row: { + partOfSystemRelationTitle?: string; + partOfSystemRelations: EntityName[]; + ownedByRelationsTitle?: string; + ownedByRelations: EntityName[]; + }; +}; + +const columns: TableColumn[] = [ { title: 'Name', field: 'metadata.name', highlight: true, - render: (entity: any) => ( - {entity.metadata.name} + render: entity => ( + {entity.metadata.name} + ), + }, + { + title: 'System', + field: 'row.partOfSystemRelationTitle', + render: entity => ( + ), }, { title: 'Owner', - field: 'spec.owner', + field: 'row.ownedByRelationsTitle', + render: entity => ( + + ), }, { title: 'Lifecycle', @@ -40,7 +75,7 @@ const columns: TableColumn[] = [ { title: 'Type', field: 'spec.type', - render: (entity: ApiEntity) => , + render: entity => , }, { title: 'Description', @@ -65,8 +100,35 @@ export const ApisTable = ({ entities, title, variant = 'gridItem' }: Props) => { tableStyle.height = 'calc(100% - 10px)'; } + const rows = entities + // TODO: For now we skip all APIs that we can't find without a warning! + .filter(e => e !== undefined) + .map(e => { + const partOfSystemRelations = getEntityRelations(e, RELATION_PART_OF, { + kind: 'system', + }); + const ownedByRelations = getEntityRelations(e, RELATION_OWNED_BY); + + return { + ...(e as ApiEntity), + row: { + ownedByRelationsTitle: ownedByRelations + .map(r => formatEntityRefTitle(r, { defaultKind: 'group' })) + .join(', '), + ownedByRelations, + partOfSystemRelationTitle: + partOfSystemRelations.length > 0 + ? formatEntityRefTitle(partOfSystemRelations[0], { + defaultKind: 'system', + }) + : undefined, + partOfSystemRelations, + }, + }; + }); + return ( - + columns={columns} title={title} style={tableStyle} @@ -77,8 +139,7 @@ export const ApisTable = ({ entities, title, variant = 'gridItem' }: Props) => { actionsColumnIndex: -1, padding: 'dense', }} - // TODO: For now we skip all APIs that we can't find without a warning! - data={entities.filter(e => e !== undefined) as ApiEntity[]} + data={rows} /> ); }; diff --git a/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.test.tsx b/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.test.tsx index d406e160fc..54cf08a429 100644 --- a/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.test.tsx +++ b/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.test.tsx @@ -14,7 +14,12 @@ * limitations under the License. */ -import { Entity, RELATION_CONSUMES_API } from '@backstage/catalog-model'; +import { + Entity, + RELATION_CONSUMES_API, + RELATION_OWNED_BY, + RELATION_PART_OF, +} from '@backstage/catalog-model'; import { ApiProvider, ApiRegistry } from '@backstage/core'; import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog-react'; import { renderInTestApp } from '@backstage/test-utils'; @@ -99,10 +104,27 @@ describe('', () => { }, spec: { type: 'openapi', - owner: 'Test', lifecycle: 'production', definition: '...', }, + relations: [ + { + type: RELATION_PART_OF, + target: { + kind: 'System', + name: 'MySystem', + namespace: 'default', + }, + }, + { + type: RELATION_OWNED_BY, + target: { + kind: 'Group', + name: 'Test', + namespace: 'default', + }, + }, + ], }); apiDocsConfig.getApiDefinitionWidget.mockReturnValue({ type: 'openapi', @@ -121,6 +143,7 @@ describe('', () => { expect(getByText(/target-name/i)).toBeInTheDocument(); expect(getByText(/OpenAPI/)).toBeInTheDocument(); expect(getByText(/Test/i)).toBeInTheDocument(); + expect(getByText(/MySystem/i)).toBeInTheDocument(); expect(getByText(/production/i)).toBeInTheDocument(); }); }); diff --git a/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.test.tsx b/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.test.tsx index 7b6ce6e18a..4300aeff6d 100644 --- a/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.test.tsx +++ b/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.test.tsx @@ -14,7 +14,12 @@ * limitations under the License. */ -import { Entity, RELATION_PROVIDES_API } from '@backstage/catalog-model'; +import { + Entity, + RELATION_OWNED_BY, + RELATION_PART_OF, + RELATION_PROVIDES_API, +} from '@backstage/catalog-model'; import { ApiProvider, ApiRegistry } from '@backstage/core'; import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog-react'; import { renderInTestApp } from '@backstage/test-utils'; @@ -99,10 +104,27 @@ describe('', () => { }, spec: { type: 'openapi', - owner: 'Test', lifecycle: 'production', definition: '...', }, + relations: [ + { + type: RELATION_PART_OF, + target: { + kind: 'System', + name: 'MySystem', + namespace: 'default', + }, + }, + { + type: RELATION_OWNED_BY, + target: { + kind: 'Group', + name: 'Test', + namespace: 'default', + }, + }, + ], }); apiDocsConfig.getApiDefinitionWidget.mockReturnValue({ type: 'openapi', @@ -120,6 +142,7 @@ describe('', () => { expect(getByText(/Provided APIs/i)).toBeInTheDocument(); expect(getByText(/target-name/i)).toBeInTheDocument(); expect(getByText(/OpenAPI/)).toBeInTheDocument(); + expect(getByText(/MySystem/)).toBeInTheDocument(); expect(getByText(/Test/i)).toBeInTheDocument(); expect(getByText(/production/i)).toBeInTheDocument(); }); diff --git a/plugins/api-docs/src/components/ComponentsCards/ComponentsTable.tsx b/plugins/api-docs/src/components/ComponentsCards/ComponentsTable.tsx index 1b62a56d10..2a6115f512 100644 --- a/plugins/api-docs/src/components/ComponentsCards/ComponentsTable.tsx +++ b/plugins/api-docs/src/components/ComponentsCards/ComponentsTable.tsx @@ -14,23 +14,58 @@ * limitations under the License. */ -import { ComponentEntity } from '@backstage/catalog-model'; +import { + ComponentEntity, + EntityName, + RELATION_OWNED_BY, + RELATION_PART_OF, +} from '@backstage/catalog-model'; import { Table, TableColumn } from '@backstage/core'; +import { + EntityRefLink, + EntityRefLinks, + formatEntityRefTitle, + getEntityRelations, +} from '@backstage/plugin-catalog-react'; import React from 'react'; -import { EntityLink } from '../EntityLink'; -const columns: TableColumn[] = [ +type EntityRow = ComponentEntity & { + row: { + partOfSystemRelationTitle?: string; + partOfSystemRelations: EntityName[]; + ownedByRelationsTitle?: string; + ownedByRelations: EntityName[]; + }; +}; + +const columns: TableColumn[] = [ { title: 'Name', field: 'metadata.name', highlight: true, - render: (entity: any) => ( - {entity.metadata.name} + render: entity => ( + {entity.metadata.name} + ), + }, + { + title: 'System', + field: 'row.partOfSystemRelationTitle', + render: entity => ( + ), }, { title: 'Owner', - field: 'spec.owner', + field: 'row.ownedByRelationsTitle', + render: entity => ( + + ), }, { title: 'Lifecycle', @@ -68,8 +103,35 @@ export const ComponentsTable = ({ tableStyle.height = 'calc(100% - 10px)'; } + const rows = entities + // TODO: For now we skip all Components that we can't find without a warning! + .filter(e => e !== undefined) + .map(e => { + const partOfSystemRelations = getEntityRelations(e, RELATION_PART_OF, { + kind: 'system', + }); + const ownedByRelations = getEntityRelations(e, RELATION_OWNED_BY); + + return { + ...(e as ComponentEntity), + row: { + ownedByRelationsTitle: ownedByRelations + .map(r => formatEntityRefTitle(r, { defaultKind: 'group' })) + .join(', '), + ownedByRelations, + partOfSystemRelationTitle: + partOfSystemRelations.length > 0 + ? formatEntityRefTitle(partOfSystemRelations[0], { + defaultKind: 'system', + }) + : undefined, + partOfSystemRelations, + }, + }; + }); + return ( - + columns={columns} title={title} style={tableStyle} @@ -80,8 +142,7 @@ export const ComponentsTable = ({ actionsColumnIndex: -1, padding: 'dense', }} - // TODO: For now we skip all APIs that we can't find without a warning! - data={entities.filter(e => e !== undefined) as ComponentEntity[]} + data={rows} /> ); }; diff --git a/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.test.tsx b/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.test.tsx index 99a8c0dc28..de66b2af06 100644 --- a/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.test.tsx +++ b/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.test.tsx @@ -14,7 +14,12 @@ * limitations under the License. */ -import { Entity, RELATION_API_CONSUMED_BY } from '@backstage/catalog-model'; +import { + Entity, + RELATION_API_CONSUMED_BY, + RELATION_OWNED_BY, + RELATION_PART_OF, +} from '@backstage/catalog-model'; import { ApiProvider, ApiRegistry } from '@backstage/core'; import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog-react'; import { renderInTestApp } from '@backstage/test-utils'; @@ -104,9 +109,26 @@ describe('', () => { }, spec: { type: 'service', - owner: 'Test', lifecycle: 'production', }, + relations: [ + { + type: RELATION_PART_OF, + target: { + kind: 'System', + name: 'MySystem', + namespace: 'default', + }, + }, + { + type: RELATION_OWNED_BY, + target: { + kind: 'Group', + name: 'Test', + namespace: 'default', + }, + }, + ], }); const { getByText } = await renderInTestApp( @@ -119,6 +141,7 @@ describe('', () => { expect(getByText(/Consumers/i)).toBeInTheDocument(); expect(getByText(/target-name/i)).toBeInTheDocument(); expect(getByText(/Test/i)).toBeInTheDocument(); + expect(getByText(/MySystem/i)).toBeInTheDocument(); expect(getByText(/production/i)).toBeInTheDocument(); }); }); diff --git a/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.test.tsx b/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.test.tsx index a3341ad8d0..b7b1616e00 100644 --- a/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.test.tsx +++ b/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.test.tsx @@ -14,7 +14,12 @@ * limitations under the License. */ -import { Entity, RELATION_API_PROVIDED_BY } from '@backstage/catalog-model'; +import { + Entity, + RELATION_API_PROVIDED_BY, + RELATION_OWNED_BY, + RELATION_PART_OF, +} from '@backstage/catalog-model'; import { ApiProvider, ApiRegistry } from '@backstage/core'; import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog-react'; import { renderInTestApp } from '@backstage/test-utils'; @@ -104,9 +109,26 @@ describe('', () => { }, spec: { type: 'service', - owner: 'Test', lifecycle: 'production', }, + relations: [ + { + type: RELATION_PART_OF, + target: { + kind: 'System', + name: 'MySystem', + namespace: 'default', + }, + }, + { + type: RELATION_OWNED_BY, + target: { + kind: 'Group', + name: 'Test', + namespace: 'default', + }, + }, + ], }); const { getByText } = await renderInTestApp( @@ -119,6 +141,7 @@ describe('', () => { expect(getByText(/Providers/i)).toBeInTheDocument(); expect(getByText(/target-name/i)).toBeInTheDocument(); expect(getByText(/Test/i)).toBeInTheDocument(); + expect(getByText(/MySystem/i)).toBeInTheDocument(); expect(getByText(/production/i)).toBeInTheDocument(); }); }); diff --git a/plugins/api-docs/src/components/EntityLink/EntityLink.test.tsx b/plugins/api-docs/src/components/EntityLink/EntityLink.test.tsx deleted file mode 100644 index 9221b96604..0000000000 --- a/plugins/api-docs/src/components/EntityLink/EntityLink.test.tsx +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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 { renderInTestApp } from '@backstage/test-utils'; -import React from 'react'; -import { EntityLink } from './EntityLink'; - -describe('', () => { - it('provides link to entity page', async () => { - const entity: Entity = { - apiVersion: 'v1', - kind: 'Component', - metadata: { - name: 'my-name', - namespace: 'my-namespace', - }, - }; - const { getByText } = await renderInTestApp( - inner, - ); - expect(getByText(/inner/i)).toBeInTheDocument(); - expect(getByText(/inner/i)).toHaveAttribute( - 'href', - '/catalog/my-namespace/component/my-name', - ); - }); -}); diff --git a/plugins/api-docs/src/components/EntityLink/EntityLink.tsx b/plugins/api-docs/src/components/EntityLink/EntityLink.tsx deleted file mode 100644 index dba6bb0062..0000000000 --- a/plugins/api-docs/src/components/EntityLink/EntityLink.tsx +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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 { - entityRoute, - entityRouteParams, -} from '@backstage/plugin-catalog-react'; -import { Link } from '@material-ui/core'; -import React, { PropsWithChildren } from 'react'; -import { generatePath, Link as RouterLink } from 'react-router-dom'; - -type Props = { - entity: Entity; -}; - -// TODO: Could be useful for others too, as part of the catalog plugin -export const EntityLink = ({ entity, children }: PropsWithChildren) => { - return ( - - {children} - - ); -}; diff --git a/plugins/catalog-react/src/index.ts b/plugins/catalog-react/src/index.ts index 9b6e13bb26..af3eca4e0a 100644 --- a/plugins/catalog-react/src/index.ts +++ b/plugins/catalog-react/src/index.ts @@ -24,3 +24,4 @@ export { entityRouteRef, rootRoute, } from './routes'; +export * from './utils'; diff --git a/plugins/catalog/src/components/getEntityRelations.test.ts b/plugins/catalog-react/src/utils/getEntityRelations.test.ts similarity index 100% rename from plugins/catalog/src/components/getEntityRelations.test.ts rename to plugins/catalog-react/src/utils/getEntityRelations.test.ts diff --git a/plugins/catalog/src/components/getEntityRelations.ts b/plugins/catalog-react/src/utils/getEntityRelations.ts similarity index 100% rename from plugins/catalog/src/components/getEntityRelations.ts rename to plugins/catalog-react/src/utils/getEntityRelations.ts diff --git a/plugins/api-docs/src/components/EntityLink/index.ts b/plugins/catalog-react/src/utils/index.ts similarity index 85% rename from plugins/api-docs/src/components/EntityLink/index.ts rename to plugins/catalog-react/src/utils/index.ts index 44875e4634..2efb35703e 100644 --- a/plugins/api-docs/src/components/EntityLink/index.ts +++ b/plugins/catalog-react/src/utils/index.ts @@ -13,5 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -export { EntityLink } from './EntityLink'; +export { getEntityRelations } from './getEntityRelations'; +export { isOwnerOf } from './isOwnerOf'; diff --git a/plugins/catalog/src/components/isOwnerOf.test.ts b/plugins/catalog-react/src/utils/isOwnerOf.test.ts similarity index 100% rename from plugins/catalog/src/components/isOwnerOf.test.ts rename to plugins/catalog-react/src/utils/isOwnerOf.test.ts diff --git a/plugins/catalog/src/components/isOwnerOf.ts b/plugins/catalog-react/src/utils/isOwnerOf.ts similarity index 100% rename from plugins/catalog/src/components/isOwnerOf.ts rename to plugins/catalog-react/src/utils/isOwnerOf.ts diff --git a/plugins/catalog/src/components/AboutCard/AboutContent.tsx b/plugins/catalog/src/components/AboutCard/AboutContent.tsx index bb55e00e93..75b7827111 100644 --- a/plugins/catalog/src/components/AboutCard/AboutContent.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutContent.tsx @@ -19,10 +19,12 @@ import { RELATION_OWNED_BY, RELATION_PART_OF, } from '@backstage/catalog-model'; -import { EntityRefLinks } from '@backstage/plugin-catalog-react'; +import { + EntityRefLinks, + getEntityRelations, +} from '@backstage/plugin-catalog-react'; import { Chip, Grid, makeStyles, Typography } from '@material-ui/core'; import React from 'react'; -import { getEntityRelations } from '../getEntityRelations'; import { AboutField } from './AboutField'; const useStyles = makeStyles({ diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx index 0384c75c96..1298d5e190 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx @@ -22,7 +22,7 @@ import { SupportButton, useApi, } from '@backstage/core'; -import { catalogApiRef } from '@backstage/plugin-catalog-react'; +import { catalogApiRef, isOwnerOf } from '@backstage/plugin-catalog-react'; import { rootRoute as scaffolderRootRoute } from '@backstage/plugin-scaffolder'; import { Button, makeStyles } from '@material-ui/core'; import SettingsIcon from '@material-ui/icons/Settings'; @@ -33,7 +33,6 @@ import { EntityFilterGroupsProvider, useFilteredEntities } from '../../filter'; import { useStarredEntities } from '../../hooks/useStarredEntities'; import { ButtonGroup, CatalogFilter } from '../CatalogFilter/CatalogFilter'; import { CatalogTable } from '../CatalogTable/CatalogTable'; -import { isOwnerOf } from '../isOwnerOf'; import { ResultsFilter } from '../ResultsFilter/ResultsFilter'; import { useOwnUser } from '../useOwnUser'; import CatalogLayout from './CatalogLayout'; diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx index ed0becf270..049b2a2052 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx @@ -24,6 +24,7 @@ import { EntityRefLink, EntityRefLinks, formatEntityRefTitle, + getEntityRelations, } from '@backstage/plugin-catalog-react'; import { Chip } from '@material-ui/core'; import Edit from '@material-ui/icons/Edit'; @@ -37,7 +38,6 @@ import { favouriteEntityIcon, favouriteEntityTooltip, } from '../FavouriteEntity/FavouriteEntity'; -import { getEntityRelations } from '../getEntityRelations'; type EntityRow = Entity & { row: { diff --git a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx index 4f59c4be69..60f06bd53a 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx @@ -14,12 +14,10 @@ * limitations under the License. */ -import React from 'react'; -import { InfoCard, useApi, Progress } from '@backstage/core'; import { Entity } from '@backstage/catalog-model'; -import { catalogApiRef } from '@backstage/plugin-catalog-react'; -import { useAsync } from 'react-use'; -import Alert from '@material-ui/lab/Alert'; +import { InfoCard, Progress, useApi } from '@backstage/core'; +import { catalogApiRef, isOwnerOf } from '@backstage/plugin-catalog-react'; +import { pageTheme } from '@backstage/theme'; import { Box, createStyles, @@ -28,8 +26,9 @@ import { Theme, Typography, } from '@material-ui/core'; -import { pageTheme } from '@backstage/theme'; -import { isOwnerOf } from '../../isOwnerOf'; +import Alert from '@material-ui/lab/Alert'; +import React from 'react'; +import { useAsync } from 'react-use'; type EntitiesKinds = 'Component' | 'API'; type EntitiesTypes = diff --git a/plugins/org/src/components/getEntityRelations.ts b/plugins/org/src/components/getEntityRelations.ts deleted file mode 100644 index 9230aaebee..0000000000 --- a/plugins/org/src/components/getEntityRelations.ts +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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, EntityName } from '@backstage/catalog-model'; - -// TODO: this file is copied from /packages/app/catalog/src/components/getEntityRelations.ts and -// should be replaced once common relation-functions are introduced. - -/** - * Get the related entity references. - */ -export function getEntityRelations( - entity: Entity | undefined, - relationType: string, - filter?: { kind: string }, -): EntityName[] { - let entityNames = - entity?.relations - ?.filter(r => r.type === relationType) - ?.map(r => r.target) || []; - - if (filter?.kind) { - entityNames = entityNames?.filter( - e => e.kind.toLowerCase() === filter.kind.toLowerCase(), - ); - } - - return entityNames; -} diff --git a/plugins/org/src/components/isOwnerOf.ts b/plugins/org/src/components/isOwnerOf.ts deleted file mode 100644 index dd7c7d6805..0000000000 --- a/plugins/org/src/components/isOwnerOf.ts +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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, - EntityName, - getEntityName, - RELATION_MEMBER_OF, - RELATION_OWNED_BY, -} from '@backstage/catalog-model'; -import { getEntityRelations } from './getEntityRelations'; - -// TODO: this file is copied from /packages/app/catalog/src/components/isOwnerOf.ts and -// should be replaced once common relation-functions are introduced. - -/** - * Check if one entity is owned by another. Returns true, if the entity is owned by the - * owner directly, or if the entity is owned by a group that the owner is a member of. - */ -export function isOwnerOf(owner: Entity, owned: Entity) { - const possibleOwners: EntityName[] = [ - ...getEntityRelations(owner, RELATION_MEMBER_OF, { kind: 'group' }), - ...(owner ? [getEntityName(owner)] : []), - ]; - - const owners = getEntityRelations(owned, RELATION_OWNED_BY); - - for (const owner of owners) { - if ( - possibleOwners.find( - o => - owner.kind.toLowerCase() === o.kind.toLowerCase() && - owner.namespace.toLowerCase() === o.namespace.toLowerCase() && - owner.name.toLowerCase() === o.name.toLowerCase(), - ) !== undefined - ) { - return true; - } - } - - return false; -}