Merge pull request #4090 from SDA-SE/feat/display-systems-in-catalog
Display the owner and system as links to the entity pages in the about card
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/plugin-catalog': patch
|
||||
---
|
||||
|
||||
Display the owner, system, and domain as links to the entity pages in the about card.
|
||||
Only display fields in the about card that are applicable to the entity kind.
|
||||
@@ -14,15 +14,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Grid, Typography, Chip, makeStyles } from '@material-ui/core';
|
||||
import { AboutField } from './AboutField';
|
||||
import {
|
||||
Entity,
|
||||
ENTITY_DEFAULT_NAMESPACE,
|
||||
RELATION_OWNED_BY,
|
||||
serializeEntityRef,
|
||||
RELATION_PART_OF,
|
||||
} from '@backstage/catalog-model';
|
||||
import { Chip, Grid, makeStyles, Typography } from '@material-ui/core';
|
||||
import React from 'react';
|
||||
import { EntityRefLink } from '../EntityRefLink';
|
||||
import { getEntityRelations } from '../getEntityRelations';
|
||||
import { AboutField } from './AboutField';
|
||||
|
||||
const useStyles = makeStyles({
|
||||
description: {
|
||||
@@ -36,6 +37,17 @@ type Props = {
|
||||
|
||||
export const AboutContent = ({ entity }: Props) => {
|
||||
const classes = useStyles();
|
||||
const isSystem = entity.kind.toLowerCase() === 'system';
|
||||
const isDomain = entity.kind.toLowerCase() === 'domain';
|
||||
const isResource = entity.kind.toLowerCase() === 'resource';
|
||||
const [partOfSystemRelation] = getEntityRelations(entity, RELATION_PART_OF, {
|
||||
kind: 'system',
|
||||
});
|
||||
const [partOfDomainRelation] = getEntityRelations(entity, RELATION_PART_OF, {
|
||||
kind: 'domain',
|
||||
});
|
||||
const ownedByRelations = getEntityRelations(entity, RELATION_OWNED_BY);
|
||||
|
||||
return (
|
||||
<Grid container>
|
||||
<AboutField label="Description" gridSizes={{ xs: 12 }}>
|
||||
@@ -43,32 +55,56 @@ export const AboutContent = ({ entity }: Props) => {
|
||||
{entity?.metadata?.description || 'No description'}
|
||||
</Typography>
|
||||
</AboutField>
|
||||
<AboutField
|
||||
label="Owner"
|
||||
value={entity?.relations
|
||||
?.filter(r => r.type === RELATION_OWNED_BY)
|
||||
.map(({ target: { kind, name, namespace } }) =>
|
||||
// TODO(Rugvip): we want to provide some utils for this
|
||||
serializeEntityRef({
|
||||
kind,
|
||||
name,
|
||||
namespace:
|
||||
namespace === ENTITY_DEFAULT_NAMESPACE ? undefined : namespace,
|
||||
}),
|
||||
)
|
||||
.join(', ')}
|
||||
gridSizes={{ xs: 12, sm: 6, lg: 4 }}
|
||||
/>
|
||||
<AboutField
|
||||
label="Type"
|
||||
value={entity?.spec?.type as string}
|
||||
gridSizes={{ xs: 12, sm: 6, lg: 4 }}
|
||||
/>
|
||||
<AboutField
|
||||
label="Lifecycle"
|
||||
value={entity?.spec?.lifecycle as string}
|
||||
gridSizes={{ xs: 12, sm: 6, lg: 4 }}
|
||||
/>
|
||||
<AboutField label="Owner" gridSizes={{ xs: 12, sm: 6, lg: 4 }}>
|
||||
{ownedByRelations.map((t, i) => (
|
||||
<React.Fragment key={i}>
|
||||
{i > 0 && ', '}
|
||||
<EntityRefLink entityRef={t} />
|
||||
</React.Fragment>
|
||||
))}
|
||||
</AboutField>
|
||||
{isSystem && (
|
||||
<AboutField
|
||||
label="Domain"
|
||||
value="No Domain"
|
||||
gridSizes={{ xs: 12, sm: 6, lg: 4 }}
|
||||
>
|
||||
{partOfDomainRelation && (
|
||||
<EntityRefLink
|
||||
entityRef={partOfDomainRelation}
|
||||
defaultKind="domain"
|
||||
/>
|
||||
)}
|
||||
</AboutField>
|
||||
)}
|
||||
{!isSystem && !isDomain && (
|
||||
<AboutField
|
||||
label="System"
|
||||
value="No System"
|
||||
gridSizes={{ xs: 12, sm: 6, lg: 4 }}
|
||||
>
|
||||
{partOfSystemRelation && (
|
||||
<EntityRefLink
|
||||
entityRef={partOfSystemRelation}
|
||||
defaultKind="system"
|
||||
/>
|
||||
)}
|
||||
</AboutField>
|
||||
)}
|
||||
{!isSystem && !isDomain && (
|
||||
<AboutField
|
||||
label="Type"
|
||||
value={entity?.spec?.type as string}
|
||||
gridSizes={{ xs: 12, sm: 6, lg: 4 }}
|
||||
/>
|
||||
)}
|
||||
{!isSystem && !isDomain && !isResource && (
|
||||
<AboutField
|
||||
label="Lifecycle"
|
||||
value={entity?.spec?.lifecycle as string}
|
||||
gridSizes={{ xs: 12, sm: 6, lg: 4 }}
|
||||
/>
|
||||
)}
|
||||
<AboutField
|
||||
label="Tags"
|
||||
value="No Tags"
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* 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 { render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { MemoryRouter } from 'react-router';
|
||||
import { EntityRefLink } from './EntityRefLink';
|
||||
|
||||
describe('<EntityRefLink />', () => {
|
||||
it('renders link for entity in default namespace', () => {
|
||||
const entity = {
|
||||
apiVersion: 'v1',
|
||||
kind: 'Component',
|
||||
metadata: {
|
||||
name: 'software',
|
||||
},
|
||||
spec: {
|
||||
owner: 'guest',
|
||||
type: 'service',
|
||||
lifecycle: 'production',
|
||||
},
|
||||
};
|
||||
const { getByText } = render(<EntityRefLink entityRef={entity} />, {
|
||||
wrapper: MemoryRouter,
|
||||
});
|
||||
|
||||
expect(getByText('component:software')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders link for entity in other namespace', () => {
|
||||
const entity = {
|
||||
apiVersion: 'v1',
|
||||
kind: 'Component',
|
||||
metadata: {
|
||||
name: 'software',
|
||||
namespace: 'test',
|
||||
},
|
||||
spec: {
|
||||
owner: 'guest',
|
||||
type: 'service',
|
||||
lifecycle: 'production',
|
||||
},
|
||||
};
|
||||
const { getByText } = render(<EntityRefLink entityRef={entity} />, {
|
||||
wrapper: MemoryRouter,
|
||||
});
|
||||
expect(getByText('component:test/software')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders link for entity and hides default kind', () => {
|
||||
const entity = {
|
||||
apiVersion: 'v1',
|
||||
kind: 'Component',
|
||||
metadata: {
|
||||
name: 'software',
|
||||
namespace: 'test',
|
||||
},
|
||||
spec: {
|
||||
owner: 'guest',
|
||||
type: 'service',
|
||||
lifecycle: 'production',
|
||||
},
|
||||
};
|
||||
const { getByText } = render(
|
||||
<EntityRefLink entityRef={entity} defaultKind="Component" />,
|
||||
{
|
||||
wrapper: MemoryRouter,
|
||||
},
|
||||
);
|
||||
expect(getByText('test/software')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders link for entity name in default namespace', () => {
|
||||
const entityName = {
|
||||
kind: 'Component',
|
||||
namespace: 'default',
|
||||
name: 'software',
|
||||
};
|
||||
const { getByText } = render(<EntityRefLink entityRef={entityName} />, {
|
||||
wrapper: MemoryRouter,
|
||||
});
|
||||
expect(getByText('component:software')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders link for entity name in other namespace', () => {
|
||||
const entityName = {
|
||||
kind: 'Component',
|
||||
namespace: 'test',
|
||||
name: 'software',
|
||||
};
|
||||
const { getByText } = render(<EntityRefLink entityRef={entityName} />, {
|
||||
wrapper: MemoryRouter,
|
||||
});
|
||||
expect(getByText('component:test/software')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders link for entity name and hides default kind', () => {
|
||||
const entityName = {
|
||||
kind: 'Component',
|
||||
namespace: 'test',
|
||||
name: 'software',
|
||||
};
|
||||
const { getByText } = render(
|
||||
<EntityRefLink entityRef={entityName} defaultKind="component" />,
|
||||
{
|
||||
wrapper: MemoryRouter,
|
||||
},
|
||||
);
|
||||
expect(getByText('test/software')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import {
|
||||
Entity,
|
||||
EntityName,
|
||||
ENTITY_DEFAULT_NAMESPACE,
|
||||
serializeEntityRef,
|
||||
} from '@backstage/catalog-model';
|
||||
import { Link } from '@material-ui/core';
|
||||
import React from 'react';
|
||||
import { generatePath } from 'react-router';
|
||||
import { Link as RouterLink } from 'react-router-dom';
|
||||
import { entityRoute } from '../../routes';
|
||||
|
||||
type EntityRefLinkProps = {
|
||||
entityRef: Entity | EntityName;
|
||||
defaultKind?: string;
|
||||
};
|
||||
|
||||
// TODO: This component is private for now, as it should probably belong into
|
||||
// some kind of helper module for the catalog plugin to avoid a dependency on
|
||||
// the catalog plugin itself.
|
||||
export const EntityRefLink = ({
|
||||
entityRef,
|
||||
defaultKind,
|
||||
}: EntityRefLinkProps) => {
|
||||
let kind;
|
||||
let namespace;
|
||||
let name;
|
||||
|
||||
if ('metadata' in entityRef) {
|
||||
kind = entityRef.kind;
|
||||
namespace = entityRef.metadata.namespace;
|
||||
name = entityRef.metadata.name;
|
||||
} else {
|
||||
kind = entityRef.kind;
|
||||
namespace = entityRef.namespace;
|
||||
name = entityRef.name;
|
||||
}
|
||||
|
||||
if (namespace === ENTITY_DEFAULT_NAMESPACE) {
|
||||
namespace = undefined;
|
||||
}
|
||||
|
||||
kind = kind.toLowerCase();
|
||||
|
||||
const title = `${serializeEntityRef({
|
||||
kind: defaultKind && defaultKind.toLowerCase() === kind ? undefined : kind,
|
||||
name,
|
||||
namespace,
|
||||
})}`;
|
||||
const routeParams = {
|
||||
kind,
|
||||
namespace: namespace?.toLowerCase() ?? ENTITY_DEFAULT_NAMESPACE,
|
||||
name,
|
||||
};
|
||||
|
||||
// TODO: Use useRouteRef here to generate the path
|
||||
return (
|
||||
<Link
|
||||
component={RouterLink}
|
||||
to={generatePath(`/catalog/${entityRoute.path}`, routeParams)}
|
||||
>
|
||||
{title}
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
@@ -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 { EntityRefLink } from './EntityRefLink';
|
||||
Reference in New Issue
Block a user