Merge branch 'master' of github.com:spotify/backstage into shmidt-i/app-catalog-tabs-routes-everything-is-connected
This commit is contained in:
@@ -48,6 +48,8 @@ lighthouse:
|
||||
baseUrl: http://localhost:3003
|
||||
|
||||
catalog:
|
||||
rules:
|
||||
allow: [Component, API, Group, Template]
|
||||
processors:
|
||||
githubApi:
|
||||
privateToken:
|
||||
|
||||
@@ -32,7 +32,7 @@ For example, given the following configuration:
|
||||
```yaml
|
||||
catalog:
|
||||
rules:
|
||||
- allow: [Component, API, System]
|
||||
- allow: [Component, API, Template]
|
||||
|
||||
locations:
|
||||
- type: github
|
||||
@@ -40,7 +40,7 @@ catalog:
|
||||
allow: [Group]
|
||||
```
|
||||
|
||||
We are able to add entities of kind `Component`, `API`, or `System` from any
|
||||
We are able to add entities of kind `Component`, `API`, or `Template` from any
|
||||
location, and `Group` entities from the `org-data.yaml`, which will also be read
|
||||
as statically configured location.
|
||||
|
||||
|
||||
@@ -58,6 +58,28 @@ Currently the catalog supports loading definitions from GitHub + Local Files. To
|
||||
load from other places, not only will there need to be another preparer, but the
|
||||
support to load the location will also need to be added to the Catalog.
|
||||
|
||||
You can add the template files to the catalog through
|
||||
[static location configuration](../software-catalog/configuration.md#static-location-configuration),
|
||||
for example
|
||||
|
||||
```yaml
|
||||
catalog:
|
||||
locations:
|
||||
- type: github
|
||||
target: https://github.com/spotify/cookiecutter-golang/blob/master/template.yaml
|
||||
allow: [Template]
|
||||
```
|
||||
|
||||
Templates can also be added by posting the to the catalog directly. Note that if
|
||||
you're doing this, you need to configure the catalog to allow template entities
|
||||
to be ingested from any source, for example:
|
||||
|
||||
```yaml
|
||||
catalog:
|
||||
rules:
|
||||
allow: [Component, API, Template]
|
||||
```
|
||||
|
||||
For loading from a file, the following command should work when the backend is
|
||||
running:
|
||||
|
||||
|
||||
@@ -2,13 +2,13 @@ import { createPlugin, createRouteRef } from '@backstage/core';
|
||||
import ExampleComponent from './components/ExampleComponent';
|
||||
|
||||
export const rootRouteRef = createRouteRef({
|
||||
path: '/{{ id }}',
|
||||
title: '{{ id }}',
|
||||
path: '/{{ id }}',
|
||||
title: '{{ id }}',
|
||||
});
|
||||
|
||||
export const plugin = createPlugin({
|
||||
id: '{{ id }}',
|
||||
register({ router }) {
|
||||
router.addRoute(rootRouteRef, ExampleComponent);
|
||||
},
|
||||
id: '{{ id }}',
|
||||
register({ router }) {
|
||||
router.addRoute(rootRouteRef, ExampleComponent);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -38,7 +38,7 @@ const useStyles = makeStyles<BackstageTheme, { backgroundImage: string }>(
|
||||
alignItems: 'center',
|
||||
backgroundImage: props => props.backgroundImage,
|
||||
backgroundPosition: 'center',
|
||||
backgroundSize: '100% 400px',
|
||||
backgroundSize: 'cover',
|
||||
},
|
||||
leftItemsBox: {
|
||||
flex: '1 1 auto',
|
||||
|
||||
@@ -69,11 +69,16 @@ catalog:
|
||||
# Backstage example templates
|
||||
- type: github
|
||||
target: https://github.com/spotify/backstage/blob/master/plugins/scaffolder-backend/sample-templates/react-ssr-template/template.yaml
|
||||
allow: [Template]
|
||||
- type: github
|
||||
target: https://github.com/spotify/backstage/blob/master/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/template.yaml
|
||||
allow: [Template]
|
||||
- type: github
|
||||
target: https://github.com/spotify/backstage/blob/master/plugins/scaffolder-backend/sample-templates/create-react-app/template.yaml
|
||||
allow: [Template]
|
||||
- type: github
|
||||
target: https://github.com/spotify/cookiecutter-golang/blob/master/template.yaml
|
||||
allow: [Template]
|
||||
- type: github
|
||||
target: https://github.com/spotify/backstage/blob/master/plugins/scaffolder-backend/sample-templates/docs-template/template.yaml
|
||||
allow: [Template]
|
||||
|
||||
@@ -199,8 +199,17 @@ class DevAppBuilder {
|
||||
|
||||
for (const plugin of plugins) {
|
||||
for (const output of plugin.output()) {
|
||||
if (output.type === 'legacy-route') {
|
||||
paths.push(output.path);
|
||||
switch (output.type) {
|
||||
case 'legacy-route': {
|
||||
paths.push(output.path);
|
||||
break;
|
||||
}
|
||||
case 'route': {
|
||||
paths.push(output.target.path);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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 { render } from '@testing-library/react';
|
||||
import { AboutCard } from './AboutCard';
|
||||
|
||||
describe('<AboutCard />', () => {
|
||||
it('renders info and "view source" link', () => {
|
||||
const entity = {
|
||||
apiVersion: 'v1',
|
||||
kind: 'Component',
|
||||
metadata: {
|
||||
name: 'software',
|
||||
annotations: {
|
||||
'backstage.io/managed-by-location':
|
||||
'github:https://github.com/spotify/backstage/blob/master/software.yaml',
|
||||
},
|
||||
},
|
||||
spec: {
|
||||
owner: 'guest',
|
||||
type: 'service',
|
||||
lifecycle: 'production',
|
||||
},
|
||||
};
|
||||
const { getByText } = render(<AboutCard entity={entity} />);
|
||||
expect(getByText('service')).toBeInTheDocument();
|
||||
expect(getByText('View Source').closest('a')).toHaveAttribute(
|
||||
'href',
|
||||
'https://github.com/spotify/backstage/blob/master/software.yaml',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* 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 {
|
||||
Grid,
|
||||
Typography,
|
||||
makeStyles,
|
||||
Chip,
|
||||
IconButton,
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
Divider,
|
||||
} from '@material-ui/core';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
|
||||
import GitHubIcon from '@material-ui/icons/GitHub';
|
||||
import { IconLinkVertical } from './IconLinkVertical';
|
||||
import EditIcon from '@material-ui/icons/Edit';
|
||||
import DocsIcon from '@material-ui/icons/Description';
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
links: {
|
||||
margin: theme.spacing(2, 0),
|
||||
display: 'grid',
|
||||
gridAutoFlow: 'column',
|
||||
gridAutoColumns: 'min-content',
|
||||
gridGap: theme.spacing(3),
|
||||
},
|
||||
label: {
|
||||
color: theme.palette.text.secondary,
|
||||
textTransform: 'uppercase',
|
||||
fontSize: '10px',
|
||||
fontWeight: 'bold',
|
||||
letterSpacing: 0.5,
|
||||
overflow: 'hidden',
|
||||
whiteSpace: 'nowrap',
|
||||
},
|
||||
value: {
|
||||
fontWeight: 'bold',
|
||||
overflow: 'hidden',
|
||||
lineHeight: '24px',
|
||||
wordBreak: 'break-word',
|
||||
},
|
||||
description: {
|
||||
wordBreak: 'break-word',
|
||||
},
|
||||
}));
|
||||
|
||||
const iconMap: Record<string, React.ReactNode> = {
|
||||
github: <GitHubIcon />,
|
||||
};
|
||||
|
||||
type CodeLinkInfo = { icon?: React.ReactNode; href?: string };
|
||||
|
||||
function getCodeLinkInfo(entity: Entity): CodeLinkInfo {
|
||||
const location =
|
||||
entity?.metadata?.annotations?.['backstage.io/managed-by-location'];
|
||||
|
||||
if (location) {
|
||||
// split by first `:`
|
||||
// e.g. "github:https://github.com/spotify/backstage/blob/master/software.yaml"
|
||||
const [type, target] = location.split(/:(.+)/);
|
||||
|
||||
return { icon: iconMap[type], href: target };
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
type AboutCardProps = {
|
||||
entity: Entity;
|
||||
};
|
||||
|
||||
export function AboutCard({ entity }: AboutCardProps) {
|
||||
const classes = useStyles();
|
||||
const codeLink = getCodeLinkInfo(entity);
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader
|
||||
title="About"
|
||||
action={
|
||||
<IconButton href={codeLink.href || '#'} aria-label="Edit">
|
||||
<EditIcon />
|
||||
</IconButton>
|
||||
}
|
||||
subheader={
|
||||
<nav className={classes.links}>
|
||||
<IconLinkVertical label="View Source" {...codeLink} />
|
||||
<IconLinkVertical
|
||||
label="View Techdocs"
|
||||
icon={<DocsIcon />}
|
||||
href={`/docs/${''}`}
|
||||
/>
|
||||
</nav>
|
||||
}
|
||||
/>
|
||||
<Divider />
|
||||
<CardContent>
|
||||
<Grid container>
|
||||
<AboutField label="Description" gridSizes={{ xs: 12 }}>
|
||||
<Typography
|
||||
variant="body2"
|
||||
paragraph
|
||||
className={classes.description}
|
||||
>
|
||||
{entity?.metadata?.description || 'No description'}
|
||||
</Typography>
|
||||
</AboutField>
|
||||
<AboutField
|
||||
label="Owner"
|
||||
value={entity?.spec?.owner as string}
|
||||
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="Tags"
|
||||
value="No Tags"
|
||||
gridSizes={{ xs: 12, sm: 6, lg: 4 }}
|
||||
>
|
||||
{(entity?.metadata?.tags || []).map(t => (
|
||||
<Chip key={t} size="small" label={t} />
|
||||
))}
|
||||
</AboutField>
|
||||
</Grid>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
function AboutField({
|
||||
label,
|
||||
value,
|
||||
gridSizes,
|
||||
children,
|
||||
}: {
|
||||
label: string;
|
||||
value?: string;
|
||||
gridSizes?: Record<string, number>;
|
||||
children?: React.ReactNode;
|
||||
}) {
|
||||
const classes = useStyles();
|
||||
|
||||
// Content is either children or a string prop `value`
|
||||
const content = React.Children.count(children) ? (
|
||||
children
|
||||
) : (
|
||||
<Typography variant="body2" className={classes.value}>
|
||||
{value || `unknown`}
|
||||
</Typography>
|
||||
);
|
||||
return (
|
||||
<Grid item {...gridSizes}>
|
||||
<Typography variant="subtitle2" className={classes.label}>
|
||||
{label}
|
||||
</Typography>
|
||||
{content}
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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 * as React from 'react';
|
||||
import { makeStyles, Link } from '@material-ui/core';
|
||||
import LinkIcon from '@material-ui/icons/Link';
|
||||
|
||||
export type IconLinkVerticalProps = {
|
||||
icon?: React.ReactNode;
|
||||
href?: string;
|
||||
label: string;
|
||||
};
|
||||
|
||||
const useIconStyles = makeStyles({
|
||||
link: {
|
||||
display: 'grid',
|
||||
justifyItems: 'center',
|
||||
gridGap: 4,
|
||||
textAlign: 'center',
|
||||
},
|
||||
label: {
|
||||
fontSize: '0.7rem',
|
||||
textTransform: 'uppercase',
|
||||
fontWeight: 600,
|
||||
letterSpacing: 1.2,
|
||||
},
|
||||
});
|
||||
|
||||
export function IconLinkVertical({
|
||||
icon = <LinkIcon />,
|
||||
href = '#',
|
||||
...props
|
||||
}: IconLinkVerticalProps) {
|
||||
const classes = useIconStyles();
|
||||
return (
|
||||
<Link className={classes.link} href={href} {...props}>
|
||||
{icon}
|
||||
<span className={classes.label}>{props.label}</span>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
+1
-13
@@ -14,16 +14,4 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { InfoCard, StructuredMetadataTable } from '@backstage/core';
|
||||
import React, { FC } from 'react';
|
||||
|
||||
type Props = {
|
||||
entity: Entity;
|
||||
};
|
||||
|
||||
export const EntityMetadataCard: FC<Props> = ({ entity }) => (
|
||||
<InfoCard title="Information">
|
||||
<StructuredMetadataTable metadata={entity.metadata} />
|
||||
</InfoCard>
|
||||
);
|
||||
export { IconLinkVertical } from './IconLinkVertical';
|
||||
+1
-16
@@ -14,19 +14,4 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { EntityMetadataCard } from './EntityMetadataCard';
|
||||
|
||||
describe('EntityMetadataCard component', () => {
|
||||
it('should display entity name if provided', async () => {
|
||||
const testEntity: Entity = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Component',
|
||||
metadata: { name: 'test' },
|
||||
};
|
||||
const rendered = await render(<EntityMetadataCard entity={testEntity} />);
|
||||
expect(await rendered.findByText('test')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
export { AboutCard } from './AboutCard';
|
||||
@@ -24,14 +24,14 @@ import {
|
||||
} from '@backstage/plugin-jenkins';
|
||||
import { Grid } from '@material-ui/core';
|
||||
import React, { FC } from 'react';
|
||||
import { EntityMetadataCard } from '../EntityMetadataCard/EntityMetadataCard';
|
||||
import { AboutCard } from '../AboutCard';
|
||||
|
||||
export const EntityPageOverview: FC<{ entity: Entity }> = ({ entity }) => {
|
||||
return (
|
||||
<Content>
|
||||
<Grid container spacing={3}>
|
||||
<Grid item sm={4}>
|
||||
<EntityMetadataCard entity={entity} />
|
||||
<AboutCard entity={entity} />
|
||||
</Grid>
|
||||
{entity.metadata?.annotations?.[
|
||||
'backstage.io/jenkins-github-folder'
|
||||
|
||||
Reference in New Issue
Block a user