From 660de68e7e41b4c855ee084973ceabf96583fc0f Mon Sep 17 00:00:00 2001 From: nikek Date: Mon, 31 Aug 2020 23:01:40 +0200 Subject: [PATCH 1/5] Add AboutCard component to EntityPageOverview --- packages/core/src/layout/Header/Header.tsx | 2 +- .../components/AboutCard/AboutCard.test.tsx | 46 +++++ .../src/components/AboutCard/AboutCard.tsx | 182 ++++++++++++++++++ .../IconLinkVertical/IconLinkVertical.tsx | 53 +++++ .../IconLinkVertical/index.ts} | 14 +- .../index.ts} | 17 +- .../EntityPageOverview/EntityPageOverview.tsx | 4 +- 7 files changed, 286 insertions(+), 32 deletions(-) create mode 100644 plugins/catalog/src/components/AboutCard/AboutCard.test.tsx create mode 100644 plugins/catalog/src/components/AboutCard/AboutCard.tsx create mode 100644 plugins/catalog/src/components/AboutCard/IconLinkVertical/IconLinkVertical.tsx rename plugins/catalog/src/components/{EntityMetadataCard/EntityMetadataCard.tsx => AboutCard/IconLinkVertical/index.ts} (61%) rename plugins/catalog/src/components/{EntityMetadataCard/EntityMetadataCard.test.tsx => AboutCard/index.ts} (50%) diff --git a/packages/core/src/layout/Header/Header.tsx b/packages/core/src/layout/Header/Header.tsx index 7aafc2730e..6b9ea05245 100644 --- a/packages/core/src/layout/Header/Header.tsx +++ b/packages/core/src/layout/Header/Header.tsx @@ -38,7 +38,7 @@ const useStyles = makeStyles( alignItems: 'center', backgroundImage: props => props.backgroundImage, backgroundPosition: 'center', - backgroundSize: '100% 400px', + backgroundSize: 'cover', }, leftItemsBox: { flex: '1 1 auto', diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx new file mode 100644 index 0000000000..bf69818995 --- /dev/null +++ b/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx @@ -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('', () => { + 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(); + expect(getByText('service')).toBeInTheDocument(); + expect(getByText('View Source').closest('a')).toHaveAttribute( + 'href', + 'https://github.com/spotify/backstage/blob/master/software.yaml', + ); + }); +}); diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.tsx new file mode 100644 index 0000000000..d27175fcf3 --- /dev/null +++ b/plugins/catalog/src/components/AboutCard/AboutCard.tsx @@ -0,0 +1,182 @@ +/* + * 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, 2), + display: 'grid', + gridAutoFlow: 'column', + gridAutoColumns: 'min-content', + gridGap: theme.spacing(2), + }, + label: { + color: '#9e9e9e', + textTransform: 'uppercase', + fontSize: '12px', + fontWeight: 'bold', + overflow: 'hidden', + whiteSpace: 'nowrap', + }, + value: { + fontWeight: 'bold', + overflow: 'hidden', + lineHeight: '24px', + wordBreak: 'break-word', + }, + description: { + wordBreak: 'break-word', + }, +})); + +const iconMap: Record = { + github: , +}; + +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 ( + + + + + } + subheader={ + + } + /> + + + + + + {entity?.metadata?.description || 'No description'} + + + + + + + {(entity?.metadata?.tags || []).map(t => ( + + ))} + + + + + ); +} + +function AboutField({ + label, + value, + gridSizes, + children, +}: { + label: string; + value?: string; + gridSizes?: Record; + children?: React.ReactNode; +}) { + const classes = useStyles(); + + // Content is either children or a string prop `value` + const content = React.Children.count(children) ? ( + children + ) : ( + + {value || `unknown`} + + ); + return ( + + + {label} + + {content} + + ); +} diff --git a/plugins/catalog/src/components/AboutCard/IconLinkVertical/IconLinkVertical.tsx b/plugins/catalog/src/components/AboutCard/IconLinkVertical/IconLinkVertical.tsx new file mode 100644 index 0000000000..91a5211925 --- /dev/null +++ b/plugins/catalog/src/components/AboutCard/IconLinkVertical/IconLinkVertical.tsx @@ -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 = , + href = '#', + ...props +}: IconLinkVerticalProps) { + const classes = useIconStyles(); + return ( + + {icon} + {props.label} + + ); +} diff --git a/plugins/catalog/src/components/EntityMetadataCard/EntityMetadataCard.tsx b/plugins/catalog/src/components/AboutCard/IconLinkVertical/index.ts similarity index 61% rename from plugins/catalog/src/components/EntityMetadataCard/EntityMetadataCard.tsx rename to plugins/catalog/src/components/AboutCard/IconLinkVertical/index.ts index 57082a9c91..ddc146dbd8 100644 --- a/plugins/catalog/src/components/EntityMetadataCard/EntityMetadataCard.tsx +++ b/plugins/catalog/src/components/AboutCard/IconLinkVertical/index.ts @@ -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 = ({ entity }) => ( - - - -); +export { IconLinkVertical } from './IconLinkVertical'; diff --git a/plugins/catalog/src/components/EntityMetadataCard/EntityMetadataCard.test.tsx b/plugins/catalog/src/components/AboutCard/index.ts similarity index 50% rename from plugins/catalog/src/components/EntityMetadataCard/EntityMetadataCard.test.tsx rename to plugins/catalog/src/components/AboutCard/index.ts index 4bb64f221d..93765ff942 100644 --- a/plugins/catalog/src/components/EntityMetadataCard/EntityMetadataCard.test.tsx +++ b/plugins/catalog/src/components/AboutCard/index.ts @@ -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(); - expect(await rendered.findByText('test')).toBeInTheDocument(); - }); -}); +export { AboutCard } from './AboutCard'; diff --git a/plugins/catalog/src/components/EntityPageOverview/EntityPageOverview.tsx b/plugins/catalog/src/components/EntityPageOverview/EntityPageOverview.tsx index 45a62198c7..4067fef29a 100644 --- a/plugins/catalog/src/components/EntityPageOverview/EntityPageOverview.tsx +++ b/plugins/catalog/src/components/EntityPageOverview/EntityPageOverview.tsx @@ -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 ( - + {entity.metadata?.annotations?.[ 'backstage.io/jenkins-github-folder' From f7ac665b75ef63951a60be700dbede728806d6aa Mon Sep 17 00:00:00 2001 From: nikek Date: Tue, 1 Sep 2020 10:26:16 +0200 Subject: [PATCH 2/5] Add semantic color to AboutCard field titles --- plugins/catalog/src/components/AboutCard/AboutCard.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.tsx index d27175fcf3..d83beaedb5 100644 --- a/plugins/catalog/src/components/AboutCard/AboutCard.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutCard.tsx @@ -35,17 +35,18 @@ import DocsIcon from '@material-ui/icons/Description'; const useStyles = makeStyles(theme => ({ links: { - margin: theme.spacing(2, 0, 2), + margin: theme.spacing(2, 0), display: 'grid', gridAutoFlow: 'column', gridAutoColumns: 'min-content', - gridGap: theme.spacing(2), + gridGap: theme.spacing(3), }, label: { - color: '#9e9e9e', + color: theme.palette.text.secondary, textTransform: 'uppercase', - fontSize: '12px', + fontSize: '10px', fontWeight: 'bold', + letterSpacing: 0.5, overflow: 'hidden', whiteSpace: 'nowrap', }, From 81cb294dacbf23bc863afb3b287a24ff9a68d61b Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 1 Sep 2020 10:36:21 +0200 Subject: [PATCH 3/5] config,docs: add templates as allowed kind and document process --- app-config.yaml | 2 ++ .../software-catalog/configuration.md | 4 ++-- .../software-templates/adding-templates.md | 22 +++++++++++++++++++ .../templates/default-app/app-config.yaml.hbs | 5 +++++ 4 files changed, 31 insertions(+), 2 deletions(-) diff --git a/app-config.yaml b/app-config.yaml index 3b3733cf54..ba556873aa 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -48,6 +48,8 @@ lighthouse: baseUrl: http://localhost:3003 catalog: + rules: + allow: [Component, API, Group, Template] processors: githubApi: privateToken: diff --git a/docs/features/software-catalog/configuration.md b/docs/features/software-catalog/configuration.md index d4cc7fc0c2..acfaf115de 100644 --- a/docs/features/software-catalog/configuration.md +++ b/docs/features/software-catalog/configuration.md @@ -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. diff --git a/docs/features/software-templates/adding-templates.md b/docs/features/software-templates/adding-templates.md index 67467bd98e..16419b4dfe 100644 --- a/docs/features/software-templates/adding-templates.md +++ b/docs/features/software-templates/adding-templates.md @@ -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: diff --git a/packages/create-app/templates/default-app/app-config.yaml.hbs b/packages/create-app/templates/default-app/app-config.yaml.hbs index aff375ad29..e194295df3 100644 --- a/packages/create-app/templates/default-app/app-config.yaml.hbs +++ b/packages/create-app/templates/default-app/app-config.yaml.hbs @@ -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] From a85ca3123c8a00b590be214cb3f992965d2bf598 Mon Sep 17 00:00:00 2001 From: Marcus Eide Date: Tue, 1 Sep 2020 10:46:52 +0200 Subject: [PATCH 4/5] Add non-legacy routes when in isolation --- packages/dev-utils/src/devApp/render.tsx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/dev-utils/src/devApp/render.tsx b/packages/dev-utils/src/devApp/render.tsx index 0e28a5bf8b..410f7592a5 100644 --- a/packages/dev-utils/src/devApp/render.tsx +++ b/packages/dev-utils/src/devApp/render.tsx @@ -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; } } } From 042f2cbb44faeb2a92ad43841a84776681947c49 Mon Sep 17 00:00:00 2001 From: Marcus Eide Date: Tue, 1 Sep 2020 10:53:55 +0200 Subject: [PATCH 5/5] Fix formatting in plugin template --- .../cli/templates/default-plugin/src/plugin.ts.hbs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/cli/templates/default-plugin/src/plugin.ts.hbs b/packages/cli/templates/default-plugin/src/plugin.ts.hbs index 6a410a6a28..1bf4d07cdb 100644 --- a/packages/cli/templates/default-plugin/src/plugin.ts.hbs +++ b/packages/cli/templates/default-plugin/src/plugin.ts.hbs @@ -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); + }, });