Merge branch 'master' of github.com:spotify/backstage into blam/scaffolder

* 'master' of github.com:spotify/backstage:
  [Plugin] Explore features in Backstage (#696)
  [Storybook] Fix Storybook compilation warnings (#701)
  Make the inventory backend standalone runnable
This commit is contained in:
blam
2020-05-03 15:46:23 +02:00
36 changed files with 806 additions and 43 deletions
+1 -1
View File
@@ -4,4 +4,4 @@
# The last matching pattern takes precedence.
# https://help.github.com/articles/about-codeowners/
* @spotify/backstage-core
* @spotify/backstage-core
+1 -1
View File
@@ -32,7 +32,7 @@
"husky": "^4.2.3",
"lerna": "^3.20.2",
"lint-staged": "^10.1.0",
"prettier": "^1.19.1"
"prettier": "^2.0.5"
},
"husky": {
"hooks": {
+1
View File
@@ -20,6 +20,7 @@
"dependencies": {
"@backstage/cli": "^0.1.1-alpha.4",
"@backstage/core": "^0.1.1-alpha.4",
"@backstage/plugin-explore": "^0.1.1-alpha.4",
"@backstage/plugin-home-page": "^0.1.1-alpha.4",
"@backstage/plugin-inventory": "^0.1.1-alpha.4",
"@backstage/plugin-lighthouse": "^0.1.1-alpha.4",
@@ -18,6 +18,7 @@ import React, { FC, useContext } from 'react';
import PropTypes from 'prop-types';
import { Link, makeStyles, Typography } from '@material-ui/core';
import HomeIcon from '@material-ui/icons/Home';
import ExploreIcon from '@material-ui/icons/Explore';
import AccountCircle from '@material-ui/icons/AccountCircle';
import CreateComponentIcon from '@material-ui/icons/Create';
import AccountTreeIcon from '@material-ui/icons/AccountTree';
@@ -81,7 +82,9 @@ const Root: FC<{}> = ({ children }) => (
<SidebarSpacer />
<SidebarDivider />
<SidebarItem icon={HomeIcon} to="/" text="Home" />
<SidebarItem icon={ExploreIcon} to="/explore" text="Explore" />
<SidebarItem icon={CreateComponentIcon} to="/scaffolder" text="Create" />
<SidebarDivider />
<SidebarItem icon={AccountTreeIcon} to="/inventory" text="Inventory" />
<SidebarItem icon={AccountCircle} to="/login" text="Login" />
<SidebarDivider />
+1 -1
View File
@@ -13,10 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { plugin as HomePagePlugin } from '@backstage/plugin-home-page';
export { plugin as WelcomePlugin } from '@backstage/plugin-welcome';
export { plugin as LighthousePlugin } from '@backstage/plugin-lighthouse';
export { plugin as InventoryPlugin } from '@backstage/plugin-inventory';
export { plugin as ScaffolderPlugin } from '@backstage/plugin-scaffolder';
export { plugin as TechRadar } from '@backstage/plugin-tech-radar';
export { plugin as Explore } from '@backstage/plugin-explore';
+1 -1
View File
@@ -24,8 +24,8 @@ import {
const app = express();
app.use(requestLoggingHandler());
app.use('/home', myHomeRouter);
app.use(errorHandler());
app.use(notFoundHandler());
app.use(errorHandler());
app.listen(PORT, () => {
getRootLogger().info(`Listening on port ${PORT}`);
+24 -18
View File
@@ -28,32 +28,38 @@ import {
notFoundHandler,
requestLoggingHandler,
} from '@backstage/backend-common';
import { router as inventoryRouter } from '@backstage/plugin-inventory-backend';
import { createRouter as inventoryRouter } from '@backstage/plugin-inventory-backend';
import { createScaffolder } from '@backstage/plugin-scaffolder-backend';
import compression from 'compression';
import cors from 'cors';
import express from 'express';
import helmet from 'helmet';
import { testRouter } from './test';
import { createScaffolder } from '@backstage/plugin-scaffolder-backend';
const DEFAULT_PORT = 7000;
const PORT = parseInt(process.env.PORT ?? '', 10) || DEFAULT_PORT;
const app = express();
const logger = getRootLogger().child({ type: 'plugin' });
const scaffolder = createScaffolder();
async function main() {
const inventory = await inventoryRouter({ logger });
const scaffolder = createScaffolder();
app.use(helmet());
app.use(cors());
app.use(compression());
app.use(express.json());
app.use(requestLoggingHandler());
app.use('/test', testRouter);
app.use('/inventory', inventoryRouter);
app.use('/scaffolder', scaffolder);
app.use(errorHandler());
app.use(notFoundHandler());
const app = express();
app.listen(PORT, () => {
getRootLogger().info(`Listening on port ${PORT}`);
});
app.use(helmet());
app.use(cors());
app.use(compression());
app.use(express.json());
app.use(requestLoggingHandler());
app.use('/test', testRouter);
app.use('/inventory', inventory);
app.use('/scaffolder', scaffolder);
app.use(notFoundHandler());
app.use(errorHandler());
app.listen(PORT, () => {
getRootLogger().info(`Listening on port ${PORT}`);
});
}
main();
@@ -14,4 +14,5 @@
* limitations under the License.
*/
export { default, StepType } from './Sequence';
export { default } from './Sequence';
export type { StepType } from './Sequence';
+2 -1
View File
@@ -16,7 +16,8 @@
export * from './api';
export { default as Page } from './layout/Page';
export { gradients, pageTheme, PageTheme } from './layout/Page';
export { gradients, pageTheme } from './layout/Page';
export type { PageTheme } from './layout/Page';
export { default as Content } from './layout/Content/Content';
export { default as ContentHeader } from './layout/ContentHeader/ContentHeader';
export { default as Header } from './layout/Header/Header';
+2 -1
View File
@@ -14,4 +14,5 @@
* limitations under the License.
*/
export { default, Props } from './BottomLink';
export { default } from './BottomLink';
export type { Props } from './BottomLink';
+2 -1
View File
@@ -15,4 +15,5 @@
*/
export { default } from './Page';
export { gradients, pageTheme, PageTheme } from './PageThemeProvider';
export { gradients, pageTheme } from './PageThemeProvider';
export type { PageTheme } from './PageThemeProvider';
+3
View File
@@ -0,0 +1,3 @@
module.exports = {
extends: [require.resolve('@backstage/cli/config/eslint')],
};
+6
View File
@@ -0,0 +1,6 @@
# Title
Welcome to the explore plugin!
## Sub-section 1
## Sub-section 2
+39
View File
@@ -0,0 +1,39 @@
{
"name": "@backstage/plugin-explore",
"version": "0.1.1-alpha.4",
"main": "dist/index.esm.js",
"types": "dist/index.d.ts",
"license": "Apache-2.0",
"private": true,
"scripts": {
"build": "backstage-cli plugin:build",
"lint": "backstage-cli lint",
"test": "backstage-cli test",
"clean": "backstage-cli clean"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.4",
"@backstage/test-utils": "^0.1.1-alpha.4",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"@types/jest": "^24.0.0",
"@types/node": "^12.0.0",
"@types/testing-library__jest-dom": "5.0.2",
"jest-fetch-mock": "^3.0.3"
},
"dependencies": {
"@backstage/core": "^0.1.1-alpha.4",
"@backstage/theme": "^0.1.1-alpha.4",
"@material-ui/core": "^4.9.1",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"classnames": "^2.2.6",
"react": "16.13.1",
"react-dom": "16.13.1",
"react-use": "^13.0.0"
},
"files": [
"dist"
]
}
@@ -0,0 +1,93 @@
/*
* 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 { wrapInThemedTestApp } from '@backstage/test-utils';
import ExploreCard from './ExploreCard';
const minProps = {
card: {
title: 'Title',
description: 'Something something',
url: 'http://spotify.com/',
image: 'https://developer.spotify.com/assets/WebAPI_intro.png',
tags: ['tag1', 'tag2'],
},
};
describe('<ExploreCard />', () => {
it('renders without exploding', () => {
const { getByText } = render(
wrapInThemedTestApp(<ExploreCard {...minProps} />),
);
expect(getByText('Explore')).toBeInTheDocument();
});
it('renders props correctly', () => {
const { getByText } = render(
wrapInThemedTestApp(<ExploreCard {...minProps} />),
);
expect(getByText(minProps.card.title)).toBeInTheDocument();
expect(getByText(minProps.card.description)).toBeInTheDocument();
});
it('should link out', () => {
const rendered = render(wrapInThemedTestApp(<ExploreCard {...minProps} />));
const anchor = rendered.container.querySelector('a');
expect(anchor.href).toBe(minProps.card.url);
});
it('renders default description when missing', () => {
const propsWithoutDescription = {
card: {
card: {
title: 'Title',
url: 'http://spotify.com/',
image: 'https://developer.spotify.com/assets/WebAPI_intro.png',
},
},
};
const { getByText } = render(
wrapInThemedTestApp(<ExploreCard {...propsWithoutDescription} />),
);
expect(getByText('Description missing')).toBeInTheDocument();
});
it('renders lifecycle correctly', () => {
const propsWithLifecycle = {
card: {
title: 'Title',
url: 'http://spotify.com/',
image: 'https://developer.spotify.com/assets/WebAPI_intro.png',
lifecycle: 'GA',
},
};
const { queryByText } = render(
wrapInThemedTestApp(<ExploreCard {...propsWithLifecycle} />),
);
expect(queryByText('GA')).not.toBeInTheDocument();
});
it('renders tags correctly', () => {
const { getByText } = render(
wrapInThemedTestApp(<ExploreCard {...minProps} />),
);
expect(getByText(minProps.card.tags[0])).toBeInTheDocument();
expect(getByText(minProps.card.tags[1])).toBeInTheDocument();
});
});
@@ -0,0 +1,134 @@
/*
* 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, { FC } from 'react';
import classNames from 'classnames';
import {
Button,
Card,
CardActions,
CardContent,
CardMedia,
Chip,
Typography,
makeStyles,
} from '@material-ui/core';
import { BackstageTheme } from '@backstage/theme';
const useStyles = makeStyles<BackstageTheme>(theme => ({
card: {
display: 'flex',
flexDirection: 'column',
},
cardActions: {
flexGrow: 1,
alignItems: 'flex-end',
},
media: {
height: 128,
},
mediaContain: {
backgroundSize: 'contain',
},
lifecycle: {
lineHeight: '0.8em',
color: 'white',
},
ga: {
backgroundColor: theme.palette.status.ok,
},
alpha: {
backgroundColor: theme.palette.status.error,
},
beta: {
backgroundColor: theme.palette.status.warning,
},
domains: {
position: 'relative',
top: theme.spacing(2),
},
spaceBetween: {
justifyContent: 'space-between',
},
}));
export type CardData = {
title: string;
description: string;
url: string;
image: string;
tags?: string[];
lifecycle?: string;
newsTag?: string;
};
type Props = {
card: CardData;
objectFit?: 'cover' | 'contain';
};
const ExploreCard: FC<Props> = ({ card, objectFit }) => {
const classes = useStyles();
const { title, description, url, image, lifecycle, newsTag, tags } = card;
return (
<Card key={title} className={classes.card}>
<CardMedia
image={image}
title={title}
className={classNames(classes.media, {
[classes.mediaContain]: objectFit === 'contain',
})}
/>
<CardContent>
<Typography gutterBottom variant="h5">
{title}{' '}
{lifecycle && lifecycle.toLowerCase() !== 'ga' && (
<Chip
label={lifecycle}
className={classNames(
classes.lifecycle,
classes[lifecycle.toLowerCase()],
)}
/>
)}
</Typography>
<Typography paragraph>
{description || 'Description missing'}
</Typography>
{tags && (
<div className={classes.domains}>
{tags.map((item, idx) => (
<Chip key={idx} label={item} />
))}
</div>
)}
</CardContent>
<CardActions
className={classNames(classes.cardActions, {
[classes.spaceBetween]: newsTag,
})}
>
<Button size="small" color="primary" href={url} disabled={!url}>
Explore
</Button>
</CardActions>
</Card>
);
};
export default ExploreCard;
@@ -0,0 +1,93 @@
/*
* 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, { FC } from 'react';
import { makeStyles, Typography } from '@material-ui/core';
import {
Content,
ContentHeader,
Header,
Page,
pageTheme,
SupportButton,
} from '@backstage/core';
import ExploreCard, { CardData } from './ExploreCard';
import { BackstageTheme } from '@backstage/theme';
const useStyles = makeStyles<BackstageTheme>(theme => ({
container: {
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, 296px)',
gridGap: theme.spacing(3),
marginBottom: theme.spacing(6),
},
}));
const toolsCards = [
{
title: 'Lighthouse',
description:
"Google's Lighthouse tool is a great resource for benchmarking and improving the accessibility, performance, SEO, and best practices of your website.",
url: '/lighthouse',
image:
'https://raw.githubusercontent.com/GoogleChrome/lighthouse/8b3d7f052b2e64dd857e741d7395647f487697e7/assets/lighthouse-logo.png',
tags: ['web', 'seo', 'accessibility', 'performance'],
},
{
title: 'Tech Radar',
description:
'Tech Radar is a list of technologies, complemented by an assessment result, called ring assignment.',
url: '/tech-radar',
image:
'https://storage.googleapis.com/wf-blogs-engineering-media/2018/09/fe13bb32-wf-tech-radar-hero-1024x597.png',
tags: ['standards', 'landscape'],
},
{
title: 'GraphiQL',
description:
'Integrates GraphiQL as a tool to browse GraphiQL endpoints inside Backstage.',
url: 'graphiql',
image:
'https://camo.githubusercontent.com/517398c3fbe0687d3d4dcbe05da82970b882e75a/68747470733a2f2f64337676366c703535716a6171632e636c6f756466726f6e742e6e65742f6974656d732f33413061324e314c3346324f304c3377326e316a2f477261706869514c382e706e673f582d436c6f75644170702d56697369746f722d49643d3433363432',
tags: ['graphql', 'dev'],
},
];
const ExplorePluginPage: FC<{}> = () => {
const classes = useStyles();
return (
<Page theme={pageTheme.home}>
<Header
title="Explore"
subtitle="Tools and services available in Backstage"
/>
<Content>
<ContentHeader title="Tools">
<SupportButton>
<Typography>Explore tools available in Backstage</Typography>
</SupportButton>
</ContentHeader>
<div className={classes.container}>
{toolsCards.map((card: CardData, ix: any) => (
<ExploreCard card={card} key={ix} />
))}
</div>
</Content>
</Page>
);
};
export default ExplorePluginPage;
+17
View File
@@ -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 { plugin } from './plugin';
+23
View File
@@ -0,0 +1,23 @@
/*
* 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 { plugin } from './plugin';
describe('explore', () => {
it('should export plugin', () => {
expect(plugin).toBeDefined();
});
});
@@ -14,17 +14,12 @@
* limitations under the License.
*/
import express from 'express';
import { createPlugin } from '@backstage/core';
import ExplorePluginPage from './components/ExplorePluginPage';
export const router = express.Router();
router.get('/', async (_, res) => {
res
.status(200)
.send([
{ id: 'component1' },
{ id: 'component2' },
{ id: 'component3' },
{ id: 'component4' },
]);
export const plugin = createPlugin({
id: 'explore',
register({ router }) {
router.registerRoute('/explore', ExplorePluginPage);
},
});
+18
View File
@@ -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.
*/
import '@testing-library/jest-dom/extend-expect';
require('jest-fetch-mock').enableMocks();
+7
View File
@@ -0,0 +1,7 @@
{
"extends": "../../tsconfig.json",
"include": ["src"],
"compilerOptions": {
"baseUrl": "src"
}
}
+15 -3
View File
@@ -5,18 +5,30 @@
"license": "Apache-2.0",
"private": true,
"scripts": {
"start": "tsc-watch --onFirstSuccess \"nodemon dist/run.js\"",
"build": "tsc",
"lint": "backstage-cli lint",
"test": "backstage-cli test",
"clean": "backstage-cli clean"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.4"
"@backstage/cli": "^0.1.1-alpha.4",
"jest-fetch-mock": "^3.0.3",
"tsc-watch": "^4.2.3"
},
"dependencies": {
"express": "^4.17.1"
"@backstage/backend-common": "0.1.1-alpha.4",
"compression": "^1.7.4",
"cors": "^2.8.5",
"express": "^4.17.1",
"helmet": "^3.22.0",
"morgan": "^1.10.0",
"winston": "^3.2.1"
},
"files": [
"dist"
]
],
"nodemonConfig": {
"watch": "./dist"
}
}
+1 -1
View File
@@ -14,4 +14,4 @@
* limitations under the License.
*/
export { router } from './plugin';
export * from './service/router';
@@ -0,0 +1,35 @@
/*
* 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 { Component, Inventory } from './types';
export class AggregatorInventory implements Inventory {
inventories: Inventory[] = [];
list(): Promise<Array<Component>> {
return Promise.all(this.inventories.map(i => i.list())).then(lists =>
lists.flat(),
);
}
item(id: string): Promise<Component | undefined> {
return this.list().then(items => items.find(i => i.id === id));
}
enlist(inventory: Inventory) {
this.inventories.push(inventory);
}
}
@@ -0,0 +1,29 @@
/*
* 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 { Component, Inventory } from './types';
export class StaticInventory implements Inventory {
constructor(private components: Component[]) {}
list(): Promise<Array<Component>> {
return Promise.resolve([...this.components]);
}
item(id: string): Promise<Component | undefined> {
return this.list().then(items => items.find(i => i.id === id));
}
}
@@ -0,0 +1,19 @@
/*
* 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 * from './AggregatorInventory';
export * from './StaticInventory';
export * from './types';
@@ -0,0 +1,24 @@
/*
* 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 type Component = {
id: string;
};
export type Inventory = {
list: () => Promise<Component[]>;
item: (id: string) => Promise<Component | undefined>;
};
+34
View File
@@ -0,0 +1,34 @@
/*
* 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 { getRootLogger } from '@backstage/backend-common';
import { startServer } from './service/server';
startServer({
port: process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 3003,
enableCors: process.env.PLUGIN_CORS
? Boolean(process.env.PLUGIN_CORS)
: false,
logger: getRootLogger(),
}).catch(err => {
getRootLogger().error(err);
process.exit(1);
});
process.on('SIGINT', () => {
getRootLogger().info('CTRL+C pressed; exiting.');
process.exit(0);
});
@@ -0,0 +1,51 @@
/*
* 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 {
errorHandler,
notFoundHandler,
requestLoggingHandler,
} from '@backstage/backend-common';
import compression from 'compression';
import cors from 'cors';
import express from 'express';
import helmet from 'helmet';
import { Logger } from 'winston';
import { createRouter } from './router';
export interface ApplicationOptions {
enableCors: boolean;
logger: Logger;
}
export async function createApplication(
options: ApplicationOptions,
): Promise<express.Application> {
const app = express();
app.use(helmet());
if (options.enableCors) {
app.use(cors());
}
app.use(compression());
app.use(express.json());
app.use(requestLoggingHandler());
app.use('/', await createRouter({ logger: options.logger }));
app.use(notFoundHandler());
app.use(errorHandler());
return app;
}
@@ -0,0 +1,61 @@
/*
* 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 express from 'express';
import { Logger } from 'winston';
import { AggregatorInventory, StaticInventory } from '../inventory';
export interface RouterOptions {
logger: Logger;
}
export async function createRouter(
options: RouterOptions,
): Promise<express.Router> {
const logger = options.logger.child({ plugin: 'inventory' });
const inventory = new AggregatorInventory();
inventory.enlist(
new StaticInventory([
{ id: 'component1' },
{ id: 'component2' },
{ id: 'component3' },
{ id: 'component4' },
]),
);
const router = express.Router();
router
.get('/', async (req, res) => {
const components = await inventory.list();
res.status(200).send(components);
})
.get('/:id', async (req, res) => {
const { id } = req.params;
const component = await inventory.item(id);
if (component) {
res.status(200).send(component);
} else {
res.status(404).send();
}
});
const app = express();
app.set('logger', logger);
app.use('/', router);
return app;
}
@@ -0,0 +1,48 @@
/*
* 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 { Server } from 'http';
import { Logger } from 'winston';
import { createApplication } from './application';
export interface ServerOptions {
port: number;
enableCors: boolean;
logger: Logger;
}
export async function startServer(options: ServerOptions): Promise<Server> {
const logger = options.logger.child({ service: 'inventory-backend' });
logger.debug('Creating application...');
const app = await createApplication({
enableCors: options.enableCors,
logger,
});
logger.debug('Starting application server...');
return await new Promise((resolve, reject) => {
const server = app.listen(options.port, (err?: Error) => {
if (err) {
reject(err);
return;
}
logger.info(`Listening on port ${options.port}`);
resolve(server);
});
});
}
@@ -13,3 +13,5 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
require('jest-fetch-mock').enableMocks();
+1
View File
@@ -10,6 +10,7 @@
"target": "es5",
"module": "commonjs",
"esModuleInterop": true,
"lib": ["es2019"],
"types": ["node", "jest"]
}
}
+6 -1
View File
@@ -16680,11 +16680,16 @@ prepend-http@^1.0.0, prepend-http@^1.0.1:
resolved "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=
prettier@^1.16.4, prettier@^1.18.2, prettier@^1.19.1:
prettier@^1.16.4, prettier@^1.18.2:
version "1.19.1"
resolved "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb"
integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==
prettier@^2.0.5:
version "2.0.5"
resolved "https://registry.npmjs.org/prettier/-/prettier-2.0.5.tgz#d6d56282455243f2f92cc1716692c08aa31522d4"
integrity sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg==
pretty-bytes@5.3.0, pretty-bytes@^5.1.0:
version "5.3.0"
resolved "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.3.0.tgz#f2849e27db79fb4d6cfe24764fc4134f165989f2"