Merge pull request #5361 from jmgrimes/catalog-model-system-resources-and-component-dependencies-cards
Added system resources and component dependencies (components/resources) cards
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
---
|
||||
'@backstage/plugin-catalog': patch
|
||||
---
|
||||
|
||||
- Added `RelatedEntitesCard` as a base implementation of displaying entities that are related to another entity.
|
||||
- Added `HasResourcesCard` to display resources that are part of a system.
|
||||
- Added `DependsOnComponentsCard` to display components that are dependencies of a component.
|
||||
- Added `DependsOnResourcesCard` to display resources that are dependencies of a component.
|
||||
- Refactored `HasComponentsCard` to use base `RelatedEntitiesCard`. Card remains backwards compatible.
|
||||
- Refactored `HasSubcomponentsCard` to use base `RelatedEntitiesCard`. Card remains backwards compatible.
|
||||
- Refactored `HasSystemsCard` to use base `RelatedEntitiesCard`. Card remains backwards compatible.
|
||||
- Updated the example app to take advantage of these new components.
|
||||
@@ -28,6 +28,8 @@ import {
|
||||
import { EntityBadgesDialog } from '@backstage/plugin-badges';
|
||||
import {
|
||||
EntityAboutCard,
|
||||
EntityDependsOnComponentsCard,
|
||||
EntityDependsOnResourcesCard,
|
||||
EntityHasComponentsCard,
|
||||
EntityHasSubcomponentsCard,
|
||||
EntityHasSystemsCard,
|
||||
@@ -37,6 +39,7 @@ import {
|
||||
EntitySwitch,
|
||||
isComponentType,
|
||||
isKind,
|
||||
EntityHasResourcesCard,
|
||||
} from '@backstage/plugin-catalog';
|
||||
import {
|
||||
EntityCircleCIContent,
|
||||
@@ -288,6 +291,17 @@ const serviceEntityPage = (
|
||||
</Grid>
|
||||
</EntityLayout.Route>
|
||||
|
||||
<EntityLayout.Route path="/dependencies" title="Dependencies">
|
||||
<Grid container spacing={3} alignItems="stretch">
|
||||
<Grid item md={6}>
|
||||
<EntityDependsOnComponentsCard variant="gridItem" />
|
||||
</Grid>
|
||||
<Grid item md={6}>
|
||||
<EntityDependsOnResourcesCard variant="gridItem" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</EntityLayout.Route>
|
||||
|
||||
<EntityLayout.Route path="/docs" title="Docs">
|
||||
<EntityTechdocsContent />
|
||||
</EntityLayout.Route>
|
||||
@@ -336,6 +350,17 @@ const websiteEntityPage = (
|
||||
{errorsContent}
|
||||
</EntityLayout.Route>
|
||||
|
||||
<EntityLayout.Route path="/dependencies" title="Dependencies">
|
||||
<Grid container spacing={3} alignItems="stretch">
|
||||
<Grid item md={6}>
|
||||
<EntityDependsOnComponentsCard variant="gridItem" />
|
||||
</Grid>
|
||||
<Grid item md={6}>
|
||||
<EntityDependsOnResourcesCard variant="gridItem" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</EntityLayout.Route>
|
||||
|
||||
<EntityLayout.Route path="/docs" title="Docs">
|
||||
<EntityTechdocsContent />
|
||||
</EntityLayout.Route>
|
||||
@@ -466,6 +491,9 @@ const systemPage = (
|
||||
<Grid item md={6}>
|
||||
<EntityHasApisCard variant="gridItem" />
|
||||
</Grid>
|
||||
<Grid item md={6}>
|
||||
<EntityHasResourcesCard variant="gridItem" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</EntityLayout.Route>
|
||||
<EntityLayout.Route path="/diagram" title="Diagram">
|
||||
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Entity, RELATION_DEPENDS_ON } from '@backstage/catalog-model';
|
||||
import { ApiProvider, ApiRegistry } from '@backstage/core';
|
||||
import {
|
||||
CatalogApi,
|
||||
catalogApiRef,
|
||||
EntityProvider,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
import { waitFor } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { DependsOnComponentsCard } from './DependsOnComponentsCard';
|
||||
|
||||
describe('<DependsOnComponentsCard />', () => {
|
||||
const catalogApi: jest.Mocked<CatalogApi> = {
|
||||
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 }) => (
|
||||
<ApiProvider apis={apis}>{children}</ApiProvider>
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => jest.resetAllMocks());
|
||||
|
||||
it('shows empty list if no dependencies', async () => {
|
||||
const entity: Entity = {
|
||||
apiVersion: 'v1',
|
||||
kind: 'Component',
|
||||
metadata: {
|
||||
name: 'my-component',
|
||||
namespace: 'my-namespace',
|
||||
},
|
||||
relations: [],
|
||||
};
|
||||
|
||||
const { getByText } = await renderInTestApp(
|
||||
<Wrapper>
|
||||
<EntityProvider entity={entity}>
|
||||
<DependsOnComponentsCard />
|
||||
</EntityProvider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
expect(getByText('Components')).toBeInTheDocument();
|
||||
expect(
|
||||
getByText(/No component is a dependency of this component/i),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows dependency components', async () => {
|
||||
const entity: Entity = {
|
||||
apiVersion: 'v1',
|
||||
kind: 'Component',
|
||||
metadata: {
|
||||
name: 'my-component',
|
||||
namespace: 'my-namespace',
|
||||
},
|
||||
relations: [
|
||||
{
|
||||
target: {
|
||||
kind: 'Component',
|
||||
namespace: 'my-namespace',
|
||||
name: 'target-name',
|
||||
},
|
||||
type: RELATION_DEPENDS_ON,
|
||||
},
|
||||
],
|
||||
};
|
||||
catalogApi.getEntities.mockResolvedValue({
|
||||
items: [
|
||||
{
|
||||
apiVersion: 'v1',
|
||||
kind: 'Component',
|
||||
metadata: {
|
||||
namespace: 'my-namespace',
|
||||
name: 'target-name',
|
||||
},
|
||||
spec: {},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const { getByText } = await renderInTestApp(
|
||||
<Wrapper>
|
||||
<EntityProvider entity={entity}>
|
||||
<DependsOnComponentsCard />
|
||||
</EntityProvider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getByText('Components')).toBeInTheDocument();
|
||||
expect(getByText(/target-name/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { RELATION_DEPENDS_ON } from '@backstage/catalog-model';
|
||||
import React from 'react';
|
||||
import {
|
||||
asComponentEntities,
|
||||
componentEntityColumns,
|
||||
componentEntityHelpLink,
|
||||
RelatedEntitiesCard,
|
||||
} from '../RelatedEntitiesCard';
|
||||
|
||||
type Props = {
|
||||
variant?: 'gridItem';
|
||||
};
|
||||
|
||||
export const DependsOnComponentsCard = ({ variant = 'gridItem' }: Props) => {
|
||||
return (
|
||||
<RelatedEntitiesCard
|
||||
variant={variant}
|
||||
title="Components"
|
||||
entityKind="Component"
|
||||
relationType={RELATION_DEPENDS_ON}
|
||||
columns={componentEntityColumns}
|
||||
emptyMessage="No component is a dependency of this component"
|
||||
emptyHelpLink={componentEntityHelpLink}
|
||||
asRenderableEntities={asComponentEntities}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { DependsOnComponentsCard } from './DependsOnComponentsCard';
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Entity, RELATION_DEPENDS_ON } from '@backstage/catalog-model';
|
||||
import { ApiProvider, ApiRegistry } from '@backstage/core';
|
||||
import {
|
||||
CatalogApi,
|
||||
catalogApiRef,
|
||||
EntityProvider,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
import { waitFor } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { DependsOnResourcesCard } from './DependsOnResourcesCard';
|
||||
|
||||
describe('<DependsOnResourcesCard />', () => {
|
||||
const catalogApi: jest.Mocked<CatalogApi> = {
|
||||
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 }) => (
|
||||
<ApiProvider apis={apis}>{children}</ApiProvider>
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => jest.resetAllMocks());
|
||||
|
||||
it('shows empty list if no dependencies', async () => {
|
||||
const entity: Entity = {
|
||||
apiVersion: 'v1',
|
||||
kind: 'Component',
|
||||
metadata: {
|
||||
name: 'my-component',
|
||||
namespace: 'my-namespace',
|
||||
},
|
||||
relations: [],
|
||||
};
|
||||
|
||||
const { getByText } = await renderInTestApp(
|
||||
<Wrapper>
|
||||
<EntityProvider entity={entity}>
|
||||
<DependsOnResourcesCard />
|
||||
</EntityProvider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
expect(getByText('Resources')).toBeInTheDocument();
|
||||
expect(
|
||||
getByText(/No resource is a dependency of this component/i),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows dependency resources', async () => {
|
||||
const entity: Entity = {
|
||||
apiVersion: 'v1',
|
||||
kind: 'Component',
|
||||
metadata: {
|
||||
name: 'my-component',
|
||||
namespace: 'my-namespace',
|
||||
},
|
||||
relations: [
|
||||
{
|
||||
target: {
|
||||
kind: 'Resource',
|
||||
namespace: 'my-namespace',
|
||||
name: 'target-name',
|
||||
},
|
||||
type: RELATION_DEPENDS_ON,
|
||||
},
|
||||
],
|
||||
};
|
||||
catalogApi.getEntities.mockResolvedValue({
|
||||
items: [
|
||||
{
|
||||
apiVersion: 'v1',
|
||||
kind: 'Resource',
|
||||
metadata: {
|
||||
namespace: 'my-namespace',
|
||||
name: 'target-name',
|
||||
},
|
||||
spec: {},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const { getByText } = await renderInTestApp(
|
||||
<Wrapper>
|
||||
<EntityProvider entity={entity}>
|
||||
<DependsOnResourcesCard />
|
||||
</EntityProvider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getByText('Resources')).toBeInTheDocument();
|
||||
expect(getByText(/target-name/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { RELATION_DEPENDS_ON } from '@backstage/catalog-model';
|
||||
import React from 'react';
|
||||
import {
|
||||
asResourceEntities,
|
||||
componentEntityHelpLink,
|
||||
RelatedEntitiesCard,
|
||||
resourceEntityColumns,
|
||||
} from '../RelatedEntitiesCard';
|
||||
|
||||
type Props = {
|
||||
variant?: 'gridItem';
|
||||
};
|
||||
|
||||
export const DependsOnResourcesCard = ({ variant = 'gridItem' }: Props) => {
|
||||
return (
|
||||
<RelatedEntitiesCard
|
||||
variant={variant}
|
||||
title="Resources"
|
||||
entityKind="Resource"
|
||||
relationType={RELATION_DEPENDS_ON}
|
||||
columns={resourceEntityColumns}
|
||||
emptyMessage="No resource is a dependency of this component"
|
||||
emptyHelpLink={componentEntityHelpLink}
|
||||
asRenderableEntities={asResourceEntities}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { DependsOnResourcesCard } from './DependsOnResourcesCard';
|
||||
@@ -14,79 +14,30 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ComponentEntity, RELATION_HAS_PART } from '@backstage/catalog-model';
|
||||
import {
|
||||
CodeSnippet,
|
||||
InfoCard,
|
||||
Link,
|
||||
Progress,
|
||||
WarningPanel,
|
||||
} from '@backstage/core';
|
||||
import { Typography } from '@material-ui/core';
|
||||
import {
|
||||
EntityTable,
|
||||
useEntity,
|
||||
useRelatedEntities,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import { RELATION_HAS_PART } from '@backstage/catalog-model';
|
||||
import React from 'react';
|
||||
import {
|
||||
asComponentEntities,
|
||||
componentEntityColumns,
|
||||
componentEntityHelpLink,
|
||||
RelatedEntitiesCard,
|
||||
} from '../RelatedEntitiesCard';
|
||||
|
||||
type Props = {
|
||||
variant?: 'gridItem';
|
||||
};
|
||||
|
||||
const columns = [
|
||||
EntityTable.columns.createEntityRefColumn({ defaultKind: 'component' }),
|
||||
EntityTable.columns.createOwnerColumn(),
|
||||
EntityTable.columns.createSpecTypeColumn(),
|
||||
EntityTable.columns.createSpecLifecycleColumn(),
|
||||
EntityTable.columns.createMetadataDescriptionColumn(),
|
||||
];
|
||||
|
||||
export const HasComponentsCard = ({ variant = 'gridItem' }: Props) => {
|
||||
const { entity } = useEntity();
|
||||
const { entities, loading, error } = useRelatedEntities(entity, {
|
||||
type: RELATION_HAS_PART,
|
||||
kind: 'Component',
|
||||
});
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<InfoCard variant={variant} title="Components">
|
||||
<Progress />
|
||||
</InfoCard>
|
||||
);
|
||||
}
|
||||
|
||||
if (error || !entities) {
|
||||
return (
|
||||
<InfoCard variant={variant} title="Components">
|
||||
<WarningPanel
|
||||
severity="error"
|
||||
title="Could not load components"
|
||||
message={<CodeSnippet text={`${error}`} language="text" />}
|
||||
/>
|
||||
</InfoCard>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EntityTable
|
||||
title="Components"
|
||||
<RelatedEntitiesCard
|
||||
variant={variant}
|
||||
emptyContent={
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<Typography variant="body1">
|
||||
No component is part of this system.
|
||||
</Typography>
|
||||
<Typography variant="body2">
|
||||
<Link to="https://backstage.io/docs/features/software-catalog/descriptor-format#kind-component">
|
||||
Learn how to change this.
|
||||
</Link>
|
||||
</Typography>
|
||||
</div>
|
||||
}
|
||||
columns={columns}
|
||||
entities={entities as ComponentEntity[]}
|
||||
title="Components"
|
||||
entityKind="Component"
|
||||
relationType={RELATION_HAS_PART}
|
||||
columns={componentEntityColumns}
|
||||
emptyMessage="No component is part of this system"
|
||||
emptyHelpLink={componentEntityHelpLink}
|
||||
asRenderableEntities={asComponentEntities}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Entity, RELATION_HAS_PART } from '@backstage/catalog-model';
|
||||
import { ApiProvider, ApiRegistry } from '@backstage/core';
|
||||
import {
|
||||
CatalogApi,
|
||||
catalogApiRef,
|
||||
EntityProvider,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
import { waitFor } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { HasResourcesCard } from './HasResourcesCard';
|
||||
|
||||
describe('<HasResourcesCard />', () => {
|
||||
const catalogApi: jest.Mocked<CatalogApi> = {
|
||||
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 }) => (
|
||||
<ApiProvider apis={apis}>{children}</ApiProvider>
|
||||
);
|
||||
});
|
||||
|
||||
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(
|
||||
<Wrapper>
|
||||
<EntityProvider entity={entity}>
|
||||
<HasResourcesCard />
|
||||
</EntityProvider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
expect(getByText('Resources')).toBeInTheDocument();
|
||||
expect(
|
||||
getByText(/No resource is part of this system/i),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows related resources', async () => {
|
||||
const entity: Entity = {
|
||||
apiVersion: 'v1',
|
||||
kind: 'System',
|
||||
metadata: {
|
||||
name: 'my-system',
|
||||
namespace: 'my-namespace',
|
||||
},
|
||||
relations: [
|
||||
{
|
||||
target: {
|
||||
kind: 'Resource',
|
||||
namespace: 'my-namespace',
|
||||
name: 'target-name',
|
||||
},
|
||||
type: RELATION_HAS_PART,
|
||||
},
|
||||
],
|
||||
};
|
||||
catalogApi.getEntities.mockResolvedValue({
|
||||
items: [
|
||||
{
|
||||
apiVersion: 'v1',
|
||||
kind: 'Resource',
|
||||
metadata: {
|
||||
name: 'target-name',
|
||||
namespace: 'my-namespace',
|
||||
},
|
||||
spec: {},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const { getByText } = await renderInTestApp(
|
||||
<Wrapper>
|
||||
<EntityProvider entity={entity}>
|
||||
<HasResourcesCard />
|
||||
</EntityProvider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getByText('Resources')).toBeInTheDocument();
|
||||
expect(getByText(/target-name/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { RELATION_HAS_PART } from '@backstage/catalog-model';
|
||||
import React from 'react';
|
||||
import {
|
||||
asResourceEntities,
|
||||
RelatedEntitiesCard,
|
||||
resourceEntityColumns,
|
||||
resourceEntityHelpLink,
|
||||
} from '../RelatedEntitiesCard';
|
||||
|
||||
type Props = {
|
||||
variant?: 'gridItem';
|
||||
};
|
||||
|
||||
export const HasResourcesCard = ({ variant = 'gridItem' }: Props) => {
|
||||
return (
|
||||
<RelatedEntitiesCard
|
||||
variant={variant}
|
||||
title="Resources"
|
||||
entityKind="Resource"
|
||||
relationType={RELATION_HAS_PART}
|
||||
columns={resourceEntityColumns}
|
||||
asRenderableEntities={asResourceEntities}
|
||||
emptyMessage="No resource is part of this system"
|
||||
emptyHelpLink={resourceEntityHelpLink}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { HasResourcesCard } from './HasResourcesCard';
|
||||
@@ -14,79 +14,29 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ComponentEntity, RELATION_HAS_PART } from '@backstage/catalog-model';
|
||||
import {
|
||||
CodeSnippet,
|
||||
InfoCard,
|
||||
Link,
|
||||
Progress,
|
||||
WarningPanel,
|
||||
} from '@backstage/core';
|
||||
import { Typography } from '@material-ui/core';
|
||||
import {
|
||||
EntityTable,
|
||||
useEntity,
|
||||
useRelatedEntities,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import { RELATION_HAS_PART } from '@backstage/catalog-model';
|
||||
import React from 'react';
|
||||
import {
|
||||
asComponentEntities,
|
||||
componentEntityColumns,
|
||||
RelatedEntitiesCard,
|
||||
} from '../RelatedEntitiesCard';
|
||||
|
||||
type Props = {
|
||||
variant?: 'gridItem';
|
||||
};
|
||||
|
||||
const columns = [
|
||||
EntityTable.columns.createEntityRefColumn({ defaultKind: 'component' }),
|
||||
EntityTable.columns.createOwnerColumn(),
|
||||
EntityTable.columns.createSpecTypeColumn(),
|
||||
EntityTable.columns.createSpecLifecycleColumn(),
|
||||
EntityTable.columns.createMetadataDescriptionColumn(),
|
||||
];
|
||||
|
||||
export const HasSubcomponentsCard = ({ variant = 'gridItem' }: Props) => {
|
||||
const { entity } = useEntity();
|
||||
const { entities, loading, error } = useRelatedEntities(entity, {
|
||||
type: RELATION_HAS_PART,
|
||||
kind: 'Component',
|
||||
});
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<InfoCard variant={variant} title="Subcomponents">
|
||||
<Progress />
|
||||
</InfoCard>
|
||||
);
|
||||
}
|
||||
|
||||
if (error || !entities) {
|
||||
return (
|
||||
<InfoCard variant={variant} title="Subcomponents">
|
||||
<WarningPanel
|
||||
severity="error"
|
||||
title="Could not load subcomponents"
|
||||
message={<CodeSnippet text={`${error}`} language="text" />}
|
||||
/>
|
||||
</InfoCard>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EntityTable
|
||||
title="Subcomponents"
|
||||
<RelatedEntitiesCard
|
||||
variant={variant}
|
||||
emptyContent={
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<Typography variant="body1">
|
||||
No subcomponent is part of this component.
|
||||
</Typography>
|
||||
<Typography variant="body2">
|
||||
<Link to="https://backstage.io/docs/features/software-catalog/descriptor-format#specsubcomponentof-optional">
|
||||
Learn how to change this.
|
||||
</Link>
|
||||
</Typography>
|
||||
</div>
|
||||
}
|
||||
columns={columns}
|
||||
entities={entities as ComponentEntity[]}
|
||||
title="Subcomponents"
|
||||
entityKind="Component"
|
||||
relationType={RELATION_HAS_PART}
|
||||
columns={componentEntityColumns}
|
||||
asRenderableEntities={asComponentEntities}
|
||||
emptyMessage="No subcomponent is part of this component"
|
||||
emptyHelpLink="https://backstage.io/docs/features/software-catalog/descriptor-format#specsubcomponentof-optional"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -14,76 +14,30 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { RELATION_HAS_PART, SystemEntity } from '@backstage/catalog-model';
|
||||
import {
|
||||
CodeSnippet,
|
||||
InfoCard,
|
||||
Link,
|
||||
Progress,
|
||||
WarningPanel,
|
||||
} from '@backstage/core';
|
||||
import { Typography } from '@material-ui/core';
|
||||
import {
|
||||
EntityTable,
|
||||
useEntity,
|
||||
useRelatedEntities,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import { RELATION_HAS_PART } from '@backstage/catalog-model';
|
||||
import React from 'react';
|
||||
import {
|
||||
asSystemEntities,
|
||||
RelatedEntitiesCard,
|
||||
systemEntityColumns,
|
||||
systemEntityHelpLink,
|
||||
} from '../RelatedEntitiesCard';
|
||||
|
||||
type Props = {
|
||||
variant?: 'gridItem';
|
||||
};
|
||||
|
||||
const columns = [
|
||||
EntityTable.columns.createEntityRefColumn({ defaultKind: 'system' }),
|
||||
EntityTable.columns.createOwnerColumn(),
|
||||
EntityTable.columns.createMetadataDescriptionColumn(),
|
||||
];
|
||||
|
||||
export const HasSystemsCard = ({ variant = 'gridItem' }: Props) => {
|
||||
const { entity } = useEntity();
|
||||
const { entities, loading, error } = useRelatedEntities(entity, {
|
||||
type: RELATION_HAS_PART,
|
||||
});
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<InfoCard variant={variant} title="Systems">
|
||||
<Progress />
|
||||
</InfoCard>
|
||||
);
|
||||
}
|
||||
|
||||
if (error || !entities) {
|
||||
return (
|
||||
<InfoCard variant={variant} title="Systems">
|
||||
<WarningPanel
|
||||
severity="error"
|
||||
title="Could not load systems"
|
||||
message={<CodeSnippet text={`${error}`} language="text" />}
|
||||
/>
|
||||
</InfoCard>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EntityTable
|
||||
title="Systems"
|
||||
<RelatedEntitiesCard
|
||||
variant={variant}
|
||||
emptyContent={
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<Typography variant="body1">
|
||||
No system is part of this domain.
|
||||
</Typography>
|
||||
<Typography variant="body2">
|
||||
<Link to="https://backstage.io/docs/features/software-catalog/descriptor-format#kind-system">
|
||||
Learn how to change this.
|
||||
</Link>
|
||||
</Typography>
|
||||
</div>
|
||||
}
|
||||
columns={columns}
|
||||
entities={entities as SystemEntity[]}
|
||||
title="Systems"
|
||||
entityKind="System"
|
||||
relationType={RELATION_HAS_PART}
|
||||
columns={systemEntityColumns}
|
||||
asRenderableEntities={asSystemEntities}
|
||||
emptyMessage="No system is part of this domain"
|
||||
emptyHelpLink={systemEntityHelpLink}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import {
|
||||
InfoCard,
|
||||
Link,
|
||||
Progress,
|
||||
ResponseErrorPanel,
|
||||
TableColumn,
|
||||
} from '@backstage/core';
|
||||
import { Typography } from '@material-ui/core';
|
||||
import {
|
||||
EntityTable,
|
||||
useEntity,
|
||||
useRelatedEntities,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import React from 'react';
|
||||
|
||||
type Props<T extends Entity> = {
|
||||
variant?: 'gridItem';
|
||||
title: string;
|
||||
columns: TableColumn<T>[];
|
||||
entityKind: string;
|
||||
relationType: string;
|
||||
emptyMessage: string;
|
||||
emptyHelpLink: string;
|
||||
asRenderableEntities: (entities: Entity[]) => T[];
|
||||
};
|
||||
|
||||
export function RelatedEntitiesCard<T extends Entity>({
|
||||
variant = 'gridItem',
|
||||
title,
|
||||
columns,
|
||||
entityKind,
|
||||
relationType,
|
||||
emptyMessage,
|
||||
emptyHelpLink,
|
||||
asRenderableEntities,
|
||||
}: Props<T>) {
|
||||
const { entity } = useEntity();
|
||||
const { entities, loading, error } = useRelatedEntities(entity, {
|
||||
type: relationType,
|
||||
kind: entityKind,
|
||||
});
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<InfoCard variant={variant} title={title}>
|
||||
<Progress />
|
||||
</InfoCard>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<InfoCard variant={variant} title={title}>
|
||||
<ResponseErrorPanel error={error} />
|
||||
</InfoCard>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EntityTable
|
||||
title={title}
|
||||
variant={variant}
|
||||
emptyContent={
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<Typography variant="body1">{emptyMessage}</Typography>
|
||||
<Typography variant="body2">
|
||||
<Link to={emptyHelpLink}>Learn how to change this.</Link>
|
||||
</Typography>
|
||||
</div>
|
||||
}
|
||||
columns={columns}
|
||||
entities={asRenderableEntities(entities || [])}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { RelatedEntitiesCard } from './RelatedEntitiesCard';
|
||||
export * from './presets';
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2021 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import {
|
||||
ComponentEntity,
|
||||
Entity,
|
||||
ResourceEntity,
|
||||
SystemEntity,
|
||||
} from '@backstage/catalog-model';
|
||||
import { TableColumn } from '@backstage/core';
|
||||
import { EntityTable } from '@backstage/plugin-catalog-react';
|
||||
|
||||
export const componentEntityColumns: TableColumn<ComponentEntity>[] = [
|
||||
EntityTable.columns.createEntityRefColumn({ defaultKind: 'component' }),
|
||||
EntityTable.columns.createOwnerColumn(),
|
||||
EntityTable.columns.createSpecTypeColumn(),
|
||||
EntityTable.columns.createSpecLifecycleColumn(),
|
||||
EntityTable.columns.createMetadataDescriptionColumn(),
|
||||
];
|
||||
export const componentEntityHelpLink: string =
|
||||
'https://backstage.io/docs/features/software-catalog/descriptor-format#kind-component';
|
||||
export const asComponentEntities = (entities: Entity[]): ComponentEntity[] =>
|
||||
entities as ComponentEntity[];
|
||||
|
||||
export const resourceEntityColumns: TableColumn<ResourceEntity>[] = [
|
||||
EntityTable.columns.createEntityRefColumn({ defaultKind: 'resource' }),
|
||||
EntityTable.columns.createOwnerColumn(),
|
||||
EntityTable.columns.createSpecTypeColumn(),
|
||||
EntityTable.columns.createSpecLifecycleColumn(),
|
||||
EntityTable.columns.createMetadataDescriptionColumn(),
|
||||
];
|
||||
export const resourceEntityHelpLink: string =
|
||||
'https://backstage.io/docs/features/software-catalog/descriptor-format#kind-resource';
|
||||
export const asResourceEntities = (entities: Entity[]): ResourceEntity[] =>
|
||||
entities as ResourceEntity[];
|
||||
|
||||
export const systemEntityColumns: TableColumn<SystemEntity>[] = [
|
||||
EntityTable.columns.createEntityRefColumn({ defaultKind: 'system' }),
|
||||
EntityTable.columns.createOwnerColumn(),
|
||||
EntityTable.columns.createMetadataDescriptionColumn(),
|
||||
];
|
||||
export const systemEntityHelpLink: string =
|
||||
'https://backstage.io/docs/features/software-catalog/descriptor-format#kind-system';
|
||||
export const asSystemEntities = (entities: Entity[]): SystemEntity[] =>
|
||||
entities as SystemEntity[];
|
||||
@@ -26,7 +26,10 @@ export {
|
||||
catalogPlugin,
|
||||
catalogPlugin as plugin,
|
||||
EntityAboutCard,
|
||||
EntityDependsOnComponentsCard,
|
||||
EntityDependsOnResourcesCard,
|
||||
EntityHasComponentsCard,
|
||||
EntityHasResourcesCard,
|
||||
EntityHasSubcomponentsCard,
|
||||
EntityHasSystemsCard,
|
||||
EntityLinksCard,
|
||||
|
||||
@@ -115,6 +115,37 @@ export const EntityHasSubcomponentsCard = catalogPlugin.provide(
|
||||
}),
|
||||
);
|
||||
|
||||
export const EntityHasResourcesCard = catalogPlugin.provide(
|
||||
createComponentExtension({
|
||||
component: {
|
||||
lazy: () =>
|
||||
import('./components/HasResourcesCard').then(m => m.HasResourcesCard),
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
export const EntityDependsOnComponentsCard = catalogPlugin.provide(
|
||||
createComponentExtension({
|
||||
component: {
|
||||
lazy: () =>
|
||||
import('./components/DependsOnComponentsCard').then(
|
||||
m => m.DependsOnComponentsCard,
|
||||
),
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
export const EntityDependsOnResourcesCard = catalogPlugin.provide(
|
||||
createComponentExtension({
|
||||
component: {
|
||||
lazy: () =>
|
||||
import('./components/DependsOnResourcesCard').then(
|
||||
m => m.DependsOnResourcesCard,
|
||||
),
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
export const EntitySystemDiagramCard = catalogPlugin.provide(
|
||||
createComponentExtension({
|
||||
component: {
|
||||
|
||||
Reference in New Issue
Block a user