From 10a0124e0c977407ba7e1eaf459ca58377989156 Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Wed, 3 Feb 2021 11:20:35 +0100 Subject: [PATCH 01/17] Move useRelatedEntities to catalog-react --- .changeset/spicy-pants-teach.md | 6 ++++ .../components/ApisCards/ConsumedApisCard.tsx | 12 ++++---- .../components/ApisCards/ProvidedApisCard.tsx | 12 ++++---- .../ConsumingComponentsCard.tsx | 10 +++---- .../ProvidingComponentsCard.tsx | 10 +++---- plugins/catalog-react/src/hooks/index.ts | 1 + .../src/hooks}/useRelatedEntities.ts | 30 +++++++++++++------ 7 files changed, 46 insertions(+), 35 deletions(-) create mode 100644 .changeset/spicy-pants-teach.md rename plugins/{api-docs/src/components => catalog-react/src/hooks}/useRelatedEntities.ts (53%) diff --git a/.changeset/spicy-pants-teach.md b/.changeset/spicy-pants-teach.md new file mode 100644 index 0000000000..8e16ae089e --- /dev/null +++ b/.changeset/spicy-pants-teach.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-catalog-react': patch +--- + +Expose `useRelatedEntities` from `@backstage/plugin-catalog-react` to retrieve +entities references via relations from the API. diff --git a/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.tsx b/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.tsx index 8a808a6488..6124ea979a 100644 --- a/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.tsx +++ b/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.tsx @@ -19,12 +19,11 @@ import { Entity, RELATION_CONSUMES_API, } from '@backstage/catalog-model'; -import { useEntity } from '@backstage/plugin-catalog-react'; import { EmptyState, InfoCard, Progress } from '@backstage/core'; +import { useEntity, useRelatedEntities } from '@backstage/plugin-catalog-react'; import React, { PropsWithChildren } from 'react'; -import { ApisTable } from './ApisTable'; import { MissingConsumesApisEmptyState } from '../EmptyState'; -import { useRelatedEntities } from '../useRelatedEntities'; +import { ApisTable } from './ApisTable'; const ApisCard = ({ children, @@ -45,10 +44,9 @@ type Props = { export const ConsumedApisCard = ({ variant = 'gridItem' }: Props) => { const { entity } = useEntity(); - const { entities, loading, error } = useRelatedEntities( - entity, - RELATION_CONSUMES_API, - ); + const { entities, loading, error } = useRelatedEntities(entity, { + type: RELATION_CONSUMES_API, + }); if (loading) { return ( diff --git a/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.tsx b/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.tsx index ed6e893a03..92155e65f5 100644 --- a/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.tsx +++ b/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.tsx @@ -19,12 +19,11 @@ import { Entity, RELATION_PROVIDES_API, } from '@backstage/catalog-model'; -import { useEntity } from '@backstage/plugin-catalog-react'; import { EmptyState, InfoCard, Progress } from '@backstage/core'; +import { useEntity, useRelatedEntities } from '@backstage/plugin-catalog-react'; import React, { PropsWithChildren } from 'react'; -import { ApisTable } from './ApisTable'; import { MissingProvidesApisEmptyState } from '../EmptyState'; -import { useRelatedEntities } from '../useRelatedEntities'; +import { ApisTable } from './ApisTable'; const ApisCard = ({ children, @@ -45,10 +44,9 @@ type Props = { export const ProvidedApisCard = ({ variant = 'gridItem' }: Props) => { const { entity } = useEntity(); - const { entities, loading, error } = useRelatedEntities( - entity, - RELATION_PROVIDES_API, - ); + const { entities, loading, error } = useRelatedEntities(entity, { + type: RELATION_PROVIDES_API, + }); if (loading) { return ( diff --git a/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.tsx b/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.tsx index 56d2fb977d..9ed5bf6d2a 100644 --- a/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.tsx +++ b/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.tsx @@ -19,11 +19,10 @@ import { Entity, RELATION_API_CONSUMED_BY, } from '@backstage/catalog-model'; -import { useEntity } from '@backstage/plugin-catalog-react'; import { EmptyState, InfoCard, Progress } from '@backstage/core'; +import { useEntity, useRelatedEntities } from '@backstage/plugin-catalog-react'; import React, { PropsWithChildren } from 'react'; import { MissingConsumesApisEmptyState } from '../EmptyState'; -import { useRelatedEntities } from '../useRelatedEntities'; import { ComponentsTable } from './ComponentsTable'; const ComponentsCard = ({ @@ -45,10 +44,9 @@ type Props = { export const ConsumingComponentsCard = ({ variant = 'gridItem' }: Props) => { const { entity } = useEntity(); - const { entities, loading, error } = useRelatedEntities( - entity, - RELATION_API_CONSUMED_BY, - ); + const { entities, loading, error } = useRelatedEntities(entity, { + type: RELATION_API_CONSUMED_BY, + }); if (loading) { return ( diff --git a/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.tsx b/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.tsx index 959ff3feed..06377ca0d9 100644 --- a/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.tsx +++ b/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.tsx @@ -19,11 +19,10 @@ import { Entity, RELATION_API_PROVIDED_BY, } from '@backstage/catalog-model'; -import { useEntity } from '@backstage/plugin-catalog-react'; import { EmptyState, InfoCard, Progress } from '@backstage/core'; +import { useEntity, useRelatedEntities } from '@backstage/plugin-catalog-react'; import React, { PropsWithChildren } from 'react'; import { MissingProvidesApisEmptyState } from '../EmptyState'; -import { useRelatedEntities } from '../useRelatedEntities'; import { ComponentsTable } from './ComponentsTable'; const ComponentsCard = ({ @@ -45,10 +44,9 @@ type Props = { export const ProvidingComponentsCard = ({ variant = 'gridItem' }: Props) => { const { entity } = useEntity(); - const { entities, loading, error } = useRelatedEntities( - entity, - RELATION_API_PROVIDED_BY, - ); + const { entities, loading, error } = useRelatedEntities(entity, { + type: RELATION_API_PROVIDED_BY, + }); if (loading) { return ( diff --git a/plugins/catalog-react/src/hooks/index.ts b/plugins/catalog-react/src/hooks/index.ts index 77de70b8d4..2ecf6e9a90 100644 --- a/plugins/catalog-react/src/hooks/index.ts +++ b/plugins/catalog-react/src/hooks/index.ts @@ -15,3 +15,4 @@ */ export { EntityContext, useEntity, useEntityFromUrl } from './useEntity'; export { useEntityCompoundName } from './useEntityCompoundName'; +export { useRelatedEntities } from './useRelatedEntities'; diff --git a/plugins/api-docs/src/components/useRelatedEntities.ts b/plugins/catalog-react/src/hooks/useRelatedEntities.ts similarity index 53% rename from plugins/api-docs/src/components/useRelatedEntities.ts rename to plugins/catalog-react/src/hooks/useRelatedEntities.ts index 5c01d38189..281ef217c1 100644 --- a/plugins/api-docs/src/components/useRelatedEntities.ts +++ b/plugins/catalog-react/src/hooks/useRelatedEntities.ts @@ -15,36 +15,48 @@ */ import { Entity } from '@backstage/catalog-model'; import { useApi } from '@backstage/core'; -import { catalogApiRef } from '@backstage/plugin-catalog-react'; import { useAsyncRetry } from 'react-use'; +import { catalogApiRef } from '../api'; -// TODO: Maybe this hook is interesting for others too? export function useRelatedEntities( entity: Entity, - type: string, + { type, kind }: { type?: string; kind?: string }, ): { - entities: (Entity | undefined)[] | undefined; + entities: Entity[] | undefined; loading: boolean; error: Error | undefined; } { const catalogApi = useApi(catalogApiRef); - const { loading, value, error } = useAsyncRetry< - (Entity | undefined)[] + const { loading, value: entities, error } = useAsyncRetry< + Entity[] >(async () => { const relations = - entity.relations && entity.relations.filter(r => r.type === type); + entity.relations && + entity.relations.filter( + r => + (!type || r.type === type) && + (!kind || + r.target.kind.toLocaleLowerCase() === kind.toLocaleLowerCase()), + ); if (!relations) { return []; } - return await Promise.all( + // TODO: This code could be more efficient if there was an endpoint in the + // backend that either returns the relations of entity (filtered by type) + // or if there is a way to perform a batch request by entity name. However, + // such an implementation would probably be better placed in the graphql API. + const results = await Promise.all( relations?.map(r => catalogApi.getEntityByName(r.target)), ); + // Skip entities that where not found, for example if a relation references + // an entity that doesn't exist. + return results.filter(e => e) as Entity[]; }, [entity, type]); return { - entities: value, + entities, loading, error, }; From 96f378d108274bbff01b425345bc847989f04050 Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Thu, 4 Feb 2021 10:54:58 +0100 Subject: [PATCH 02/17] Add support for custom empty state of `Table` components --- .changeset/violet-maps-occur.md | 8 ++ .../src/components/Table/Table.stories.tsx | 77 ++++++++++++++++--- .../core/src/components/Table/Table.test.tsx | 12 +++ packages/core/src/components/Table/Table.tsx | 21 +++++ 4 files changed, 109 insertions(+), 9 deletions(-) create mode 100644 .changeset/violet-maps-occur.md diff --git a/.changeset/violet-maps-occur.md b/.changeset/violet-maps-occur.md new file mode 100644 index 0000000000..e746ca3323 --- /dev/null +++ b/.changeset/violet-maps-occur.md @@ -0,0 +1,8 @@ +--- +'@backstage/core': patch +--- + +Add support for custom empty state of `Table` components. + +You can now optionally pass `emptyComponent` to `Table` that is displayed +if the table has now rows. diff --git a/packages/core/src/components/Table/Table.stories.tsx b/packages/core/src/components/Table/Table.stories.tsx index 4be85e494f..fe3fe89941 100644 --- a/packages/core/src/components/Table/Table.stories.tsx +++ b/packages/core/src/components/Table/Table.stories.tsx @@ -14,8 +14,10 @@ * limitations under the License. */ +import { makeStyles } from '@material-ui/core'; import React from 'react'; -import { Table, SubvalueCell, TableColumn } from './'; +import { Link } from '../Link'; +import { SubvalueCell, Table, TableColumn } from './'; import { TableFilter } from './Table'; export default { @@ -23,7 +25,16 @@ export default { component: Table, }; -const containerStyle = { width: 850 }; +const useStyles = makeStyles(theme => ({ + container: { + width: 850, + }, + empty: { + padding: theme.spacing(2), + display: 'flex', + justifyContent: 'center', + }, +})); const generateTestData: (number: number) => Array<{}> = (rows = 10) => { const data: Array<{}> = []; @@ -43,6 +54,7 @@ const generateTestData: (number: number) => Array<{}> = (rows = 10) => { const testData10 = generateTestData(10); export const DefaultTable = () => { + const classes = useStyles(); const columns: TableColumn[] = [ { title: 'Column 1', @@ -66,7 +78,7 @@ export const DefaultTable = () => { ]; return ( -
+
{ ); }; -export const SubtitleTable = () => { +export const EmptyTable = () => { + const classes = useStyles(); const columns: TableColumn[] = [ { title: 'Column 1', @@ -101,7 +114,49 @@ export const SubtitleTable = () => { ]; return ( -
+
+
+ No data was added yet,  + learn how to add data. + + } + title="Backstage Table" + /> + + ); +}; + +export const SubtitleTable = () => { + const classes = useStyles(); + const columns: TableColumn[] = [ + { + title: 'Column 1', + field: 'col1', + highlight: true, + }, + { + title: 'Column 2', + field: 'col2', + }, + { + title: 'Numeric value', + field: 'number', + type: 'numeric', + }, + { + title: 'A Date', + field: 'date', + type: 'date', + }, + ]; + + return ( +
{ }; export const HiddenSearchTable = () => { + const classes = useStyles(); const columns: TableColumn[] = [ { title: 'Column 1', @@ -137,7 +193,7 @@ export const HiddenSearchTable = () => { ]; return ( -
+
{ }; export const SubvalueTable = () => { + const classes = useStyles(); const columns: TableColumn[] = [ { title: 'Column 1', @@ -181,13 +238,14 @@ export const SubvalueTable = () => { ]; return ( -
+
); }; export const DenseTable = () => { + const classes = useStyles(); const columns: TableColumn[] = [ { title: 'Column 1', @@ -211,7 +269,7 @@ export const DenseTable = () => { ]; return ( -
+
{ }; export const FilterTable = () => { + const classes = useStyles(); const columns: TableColumn[] = [ { title: 'Column 1', @@ -261,7 +320,7 @@ export const FilterTable = () => { ]; return ( -
+
', () => { ); expect(rendered.getByText('subtitle')).toBeInTheDocument(); }); + + it('renders custom empty component if empty', async () => { + const rendered = await renderInTestApp( +
EMPTY} + columns={minProps.columns} + data={[]} + />, + ); + expect(rendered.getByText('EMPTY')).toBeInTheDocument(); + }); }); diff --git a/packages/core/src/components/Table/Table.tsx b/packages/core/src/components/Table/Table.tsx index dbb2964845..033ec01d3c 100644 --- a/packages/core/src/components/Table/Table.tsx +++ b/packages/core/src/components/Table/Table.tsx @@ -42,6 +42,7 @@ import MTable, { Column, Icons, MaterialTableProps, + MTableBody, MTableHeader, MTableToolbar, Options, @@ -202,6 +203,7 @@ export interface TableProps subtitle?: string; filters?: TableFilter[]; initialState?: TableState; + emptyComponent?: JSX.Element; onStateChange?: (state: TableState) => any; } @@ -212,6 +214,7 @@ export function Table({ subtitle, filters, initialState, + emptyComponent, onStateChange, ...props }: TableProps) { @@ -423,6 +426,23 @@ export function Table({ ], ); + const Body = useCallback( + bodyProps => { + if (emptyComponent && data.length === 0) { + return ( + + + + + + ); + } + + return ; + }, + [data, emptyComponent, columns], + ); + return (
{filtersOpen && data && filters?.length && ( @@ -438,6 +458,7 @@ export function Table({ ), Toolbar, + Body, }} options={{ ...defaultOptions, ...options }} columns={MTColumns} From 0af242b6d8dd13598a6a131cf5c426975baebcf3 Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Thu, 4 Feb 2021 17:37:10 +0100 Subject: [PATCH 03/17] Introduce new cards to `@backstage/plugin-catalog` that can be added to entity pages --- .changeset/pink-coins-sniff.md | 16 ++ .../app/src/components/catalog/EntityPage.tsx | 58 +++++++ .../src/components/ApisCards/ApisTable.tsx | 84 ++++++---- .../ApisCards/ConsumedApisCard.test.tsx | 44 +---- .../components/ApisCards/ConsumedApisCard.tsx | 37 +++-- .../components/ApisCards/HasApisCard.test.tsx | 122 ++++++++++++++ .../src/components/ApisCards/HasApisCard.tsx | 86 ++++++++++ .../ApisCards/ProvidedApisCard.test.tsx | 42 +---- .../components/ApisCards/ProvidedApisCard.tsx | 37 +++-- .../src/components/ApisCards/index.ts | 1 + .../ConsumingComponentsCard.test.tsx | 39 +---- .../ConsumingComponentsCard.tsx | 45 +++-- .../ProvidingComponentsCard.test.tsx | 39 +---- .../ProvidingComponentsCard.tsx | 44 +++-- plugins/api-docs/src/index.ts | 1 + plugins/api-docs/src/plugin.ts | 8 + .../EntityTables/ComponentsTable.test.tsx | 95 +++++++++++ .../EntityTables}/ComponentsTable.tsx | 85 +++++----- .../EntityTables/SystemsTable.test.tsx | 91 ++++++++++ .../components/EntityTables/SystemsTable.tsx | 157 ++++++++++++++++++ .../src/components/EntityTables/index.ts | 17 ++ plugins/catalog-react/src/components/index.ts | 3 +- .../ComponentsCard/HasComponentsCard.test.tsx | 117 +++++++++++++ .../ComponentsCard/HasComponentsCard.tsx | 89 ++++++++++ .../src/components/ComponentsCard/index.ts | 17 ++ .../HasSubcomponentsCard.test.tsx | 117 +++++++++++++ .../HasSubcomponentsCard.tsx | 89 ++++++++++ .../components/HasSubcomponentsCard/index.ts | 17 ++ .../HasSystemsCard/HasSystemsCard.test.tsx | 115 +++++++++++++ .../HasSystemsCard/HasSystemsCard.tsx | 88 ++++++++++ .../src/components/HasSystemsCard/index.ts | 17 ++ plugins/catalog/src/index.ts | 10 +- plugins/catalog/src/plugin.ts | 37 ++++- 33 files changed, 1569 insertions(+), 295 deletions(-) create mode 100644 .changeset/pink-coins-sniff.md create mode 100644 plugins/api-docs/src/components/ApisCards/HasApisCard.test.tsx create mode 100644 plugins/api-docs/src/components/ApisCards/HasApisCard.tsx create mode 100644 plugins/catalog-react/src/components/EntityTables/ComponentsTable.test.tsx rename plugins/{api-docs/src/components/ComponentsCards => catalog-react/src/components/EntityTables}/ComponentsTable.tsx (64%) create mode 100644 plugins/catalog-react/src/components/EntityTables/SystemsTable.test.tsx create mode 100644 plugins/catalog-react/src/components/EntityTables/SystemsTable.tsx create mode 100644 plugins/catalog-react/src/components/EntityTables/index.ts create mode 100644 plugins/catalog/src/components/ComponentsCard/HasComponentsCard.test.tsx create mode 100644 plugins/catalog/src/components/ComponentsCard/HasComponentsCard.tsx create mode 100644 plugins/catalog/src/components/ComponentsCard/index.ts create mode 100644 plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.test.tsx create mode 100644 plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx create mode 100644 plugins/catalog/src/components/HasSubcomponentsCard/index.ts create mode 100644 plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.test.tsx create mode 100644 plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx create mode 100644 plugins/catalog/src/components/HasSystemsCard/index.ts diff --git a/.changeset/pink-coins-sniff.md b/.changeset/pink-coins-sniff.md new file mode 100644 index 0000000000..dd2bc86b2a --- /dev/null +++ b/.changeset/pink-coins-sniff.md @@ -0,0 +1,16 @@ +--- +'@backstage/plugin-api-docs': patch +'@backstage/plugin-catalog': patch +'@backstage/plugin-catalog-react': patch +--- + +Introduce new cards to `@backstage/plugin-catalog` that can be added to entity pages: + +- `EntityHasSystemsCard` to display systems of a domain. +- `EntityHasComponentsCard` to display components of a system. +- `EntityHasSubcomponentsCard` to display subcomponents of a subcomponent. +- In addition, `EntityHasApisCard` to display APIs of a system is added to `@backstage/plugin-api-docs`. + +`@backstage/plugin-catalog-react` now provides `and` to build own cards for components and systems. +The styling of the tables and new cards was also applied to the existing `EntityConsumedApisCard`, +`EntityConsumingComponentsCard`, `EntityProvidedApisCard`, and `EntityProvidingComponentsCard`. diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx index e849667c19..7fb624b9e9 100644 --- a/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/app/src/components/catalog/EntityPage.tsx @@ -16,8 +16,10 @@ import { ApiEntity, + DomainEntity, Entity, GroupEntity, + SystemEntity, UserEntity, } from '@backstage/catalog-model'; import { EmptyState } from '@backstage/core'; @@ -25,11 +27,15 @@ import { ApiDefinitionCard, ConsumedApisCard, ConsumingComponentsCard, + EntityHasApisCard, ProvidedApisCard, ProvidingComponentsCard, } from '@backstage/plugin-api-docs'; import { AboutCard, + EntityHasComponentsCard, + EntityHasSubcomponentsCard, + EntityHasSystemsCard, EntityLinksCard, EntityPageLayout, } from '@backstage/plugin-catalog'; @@ -206,6 +212,9 @@ const ComponentOverviewContent = ({ entity }: { entity: Entity }) => ( )} + + + ); @@ -425,6 +434,51 @@ const GroupEntityPage = ({ entity }: { entity: Entity }) => ( ); +const SystemOverviewContent = ({ entity }: { entity: SystemEntity }) => ( + + + + + + + + + + + +); + +const SystemEntityPage = ({ entity }: { entity: Entity }) => ( + + } + /> + +); + +const DomainOverviewContent = ({ entity }: { entity: DomainEntity }) => ( + + + + + + + + +); + +const DomainEntityPage = ({ entity }: { entity: Entity }) => ( + + } + /> + +); + export const EntityPage = () => { const { entity } = useEntity(); @@ -437,6 +491,10 @@ export const EntityPage = () => { return ; case 'user': return ; + case 'system': + return ; + case 'domain': + return ; default: return ; } diff --git a/plugins/api-docs/src/components/ApisCards/ApisTable.tsx b/plugins/api-docs/src/components/ApisCards/ApisTable.tsx index 30716a61fb..76daf15945 100644 --- a/plugins/api-docs/src/components/ApisCards/ApisTable.tsx +++ b/plugins/api-docs/src/components/ApisCards/ApisTable.tsx @@ -27,12 +27,14 @@ import { formatEntityRefTitle, getEntityRelations, } from '@backstage/plugin-catalog-react'; +import { makeStyles } from '@material-ui/core'; import React from 'react'; import { ApiTypeTitle } from '../ApiDefinitionCard'; type EntityRow = { entity: ApiEntity; resolved: { + name: string; partOfSystemRelationTitle?: string; partOfSystemRelations: EntityName[]; ownedByRelationsTitle?: string; @@ -43,10 +45,10 @@ type EntityRow = { const columns: TableColumn[] = [ { title: 'Name', - field: 'entity.metadata.name', + field: 'resolved.name', highlight: true, render: ({ entity }) => ( - {entity.metadata.name} + ), }, { @@ -88,10 +90,25 @@ const columns: TableColumn[] = [ type Props = { title: string; variant?: string; - entities: (ApiEntity | undefined)[]; + entities: ApiEntity[]; + emptyComponent?: JSX.Element; }; -export const ApisTable = ({ entities, title, variant = 'gridItem' }: Props) => { +const useStyles = makeStyles(theme => ({ + empty: { + padding: theme.spacing(2), + display: 'flex', + justifyContent: 'center', + }, +})); + +export const ApisTable = ({ + entities, + title, + emptyComponent, + variant = 'gridItem', +}: Props) => { + const classes = useStyles(); const tableStyle: React.CSSProperties = { minWidth: '0', width: '100%', @@ -101,43 +118,42 @@ 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(entity => { - const partOfSystemRelations = getEntityRelations( - entity, - RELATION_PART_OF, - { - kind: 'system', - }, - ); - const ownedByRelations = getEntityRelations(entity, RELATION_OWNED_BY); - - return { - entity: entity as ApiEntity, - resolved: { - ownedByRelationsTitle: ownedByRelations - .map(r => formatEntityRefTitle(r, { defaultKind: 'group' })) - .join(', '), - ownedByRelations, - partOfSystemRelationTitle: partOfSystemRelations - .map(r => - formatEntityRefTitle(r, { - defaultKind: 'system', - }), - ) - .join(', '), - partOfSystemRelations, - }, - }; + const rows = entities.map(entity => { + const partOfSystemRelations = getEntityRelations(entity, RELATION_PART_OF, { + kind: 'system', }); + const ownedByRelations = getEntityRelations(entity, RELATION_OWNED_BY); + + return { + entity, + resolved: { + name: formatEntityRefTitle(entity, { + defaultKind: 'API', + }), + ownedByRelationsTitle: ownedByRelations + .map(r => formatEntityRefTitle(r, { defaultKind: 'group' })) + .join(', '), + ownedByRelations, + partOfSystemRelationTitle: partOfSystemRelations + .map(r => + formatEntityRefTitle(r, { + defaultKind: 'system', + }), + ) + .join(', '), + partOfSystemRelations, + }, + }; + }); return ( columns={columns} title={title} style={tableStyle} + emptyComponent={ + emptyComponent &&
{emptyComponent}
+ } options={{ // TODO: Toolbar padding if off compared to other cards, should be: padding: 16px 24px; search: false, diff --git a/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.test.tsx b/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.test.tsx index 051b9588a0..5d2978a198 100644 --- a/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.test.tsx +++ b/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.test.tsx @@ -14,12 +14,7 @@ * limitations under the License. */ -import { - Entity, - RELATION_CONSUMES_API, - RELATION_OWNED_BY, - RELATION_PART_OF, -} from '@backstage/catalog-model'; +import { Entity, RELATION_CONSUMES_API } from '@backstage/catalog-model'; import { ApiProvider, ApiRegistry } from '@backstage/core'; import { CatalogApi, @@ -79,7 +74,7 @@ describe('', () => { ); expect(getByText(/Consumed APIs/i)).toBeInTheDocument(); - expect(getByText(/No APIs consumed by this entity/i)).toBeInTheDocument(); + expect(getByText(/No Component consumes this API/i)).toBeInTheDocument(); }); it('shows consumed APIs', async () => { @@ -108,34 +103,7 @@ describe('', () => { name: 'target-name', namespace: 'my-namespace', }, - spec: { - type: 'openapi', - 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', - title: 'OpenAPI', - component: () =>
, + spec: {}, }); const { getByText } = await renderInTestApp( @@ -147,12 +115,8 @@ describe('', () => { ); await waitFor(() => { - expect(getByText(/Consumed APIs/i)).toBeInTheDocument(); + expect(getByText('Consumed APIs')).toBeInTheDocument(); 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/ConsumedApisCard.tsx b/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.tsx index 6124ea979a..7e57a23b06 100644 --- a/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.tsx +++ b/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.tsx @@ -19,10 +19,15 @@ import { Entity, RELATION_CONSUMES_API, } from '@backstage/catalog-model'; -import { EmptyState, InfoCard, Progress } from '@backstage/core'; +import { + CodeSnippet, + InfoCard, + Link, + Progress, + WarningPanel, +} from '@backstage/core'; import { useEntity, useRelatedEntities } from '@backstage/plugin-catalog-react'; import React, { PropsWithChildren } from 'react'; -import { MissingConsumesApisEmptyState } from '../EmptyState'; import { ApisTable } from './ApisTable'; const ApisCard = ({ @@ -56,31 +61,31 @@ export const ConsumedApisCard = ({ variant = 'gridItem' }: Props) => { ); } - if (error) { + if (error || !entities) { return ( - } /> ); } - if (!entities || entities.length === 0) { - return ( - - - - ); - } - return ( + No Component consumes this API.{' '} + + Learn how to consume APIs. + +
+ } + entities={entities as ApiEntity[]} /> ); }; diff --git a/plugins/api-docs/src/components/ApisCards/HasApisCard.test.tsx b/plugins/api-docs/src/components/ApisCards/HasApisCard.test.tsx new file mode 100644 index 0000000000..7b97f0867e --- /dev/null +++ b/plugins/api-docs/src/components/ApisCards/HasApisCard.test.tsx @@ -0,0 +1,122 @@ +/* + * 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 { ApiDocsConfig, apiDocsConfigRef } from '../../config'; +import { HasApisCard } from './HasApisCard'; + +describe('', () => { + const apiDocsConfig: jest.Mocked = { + getApiDefinitionWidget: jest.fn(), + } as any; + 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).with( + apiDocsConfigRef, + apiDocsConfig, + ); + + 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('APIs')).toBeInTheDocument(); + expect(getByText(/No API is part of this system/i)).toBeInTheDocument(); + }); + + it('shows related APIs', async () => { + const entity: Entity = { + apiVersion: 'v1', + kind: 'System', + metadata: { + name: 'my-system', + namespace: 'my-namespace', + }, + relations: [ + { + target: { + kind: 'API', + namespace: 'my-namespace', + name: 'target-name', + }, + type: RELATION_HAS_PART, + }, + ], + }; + catalogApi.getEntityByName.mockResolvedValue({ + apiVersion: 'v1', + kind: 'API', + metadata: { + name: 'target-name', + namespace: 'my-namespace', + }, + spec: {}, + }); + + const { getByText } = await renderInTestApp( + + + + + , + ); + + await waitFor(() => { + expect(getByText('APIs')).toBeInTheDocument(); + expect(getByText(/target-name/i)).toBeInTheDocument(); + }); + }); +}); diff --git a/plugins/api-docs/src/components/ApisCards/HasApisCard.tsx b/plugins/api-docs/src/components/ApisCards/HasApisCard.tsx new file mode 100644 index 0000000000..5ed38e91c7 --- /dev/null +++ b/plugins/api-docs/src/components/ApisCards/HasApisCard.tsx @@ -0,0 +1,86 @@ +/* + * 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 { ApiEntity, RELATION_HAS_PART } from '@backstage/catalog-model'; +import { + CodeSnippet, + InfoCard, + Link, + Progress, + WarningPanel, +} from '@backstage/core'; +import { useEntity, useRelatedEntities } from '@backstage/plugin-catalog-react'; +import React, { PropsWithChildren } from 'react'; +import { ApisTable } from './ApisTable'; + +const ApisCard = ({ + children, + variant = 'gridItem', +}: PropsWithChildren<{ variant?: string }>) => { + return ( + + {children} + + ); +}; + +type Props = { + variant?: string; +}; + +export const HasApisCard = ({ variant = 'gridItem' }: Props) => { + const { entity } = useEntity(); + const { entities, loading, error } = useRelatedEntities(entity, { + type: RELATION_HAS_PART, + kind: 'API', + }); + + if (loading) { + return ( + + + + ); + } + + if (error || !entities) { + return ( + + } + /> + + ); + } + + return ( + + No API is part of this system.{' '} + + Learn how to add APIs. + +
+ } + entities={entities as ApiEntity[]} + /> + ); +}; diff --git a/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.test.tsx b/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.test.tsx index 482534f58a..bb4d0ac82f 100644 --- a/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.test.tsx +++ b/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.test.tsx @@ -14,12 +14,7 @@ * limitations under the License. */ -import { - Entity, - RELATION_OWNED_BY, - RELATION_PART_OF, - RELATION_PROVIDES_API, -} from '@backstage/catalog-model'; +import { Entity, RELATION_PROVIDES_API } from '@backstage/catalog-model'; import { ApiProvider, ApiRegistry } from '@backstage/core'; import { CatalogApi, @@ -79,7 +74,7 @@ describe('', () => { ); expect(getByText(/Provided APIs/i)).toBeInTheDocument(); - expect(getByText(/No APIs provided by this entity/i)).toBeInTheDocument(); + expect(getByText(/No Component provides this API/i)).toBeInTheDocument(); }); it('shows consumed APIs', async () => { @@ -108,34 +103,7 @@ describe('', () => { name: 'target-name', namespace: 'my-namespace', }, - spec: { - type: 'openapi', - 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', - title: 'OpenAPI', - component: () =>
, + spec: {}, }); const { getByText } = await renderInTestApp( @@ -149,10 +117,6 @@ describe('', () => { await waitFor(() => { 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/ApisCards/ProvidedApisCard.tsx b/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.tsx index 92155e65f5..e760d7a8f0 100644 --- a/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.tsx +++ b/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.tsx @@ -19,10 +19,15 @@ import { Entity, RELATION_PROVIDES_API, } from '@backstage/catalog-model'; -import { EmptyState, InfoCard, Progress } from '@backstage/core'; +import { + CodeSnippet, + InfoCard, + Link, + Progress, + WarningPanel, +} from '@backstage/core'; import { useEntity, useRelatedEntities } from '@backstage/plugin-catalog-react'; import React, { PropsWithChildren } from 'react'; -import { MissingProvidesApisEmptyState } from '../EmptyState'; import { ApisTable } from './ApisTable'; const ApisCard = ({ @@ -56,31 +61,31 @@ export const ProvidedApisCard = ({ variant = 'gridItem' }: Props) => { ); } - if (error) { + if (error || !entities) { return ( - } /> ); } - if (!entities || entities.length === 0) { - return ( - - - - ); - } - return ( + No Component provides this API.{' '} + + Learn how to provide APIs. + +
+ } + entities={entities as ApiEntity[]} /> ); }; diff --git a/plugins/api-docs/src/components/ApisCards/index.ts b/plugins/api-docs/src/components/ApisCards/index.ts index 2a01a1dc6e..9243bdedd2 100644 --- a/plugins/api-docs/src/components/ApisCards/index.ts +++ b/plugins/api-docs/src/components/ApisCards/index.ts @@ -15,4 +15,5 @@ */ export { ConsumedApisCard } from './ConsumedApisCard'; +export { HasApisCard } from './HasApisCard'; export { ProvidedApisCard } from './ProvidedApisCard'; diff --git a/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.test.tsx b/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.test.tsx index f45a48842d..7944b3f90a 100644 --- a/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.test.tsx +++ b/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.test.tsx @@ -14,12 +14,7 @@ * limitations under the License. */ -import { - Entity, - RELATION_API_CONSUMED_BY, - RELATION_OWNED_BY, - RELATION_PART_OF, -} from '@backstage/catalog-model'; +import { Entity, RELATION_API_CONSUMED_BY } from '@backstage/catalog-model'; import { ApiProvider, ApiRegistry } from '@backstage/core'; import { CatalogApi, @@ -77,8 +72,8 @@ describe('', () => { , ); - expect(getByText(/Consumers/i)).toBeInTheDocument(); - expect(getByText(/No APIs consumed by this entity/i)).toBeInTheDocument(); + expect(getByText('Consumers')).toBeInTheDocument(); + expect(getByText(/No component consumes this API/i)).toBeInTheDocument(); }); it('shows consuming components', async () => { @@ -113,28 +108,7 @@ describe('', () => { name: 'target-name', namespace: 'my-namespace', }, - spec: { - type: 'service', - 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', - }, - }, - ], + spec: {}, }); const { getByText } = await renderInTestApp( @@ -146,11 +120,8 @@ describe('', () => { ); await waitFor(() => { - expect(getByText(/Consumers/i)).toBeInTheDocument(); + expect(getByText('Consumers')).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/ConsumingComponentsCard.tsx b/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.tsx index 9ed5bf6d2a..ed987b33f5 100644 --- a/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.tsx +++ b/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.tsx @@ -19,11 +19,20 @@ import { Entity, RELATION_API_CONSUMED_BY, } from '@backstage/catalog-model'; -import { EmptyState, InfoCard, Progress } from '@backstage/core'; -import { useEntity, useRelatedEntities } from '@backstage/plugin-catalog-react'; +import { + CodeSnippet, + InfoCard, + Link, + Progress, + WarningPanel, +} from '@backstage/core'; +import { + ComponentsTable, + useEntity, + useRelatedEntities, +} from '@backstage/plugin-catalog-react'; + import React, { PropsWithChildren } from 'react'; -import { MissingConsumesApisEmptyState } from '../EmptyState'; -import { ComponentsTable } from './ComponentsTable'; const ComponentsCard = ({ children, @@ -56,31 +65,31 @@ export const ConsumingComponentsCard = ({ variant = 'gridItem' }: Props) => { ); } - if (error) { + if (error || !entities) { return ( - } /> ); } - if (!entities || entities.length === 0) { - return ( - - - - ); - } - return ( + No component consumes this API.{' '} + + Learn how to consume APIs. + + + } + entities={entities as ComponentEntity[]} /> ); }; diff --git a/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.test.tsx b/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.test.tsx index 4c55393d13..ab1751b9ba 100644 --- a/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.test.tsx +++ b/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.test.tsx @@ -14,12 +14,7 @@ * limitations under the License. */ -import { - Entity, - RELATION_API_PROVIDED_BY, - RELATION_OWNED_BY, - RELATION_PART_OF, -} from '@backstage/catalog-model'; +import { Entity, RELATION_API_PROVIDED_BY } from '@backstage/catalog-model'; import { ApiProvider, ApiRegistry } from '@backstage/core'; import { CatalogApi, @@ -77,8 +72,8 @@ describe('', () => { , ); - expect(getByText(/Providers/i)).toBeInTheDocument(); - expect(getByText(/No APIs provided by this entity/i)).toBeInTheDocument(); + expect(getByText('Providers')).toBeInTheDocument(); + expect(getByText(/No component provides this API/i)).toBeInTheDocument(); }); it('shows providing components', async () => { @@ -113,28 +108,7 @@ describe('', () => { name: 'target-name', namespace: 'my-namespace', }, - spec: { - type: 'service', - 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', - }, - }, - ], + spec: {}, }); const { getByText } = await renderInTestApp( @@ -146,11 +120,8 @@ describe('', () => { ); await waitFor(() => { - expect(getByText(/Providers/i)).toBeInTheDocument(); + expect(getByText('Providers')).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.tsx b/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.tsx index 06377ca0d9..39e179dd10 100644 --- a/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.tsx +++ b/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.tsx @@ -19,11 +19,19 @@ import { Entity, RELATION_API_PROVIDED_BY, } from '@backstage/catalog-model'; -import { EmptyState, InfoCard, Progress } from '@backstage/core'; -import { useEntity, useRelatedEntities } from '@backstage/plugin-catalog-react'; +import { + CodeSnippet, + InfoCard, + Link, + Progress, + WarningPanel, +} from '@backstage/core'; +import { + ComponentsTable, + useEntity, + useRelatedEntities, +} from '@backstage/plugin-catalog-react'; import React, { PropsWithChildren } from 'react'; -import { MissingProvidesApisEmptyState } from '../EmptyState'; -import { ComponentsTable } from './ComponentsTable'; const ComponentsCard = ({ children, @@ -56,31 +64,31 @@ export const ProvidingComponentsCard = ({ variant = 'gridItem' }: Props) => { ); } - if (error) { + if (error || !entities) { return ( - } /> ); } - if (!entities || entities.length === 0) { - return ( - - - - ); - } - return ( + No component provides this API.{' '} + + Learn how to provide APIs. + + + } + entities={entities as ComponentEntity[]} /> ); }; diff --git a/plugins/api-docs/src/index.ts b/plugins/api-docs/src/index.ts index e7484f77c3..8bab7f28f4 100644 --- a/plugins/api-docs/src/index.ts +++ b/plugins/api-docs/src/index.ts @@ -24,4 +24,5 @@ export { EntityConsumingComponentsCard, EntityProvidedApisCard, EntityProvidingComponentsCard, + EntityHasApisCard, } from './plugin'; diff --git a/plugins/api-docs/src/plugin.ts b/plugins/api-docs/src/plugin.ts index 429fd60095..1d57cc6cc2 100644 --- a/plugins/api-docs/src/plugin.ts +++ b/plugins/api-docs/src/plugin.ts @@ -106,3 +106,11 @@ export const EntityProvidingComponentsCard = apiDocsPlugin.provide( }, }), ); + +export const EntityHasApisCard = apiDocsPlugin.provide( + createComponentExtension({ + component: { + lazy: () => import('./components/ApisCards').then(m => m.HasApisCard), + }, + }), +); diff --git a/plugins/catalog-react/src/components/EntityTables/ComponentsTable.test.tsx b/plugins/catalog-react/src/components/EntityTables/ComponentsTable.test.tsx new file mode 100644 index 0000000000..f29d1cfe60 --- /dev/null +++ b/plugins/catalog-react/src/components/EntityTables/ComponentsTable.test.tsx @@ -0,0 +1,95 @@ +/* + * 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_OWNED_BY, + RELATION_PART_OF, +} from '@backstage/catalog-model'; +import { renderInTestApp } from '@backstage/test-utils'; +import { waitFor } from '@testing-library/react'; +import React from 'react'; +import { ComponentsTable } from './ComponentsTable'; + +describe('', () => { + it('shows empty table', async () => { + const { getByText } = await renderInTestApp( + EMPTY} + />, + ); + + expect(getByText('My Components')).toBeInTheDocument(); + expect(getByText('EMPTY')).toBeInTheDocument(); + }); + + it('shows components', async () => { + const entities: ComponentEntity[] = [ + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'my-component', + namespace: 'my-namespace', + description: 'Some description', + }, + spec: { + type: 'service', + lifecycle: 'production', + owner: 'owner-data', + }, + relations: [ + { + type: RELATION_PART_OF, + target: { + kind: 'System', + name: 'my-system', + namespace: 'my-namespace', + }, + }, + { + type: RELATION_OWNED_BY, + target: { + kind: 'Group', + name: 'Test', + namespace: 'default', + }, + }, + ], + }, + ]; + + const { getByText } = await renderInTestApp( + EMPTY} + />, + ); + + await waitFor(() => { + expect(getByText('My Components')).toBeInTheDocument(); + expect(getByText('my-namespace/my-component')).toBeInTheDocument(); + expect(getByText('my-namespace/my-system')).toBeInTheDocument(); + expect(getByText('Test')).toBeInTheDocument(); + expect(getByText('production')).toBeInTheDocument(); + expect(getByText('service')).toBeInTheDocument(); + expect(getByText('Some description')).toBeInTheDocument(); + }); + }); +}); diff --git a/plugins/api-docs/src/components/ComponentsCards/ComponentsTable.tsx b/plugins/catalog-react/src/components/EntityTables/ComponentsTable.tsx similarity index 64% rename from plugins/api-docs/src/components/ComponentsCards/ComponentsTable.tsx rename to plugins/catalog-react/src/components/EntityTables/ComponentsTable.tsx index bd9b0c765f..b79c7e705a 100644 --- a/plugins/api-docs/src/components/ComponentsCards/ComponentsTable.tsx +++ b/plugins/catalog-react/src/components/EntityTables/ComponentsTable.tsx @@ -21,17 +21,19 @@ import { RELATION_PART_OF, } from '@backstage/catalog-model'; import { Table, TableColumn } from '@backstage/core'; +import { makeStyles } from '@material-ui/core'; +import React from 'react'; +import { getEntityRelations } from '../../utils'; import { EntityRefLink, EntityRefLinks, formatEntityRefTitle, - getEntityRelations, -} from '@backstage/plugin-catalog-react'; -import React from 'react'; +} from '../EntityRefLink'; type EntityRow = { entity: ComponentEntity; resolved: { + name: string; partOfSystemRelationTitle?: string; partOfSystemRelations: EntityName[]; ownedByRelationsTitle?: string; @@ -42,10 +44,10 @@ type EntityRow = { const columns: TableColumn[] = [ { title: 'Name', - field: 'entity.metadata.name', + field: 'resolved.name', highlight: true, render: ({ entity }) => ( - {entity.metadata.name} + ), }, { @@ -86,15 +88,25 @@ const columns: TableColumn[] = [ type Props = { title: string; variant?: string; - entities: (ComponentEntity | undefined)[]; + entities: ComponentEntity[]; + emptyComponent?: JSX.Element; }; -// TODO: In theory this could also be systems! +const useStyles = makeStyles(theme => ({ + empty: { + padding: theme.spacing(2), + display: 'flex', + justifyContent: 'center', + }, +})); + export const ComponentsTable = ({ entities, title, + emptyComponent, variant = 'gridItem', }: Props) => { + const classes = useStyles(); const tableStyle: React.CSSProperties = { minWidth: '0', width: '100%', @@ -104,43 +116,42 @@ 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(entity => { - const partOfSystemRelations = getEntityRelations( - entity, - RELATION_PART_OF, - { - kind: 'system', - }, - ); - const ownedByRelations = getEntityRelations(entity, RELATION_OWNED_BY); - - return { - entity: entity as ComponentEntity, - resolved: { - ownedByRelationsTitle: ownedByRelations - .map(r => formatEntityRefTitle(r, { defaultKind: 'group' })) - .join(', '), - ownedByRelations, - partOfSystemRelationTitle: partOfSystemRelations - .map(r => - formatEntityRefTitle(r, { - defaultKind: 'system', - }), - ) - .join(', '), - partOfSystemRelations, - }, - }; + const rows = entities.map(entity => { + const partOfSystemRelations = getEntityRelations(entity, RELATION_PART_OF, { + kind: 'system', }); + const ownedByRelations = getEntityRelations(entity, RELATION_OWNED_BY); + + return { + entity: entity, + resolved: { + name: formatEntityRefTitle(entity, { + defaultKind: 'Component', + }), + ownedByRelationsTitle: ownedByRelations + .map(r => formatEntityRefTitle(r, { defaultKind: 'group' })) + .join(', '), + ownedByRelations, + partOfSystemRelationTitle: partOfSystemRelations + .map(r => + formatEntityRefTitle(r, { + defaultKind: 'system', + }), + ) + .join(', '), + partOfSystemRelations, + }, + }; + }); return ( columns={columns} title={title} style={tableStyle} + emptyComponent={ + emptyComponent &&
{emptyComponent}
+ } options={{ // TODO: Toolbar padding if off compared to other cards, should be: padding: 16px 24px; search: false, diff --git a/plugins/catalog-react/src/components/EntityTables/SystemsTable.test.tsx b/plugins/catalog-react/src/components/EntityTables/SystemsTable.test.tsx new file mode 100644 index 0000000000..9f89d7592e --- /dev/null +++ b/plugins/catalog-react/src/components/EntityTables/SystemsTable.test.tsx @@ -0,0 +1,91 @@ +/* + * 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_OWNED_BY, + RELATION_PART_OF, + SystemEntity, +} from '@backstage/catalog-model'; +import { renderInTestApp } from '@backstage/test-utils'; +import { waitFor } from '@testing-library/react'; +import React from 'react'; +import { SystemsTable } from './SystemsTable'; + +describe('', () => { + it('shows empty table', async () => { + const { getByText } = await renderInTestApp( + EMPTY} + />, + ); + + expect(getByText('My Systems')).toBeInTheDocument(); + expect(getByText('EMPTY')).toBeInTheDocument(); + }); + + it('shows systems', async () => { + const entities: SystemEntity[] = [ + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'System', + metadata: { + name: 'my-system', + namespace: 'my-namespace', + description: 'Some description', + }, + spec: { + owner: 'owner-data', + }, + relations: [ + { + type: RELATION_PART_OF, + target: { + kind: 'Domain', + name: 'my-domain', + namespace: 'my-namespace', + }, + }, + { + type: RELATION_OWNED_BY, + target: { + kind: 'Group', + name: 'Test', + namespace: 'default', + }, + }, + ], + }, + ]; + + const { getByText } = await renderInTestApp( + EMPTY} + />, + ); + + await waitFor(() => { + expect(getByText('My Systems')).toBeInTheDocument(); + expect(getByText('my-namespace/my-system')).toBeInTheDocument(); + expect(getByText('my-namespace/my-domain')).toBeInTheDocument(); + expect(getByText('Test')).toBeInTheDocument(); + expect(getByText('Some description')).toBeInTheDocument(); + }); + }); +}); diff --git a/plugins/catalog-react/src/components/EntityTables/SystemsTable.tsx b/plugins/catalog-react/src/components/EntityTables/SystemsTable.tsx new file mode 100644 index 0000000000..8f30ea0543 --- /dev/null +++ b/plugins/catalog-react/src/components/EntityTables/SystemsTable.tsx @@ -0,0 +1,157 @@ +/* + * 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 { + EntityName, + RELATION_OWNED_BY, + RELATION_PART_OF, + SystemEntity, +} from '@backstage/catalog-model'; +import { Table, TableColumn } from '@backstage/core'; +import { makeStyles } from '@material-ui/core'; +import React from 'react'; +import { getEntityRelations } from '../../utils'; +import { + EntityRefLink, + EntityRefLinks, + formatEntityRefTitle, +} from '../EntityRefLink'; + +type EntityRow = { + entity: SystemEntity; + resolved: { + name: string; + partOfDomainRelationTitle?: string; + partOfDomainRelations: EntityName[]; + ownedByRelationsTitle?: string; + ownedByRelations: EntityName[]; + }; +}; + +const columns: TableColumn[] = [ + { + title: 'Name', + field: 'resolved.name', + highlight: true, + render: ({ entity }) => ( + + ), + }, + { + title: 'Domain', + field: 'resolved.partOfDomainRelationTitle', + render: ({ resolved }) => ( + + ), + }, + { + title: 'Owner', + field: 'resolved.ownedByRelationsTitle', + render: ({ resolved }) => ( + + ), + }, + { + title: 'Description', + field: 'entity.metadata.description', + width: 'auto', + }, +]; + +type Props = { + title: string; + variant?: string; + entities: SystemEntity[]; + emptyComponent?: JSX.Element; +}; + +const useStyles = makeStyles(theme => ({ + empty: { + padding: theme.spacing(2), + display: 'flex', + justifyContent: 'center', + }, +})); + +export const SystemsTable = ({ + entities, + title, + emptyComponent, + variant = 'gridItem', +}: Props) => { + const classes = useStyles(); + const tableStyle: React.CSSProperties = { + minWidth: '0', + width: '100%', + }; + + if (variant === 'gridItem') { + tableStyle.height = 'calc(100% - 10px)'; + } + + const rows = entities.map(entity => { + const partOfDomainRelations = getEntityRelations(entity, RELATION_PART_OF, { + kind: 'domain', + }); + const ownedByRelations = getEntityRelations(entity, RELATION_OWNED_BY); + + return { + entity, + resolved: { + name: formatEntityRefTitle(entity, { + defaultKind: 'System', + }), + ownedByRelationsTitle: ownedByRelations + .map(r => formatEntityRefTitle(r, { defaultKind: 'group' })) + .join(', '), + ownedByRelations, + partOfDomainRelationTitle: partOfDomainRelations + .map(r => + formatEntityRefTitle(r, { + defaultKind: 'domain', + }), + ) + .join(', '), + partOfDomainRelations, + }, + }; + }); + + return ( + + columns={columns} + title={title} + style={tableStyle} + emptyComponent={ + emptyComponent &&
{emptyComponent}
+ } + options={{ + // TODO: Toolbar padding if off compared to other cards, should be: padding: 16px 24px; + search: false, + paging: false, + actionsColumnIndex: -1, + padding: 'dense', + }} + data={rows} + /> + ); +}; diff --git a/plugins/catalog-react/src/components/EntityTables/index.ts b/plugins/catalog-react/src/components/EntityTables/index.ts new file mode 100644 index 0000000000..f3e4f416ac --- /dev/null +++ b/plugins/catalog-react/src/components/EntityTables/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 { ComponentsTable } from './ComponentsTable'; +export { SystemsTable } from './SystemsTable'; diff --git a/plugins/catalog-react/src/components/index.ts b/plugins/catalog-react/src/components/index.ts index fcfa8207b3..463aeb6250 100644 --- a/plugins/catalog-react/src/components/index.ts +++ b/plugins/catalog-react/src/components/index.ts @@ -13,5 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export * from './EntityRefLink'; export * from './EntityProvider'; +export * from './EntityRefLink'; +export * from './EntityTables'; diff --git a/plugins/catalog/src/components/ComponentsCard/HasComponentsCard.test.tsx b/plugins/catalog/src/components/ComponentsCard/HasComponentsCard.test.tsx new file mode 100644 index 0000000000..58df0a53f8 --- /dev/null +++ b/plugins/catalog/src/components/ComponentsCard/HasComponentsCard.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 { HasComponentsCard } from './HasComponentsCard'; + +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('Components')).toBeInTheDocument(); + expect( + getByText(/No component is part of this system/i), + ).toBeInTheDocument(); + }); + + it('shows related components', async () => { + const entity: Entity = { + apiVersion: 'v1', + kind: 'System', + metadata: { + name: 'my-system', + namespace: 'my-namespace', + }, + relations: [ + { + target: { + kind: 'Component', + namespace: 'my-namespace', + name: 'target-name', + }, + type: RELATION_HAS_PART, + }, + ], + }; + catalogApi.getEntityByName.mockResolvedValue({ + apiVersion: 'v1', + kind: 'Component', + metadata: { + name: 'target-name', + namespace: 'my-namespace', + }, + spec: {}, + }); + + const { getByText } = await renderInTestApp( + + + + + , + ); + + await waitFor(() => { + expect(getByText('Components')).toBeInTheDocument(); + expect(getByText(/target-name/i)).toBeInTheDocument(); + }); + }); +}); diff --git a/plugins/catalog/src/components/ComponentsCard/HasComponentsCard.tsx b/plugins/catalog/src/components/ComponentsCard/HasComponentsCard.tsx new file mode 100644 index 0000000000..c9b5879a05 --- /dev/null +++ b/plugins/catalog/src/components/ComponentsCard/HasComponentsCard.tsx @@ -0,0 +1,89 @@ +/* + * 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_HAS_PART } from '@backstage/catalog-model'; +import { + CodeSnippet, + InfoCard, + Link, + Progress, + WarningPanel, +} from '@backstage/core'; +import { + ComponentsTable, + useEntity, + useRelatedEntities, +} from '@backstage/plugin-catalog-react'; +import React, { PropsWithChildren } from 'react'; + +const ComponentsCard = ({ + children, + variant = 'gridItem', +}: PropsWithChildren<{ variant?: string }>) => { + return ( + + {children} + + ); +}; + +type Props = { + variant?: string; +}; + +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 add components. + + + } + entities={entities as ComponentEntity[]} + /> + ); +}; diff --git a/plugins/catalog/src/components/ComponentsCard/index.ts b/plugins/catalog/src/components/ComponentsCard/index.ts new file mode 100644 index 0000000000..059392c3e8 --- /dev/null +++ b/plugins/catalog/src/components/ComponentsCard/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 { HasComponentsCard } from './HasComponentsCard'; diff --git a/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.test.tsx b/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.test.tsx new file mode 100644 index 0000000000..79b69d35ff --- /dev/null +++ b/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.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 { HasSubcomponentsCard } from './HasSubcomponentsCard'; + +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: 'Component', + metadata: { + name: 'my-components', + namespace: 'my-namespace', + }, + relations: [], + }; + + const { getByText } = await renderInTestApp( + + + + + , + ); + + expect(getByText('Subcomponents')).toBeInTheDocument(); + expect( + getByText(/No subcomponent is part of this component/i), + ).toBeInTheDocument(); + }); + + it('shows related subcomponents', 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_HAS_PART, + }, + ], + }; + catalogApi.getEntityByName.mockResolvedValue({ + apiVersion: 'v1', + kind: 'Component', + metadata: { + name: 'target-name', + namespace: 'my-namespace', + }, + spec: {}, + }); + + const { getByText } = await renderInTestApp( + + + + + , + ); + + await waitFor(() => { + expect(getByText('Subcomponents')).toBeInTheDocument(); + expect(getByText(/target-name/i)).toBeInTheDocument(); + }); + }); +}); diff --git a/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx b/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx new file mode 100644 index 0000000000..541ccf795d --- /dev/null +++ b/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx @@ -0,0 +1,89 @@ +/* + * 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_HAS_PART } from '@backstage/catalog-model'; +import { + CodeSnippet, + InfoCard, + Link, + Progress, + WarningPanel, +} from '@backstage/core'; +import { + ComponentsTable, + useEntity, + useRelatedEntities, +} from '@backstage/plugin-catalog-react'; +import React, { PropsWithChildren } from 'react'; + +const SubcomponentsCard = ({ + children, + variant = 'gridItem', +}: PropsWithChildren<{ variant?: string }>) => { + return ( + + {children} + + ); +}; + +type Props = { + variant?: string; +}; + +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 add subcomponents. + + + } + entities={entities as ComponentEntity[]} + /> + ); +}; diff --git a/plugins/catalog/src/components/HasSubcomponentsCard/index.ts b/plugins/catalog/src/components/HasSubcomponentsCard/index.ts new file mode 100644 index 0000000000..cef0221d3b --- /dev/null +++ b/plugins/catalog/src/components/HasSubcomponentsCard/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 { HasSubcomponentsCard } from './HasSubcomponentsCard'; diff --git a/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.test.tsx b/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.test.tsx new file mode 100644 index 0000000000..08923a59c8 --- /dev/null +++ b/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.test.tsx @@ -0,0 +1,115 @@ +/* + * 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 { HasSystemsCard } from './HasSystemsCard'; + +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: 'Domain', + metadata: { + name: 'my-domain', + namespace: 'my-namespace', + }, + relations: [], + }; + + const { getByText } = await renderInTestApp( + + + + + , + ); + + expect(getByText('Systems')).toBeInTheDocument(); + expect(getByText(/No system is part of this domain/i)).toBeInTheDocument(); + }); + + it('shows related systems', async () => { + const entity: Entity = { + apiVersion: 'v1', + kind: 'Domain', + metadata: { + name: 'my-domain', + namespace: 'my-namespace', + }, + relations: [ + { + target: { + kind: 'System', + namespace: 'my-namespace', + name: 'target-name', + }, + type: RELATION_HAS_PART, + }, + ], + }; + catalogApi.getEntityByName.mockResolvedValue({ + apiVersion: 'v1', + kind: 'System', + metadata: { + name: 'target-name', + namespace: 'my-namespace', + }, + spec: {}, + }); + + const { getByText } = await renderInTestApp( + + + + + , + ); + + await waitFor(() => { + expect(getByText('Systems')).toBeInTheDocument(); + expect(getByText(/target-name/i)).toBeInTheDocument(); + }); + }); +}); diff --git a/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx b/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx new file mode 100644 index 0000000000..0af5f1747d --- /dev/null +++ b/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx @@ -0,0 +1,88 @@ +/* + * 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, SystemEntity } from '@backstage/catalog-model'; +import { + CodeSnippet, + InfoCard, + Link, + Progress, + WarningPanel, +} from '@backstage/core'; +import { + SystemsTable, + useEntity, + useRelatedEntities, +} from '@backstage/plugin-catalog-react'; +import React, { PropsWithChildren } from 'react'; + +const SystemsCard = ({ + children, + variant = 'gridItem', +}: PropsWithChildren<{ variant?: string }>) => { + return ( + + {children} + + ); +}; + +type Props = { + variant?: string; +}; + +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 add systems. + + + } + entities={entities as SystemEntity[]} + /> + ); +}; diff --git a/plugins/catalog/src/components/HasSystemsCard/index.ts b/plugins/catalog/src/components/HasSystemsCard/index.ts new file mode 100644 index 0000000000..a29d257e7e --- /dev/null +++ b/plugins/catalog/src/components/HasSystemsCard/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 { HasSystemsCard } from './HasSystemsCard'; diff --git a/plugins/catalog/src/index.ts b/plugins/catalog/src/index.ts index ce8518eb55..6d1704503e 100644 --- a/plugins/catalog/src/index.ts +++ b/plugins/catalog/src/index.ts @@ -20,10 +20,14 @@ export { EntityPageLayout } from './components/EntityPageLayout'; export * from './components/EntitySwitch'; export { Router } from './components/Router'; export { + CatalogEntityPage, + CatalogIndexPage, catalogPlugin, catalogPlugin as plugin, - CatalogIndexPage, - CatalogEntityPage, EntityAboutCard, - EntityLinksCard, + EntityHasComponentsCard, + EntityHasSubcomponentsCard, + EntityHasSystemsCard, + EntityLinksCard } from './plugin'; + diff --git a/plugins/catalog/src/plugin.ts b/plugins/catalog/src/plugin.ts index c2b910ce68..3b2a6f58f4 100644 --- a/plugins/catalog/src/plugin.ts +++ b/plugins/catalog/src/plugin.ts @@ -17,10 +17,10 @@ import { CatalogClient } from '@backstage/catalog-client'; import { createApiFactory, - createPlugin, - discoveryApiRef, createComponentExtension, + createPlugin, createRoutableExtension, + discoveryApiRef, identityApiRef, } from '@backstage/core'; import { @@ -60,9 +60,7 @@ export const CatalogIndexPage = catalogPlugin.provide( export const CatalogEntityPage = catalogPlugin.provide( createRoutableExtension({ component: () => - import('./components/CatalogEntityPage/CatalogEntityPage').then( - m => m.CatalogEntityPage, - ), + import('./components/CatalogEntityPage').then(m => m.CatalogEntityPage), mountPoint: entityRouteRef, }), ); @@ -83,3 +81,32 @@ export const EntityLinksCard = catalogPlugin.provide( }, }), ); + +export const EntityHasSystemsCard = catalogPlugin.provide( + createComponentExtension({ + component: { + lazy: () => + import('./components/HasSystemsCard').then(m => m.HasSystemsCard), + }, + }), +); + +export const EntityHasComponentsCard = catalogPlugin.provide( + createComponentExtension({ + component: { + lazy: () => + import('./components/ComponentsCard').then(m => m.HasComponentsCard), + }, + }), +); + +export const EntityHasSubcomponentsCard = catalogPlugin.provide( + createComponentExtension({ + component: { + lazy: () => + import('./components/HasSubcomponentsCard').then( + m => m.HasSubcomponentsCard, + ), + }, + }), +); From 94d5db1e65961c297433df8f1268cda2b921b660 Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Thu, 4 Feb 2021 18:00:10 +0100 Subject: [PATCH 04/17] Add subcomponent to vocab --- .github/styles/vocab.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/styles/vocab.txt b/.github/styles/vocab.txt index 519216ecd8..dcac36b68d 100644 --- a/.github/styles/vocab.txt +++ b/.github/styles/vocab.txt @@ -221,6 +221,8 @@ squidfunk src stdout stefanalund +subcomponent +subcomponents subkey subtree superfences From c7e85614c72430ef9852dfb5cd37435d12d5f77d Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Wed, 10 Feb 2021 17:39:38 +0100 Subject: [PATCH 05/17] Update changelog --- .changeset/pink-coins-sniff.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/pink-coins-sniff.md b/.changeset/pink-coins-sniff.md index dd2bc86b2a..83fa280475 100644 --- a/.changeset/pink-coins-sniff.md +++ b/.changeset/pink-coins-sniff.md @@ -11,6 +11,6 @@ Introduce new cards to `@backstage/plugin-catalog` that can be added to entity p - `EntityHasSubcomponentsCard` to display subcomponents of a subcomponent. - In addition, `EntityHasApisCard` to display APIs of a system is added to `@backstage/plugin-api-docs`. -`@backstage/plugin-catalog-react` now provides `and` to build own cards for components and systems. +`@backstage/plugin-catalog-react` now provides `ComponentsTable` and `SystemsTable` to build own cards for components and systems. The styling of the tables and new cards was also applied to the existing `EntityConsumedApisCard`, `EntityConsumingComponentsCard`, `EntityProvidedApisCard`, and `EntityProvidingComponentsCard`. From 6141ec38fb39f06dd5126e82857dc5ad8d5276ae Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Thu, 11 Feb 2021 13:23:52 +0100 Subject: [PATCH 06/17] Add a EntityTable --- .../components/EntityTable/EntityTable.tsx | 79 +++++++++++ .../src/components/EntityTable/columns.tsx | 128 ++++++++++++++++++ .../src/components/EntityTable/index.ts | 16 +++ .../src/components/EntityTable/presets.tsx | 31 +++++ plugins/catalog-react/src/components/index.ts | 1 + .../HasSystemsCard/HasSystemsCard.tsx | 5 +- 6 files changed, 258 insertions(+), 2 deletions(-) create mode 100644 plugins/catalog-react/src/components/EntityTable/EntityTable.tsx create mode 100644 plugins/catalog-react/src/components/EntityTable/columns.tsx create mode 100644 plugins/catalog-react/src/components/EntityTable/index.ts create mode 100644 plugins/catalog-react/src/components/EntityTable/presets.tsx diff --git a/plugins/catalog-react/src/components/EntityTable/EntityTable.tsx b/plugins/catalog-react/src/components/EntityTable/EntityTable.tsx new file mode 100644 index 0000000000..a96f10fe2b --- /dev/null +++ b/plugins/catalog-react/src/components/EntityTable/EntityTable.tsx @@ -0,0 +1,79 @@ +/* + * 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 { Table, TableColumn } from '@backstage/core'; +import { makeStyles } from '@material-ui/core'; +import React from 'react'; +import * as columnFactories from './columns'; +import { systemEntityColumns } from './presets'; + +type Props = { + title: string; + variant?: string; + entities: T[]; + emptyComponent?: JSX.Element; + columns: TableColumn[]; +}; + +const useStyles = makeStyles(theme => ({ + empty: { + padding: theme.spacing(2), + display: 'flex', + justifyContent: 'center', + }, +})); + +export function EntityTable({ + entities, + title, + emptyComponent, + variant = 'gridItem', + columns, +}: Props) { + const classes = useStyles(); + const tableStyle: React.CSSProperties = { + minWidth: '0', + width: '100%', + }; + + if (variant === 'gridItem') { + tableStyle.height = 'calc(100% - 10px)'; + } + + return ( + + columns={columns} + title={title} + style={tableStyle} + emptyComponent={ + emptyComponent &&
{emptyComponent}
+ } + options={{ + // TODO: Toolbar padding if off compared to other cards, should be: padding: 16px 24px; + search: false, + paging: false, + actionsColumnIndex: -1, + padding: 'dense', + }} + data={entities} + /> + ); +} + +EntityTable.columns = columnFactories; + +EntityTable.systemEntityColumns = systemEntityColumns; diff --git a/plugins/catalog-react/src/components/EntityTable/columns.tsx b/plugins/catalog-react/src/components/EntityTable/columns.tsx new file mode 100644 index 0000000000..011ba66d59 --- /dev/null +++ b/plugins/catalog-react/src/components/EntityTable/columns.tsx @@ -0,0 +1,128 @@ +/* + * 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_OWNED_BY, + RELATION_PART_OF, +} from '@backstage/catalog-model'; +import { TableColumn } from '@backstage/core'; +import React from 'react'; +import { getEntityRelations } from '../../utils'; +import { + EntityRefLink, + EntityRefLinks, + formatEntityRefTitle, +} from '../EntityRefLink'; + +export function createEntityRefColumn({ + defaultKind, +}: { + defaultKind?: string; +}): TableColumn { + function formatContent(entity: T): string { + return formatEntityRefTitle(entity, { + defaultKind, + }); + } + + return { + title: 'Name', + highlight: true, + customFilterAndSearch(filter, entity) { + // TODO: We could implement this more efficiently, like searching over each field individually, but that migth confuse the user + return formatContent(entity).includes(filter); + }, + customSort(entity1, entity2) { + // TODO: We could implement this more efficiently by comparing field by field. + return formatContent(entity1).localeCompare(formatContent(entity2)); + }, + render: entity => ( + + ), + }; +} + +export function createOwnerColumn(): TableColumn { + function formatContent(entity: T): string { + const ownedByRelations = getEntityRelations(entity, RELATION_OWNED_BY); + return ownedByRelations + .map(r => formatEntityRefTitle(r, { defaultKind: 'group' })) + .join(', '); + } + + return { + title: 'Owner', + customFilterAndSearch(filter, entity) { + return formatContent(entity).includes(filter); + }, + customSort(entity1, entity2) { + return formatContent(entity1).localeCompare(formatContent(entity2)); + }, + render: entity => { + const ownedByRelations = getEntityRelations(entity, RELATION_OWNED_BY); + return ( + + ); + }, + }; +} + +export function createDomainColumn(): TableColumn { + function formatContent(entity: T): string { + const partOfDomainRelations = getEntityRelations(entity, RELATION_PART_OF, { + kind: 'domain', + }); + return partOfDomainRelations + .map(r => formatEntityRefTitle(r, { defaultKind: 'domain' })) + .join(', '); + } + + return { + title: 'Domain', + customFilterAndSearch(filter, entity) { + return formatContent(entity).includes(filter); + }, + customSort(entity1, entity2) { + return formatContent(entity1).localeCompare(formatContent(entity2)); + }, + render: entity => { + const partOfDomainRelations = getEntityRelations( + entity, + RELATION_PART_OF, + { + kind: 'domain', + }, + ); + return ( + + ); + }, + }; +} + +export function createMetadataDescriptionColumn< + T extends Entity +>(): TableColumn { + return { + title: 'Description', + field: 'metadata.description', + width: 'auto', + }; +} diff --git a/plugins/catalog-react/src/components/EntityTable/index.ts b/plugins/catalog-react/src/components/EntityTable/index.ts new file mode 100644 index 0000000000..8a01b9baae --- /dev/null +++ b/plugins/catalog-react/src/components/EntityTable/index.ts @@ -0,0 +1,16 @@ +/* + * 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 { EntityTable } from './EntityTable'; diff --git a/plugins/catalog-react/src/components/EntityTable/presets.tsx b/plugins/catalog-react/src/components/EntityTable/presets.tsx new file mode 100644 index 0000000000..e898185e5e --- /dev/null +++ b/plugins/catalog-react/src/components/EntityTable/presets.tsx @@ -0,0 +1,31 @@ +/* + * 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 { SystemEntity } from '@backstage/catalog-model'; +import { TableColumn } from '@backstage/core'; +import { + createEntityRefColumn, + createMetadataDescriptionColumn, + createOwnerColumn, + createDomainColumn, +} from './columns'; + +export const systemEntityColumns: TableColumn[] = [ + createEntityRefColumn({ defaultKind: 'system' }), + createOwnerColumn(), + createDomainColumn(), + createMetadataDescriptionColumn(), +]; diff --git a/plugins/catalog-react/src/components/index.ts b/plugins/catalog-react/src/components/index.ts index 463aeb6250..8508935dd6 100644 --- a/plugins/catalog-react/src/components/index.ts +++ b/plugins/catalog-react/src/components/index.ts @@ -15,4 +15,5 @@ */ export * from './EntityProvider'; export * from './EntityRefLink'; +export * from './EntityTable'; export * from './EntityTables'; diff --git a/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx b/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx index 0af5f1747d..339f83c624 100644 --- a/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx +++ b/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx @@ -23,7 +23,7 @@ import { WarningPanel, } from '@backstage/core'; import { - SystemsTable, + EntityTable, useEntity, useRelatedEntities, } from '@backstage/plugin-catalog-react'; @@ -71,7 +71,7 @@ export const HasSystemsCard = ({ variant = 'gridItem' }: Props) => { } return ( - { } + columns={EntityTable.systemEntityColumns} entities={entities as SystemEntity[]} /> ); From bd27885876bced924f27074afe8c864912b69580 Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Fri, 12 Feb 2021 10:40:42 +0100 Subject: [PATCH 07/17] Use EntityTable everywhere --- .changeset/pink-coins-sniff.md | 2 +- .../src/components/ApisCards/ApisTable.tsx | 167 ------------------ .../components/ApisCards/ConsumedApisCard.tsx | 11 +- .../src/components/ApisCards/HasApisCard.tsx | 11 +- .../components/ApisCards/ProvidedApisCard.tsx | 11 +- .../src/components/ApisCards/presets.tsx | 42 +++++ .../ConsumingComponentsCard.tsx | 6 +- .../ProvidingComponentsCard.tsx | 5 +- .../EntityTable.test.tsx} | 58 +++--- .../components/EntityTable/EntityTable.tsx | 4 +- .../src/components/EntityTable/columns.tsx | 106 +++++++---- .../presets.test.tsx} | 62 +++++-- .../src/components/EntityTable/presets.tsx | 18 +- .../EntityTables/ComponentsTable.tsx | 165 ----------------- .../components/EntityTables/SystemsTable.tsx | 157 ---------------- .../src/components/EntityTables/index.ts | 17 -- plugins/catalog-react/src/components/index.ts | 1 - .../ComponentsCard/HasComponentsCard.tsx | 5 +- .../HasSubcomponentsCard.tsx | 5 +- 19 files changed, 237 insertions(+), 616 deletions(-) delete mode 100644 plugins/api-docs/src/components/ApisCards/ApisTable.tsx create mode 100644 plugins/api-docs/src/components/ApisCards/presets.tsx rename plugins/catalog-react/src/components/{EntityTables/SystemsTable.test.tsx => EntityTable/EntityTable.test.tsx} (53%) rename plugins/catalog-react/src/components/{EntityTables/ComponentsTable.test.tsx => EntityTable/presets.test.tsx} (61%) delete mode 100644 plugins/catalog-react/src/components/EntityTables/ComponentsTable.tsx delete mode 100644 plugins/catalog-react/src/components/EntityTables/SystemsTable.tsx delete mode 100644 plugins/catalog-react/src/components/EntityTables/index.ts diff --git a/.changeset/pink-coins-sniff.md b/.changeset/pink-coins-sniff.md index 83fa280475..b9714f116a 100644 --- a/.changeset/pink-coins-sniff.md +++ b/.changeset/pink-coins-sniff.md @@ -11,6 +11,6 @@ Introduce new cards to `@backstage/plugin-catalog` that can be added to entity p - `EntityHasSubcomponentsCard` to display subcomponents of a subcomponent. - In addition, `EntityHasApisCard` to display APIs of a system is added to `@backstage/plugin-api-docs`. -`@backstage/plugin-catalog-react` now provides `ComponentsTable` and `SystemsTable` to build own cards for components and systems. +`@backstage/plugin-catalog-react` now provides an `EntityTable` to build own cards for entities. The styling of the tables and new cards was also applied to the existing `EntityConsumedApisCard`, `EntityConsumingComponentsCard`, `EntityProvidedApisCard`, and `EntityProvidingComponentsCard`. diff --git a/plugins/api-docs/src/components/ApisCards/ApisTable.tsx b/plugins/api-docs/src/components/ApisCards/ApisTable.tsx deleted file mode 100644 index 76daf15945..0000000000 --- a/plugins/api-docs/src/components/ApisCards/ApisTable.tsx +++ /dev/null @@ -1,167 +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 { - 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 { makeStyles } from '@material-ui/core'; -import React from 'react'; -import { ApiTypeTitle } from '../ApiDefinitionCard'; - -type EntityRow = { - entity: ApiEntity; - resolved: { - name: string; - partOfSystemRelationTitle?: string; - partOfSystemRelations: EntityName[]; - ownedByRelationsTitle?: string; - ownedByRelations: EntityName[]; - }; -}; - -const columns: TableColumn[] = [ - { - title: 'Name', - field: 'resolved.name', - highlight: true, - render: ({ entity }) => ( - - ), - }, - { - title: 'System', - field: 'resolved.partOfSystemRelationTitle', - render: ({ resolved }) => ( - - ), - }, - { - title: 'Owner', - field: 'resolved.ownedByRelationsTitle', - render: ({ resolved }) => ( - - ), - }, - { - title: 'Lifecycle', - field: 'entity.spec.lifecycle', - }, - { - title: 'Type', - field: 'entity.spec.type', - render: ({ entity }) => , - }, - { - title: 'Description', - field: 'entity.metadata.description', - width: 'auto', - }, -]; - -type Props = { - title: string; - variant?: string; - entities: ApiEntity[]; - emptyComponent?: JSX.Element; -}; - -const useStyles = makeStyles(theme => ({ - empty: { - padding: theme.spacing(2), - display: 'flex', - justifyContent: 'center', - }, -})); - -export const ApisTable = ({ - entities, - title, - emptyComponent, - variant = 'gridItem', -}: Props) => { - const classes = useStyles(); - const tableStyle: React.CSSProperties = { - minWidth: '0', - width: '100%', - }; - - if (variant === 'gridItem') { - tableStyle.height = 'calc(100% - 10px)'; - } - - const rows = entities.map(entity => { - const partOfSystemRelations = getEntityRelations(entity, RELATION_PART_OF, { - kind: 'system', - }); - const ownedByRelations = getEntityRelations(entity, RELATION_OWNED_BY); - - return { - entity, - resolved: { - name: formatEntityRefTitle(entity, { - defaultKind: 'API', - }), - ownedByRelationsTitle: ownedByRelations - .map(r => formatEntityRefTitle(r, { defaultKind: 'group' })) - .join(', '), - ownedByRelations, - partOfSystemRelationTitle: partOfSystemRelations - .map(r => - formatEntityRefTitle(r, { - defaultKind: 'system', - }), - ) - .join(', '), - partOfSystemRelations, - }, - }; - }); - - return ( - - columns={columns} - title={title} - style={tableStyle} - emptyComponent={ - emptyComponent &&
{emptyComponent}
- } - options={{ - // TODO: Toolbar padding if off compared to other cards, should be: padding: 16px 24px; - search: false, - paging: false, - actionsColumnIndex: -1, - padding: 'dense', - }} - data={rows} - /> - ); -}; diff --git a/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.tsx b/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.tsx index 7e57a23b06..922a133fde 100644 --- a/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.tsx +++ b/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.tsx @@ -26,9 +26,13 @@ import { Progress, WarningPanel, } from '@backstage/core'; -import { useEntity, useRelatedEntities } from '@backstage/plugin-catalog-react'; +import { + EntityTable, + useEntity, + useRelatedEntities, +} from '@backstage/plugin-catalog-react'; import React, { PropsWithChildren } from 'react'; -import { ApisTable } from './ApisTable'; +import { apiEntityColumns } from './presets'; const ApisCard = ({ children, @@ -74,7 +78,7 @@ export const ConsumedApisCard = ({ variant = 'gridItem' }: Props) => { } return ( - { } + columns={apiEntityColumns} entities={entities as ApiEntity[]} /> ); diff --git a/plugins/api-docs/src/components/ApisCards/HasApisCard.tsx b/plugins/api-docs/src/components/ApisCards/HasApisCard.tsx index 5ed38e91c7..a0e1dce052 100644 --- a/plugins/api-docs/src/components/ApisCards/HasApisCard.tsx +++ b/plugins/api-docs/src/components/ApisCards/HasApisCard.tsx @@ -22,9 +22,13 @@ import { Progress, WarningPanel, } from '@backstage/core'; -import { useEntity, useRelatedEntities } from '@backstage/plugin-catalog-react'; +import { + EntityTable, + useEntity, + useRelatedEntities, +} from '@backstage/plugin-catalog-react'; import React, { PropsWithChildren } from 'react'; -import { ApisTable } from './ApisTable'; +import { apiEntityColumns } from './presets'; const ApisCard = ({ children, @@ -69,7 +73,7 @@ export const HasApisCard = ({ variant = 'gridItem' }: Props) => { } return ( - { } + columns={apiEntityColumns} entities={entities as ApiEntity[]} /> ); diff --git a/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.tsx b/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.tsx index e760d7a8f0..191eb0465c 100644 --- a/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.tsx +++ b/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.tsx @@ -26,9 +26,13 @@ import { Progress, WarningPanel, } from '@backstage/core'; -import { useEntity, useRelatedEntities } from '@backstage/plugin-catalog-react'; +import { + EntityTable, + useEntity, + useRelatedEntities, +} from '@backstage/plugin-catalog-react'; import React, { PropsWithChildren } from 'react'; -import { ApisTable } from './ApisTable'; +import { apiEntityColumns } from './presets'; const ApisCard = ({ children, @@ -74,7 +78,7 @@ export const ProvidedApisCard = ({ variant = 'gridItem' }: Props) => { } return ( - { } + columns={apiEntityColumns} entities={entities as ApiEntity[]} /> ); diff --git a/plugins/api-docs/src/components/ApisCards/presets.tsx b/plugins/api-docs/src/components/ApisCards/presets.tsx new file mode 100644 index 0000000000..c7fc4420df --- /dev/null +++ b/plugins/api-docs/src/components/ApisCards/presets.tsx @@ -0,0 +1,42 @@ +/* + * 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 { ApiEntity } from '@backstage/catalog-model'; +import { TableColumn } from '@backstage/core'; +import { EntityTable } from '@backstage/plugin-catalog-react'; +import React from 'react'; +import { ApiTypeTitle } from '../ApiDefinitionCard'; + +export function createSpecApiTypeColumn(): TableColumn { + return { + title: 'Type', + field: 'spec.type', + render: entity => , + }; +} + +// TODO: This could be moved to plugin-catalog-react if we wouldn't have a +// special createSpecApiTypeColumn. But this is requited to use ApiTypeTitle to +// resolve the display name of an entity. Is the display name really worth it? + +export const apiEntityColumns: TableColumn[] = [ + EntityTable.columns.createEntityRefColumn({ defaultKind: 'API' }), + EntityTable.columns.createSystemColumn(), + EntityTable.columns.createOwnerColumn(), + EntityTable.columns.createSpecLifecycleColumn(), + createSpecApiTypeColumn(), + EntityTable.columns.createMetadataDescriptionColumn(), +]; diff --git a/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.tsx b/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.tsx index ed987b33f5..a35c10b5cc 100644 --- a/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.tsx +++ b/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.tsx @@ -27,11 +27,10 @@ import { WarningPanel, } from '@backstage/core'; import { - ComponentsTable, + EntityTable, useEntity, useRelatedEntities, } from '@backstage/plugin-catalog-react'; - import React, { PropsWithChildren } from 'react'; const ComponentsCard = ({ @@ -78,7 +77,7 @@ export const ConsumingComponentsCard = ({ variant = 'gridItem' }: Props) => { } return ( - { } + columns={EntityTable.componentEntityColumns} entities={entities as ComponentEntity[]} /> ); diff --git a/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.tsx b/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.tsx index 39e179dd10..04239085f0 100644 --- a/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.tsx +++ b/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.tsx @@ -27,7 +27,7 @@ import { WarningPanel, } from '@backstage/core'; import { - ComponentsTable, + EntityTable, useEntity, useRelatedEntities, } from '@backstage/plugin-catalog-react'; @@ -77,7 +77,7 @@ export const ProvidingComponentsCard = ({ variant = 'gridItem' }: Props) => { } return ( - { } + columns={EntityTable.componentEntityColumns} entities={entities as ComponentEntity[]} /> ); diff --git a/plugins/catalog-react/src/components/EntityTables/SystemsTable.test.tsx b/plugins/catalog-react/src/components/EntityTable/EntityTable.test.tsx similarity index 53% rename from plugins/catalog-react/src/components/EntityTables/SystemsTable.test.tsx rename to plugins/catalog-react/src/components/EntityTable/EntityTable.test.tsx index 9f89d7592e..96f9b4cdbd 100644 --- a/plugins/catalog-react/src/components/EntityTables/SystemsTable.test.tsx +++ b/plugins/catalog-react/src/components/EntityTable/EntityTable.test.tsx @@ -15,6 +15,7 @@ */ import { + Entity, RELATION_OWNED_BY, RELATION_PART_OF, SystemEntity, @@ -22,70 +23,51 @@ import { import { renderInTestApp } from '@backstage/test-utils'; import { waitFor } from '@testing-library/react'; import React from 'react'; -import { SystemsTable } from './SystemsTable'; +import { EntityTable } from './EntityTable'; -describe('', () => { +describe('', () => { it('shows empty table', async () => { const { getByText } = await renderInTestApp( - EMPTY} + columns={[]} />, ); - expect(getByText('My Systems')).toBeInTheDocument(); + expect(getByText('Entities')).toBeInTheDocument(); expect(getByText('EMPTY')).toBeInTheDocument(); }); - it('shows systems', async () => { - const entities: SystemEntity[] = [ + it('shows entities', async () => { + const entities: Entity[] = [ { apiVersion: 'backstage.io/v1alpha1', kind: 'System', metadata: { - name: 'my-system', - namespace: 'my-namespace', - description: 'Some description', + name: 'my-entity', }, - spec: { - owner: 'owner-data', - }, - relations: [ - { - type: RELATION_PART_OF, - target: { - kind: 'Domain', - name: 'my-domain', - namespace: 'my-namespace', - }, - }, - { - type: RELATION_OWNED_BY, - target: { - kind: 'Group', - name: 'Test', - namespace: 'default', - }, - }, - ], + spec: {}, }, ]; const { getByText } = await renderInTestApp( - EMPTY} + columns={[ + { + title: 'Name', + field: 'metadata.name', + }, + ]} />, ); await waitFor(() => { - expect(getByText('My Systems')).toBeInTheDocument(); - expect(getByText('my-namespace/my-system')).toBeInTheDocument(); - expect(getByText('my-namespace/my-domain')).toBeInTheDocument(); - expect(getByText('Test')).toBeInTheDocument(); - expect(getByText('Some description')).toBeInTheDocument(); + expect(getByText('my-entity')).toBeInTheDocument(); }); }); }); diff --git a/plugins/catalog-react/src/components/EntityTable/EntityTable.tsx b/plugins/catalog-react/src/components/EntityTable/EntityTable.tsx index a96f10fe2b..18de1ec9ab 100644 --- a/plugins/catalog-react/src/components/EntityTable/EntityTable.tsx +++ b/plugins/catalog-react/src/components/EntityTable/EntityTable.tsx @@ -19,7 +19,7 @@ import { Table, TableColumn } from '@backstage/core'; import { makeStyles } from '@material-ui/core'; import React from 'react'; import * as columnFactories from './columns'; -import { systemEntityColumns } from './presets'; +import { componentEntityColumns, systemEntityColumns } from './presets'; type Props = { title: string; @@ -77,3 +77,5 @@ export function EntityTable({ EntityTable.columns = columnFactories; EntityTable.systemEntityColumns = systemEntityColumns; + +EntityTable.componentEntityColumns = componentEntityColumns; diff --git a/plugins/catalog-react/src/components/EntityTable/columns.tsx b/plugins/catalog-react/src/components/EntityTable/columns.tsx index 011ba66d59..01ed288c13 100644 --- a/plugins/catalog-react/src/components/EntityTable/columns.tsx +++ b/plugins/catalog-react/src/components/EntityTable/columns.tsx @@ -16,6 +16,7 @@ import { Entity, + EntityName, RELATION_OWNED_BY, RELATION_PART_OF, } from '@backstage/catalog-model'; @@ -43,11 +44,17 @@ export function createEntityRefColumn({ title: 'Name', highlight: true, customFilterAndSearch(filter, entity) { - // TODO: We could implement this more efficiently, like searching over each field individually, but that migth confuse the user + // TODO: We could implement this more efficiently, like searching over + // each field that is displayed individually (kind, namespace, name). + // but that migth confuse the user as it will behave different than a + // simple text search. + // Another altnerative would be to cache the values. But writing them + // into the entity feels bad too. return formatContent(entity).includes(filter); }, customSort(entity1, entity2) { // TODO: We could implement this more efficiently by comparing field by field. + // This has similar issues as above. return formatContent(entity1).localeCompare(formatContent(entity2)); }, render: entity => ( @@ -56,16 +63,29 @@ export function createEntityRefColumn({ }; } -export function createOwnerColumn(): TableColumn { +export function createEntityRelationColumn({ + title, + relation, + defaultKind, + filter: entityFilter, +}: { + title: string; + relation: string; + defaultKind?: string; + filter?: { kind: string }; +}): TableColumn { + function getRelations(entity: T): EntityName[] { + return getEntityRelations(entity, relation, entityFilter); + } + function formatContent(entity: T): string { - const ownedByRelations = getEntityRelations(entity, RELATION_OWNED_BY); - return ownedByRelations - .map(r => formatEntityRefTitle(r, { defaultKind: 'group' })) + return getRelations(entity) + .map(r => formatEntityRefTitle(r, { defaultKind })) .join(', '); } return { - title: 'Owner', + title, customFilterAndSearch(filter, entity) { return formatContent(entity).includes(filter); }, @@ -73,48 +93,44 @@ export function createOwnerColumn(): TableColumn { return formatContent(entity1).localeCompare(formatContent(entity2)); }, render: entity => { - const ownedByRelations = getEntityRelations(entity, RELATION_OWNED_BY); return ( - + ); }, }; } -export function createDomainColumn(): TableColumn { - function formatContent(entity: T): string { - const partOfDomainRelations = getEntityRelations(entity, RELATION_PART_OF, { - kind: 'domain', - }); - return partOfDomainRelations - .map(r => formatEntityRefTitle(r, { defaultKind: 'domain' })) - .join(', '); - } +export function createOwnerColumn(): TableColumn { + return createEntityRelationColumn({ + title: 'Owner', + relation: RELATION_OWNED_BY, + defaultKind: 'group', + }); +} - return { +export function createDomainColumn(): TableColumn { + return createEntityRelationColumn({ title: 'Domain', - customFilterAndSearch(filter, entity) { - return formatContent(entity).includes(filter); + relation: RELATION_PART_OF, + defaultKind: 'domain', + filter: { + kind: 'domain', }, - customSort(entity1, entity2) { - return formatContent(entity1).localeCompare(formatContent(entity2)); + }); +} + +export function createSystemColumn(): TableColumn { + return createEntityRelationColumn({ + title: 'System', + relation: RELATION_PART_OF, + defaultKind: 'system', + filter: { + kind: 'system', }, - render: entity => { - const partOfDomainRelations = getEntityRelations( - entity, - RELATION_PART_OF, - { - kind: 'domain', - }, - ); - return ( - - ); - }, - }; + }); } export function createMetadataDescriptionColumn< @@ -126,3 +142,17 @@ export function createMetadataDescriptionColumn< width: 'auto', }; } + +export function createSpecLifecycleColumn(): TableColumn { + return { + title: 'Lifecycle', + field: 'spec.lifecycle', + }; +} + +export function createSpecTypeColumn(): TableColumn { + return { + title: 'Type', + field: 'spec.type', + }; +} diff --git a/plugins/catalog-react/src/components/EntityTables/ComponentsTable.test.tsx b/plugins/catalog-react/src/components/EntityTable/presets.test.tsx similarity index 61% rename from plugins/catalog-react/src/components/EntityTables/ComponentsTable.test.tsx rename to plugins/catalog-react/src/components/EntityTable/presets.test.tsx index f29d1cfe60..3edce87dac 100644 --- a/plugins/catalog-react/src/components/EntityTables/ComponentsTable.test.tsx +++ b/plugins/catalog-react/src/components/EntityTable/presets.test.tsx @@ -18,26 +18,68 @@ import { ComponentEntity, RELATION_OWNED_BY, RELATION_PART_OF, + SystemEntity, } from '@backstage/catalog-model'; import { renderInTestApp } from '@backstage/test-utils'; import { waitFor } from '@testing-library/react'; import React from 'react'; -import { ComponentsTable } from './ComponentsTable'; +import { EntityTable } from './EntityTable'; +import { componentEntityColumns, systemEntityColumns } from './presets'; + +describe('systemEntityColumns', () => { + it('shows systems', async () => { + const entities: SystemEntity[] = [ + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'System', + metadata: { + name: 'my-system', + namespace: 'my-namespace', + description: 'Some description', + }, + spec: { + owner: 'owner-data', + }, + relations: [ + { + type: RELATION_PART_OF, + target: { + kind: 'Domain', + name: 'my-domain', + namespace: 'my-namespace', + }, + }, + { + type: RELATION_OWNED_BY, + target: { + kind: 'Group', + name: 'Test', + namespace: 'default', + }, + }, + ], + }, + ]; -describe('', () => { - it('shows empty table', async () => { const { getByText } = await renderInTestApp( - EMPTY} + columns={systemEntityColumns} />, ); - expect(getByText('My Components')).toBeInTheDocument(); - expect(getByText('EMPTY')).toBeInTheDocument(); + await waitFor(() => { + expect(getByText('my-namespace/my-system')).toBeInTheDocument(); + expect(getByText('my-namespace/my-domain')).toBeInTheDocument(); + expect(getByText('Test')).toBeInTheDocument(); + expect(getByText('Some description')).toBeInTheDocument(); + }); }); +}); +describe('componentEntityColumns', () => { it('shows components', async () => { const entities: ComponentEntity[] = [ { @@ -75,15 +117,15 @@ describe('', () => { ]; const { getByText } = await renderInTestApp( - EMPTY} + columns={componentEntityColumns} />, ); await waitFor(() => { - expect(getByText('My Components')).toBeInTheDocument(); expect(getByText('my-namespace/my-component')).toBeInTheDocument(); expect(getByText('my-namespace/my-system')).toBeInTheDocument(); expect(getByText('Test')).toBeInTheDocument(); diff --git a/plugins/catalog-react/src/components/EntityTable/presets.tsx b/plugins/catalog-react/src/components/EntityTable/presets.tsx index e898185e5e..7ce6b75532 100644 --- a/plugins/catalog-react/src/components/EntityTable/presets.tsx +++ b/plugins/catalog-react/src/components/EntityTable/presets.tsx @@ -14,18 +14,30 @@ * limitations under the License. */ -import { SystemEntity } from '@backstage/catalog-model'; +import { ComponentEntity, SystemEntity } from '@backstage/catalog-model'; import { TableColumn } from '@backstage/core'; import { + createDomainColumn, createEntityRefColumn, createMetadataDescriptionColumn, createOwnerColumn, - createDomainColumn, + createSpecLifecycleColumn, + createSpecTypeColumn, + createSystemColumn, } from './columns'; export const systemEntityColumns: TableColumn[] = [ createEntityRefColumn({ defaultKind: 'system' }), - createOwnerColumn(), createDomainColumn(), + createOwnerColumn(), + createMetadataDescriptionColumn(), +]; + +export const componentEntityColumns: TableColumn[] = [ + createEntityRefColumn({ defaultKind: 'component' }), + createSystemColumn(), + createOwnerColumn(), + createSpecTypeColumn(), + createSpecLifecycleColumn(), createMetadataDescriptionColumn(), ]; diff --git a/plugins/catalog-react/src/components/EntityTables/ComponentsTable.tsx b/plugins/catalog-react/src/components/EntityTables/ComponentsTable.tsx deleted file mode 100644 index b79c7e705a..0000000000 --- a/plugins/catalog-react/src/components/EntityTables/ComponentsTable.tsx +++ /dev/null @@ -1,165 +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 { - ComponentEntity, - EntityName, - RELATION_OWNED_BY, - RELATION_PART_OF, -} from '@backstage/catalog-model'; -import { Table, TableColumn } from '@backstage/core'; -import { makeStyles } from '@material-ui/core'; -import React from 'react'; -import { getEntityRelations } from '../../utils'; -import { - EntityRefLink, - EntityRefLinks, - formatEntityRefTitle, -} from '../EntityRefLink'; - -type EntityRow = { - entity: ComponentEntity; - resolved: { - name: string; - partOfSystemRelationTitle?: string; - partOfSystemRelations: EntityName[]; - ownedByRelationsTitle?: string; - ownedByRelations: EntityName[]; - }; -}; - -const columns: TableColumn[] = [ - { - title: 'Name', - field: 'resolved.name', - highlight: true, - render: ({ entity }) => ( - - ), - }, - { - title: 'System', - field: 'resolved.partOfSystemRelationTitle', - render: ({ resolved }) => ( - - ), - }, - { - title: 'Owner', - field: 'resolved.ownedByRelationsTitle', - render: ({ resolved }) => ( - - ), - }, - { - title: 'Lifecycle', - field: 'entity.spec.lifecycle', - }, - { - title: 'Type', - field: 'entity.spec.type', - }, - { - title: 'Description', - field: 'entity.metadata.description', - width: 'auto', - }, -]; - -type Props = { - title: string; - variant?: string; - entities: ComponentEntity[]; - emptyComponent?: JSX.Element; -}; - -const useStyles = makeStyles(theme => ({ - empty: { - padding: theme.spacing(2), - display: 'flex', - justifyContent: 'center', - }, -})); - -export const ComponentsTable = ({ - entities, - title, - emptyComponent, - variant = 'gridItem', -}: Props) => { - const classes = useStyles(); - const tableStyle: React.CSSProperties = { - minWidth: '0', - width: '100%', - }; - - if (variant === 'gridItem') { - tableStyle.height = 'calc(100% - 10px)'; - } - - const rows = entities.map(entity => { - const partOfSystemRelations = getEntityRelations(entity, RELATION_PART_OF, { - kind: 'system', - }); - const ownedByRelations = getEntityRelations(entity, RELATION_OWNED_BY); - - return { - entity: entity, - resolved: { - name: formatEntityRefTitle(entity, { - defaultKind: 'Component', - }), - ownedByRelationsTitle: ownedByRelations - .map(r => formatEntityRefTitle(r, { defaultKind: 'group' })) - .join(', '), - ownedByRelations, - partOfSystemRelationTitle: partOfSystemRelations - .map(r => - formatEntityRefTitle(r, { - defaultKind: 'system', - }), - ) - .join(', '), - partOfSystemRelations, - }, - }; - }); - - return ( - - columns={columns} - title={title} - style={tableStyle} - emptyComponent={ - emptyComponent &&
{emptyComponent}
- } - options={{ - // TODO: Toolbar padding if off compared to other cards, should be: padding: 16px 24px; - search: false, - paging: false, - actionsColumnIndex: -1, - padding: 'dense', - }} - data={rows} - /> - ); -}; diff --git a/plugins/catalog-react/src/components/EntityTables/SystemsTable.tsx b/plugins/catalog-react/src/components/EntityTables/SystemsTable.tsx deleted file mode 100644 index 8f30ea0543..0000000000 --- a/plugins/catalog-react/src/components/EntityTables/SystemsTable.tsx +++ /dev/null @@ -1,157 +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 { - EntityName, - RELATION_OWNED_BY, - RELATION_PART_OF, - SystemEntity, -} from '@backstage/catalog-model'; -import { Table, TableColumn } from '@backstage/core'; -import { makeStyles } from '@material-ui/core'; -import React from 'react'; -import { getEntityRelations } from '../../utils'; -import { - EntityRefLink, - EntityRefLinks, - formatEntityRefTitle, -} from '../EntityRefLink'; - -type EntityRow = { - entity: SystemEntity; - resolved: { - name: string; - partOfDomainRelationTitle?: string; - partOfDomainRelations: EntityName[]; - ownedByRelationsTitle?: string; - ownedByRelations: EntityName[]; - }; -}; - -const columns: TableColumn[] = [ - { - title: 'Name', - field: 'resolved.name', - highlight: true, - render: ({ entity }) => ( - - ), - }, - { - title: 'Domain', - field: 'resolved.partOfDomainRelationTitle', - render: ({ resolved }) => ( - - ), - }, - { - title: 'Owner', - field: 'resolved.ownedByRelationsTitle', - render: ({ resolved }) => ( - - ), - }, - { - title: 'Description', - field: 'entity.metadata.description', - width: 'auto', - }, -]; - -type Props = { - title: string; - variant?: string; - entities: SystemEntity[]; - emptyComponent?: JSX.Element; -}; - -const useStyles = makeStyles(theme => ({ - empty: { - padding: theme.spacing(2), - display: 'flex', - justifyContent: 'center', - }, -})); - -export const SystemsTable = ({ - entities, - title, - emptyComponent, - variant = 'gridItem', -}: Props) => { - const classes = useStyles(); - const tableStyle: React.CSSProperties = { - minWidth: '0', - width: '100%', - }; - - if (variant === 'gridItem') { - tableStyle.height = 'calc(100% - 10px)'; - } - - const rows = entities.map(entity => { - const partOfDomainRelations = getEntityRelations(entity, RELATION_PART_OF, { - kind: 'domain', - }); - const ownedByRelations = getEntityRelations(entity, RELATION_OWNED_BY); - - return { - entity, - resolved: { - name: formatEntityRefTitle(entity, { - defaultKind: 'System', - }), - ownedByRelationsTitle: ownedByRelations - .map(r => formatEntityRefTitle(r, { defaultKind: 'group' })) - .join(', '), - ownedByRelations, - partOfDomainRelationTitle: partOfDomainRelations - .map(r => - formatEntityRefTitle(r, { - defaultKind: 'domain', - }), - ) - .join(', '), - partOfDomainRelations, - }, - }; - }); - - return ( - - columns={columns} - title={title} - style={tableStyle} - emptyComponent={ - emptyComponent &&
{emptyComponent}
- } - options={{ - // TODO: Toolbar padding if off compared to other cards, should be: padding: 16px 24px; - search: false, - paging: false, - actionsColumnIndex: -1, - padding: 'dense', - }} - data={rows} - /> - ); -}; diff --git a/plugins/catalog-react/src/components/EntityTables/index.ts b/plugins/catalog-react/src/components/EntityTables/index.ts deleted file mode 100644 index f3e4f416ac..0000000000 --- a/plugins/catalog-react/src/components/EntityTables/index.ts +++ /dev/null @@ -1,17 +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. - */ -export { ComponentsTable } from './ComponentsTable'; -export { SystemsTable } from './SystemsTable'; diff --git a/plugins/catalog-react/src/components/index.ts b/plugins/catalog-react/src/components/index.ts index 8508935dd6..5181b8f0ea 100644 --- a/plugins/catalog-react/src/components/index.ts +++ b/plugins/catalog-react/src/components/index.ts @@ -16,4 +16,3 @@ export * from './EntityProvider'; export * from './EntityRefLink'; export * from './EntityTable'; -export * from './EntityTables'; diff --git a/plugins/catalog/src/components/ComponentsCard/HasComponentsCard.tsx b/plugins/catalog/src/components/ComponentsCard/HasComponentsCard.tsx index c9b5879a05..c2652f8cff 100644 --- a/plugins/catalog/src/components/ComponentsCard/HasComponentsCard.tsx +++ b/plugins/catalog/src/components/ComponentsCard/HasComponentsCard.tsx @@ -23,7 +23,7 @@ import { WarningPanel, } from '@backstage/core'; import { - ComponentsTable, + EntityTable, useEntity, useRelatedEntities, } from '@backstage/plugin-catalog-react'; @@ -72,7 +72,7 @@ export const HasComponentsCard = ({ variant = 'gridItem' }: Props) => { } return ( - { } + columns={EntityTable.componentEntityColumns} entities={entities as ComponentEntity[]} /> ); diff --git a/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx b/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx index 541ccf795d..6bc555efc2 100644 --- a/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx +++ b/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx @@ -23,7 +23,7 @@ import { WarningPanel, } from '@backstage/core'; import { - ComponentsTable, + EntityTable, useEntity, useRelatedEntities, } from '@backstage/plugin-catalog-react'; @@ -72,7 +72,7 @@ export const HasSubcomponentsCard = ({ variant = 'gridItem' }: Props) => { } return ( - { } + columns={EntityTable.componentEntityColumns} entities={entities as ComponentEntity[]} /> ); From 8ab97e7ff6381ed5ec57154cf51bbda13a2b790e Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Fri, 12 Feb 2021 10:47:04 +0100 Subject: [PATCH 08/17] Format file --- plugins/catalog/src/index.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/catalog/src/index.ts b/plugins/catalog/src/index.ts index 6d1704503e..800c5c4693 100644 --- a/plugins/catalog/src/index.ts +++ b/plugins/catalog/src/index.ts @@ -28,6 +28,5 @@ export { EntityHasComponentsCard, EntityHasSubcomponentsCard, EntityHasSystemsCard, - EntityLinksCard + EntityLinksCard, } from './plugin'; - From a47fa912216f591fffd5fd63c0b272aa0b5b6189 Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Fri, 12 Feb 2021 10:56:24 +0100 Subject: [PATCH 09/17] Fix import --- .../src/components/EntityTable/EntityTable.test.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/plugins/catalog-react/src/components/EntityTable/EntityTable.test.tsx b/plugins/catalog-react/src/components/EntityTable/EntityTable.test.tsx index 96f9b4cdbd..8752b6af6a 100644 --- a/plugins/catalog-react/src/components/EntityTable/EntityTable.test.tsx +++ b/plugins/catalog-react/src/components/EntityTable/EntityTable.test.tsx @@ -14,12 +14,7 @@ * limitations under the License. */ -import { - Entity, - RELATION_OWNED_BY, - RELATION_PART_OF, - SystemEntity, -} from '@backstage/catalog-model'; +import { Entity } from '@backstage/catalog-model'; import { renderInTestApp } from '@backstage/test-utils'; import { waitFor } from '@testing-library/react'; import React from 'react'; From 5a892f9dc8bd6aa08839937edf00e3e998839202 Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Tue, 16 Feb 2021 12:15:16 +0100 Subject: [PATCH 10/17] Rename emptyComponent to emptyContent --- .changeset/violet-maps-occur.md | 2 +- packages/core/src/components/Table/Table.stories.tsx | 2 +- packages/core/src/components/Table/Table.test.tsx | 2 +- packages/core/src/components/Table/Table.tsx | 11 ++++++----- .../src/components/ApisCards/ConsumedApisCard.tsx | 2 +- .../api-docs/src/components/ApisCards/HasApisCard.tsx | 2 +- .../src/components/ApisCards/ProvidedApisCard.tsx | 2 +- .../ComponentsCards/ConsumingComponentsCard.tsx | 2 +- .../ComponentsCards/ProvidingComponentsCard.tsx | 2 +- .../src/components/EntityTable/EntityTable.test.tsx | 4 ++-- .../src/components/EntityTable/EntityTable.tsx | 10 +++++----- .../src/components/EntityTable/presets.test.tsx | 4 ++-- .../components/ComponentsCard/HasComponentsCard.tsx | 2 +- .../HasSubcomponentsCard/HasSubcomponentsCard.tsx | 2 +- .../src/components/HasSystemsCard/HasSystemsCard.tsx | 2 +- 15 files changed, 26 insertions(+), 25 deletions(-) diff --git a/.changeset/violet-maps-occur.md b/.changeset/violet-maps-occur.md index e746ca3323..112095d257 100644 --- a/.changeset/violet-maps-occur.md +++ b/.changeset/violet-maps-occur.md @@ -4,5 +4,5 @@ Add support for custom empty state of `Table` components. -You can now optionally pass `emptyComponent` to `Table` that is displayed +You can now optionally pass `emptyContent` to `Table` that is displayed if the table has now rows. diff --git a/packages/core/src/components/Table/Table.stories.tsx b/packages/core/src/components/Table/Table.stories.tsx index fe3fe89941..64ceb761f8 100644 --- a/packages/core/src/components/Table/Table.stories.tsx +++ b/packages/core/src/components/Table/Table.stories.tsx @@ -119,7 +119,7 @@ export const EmptyTable = () => { options={{ paging: false }} data={[]} columns={columns} - emptyComponent={ + emptyContent={
No data was added yet,  learn how to add data. diff --git a/packages/core/src/components/Table/Table.test.tsx b/packages/core/src/components/Table/Table.test.tsx index 3c29f0b65d..3ba1c12ebd 100644 --- a/packages/core/src/components/Table/Table.test.tsx +++ b/packages/core/src/components/Table/Table.test.tsx @@ -58,7 +58,7 @@ describe('
{emptyComponent}
', () => { const rendered = await renderInTestApp(
EMPTY} + emptyContent={
EMPTY
} columns={minProps.columns} data={[]} />, diff --git a/packages/core/src/components/Table/Table.tsx b/packages/core/src/components/Table/Table.tsx index 033ec01d3c..3892d82bb3 100644 --- a/packages/core/src/components/Table/Table.tsx +++ b/packages/core/src/components/Table/Table.tsx @@ -49,6 +49,7 @@ import MTable, { } from 'material-table'; import React, { forwardRef, + ReactNode, useCallback, useEffect, useRef, @@ -203,7 +204,7 @@ export interface TableProps subtitle?: string; filters?: TableFilter[]; initialState?: TableState; - emptyComponent?: JSX.Element; + emptyContent?: ReactNode; onStateChange?: (state: TableState) => any; } @@ -214,7 +215,7 @@ export function Table({ subtitle, filters, initialState, - emptyComponent, + emptyContent, onStateChange, ...props }: TableProps) { @@ -428,11 +429,11 @@ export function Table({ const Body = useCallback( bodyProps => { - if (emptyComponent && data.length === 0) { + if (emptyContent && data.length === 0) { return ( - + ); @@ -440,7 +441,7 @@ export function Table({ return ; }, - [data, emptyComponent, columns], + [data, emptyContent, columns], ); return ( diff --git a/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.tsx b/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.tsx index 922a133fde..256e55eb5f 100644 --- a/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.tsx +++ b/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.tsx @@ -81,7 +81,7 @@ export const ConsumedApisCard = ({ variant = 'gridItem' }: Props) => { No Component consumes this API.{' '} diff --git a/plugins/api-docs/src/components/ApisCards/HasApisCard.tsx b/plugins/api-docs/src/components/ApisCards/HasApisCard.tsx index a0e1dce052..9682d64c09 100644 --- a/plugins/api-docs/src/components/ApisCards/HasApisCard.tsx +++ b/plugins/api-docs/src/components/ApisCards/HasApisCard.tsx @@ -76,7 +76,7 @@ export const HasApisCard = ({ variant = 'gridItem' }: Props) => { No API is part of this system.{' '} diff --git a/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.tsx b/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.tsx index 191eb0465c..96b6fd56cf 100644 --- a/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.tsx +++ b/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.tsx @@ -81,7 +81,7 @@ export const ProvidedApisCard = ({ variant = 'gridItem' }: Props) => { No Component provides this API.{' '} diff --git a/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.tsx b/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.tsx index a35c10b5cc..5c4afebb82 100644 --- a/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.tsx +++ b/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.tsx @@ -80,7 +80,7 @@ export const ConsumingComponentsCard = ({ variant = 'gridItem' }: Props) => { No component consumes this API.{' '} diff --git a/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.tsx b/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.tsx index 04239085f0..9ebc2804ec 100644 --- a/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.tsx +++ b/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.tsx @@ -80,7 +80,7 @@ export const ProvidingComponentsCard = ({ variant = 'gridItem' }: Props) => { No component provides this API.{' '} diff --git a/plugins/catalog-react/src/components/EntityTable/EntityTable.test.tsx b/plugins/catalog-react/src/components/EntityTable/EntityTable.test.tsx index 8752b6af6a..33bbbd007d 100644 --- a/plugins/catalog-react/src/components/EntityTable/EntityTable.test.tsx +++ b/plugins/catalog-react/src/components/EntityTable/EntityTable.test.tsx @@ -26,7 +26,7 @@ describe('', () => { EMPTY} + emptyContent={
EMPTY
} columns={[]} />, ); @@ -51,7 +51,7 @@ describe('', () => { EMPTY} + emptyContent={
EMPTY
} columns={[ { title: 'Name', diff --git a/plugins/catalog-react/src/components/EntityTable/EntityTable.tsx b/plugins/catalog-react/src/components/EntityTable/EntityTable.tsx index 18de1ec9ab..ad1496f480 100644 --- a/plugins/catalog-react/src/components/EntityTable/EntityTable.tsx +++ b/plugins/catalog-react/src/components/EntityTable/EntityTable.tsx @@ -17,7 +17,7 @@ import { Entity } from '@backstage/catalog-model'; import { Table, TableColumn } from '@backstage/core'; import { makeStyles } from '@material-ui/core'; -import React from 'react'; +import React, { ReactNode } from 'react'; import * as columnFactories from './columns'; import { componentEntityColumns, systemEntityColumns } from './presets'; @@ -25,7 +25,7 @@ type Props = { title: string; variant?: string; entities: T[]; - emptyComponent?: JSX.Element; + emptyContent?: ReactNode; columns: TableColumn[]; }; @@ -40,7 +40,7 @@ const useStyles = makeStyles(theme => ({ export function EntityTable({ entities, title, - emptyComponent, + emptyContent, variant = 'gridItem', columns, }: Props) { @@ -59,8 +59,8 @@ export function EntityTable({ columns={columns} title={title} style={tableStyle} - emptyComponent={ - emptyComponent &&
{emptyComponent}
+ emptyContent={ + emptyContent &&
{emptyContent}
} options={{ // TODO: Toolbar padding if off compared to other cards, should be: padding: 16px 24px; diff --git a/plugins/catalog-react/src/components/EntityTable/presets.test.tsx b/plugins/catalog-react/src/components/EntityTable/presets.test.tsx index 3edce87dac..b6b8c26e37 100644 --- a/plugins/catalog-react/src/components/EntityTable/presets.test.tsx +++ b/plugins/catalog-react/src/components/EntityTable/presets.test.tsx @@ -65,7 +65,7 @@ describe('systemEntityColumns', () => { EMPTY} + emptyContent={
EMPTY
} columns={systemEntityColumns} />, ); @@ -120,7 +120,7 @@ describe('componentEntityColumns', () => { EMPTY} + emptyContent={
EMPTY
} columns={componentEntityColumns} />, ); diff --git a/plugins/catalog/src/components/ComponentsCard/HasComponentsCard.tsx b/plugins/catalog/src/components/ComponentsCard/HasComponentsCard.tsx index c2652f8cff..aebc2e806e 100644 --- a/plugins/catalog/src/components/ComponentsCard/HasComponentsCard.tsx +++ b/plugins/catalog/src/components/ComponentsCard/HasComponentsCard.tsx @@ -75,7 +75,7 @@ export const HasComponentsCard = ({ variant = 'gridItem' }: Props) => { No component is part of this system.{' '} diff --git a/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx b/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx index 6bc555efc2..dfa22041be 100644 --- a/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx +++ b/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx @@ -75,7 +75,7 @@ export const HasSubcomponentsCard = ({ variant = 'gridItem' }: Props) => { No subcomponent is part of this component.{' '} diff --git a/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx b/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx index 339f83c624..0fe5931c9c 100644 --- a/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx +++ b/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx @@ -74,7 +74,7 @@ export const HasSystemsCard = ({ variant = 'gridItem' }: Props) => { No system is part of this domain.{' '} From 584dd21454b846642fae4794a8ed655abcdd5aeb Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Tue, 16 Feb 2021 12:22:17 +0100 Subject: [PATCH 11/17] Use enum for variant --- .../api-docs/src/components/ApisCards/ConsumedApisCard.tsx | 4 ++-- plugins/api-docs/src/components/ApisCards/HasApisCard.tsx | 4 ++-- .../api-docs/src/components/ApisCards/ProvidedApisCard.tsx | 4 ++-- .../components/ComponentsCards/ConsumingComponentsCard.tsx | 2 +- .../components/ComponentsCards/ProvidingComponentsCard.tsx | 2 +- .../catalog-react/src/components/EntityTable/EntityTable.tsx | 2 +- .../src/components/ComponentsCard/HasComponentsCard.tsx | 2 +- .../components/HasSubcomponentsCard/HasSubcomponentsCard.tsx | 2 +- .../catalog/src/components/HasSystemsCard/HasSystemsCard.tsx | 2 +- 9 files changed, 12 insertions(+), 12 deletions(-) diff --git a/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.tsx b/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.tsx index 256e55eb5f..fb86470760 100644 --- a/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.tsx +++ b/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.tsx @@ -37,7 +37,7 @@ import { apiEntityColumns } from './presets'; const ApisCard = ({ children, variant = 'gridItem', -}: PropsWithChildren<{ variant?: string }>) => { +}: PropsWithChildren<{ variant?: 'gridItem' }>) => { return ( {children} @@ -48,7 +48,7 @@ const ApisCard = ({ type Props = { /** @deprecated The entity is now grabbed from context instead */ entity?: Entity; - variant?: string; + variant?: 'gridItem'; }; export const ConsumedApisCard = ({ variant = 'gridItem' }: Props) => { diff --git a/plugins/api-docs/src/components/ApisCards/HasApisCard.tsx b/plugins/api-docs/src/components/ApisCards/HasApisCard.tsx index 9682d64c09..7de1448462 100644 --- a/plugins/api-docs/src/components/ApisCards/HasApisCard.tsx +++ b/plugins/api-docs/src/components/ApisCards/HasApisCard.tsx @@ -33,7 +33,7 @@ import { apiEntityColumns } from './presets'; const ApisCard = ({ children, variant = 'gridItem', -}: PropsWithChildren<{ variant?: string }>) => { +}: PropsWithChildren<{ variant?: 'gridItem' }>) => { return ( {children} @@ -42,7 +42,7 @@ const ApisCard = ({ }; type Props = { - variant?: string; + variant?: 'gridItem'; }; export const HasApisCard = ({ variant = 'gridItem' }: Props) => { diff --git a/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.tsx b/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.tsx index 96b6fd56cf..b1f9f47848 100644 --- a/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.tsx +++ b/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.tsx @@ -37,7 +37,7 @@ import { apiEntityColumns } from './presets'; const ApisCard = ({ children, variant = 'gridItem', -}: PropsWithChildren<{ variant?: string }>) => { +}: PropsWithChildren<{ variant?: 'gridItem' }>) => { return ( {children} @@ -48,7 +48,7 @@ const ApisCard = ({ type Props = { /** @deprecated The entity is now grabbed from context instead */ entity?: Entity; - variant?: string; + variant?: 'gridItem'; }; export const ProvidedApisCard = ({ variant = 'gridItem' }: Props) => { diff --git a/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.tsx b/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.tsx index 5c4afebb82..2fc51dd535 100644 --- a/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.tsx +++ b/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.tsx @@ -47,7 +47,7 @@ const ComponentsCard = ({ type Props = { /** @deprecated The entity is now grabbed from context instead */ entity?: Entity; - variant?: string; + variant?: 'gridItem'; }; export const ConsumingComponentsCard = ({ variant = 'gridItem' }: Props) => { diff --git a/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.tsx b/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.tsx index 9ebc2804ec..47bd63cd6e 100644 --- a/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.tsx +++ b/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.tsx @@ -47,7 +47,7 @@ const ComponentsCard = ({ type Props = { /** @deprecated The entity is now grabbed from context instead */ entity?: Entity; - variant?: string; + variant?: 'gridItem'; }; export const ProvidingComponentsCard = ({ variant = 'gridItem' }: Props) => { diff --git a/plugins/catalog-react/src/components/EntityTable/EntityTable.tsx b/plugins/catalog-react/src/components/EntityTable/EntityTable.tsx index ad1496f480..afcaaea305 100644 --- a/plugins/catalog-react/src/components/EntityTable/EntityTable.tsx +++ b/plugins/catalog-react/src/components/EntityTable/EntityTable.tsx @@ -23,7 +23,7 @@ import { componentEntityColumns, systemEntityColumns } from './presets'; type Props = { title: string; - variant?: string; + variant?: 'gridItem'; entities: T[]; emptyContent?: ReactNode; columns: TableColumn[]; diff --git a/plugins/catalog/src/components/ComponentsCard/HasComponentsCard.tsx b/plugins/catalog/src/components/ComponentsCard/HasComponentsCard.tsx index aebc2e806e..c7b52b7238 100644 --- a/plugins/catalog/src/components/ComponentsCard/HasComponentsCard.tsx +++ b/plugins/catalog/src/components/ComponentsCard/HasComponentsCard.tsx @@ -41,7 +41,7 @@ const ComponentsCard = ({ }; type Props = { - variant?: string; + variant?: 'gridItem'; }; export const HasComponentsCard = ({ variant = 'gridItem' }: Props) => { diff --git a/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx b/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx index dfa22041be..06ee181861 100644 --- a/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx +++ b/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx @@ -41,7 +41,7 @@ const SubcomponentsCard = ({ }; type Props = { - variant?: string; + variant?: 'gridItem'; }; export const HasSubcomponentsCard = ({ variant = 'gridItem' }: Props) => { diff --git a/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx b/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx index 0fe5931c9c..bfba1bb96e 100644 --- a/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx +++ b/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx @@ -41,7 +41,7 @@ const SystemsCard = ({ }; type Props = { - variant?: string; + variant?: 'gridItem'; }; export const HasSystemsCard = ({ variant = 'gridItem' }: Props) => { From fccf63335c5bc7768cdb202e4ea067184ca763da Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Tue, 16 Feb 2021 12:25:15 +0100 Subject: [PATCH 12/17] Fix typos --- .../api-docs/src/components/ApisCards/ProvidedApisCard.test.tsx | 2 +- plugins/api-docs/src/components/ApisCards/ProvidedApisCard.tsx | 2 +- plugins/api-docs/src/components/ApisCards/presets.tsx | 2 +- plugins/catalog-react/src/components/EntityTable/columns.tsx | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.test.tsx b/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.test.tsx index bb4d0ac82f..0d49cc328d 100644 --- a/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.test.tsx +++ b/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.test.tsx @@ -74,7 +74,7 @@ describe('', () => { ); expect(getByText(/Provided APIs/i)).toBeInTheDocument(); - expect(getByText(/No Component provides this API/i)).toBeInTheDocument(); + expect(getByText(/No component provides this API/i)).toBeInTheDocument(); }); it('shows consumed APIs', async () => { diff --git a/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.tsx b/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.tsx index b1f9f47848..53e2cd9558 100644 --- a/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.tsx +++ b/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.tsx @@ -83,7 +83,7 @@ export const ProvidedApisCard = ({ variant = 'gridItem' }: Props) => { variant={variant} emptyContent={
- No Component provides this API.{' '} + No component provides this API.{' '} Learn how to provide APIs. diff --git a/plugins/api-docs/src/components/ApisCards/presets.tsx b/plugins/api-docs/src/components/ApisCards/presets.tsx index c7fc4420df..151cccf846 100644 --- a/plugins/api-docs/src/components/ApisCards/presets.tsx +++ b/plugins/api-docs/src/components/ApisCards/presets.tsx @@ -29,7 +29,7 @@ export function createSpecApiTypeColumn(): TableColumn { } // TODO: This could be moved to plugin-catalog-react if we wouldn't have a -// special createSpecApiTypeColumn. But this is requited to use ApiTypeTitle to +// special createSpecApiTypeColumn. But this is required to use ApiTypeTitle to // resolve the display name of an entity. Is the display name really worth it? export const apiEntityColumns: TableColumn[] = [ diff --git a/plugins/catalog-react/src/components/EntityTable/columns.tsx b/plugins/catalog-react/src/components/EntityTable/columns.tsx index 01ed288c13..7ca99b1100 100644 --- a/plugins/catalog-react/src/components/EntityTable/columns.tsx +++ b/plugins/catalog-react/src/components/EntityTable/columns.tsx @@ -48,7 +48,7 @@ export function createEntityRefColumn({ // each field that is displayed individually (kind, namespace, name). // but that migth confuse the user as it will behave different than a // simple text search. - // Another altnerative would be to cache the values. But writing them + // Another alternative would be to cache the values. But writing them // into the entity feels bad too. return formatContent(entity).includes(filter); }, From 8c010990615468267490ff7dd119f21322e5948e Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Tue, 16 Feb 2021 12:47:11 +0100 Subject: [PATCH 13/17] useAsync instead of useAsyncRetry --- plugins/catalog-react/src/hooks/useRelatedEntities.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/plugins/catalog-react/src/hooks/useRelatedEntities.ts b/plugins/catalog-react/src/hooks/useRelatedEntities.ts index 281ef217c1..b2ab93b607 100644 --- a/plugins/catalog-react/src/hooks/useRelatedEntities.ts +++ b/plugins/catalog-react/src/hooks/useRelatedEntities.ts @@ -15,7 +15,7 @@ */ import { Entity } from '@backstage/catalog-model'; import { useApi } from '@backstage/core'; -import { useAsyncRetry } from 'react-use'; +import { useAsync } from 'react-use'; import { catalogApiRef } from '../api'; export function useRelatedEntities( @@ -27,9 +27,7 @@ export function useRelatedEntities( error: Error | undefined; } { const catalogApi = useApi(catalogApiRef); - const { loading, value: entities, error } = useAsyncRetry< - Entity[] - >(async () => { + const { loading, value: entities, error } = useAsync(async () => { const relations = entity.relations && entity.relations.filter( From ae6ea4340893f940d456f40d7db26f4eec94bc32 Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Tue, 16 Feb 2021 12:49:02 +0100 Subject: [PATCH 14/17] Compare relation type case insensitive --- plugins/catalog-react/src/hooks/useRelatedEntities.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/plugins/catalog-react/src/hooks/useRelatedEntities.ts b/plugins/catalog-react/src/hooks/useRelatedEntities.ts index b2ab93b607..baf6cf5fc7 100644 --- a/plugins/catalog-react/src/hooks/useRelatedEntities.ts +++ b/plugins/catalog-react/src/hooks/useRelatedEntities.ts @@ -32,9 +32,8 @@ export function useRelatedEntities( entity.relations && entity.relations.filter( r => - (!type || r.type === type) && - (!kind || - r.target.kind.toLocaleLowerCase() === kind.toLocaleLowerCase()), + (!type || r.type.toLowerCase() === type.toLowerCase()) && + (!kind || r.target.kind.toLowerCase() === kind.toLowerCase()), ); if (!relations) { From e7c2e70e7722cb64c30c6e0052da4bef7619802a Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Tue, 16 Feb 2021 12:52:35 +0100 Subject: [PATCH 15/17] Fix some leftover variant usages --- .../src/components/ComponentsCards/ConsumingComponentsCard.tsx | 2 +- .../src/components/ComponentsCards/ProvidingComponentsCard.tsx | 2 +- .../catalog/src/components/ComponentsCard/HasComponentsCard.tsx | 2 +- .../components/HasSubcomponentsCard/HasSubcomponentsCard.tsx | 2 +- .../catalog/src/components/HasSystemsCard/HasSystemsCard.tsx | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.tsx b/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.tsx index 2fc51dd535..111e05a04a 100644 --- a/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.tsx +++ b/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.tsx @@ -36,7 +36,7 @@ import React, { PropsWithChildren } from 'react'; const ComponentsCard = ({ children, variant = 'gridItem', -}: PropsWithChildren<{ variant?: string }>) => { +}: PropsWithChildren<{ variant?: 'gridItem' }>) => { return ( {children} diff --git a/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.tsx b/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.tsx index 47bd63cd6e..44f4c3a854 100644 --- a/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.tsx +++ b/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.tsx @@ -36,7 +36,7 @@ import React, { PropsWithChildren } from 'react'; const ComponentsCard = ({ children, variant = 'gridItem', -}: PropsWithChildren<{ variant?: string }>) => { +}: PropsWithChildren<{ variant?: 'gridItem' }>) => { return ( {children} diff --git a/plugins/catalog/src/components/ComponentsCard/HasComponentsCard.tsx b/plugins/catalog/src/components/ComponentsCard/HasComponentsCard.tsx index c7b52b7238..b7dbb47848 100644 --- a/plugins/catalog/src/components/ComponentsCard/HasComponentsCard.tsx +++ b/plugins/catalog/src/components/ComponentsCard/HasComponentsCard.tsx @@ -32,7 +32,7 @@ import React, { PropsWithChildren } from 'react'; const ComponentsCard = ({ children, variant = 'gridItem', -}: PropsWithChildren<{ variant?: string }>) => { +}: PropsWithChildren<{ variant?: 'gridItem' }>) => { return ( {children} diff --git a/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx b/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx index 06ee181861..d039ba293f 100644 --- a/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx +++ b/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx @@ -32,7 +32,7 @@ import React, { PropsWithChildren } from 'react'; const SubcomponentsCard = ({ children, variant = 'gridItem', -}: PropsWithChildren<{ variant?: string }>) => { +}: PropsWithChildren<{ variant?: 'gridItem' }>) => { return ( {children} diff --git a/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx b/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx index bfba1bb96e..3962ea3238 100644 --- a/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx +++ b/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx @@ -32,7 +32,7 @@ import React, { PropsWithChildren } from 'react'; const SystemsCard = ({ children, variant = 'gridItem', -}: PropsWithChildren<{ variant?: string }>) => { +}: PropsWithChildren<{ variant?: 'gridItem' }>) => { return ( {children} From dd95e2efd60f9b49110211f01e25fa9f742a87a1 Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Tue, 16 Feb 2021 14:42:50 +0100 Subject: [PATCH 16/17] Remove now unused code --- .../MissingConsumesApisEmptyState.test.tsx | 28 ------- .../MissingConsumesApisEmptyState.tsx | 81 ------------------- .../MissingProvidesApisEmptyState.test.tsx | 28 ------- .../MissingProvidesApisEmptyState.tsx | 81 ------------------- .../src/components/EmptyState/index.ts | 18 ----- 5 files changed, 236 deletions(-) delete mode 100644 plugins/api-docs/src/components/EmptyState/MissingConsumesApisEmptyState.test.tsx delete mode 100644 plugins/api-docs/src/components/EmptyState/MissingConsumesApisEmptyState.tsx delete mode 100644 plugins/api-docs/src/components/EmptyState/MissingProvidesApisEmptyState.test.tsx delete mode 100644 plugins/api-docs/src/components/EmptyState/MissingProvidesApisEmptyState.tsx delete mode 100644 plugins/api-docs/src/components/EmptyState/index.ts diff --git a/plugins/api-docs/src/components/EmptyState/MissingConsumesApisEmptyState.test.tsx b/plugins/api-docs/src/components/EmptyState/MissingConsumesApisEmptyState.test.tsx deleted file mode 100644 index de753713c3..0000000000 --- a/plugins/api-docs/src/components/EmptyState/MissingConsumesApisEmptyState.test.tsx +++ /dev/null @@ -1,28 +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 { renderInTestApp } from '@backstage/test-utils'; -import React from 'react'; -import { MissingConsumesApisEmptyState } from './MissingConsumesApisEmptyState'; - -describe('', () => { - it('renders without exploding', async () => { - const { getByText } = await renderInTestApp( - , - ); - expect(getByText(/consumesApis:/i)).toBeInTheDocument(); - }); -}); diff --git a/plugins/api-docs/src/components/EmptyState/MissingConsumesApisEmptyState.tsx b/plugins/api-docs/src/components/EmptyState/MissingConsumesApisEmptyState.tsx deleted file mode 100644 index 3e71168dde..0000000000 --- a/plugins/api-docs/src/components/EmptyState/MissingConsumesApisEmptyState.tsx +++ /dev/null @@ -1,81 +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 React from 'react'; -import { Button, makeStyles, Typography } from '@material-ui/core'; -import { BackstageTheme } from '@backstage/theme'; -import { CodeSnippet, EmptyState } from '@backstage/core'; - -const COMPONENT_YAML = `# Example -apiVersion: backstage.io/v1alpha1 -kind: Component -metadata: - name: example -spec: - type: service - lifecycle: production - owner: guest - consumesApis: - - example-api -`; - -const useStyles = makeStyles(theme => ({ - code: { - borderRadius: 6, - margin: `${theme.spacing(2)}px 0px`, - background: theme.palette.type === 'dark' ? '#444' : '#fff', - }, -})); - -export const MissingConsumesApisEmptyState = () => { - const classes = useStyles(); - return ( - - Components can consume APIs that are displayed on this page. You need - to fill the consumesApis field to enable this tool. - - } - action={ - <> - - Link an API to your component as shown in the highlighted example - below: - -
- -
- - - } - /> - ); -}; diff --git a/plugins/api-docs/src/components/EmptyState/MissingProvidesApisEmptyState.test.tsx b/plugins/api-docs/src/components/EmptyState/MissingProvidesApisEmptyState.test.tsx deleted file mode 100644 index b539753a95..0000000000 --- a/plugins/api-docs/src/components/EmptyState/MissingProvidesApisEmptyState.test.tsx +++ /dev/null @@ -1,28 +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 { renderInTestApp } from '@backstage/test-utils'; -import React from 'react'; -import { MissingProvidesApisEmptyState } from './MissingProvidesApisEmptyState'; - -describe('', () => { - it('renders without exploding', async () => { - const { getByText } = await renderInTestApp( - , - ); - expect(getByText(/providesApis:/i)).toBeInTheDocument(); - }); -}); diff --git a/plugins/api-docs/src/components/EmptyState/MissingProvidesApisEmptyState.tsx b/plugins/api-docs/src/components/EmptyState/MissingProvidesApisEmptyState.tsx deleted file mode 100644 index 9bf3465a34..0000000000 --- a/plugins/api-docs/src/components/EmptyState/MissingProvidesApisEmptyState.tsx +++ /dev/null @@ -1,81 +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 React from 'react'; -import { Button, makeStyles, Typography } from '@material-ui/core'; -import { BackstageTheme } from '@backstage/theme'; -import { CodeSnippet, EmptyState } from '@backstage/core'; - -const COMPONENT_YAML = `# Example -apiVersion: backstage.io/v1alpha1 -kind: Component -metadata: - name: example -spec: - type: service - lifecycle: production - owner: guest - providesApis: - - example-api -`; - -const useStyles = makeStyles(theme => ({ - code: { - borderRadius: 6, - margin: `${theme.spacing(2)}px 0px`, - background: theme.palette.type === 'dark' ? '#444' : '#fff', - }, -})); - -export const MissingProvidesApisEmptyState = () => { - const classes = useStyles(); - return ( - - Components can implement APIs that are displayed on this page. You - need to fill the providesApis field to enable this tool. - - } - action={ - <> - - Link an API to your component as shown in the highlighted example - below: - -
- -
- - - } - /> - ); -}; diff --git a/plugins/api-docs/src/components/EmptyState/index.ts b/plugins/api-docs/src/components/EmptyState/index.ts deleted file mode 100644 index d195c43eb1..0000000000 --- a/plugins/api-docs/src/components/EmptyState/index.ts +++ /dev/null @@ -1,18 +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. - */ - -export { MissingConsumesApisEmptyState } from './MissingConsumesApisEmptyState'; -export { MissingProvidesApisEmptyState } from './MissingProvidesApisEmptyState'; From ec085622897ee2272c7e3f51dd3f612f61cf451e Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Tue, 16 Feb 2021 14:46:49 +0100 Subject: [PATCH 17/17] Avoid intermediate card component and use InfoCard directly --- .../components/ApisCards/ConsumedApisCard.tsx | 21 +++++-------------- .../src/components/ApisCards/HasApisCard.tsx | 21 +++++-------------- .../components/ApisCards/ProvidedApisCard.tsx | 21 +++++-------------- .../ConsumingComponentsCard.tsx | 21 +++++-------------- .../ProvidingComponentsCard.tsx | 21 +++++-------------- .../HasComponentsCard.test.tsx | 0 .../HasComponentsCard.tsx | 21 +++++-------------- .../index.ts | 0 .../HasSubcomponentsCard.tsx | 21 +++++-------------- .../HasSystemsCard/HasSystemsCard.tsx | 21 +++++-------------- plugins/catalog/src/plugin.ts | 2 +- 11 files changed, 41 insertions(+), 129 deletions(-) rename plugins/catalog/src/components/{ComponentsCard => HasComponentsCard}/HasComponentsCard.test.tsx (100%) rename plugins/catalog/src/components/{ComponentsCard => HasComponentsCard}/HasComponentsCard.tsx (82%) rename plugins/catalog/src/components/{ComponentsCard => HasComponentsCard}/index.ts (100%) diff --git a/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.tsx b/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.tsx index fb86470760..adfa3cd34c 100644 --- a/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.tsx +++ b/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.tsx @@ -31,20 +31,9 @@ import { useEntity, useRelatedEntities, } from '@backstage/plugin-catalog-react'; -import React, { PropsWithChildren } from 'react'; +import React from 'react'; import { apiEntityColumns } from './presets'; -const ApisCard = ({ - children, - variant = 'gridItem', -}: PropsWithChildren<{ variant?: 'gridItem' }>) => { - return ( - - {children} - - ); -}; - type Props = { /** @deprecated The entity is now grabbed from context instead */ entity?: Entity; @@ -59,21 +48,21 @@ export const ConsumedApisCard = ({ variant = 'gridItem' }: Props) => { if (loading) { return ( - + - +
); } if (error || !entities) { return ( - + } /> - +
); } diff --git a/plugins/api-docs/src/components/ApisCards/HasApisCard.tsx b/plugins/api-docs/src/components/ApisCards/HasApisCard.tsx index 7de1448462..9f1d487b65 100644 --- a/plugins/api-docs/src/components/ApisCards/HasApisCard.tsx +++ b/plugins/api-docs/src/components/ApisCards/HasApisCard.tsx @@ -27,20 +27,9 @@ import { useEntity, useRelatedEntities, } from '@backstage/plugin-catalog-react'; -import React, { PropsWithChildren } from 'react'; +import React from 'react'; import { apiEntityColumns } from './presets'; -const ApisCard = ({ - children, - variant = 'gridItem', -}: PropsWithChildren<{ variant?: 'gridItem' }>) => { - return ( - - {children} - - ); -}; - type Props = { variant?: 'gridItem'; }; @@ -54,21 +43,21 @@ export const HasApisCard = ({ variant = 'gridItem' }: Props) => { if (loading) { return ( - + - +
); } if (error || !entities) { return ( - + } /> - +
); } diff --git a/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.tsx b/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.tsx index 53e2cd9558..f02390b6d6 100644 --- a/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.tsx +++ b/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.tsx @@ -31,20 +31,9 @@ import { useEntity, useRelatedEntities, } from '@backstage/plugin-catalog-react'; -import React, { PropsWithChildren } from 'react'; +import React from 'react'; import { apiEntityColumns } from './presets'; -const ApisCard = ({ - children, - variant = 'gridItem', -}: PropsWithChildren<{ variant?: 'gridItem' }>) => { - return ( - - {children} - - ); -}; - type Props = { /** @deprecated The entity is now grabbed from context instead */ entity?: Entity; @@ -59,21 +48,21 @@ export const ProvidedApisCard = ({ variant = 'gridItem' }: Props) => { if (loading) { return ( - + - +
); } if (error || !entities) { return ( - + } /> - + ); } diff --git a/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.tsx b/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.tsx index 111e05a04a..7e4d97fece 100644 --- a/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.tsx +++ b/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.tsx @@ -31,18 +31,7 @@ import { useEntity, useRelatedEntities, } from '@backstage/plugin-catalog-react'; -import React, { PropsWithChildren } from 'react'; - -const ComponentsCard = ({ - children, - variant = 'gridItem', -}: PropsWithChildren<{ variant?: 'gridItem' }>) => { - return ( - - {children} - - ); -}; +import React from 'react'; type Props = { /** @deprecated The entity is now grabbed from context instead */ @@ -58,21 +47,21 @@ export const ConsumingComponentsCard = ({ variant = 'gridItem' }: Props) => { if (loading) { return ( - + - + ); } if (error || !entities) { return ( - + } /> - + ); } diff --git a/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.tsx b/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.tsx index 44f4c3a854..d755d8b5af 100644 --- a/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.tsx +++ b/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.tsx @@ -31,18 +31,7 @@ import { useEntity, useRelatedEntities, } from '@backstage/plugin-catalog-react'; -import React, { PropsWithChildren } from 'react'; - -const ComponentsCard = ({ - children, - variant = 'gridItem', -}: PropsWithChildren<{ variant?: 'gridItem' }>) => { - return ( - - {children} - - ); -}; +import React from 'react'; type Props = { /** @deprecated The entity is now grabbed from context instead */ @@ -58,21 +47,21 @@ export const ProvidingComponentsCard = ({ variant = 'gridItem' }: Props) => { if (loading) { return ( - + - + ); } if (error || !entities) { return ( - + } /> - + ); } diff --git a/plugins/catalog/src/components/ComponentsCard/HasComponentsCard.test.tsx b/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.test.tsx similarity index 100% rename from plugins/catalog/src/components/ComponentsCard/HasComponentsCard.test.tsx rename to plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.test.tsx diff --git a/plugins/catalog/src/components/ComponentsCard/HasComponentsCard.tsx b/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.tsx similarity index 82% rename from plugins/catalog/src/components/ComponentsCard/HasComponentsCard.tsx rename to plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.tsx index b7dbb47848..6485d4f525 100644 --- a/plugins/catalog/src/components/ComponentsCard/HasComponentsCard.tsx +++ b/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.tsx @@ -27,18 +27,7 @@ import { useEntity, useRelatedEntities, } from '@backstage/plugin-catalog-react'; -import React, { PropsWithChildren } from 'react'; - -const ComponentsCard = ({ - children, - variant = 'gridItem', -}: PropsWithChildren<{ variant?: 'gridItem' }>) => { - return ( - - {children} - - ); -}; +import React from 'react'; type Props = { variant?: 'gridItem'; @@ -53,21 +42,21 @@ export const HasComponentsCard = ({ variant = 'gridItem' }: Props) => { if (loading) { return ( - + - + ); } if (error || !entities) { return ( - + } /> - + ); } diff --git a/plugins/catalog/src/components/ComponentsCard/index.ts b/plugins/catalog/src/components/HasComponentsCard/index.ts similarity index 100% rename from plugins/catalog/src/components/ComponentsCard/index.ts rename to plugins/catalog/src/components/HasComponentsCard/index.ts diff --git a/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx b/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx index d039ba293f..c2635b68e1 100644 --- a/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx +++ b/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx @@ -27,18 +27,7 @@ import { useEntity, useRelatedEntities, } from '@backstage/plugin-catalog-react'; -import React, { PropsWithChildren } from 'react'; - -const SubcomponentsCard = ({ - children, - variant = 'gridItem', -}: PropsWithChildren<{ variant?: 'gridItem' }>) => { - return ( - - {children} - - ); -}; +import React from 'react'; type Props = { variant?: 'gridItem'; @@ -53,21 +42,21 @@ export const HasSubcomponentsCard = ({ variant = 'gridItem' }: Props) => { if (loading) { return ( - + - + ); } if (error || !entities) { return ( - + } /> - + ); } diff --git a/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx b/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx index 3962ea3238..9faa640060 100644 --- a/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx +++ b/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx @@ -27,18 +27,7 @@ import { useEntity, useRelatedEntities, } from '@backstage/plugin-catalog-react'; -import React, { PropsWithChildren } from 'react'; - -const SystemsCard = ({ - children, - variant = 'gridItem', -}: PropsWithChildren<{ variant?: 'gridItem' }>) => { - return ( - - {children} - - ); -}; +import React from 'react'; type Props = { variant?: 'gridItem'; @@ -52,21 +41,21 @@ export const HasSystemsCard = ({ variant = 'gridItem' }: Props) => { if (loading) { return ( - + - + ); } if (error || !entities) { return ( - + } /> - + ); } diff --git a/plugins/catalog/src/plugin.ts b/plugins/catalog/src/plugin.ts index 3b2a6f58f4..a0932a8dba 100644 --- a/plugins/catalog/src/plugin.ts +++ b/plugins/catalog/src/plugin.ts @@ -95,7 +95,7 @@ export const EntityHasComponentsCard = catalogPlugin.provide( createComponentExtension({ component: { lazy: () => - import('./components/ComponentsCard').then(m => m.HasComponentsCard), + import('./components/HasComponentsCard').then(m => m.HasComponentsCard), }, }), );
{emptyComponent}{emptyContent}