From f8ee87ce1d6b33c9b93f97a7f40f3a457bd674f7 Mon Sep 17 00:00:00 2001 From: Wojciech Adaszynski Date: Fri, 15 May 2020 11:29:27 +0200 Subject: [PATCH 1/7] Add Sentry plugin backend API (forwarding proxy) --- packages/backend/package.json | 1 + packages/backend/src/index.ts | 5 +++ packages/backend/src/plugins/sentry.ts | 22 ++++++++++ plugins/sentry-backend/.eslintrc.js | 3 ++ plugins/sentry-backend/README.md | 3 ++ plugins/sentry-backend/package.json | 34 +++++++++++++++ plugins/sentry-backend/src/index.ts | 16 +++++++ plugins/sentry-backend/src/service/router.ts | 35 +++++++++++++++ .../sentry-backend/src/service/sentry-api.ts | 38 ++++++++++++++++ .../src/service/standaloneApplication.ts | 42 ++++++++++++++++++ .../src/service/standaloneServer.ts | 43 +++++++++++++++++++ plugins/sentry-backend/tsconfig.json | 11 +++++ 12 files changed, 253 insertions(+) create mode 100644 packages/backend/src/plugins/sentry.ts create mode 100644 plugins/sentry-backend/.eslintrc.js create mode 100644 plugins/sentry-backend/README.md create mode 100644 plugins/sentry-backend/package.json create mode 100644 plugins/sentry-backend/src/index.ts create mode 100644 plugins/sentry-backend/src/service/router.ts create mode 100644 plugins/sentry-backend/src/service/sentry-api.ts create mode 100644 plugins/sentry-backend/src/service/standaloneApplication.ts create mode 100644 plugins/sentry-backend/src/service/standaloneServer.ts create mode 100644 plugins/sentry-backend/tsconfig.json diff --git a/packages/backend/package.json b/packages/backend/package.json index 773a4e317f..b1a0a68f15 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -20,6 +20,7 @@ "@backstage/backend-common": "^0.1.1-alpha.6", "@backstage/plugin-auth-backend": "^0.1.1-alpha.6", "@backstage/plugin-catalog-backend": "^0.1.1-alpha.6", + "@backstage/plugin-sentry-backend": "0.1.1-alpha.6", "@backstage/plugin-scaffolder-backend": "^0.1.1-alpha.6", "compression": "^1.7.4", "cors": "^2.8.5", diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index ed458dedb3..bb37adbae9 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -35,6 +35,7 @@ import helmet from 'helmet'; import knex from 'knex'; import catalog from './plugins/catalog'; import scaffolder from './plugins/scaffolder'; +import sentry from './plugins/sentry'; import auth from './plugins/auth'; import { PluginEnvironment } from './types'; @@ -64,6 +65,10 @@ async function main() { app.use(requestLoggingHandler()); app.use('/catalog', await catalog(createEnv('catalog'))); app.use('/scaffolder', await scaffolder(createEnv('scaffolder'))); + app.use( + '/sentry', + await sentry(getRootLogger().child({ type: 'plugin', plugin: 'sentry' })), + ); app.use('/auth', await auth(createEnv('auth'))); app.use(notFoundHandler()); app.use(errorHandler()); diff --git a/packages/backend/src/plugins/sentry.ts b/packages/backend/src/plugins/sentry.ts new file mode 100644 index 0000000000..ddb7ddd540 --- /dev/null +++ b/packages/backend/src/plugins/sentry.ts @@ -0,0 +1,22 @@ +/* + * 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 { createRouter } from '@backstage/plugin-sentry-backend'; +import { Logger } from 'winston'; + +export default async function(logger: Logger) { + return await createRouter(logger); +} diff --git a/plugins/sentry-backend/.eslintrc.js b/plugins/sentry-backend/.eslintrc.js new file mode 100644 index 0000000000..16a033dbc6 --- /dev/null +++ b/plugins/sentry-backend/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: [require.resolve('@backstage/cli/config/eslint.backend')], +}; diff --git a/plugins/sentry-backend/README.md b/plugins/sentry-backend/README.md new file mode 100644 index 0000000000..412306dc8b --- /dev/null +++ b/plugins/sentry-backend/README.md @@ -0,0 +1,3 @@ +# sentry-backend + +Simple plugin forwarding requests to [Sentry](https://sentry.io) API. diff --git a/plugins/sentry-backend/package.json b/plugins/sentry-backend/package.json new file mode 100644 index 0000000000..e9b7d01bf4 --- /dev/null +++ b/plugins/sentry-backend/package.json @@ -0,0 +1,34 @@ +{ + "name": "@backstage/plugin-sentry-backend", + "version": "0.1.1-alpha.5", + "main": "dist", + "types": "src/index.ts", + "license": "Apache-2.0", + "private": true, + "scripts": { + "build": "tsc", + "start": "backstage-cli plugin:serve", + "lint": "backstage-cli lint", + "test": "backstage-cli test", + "clean": "backstage-cli clean" + }, + "dependencies": { + "@backstage/backend-common": "^0.1.1-alpha.5", + "@backstage/core": "^0.1.1-alpha.5", + "axios": "^0.19.2", + "cors": "^2.8.5", + "express": "^4.17.1", + "express-promise-router": "^3.0.3", + "helmet": "^3.22.0", + "winston": "^3.2.1" + }, + "devDependencies": { + "@backstage/cli": "^0.1.1-alpha.5", + "@backstage/dev-utils": "^0.1.1-alpha.5", + "@types/jest": "^25.2.1", + "@types/node": "^12.0.0" + }, + "files": [ + "dist/**/*.{js,d.ts}" + ] +} diff --git a/plugins/sentry-backend/src/index.ts b/plugins/sentry-backend/src/index.ts new file mode 100644 index 0000000000..28c43aee33 --- /dev/null +++ b/plugins/sentry-backend/src/index.ts @@ -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 * from './service/router'; diff --git a/plugins/sentry-backend/src/service/router.ts b/plugins/sentry-backend/src/service/router.ts new file mode 100644 index 0000000000..c6a0cbd593 --- /dev/null +++ b/plugins/sentry-backend/src/service/router.ts @@ -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 { Logger } from 'winston'; +import Router from 'express-promise-router'; +import express from 'express'; +import { SentryApiForwarder } from './sentry-api'; + +export async function createRouter( + rootLogger: Logger, +): Promise { + const router = Router(); + const sentryForwarder = new SentryApiForwarder(''); + const logger = rootLogger.child({ plugin: 'sentry' }); + + router.get('*', (req, res) => sentryForwarder.fowardRequest(req, res)); + + const app = express(); + app.set('logger', logger); + app.use('/', router); + + return app; +} diff --git a/plugins/sentry-backend/src/service/sentry-api.ts b/plugins/sentry-backend/src/service/sentry-api.ts new file mode 100644 index 0000000000..f80ad5ea7f --- /dev/null +++ b/plugins/sentry-backend/src/service/sentry-api.ts @@ -0,0 +1,38 @@ +/* + * 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 axios from 'axios'; + +export class SentryApiForwarder { + constructor(private token: string) {} + public fowardRequest(request: express.Request, response: express.Response) { + const sentryUrl = request.path; + axios + .get(`https://sentry.io/${sentryUrl}`, { + headers: { + Authorization: `Bearer ${this.token}`, + }, + }) + .then(res => { + response.send(res.data); + }) + .catch(err => { + return response.status(err.response.status).json({ + detail: err.response.statusText, + }); + }); + } +} diff --git a/plugins/sentry-backend/src/service/standaloneApplication.ts b/plugins/sentry-backend/src/service/standaloneApplication.ts new file mode 100644 index 0000000000..fdaad8bb2e --- /dev/null +++ b/plugins/sentry-backend/src/service/standaloneApplication.ts @@ -0,0 +1,42 @@ +/* + * 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 cors from 'cors'; +import express from 'express'; +import helmet from 'helmet'; +import { Logger } from 'winston'; +import { createRouter } from './router'; + +export async function createStandaloneApplication( + logger: Logger, +): Promise { + const app = express(); + + app.use(helmet()); + app.use(cors()); + app.use(express.json()); + app.use(requestLoggingHandler()); + app.use('/', await createRouter(logger)); + app.use(notFoundHandler()); + app.use(errorHandler()); + + return app; +} diff --git a/plugins/sentry-backend/src/service/standaloneServer.ts b/plugins/sentry-backend/src/service/standaloneServer.ts new file mode 100644 index 0000000000..3fc55fb7d0 --- /dev/null +++ b/plugins/sentry-backend/src/service/standaloneServer.ts @@ -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 { Server } from 'http'; +import { Logger } from 'winston'; +import { createStandaloneApplication } from './standaloneApplication'; + +const PORT = 5009; + +export async function startStandaloneServer( + parentLogger: Logger, +): Promise { + const logger = parentLogger.child({ service: 'scaffolder-backend' }); + logger.debug('Creating application...'); + + const app = await createStandaloneApplication(logger); + + logger.debug('Starting application server...'); + return await new Promise((resolve, reject) => { + const server = app.listen(PORT, (err?: Error) => { + if (err) { + reject(err); + return; + } + + logger.info(`Listening on port ${PORT}`); + resolve(server); + }); + }); +} diff --git a/plugins/sentry-backend/tsconfig.json b/plugins/sentry-backend/tsconfig.json new file mode 100644 index 0000000000..7d4ea182e2 --- /dev/null +++ b/plugins/sentry-backend/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../packages/backend/tsconfig.json", + "include": [ + "./src" + ], + "compilerOptions": { + "baseUrl": "./src", + "outDir": "./dist", + "skipLibCheck": true + } +} From 3d4249b673b31ae24714ca6d256134b30c3d5019 Mon Sep 17 00:00:00 2001 From: Wojciech Adaszynski Date: Fri, 15 May 2020 14:50:15 +0200 Subject: [PATCH 2/7] Add Sentry plugin front-end --- packages/app/package.json | 1 + packages/app/src/plugins.ts | 1 + plugins/sentry/.eslintrc.js | 3 + plugins/sentry/README.md | 13 ++++ plugins/sentry/package.json | 44 +++++++++++ .../components/ErrorCell/ErrorCell.test.tsx | 46 ++++++++++++ .../src/components/ErrorCell/ErrorCell.tsx | 53 +++++++++++++ .../src/components/ErrorGraph/ErrorGraph.tsx | 33 +++++++++ .../SentryIssuesTable.test.tsx | 71 ++++++++++++++++++ .../SentryIssuesTable/SentryIssuesTable.tsx | 74 +++++++++++++++++++ .../SentryPluginPage.test.tsx | 36 +++++++++ .../SentryPluginPage/SentryPluginPage.tsx | 70 ++++++++++++++++++ .../src/components/SentryPluginPage/index.ts | 17 +++++ .../SentryPluginWidget/SentryPluginWidget.tsx | 59 +++++++++++++++ plugins/sentry/src/data/api-factory.ts | 25 +++++++ plugins/sentry/src/data/mock-api.ts | 41 ++++++++++ plugins/sentry/src/data/production-api.ts | 46 ++++++++++++ plugins/sentry/src/data/sentry-api.ts | 20 +++++ .../sentry/src/data/sentry-issue-mock.json | 61 +++++++++++++++ plugins/sentry/src/data/sentry-issue.ts | 69 +++++++++++++++++ plugins/sentry/src/index.ts | 18 +++++ plugins/sentry/src/plugin.test.ts | 23 ++++++ plugins/sentry/src/plugin.ts | 25 +++++++ plugins/sentry/src/setupTests.ts | 18 +++++ plugins/sentry/tsconfig.json | 5 ++ yarn.lock | 5 ++ 26 files changed, 877 insertions(+) create mode 100644 plugins/sentry/.eslintrc.js create mode 100644 plugins/sentry/README.md create mode 100644 plugins/sentry/package.json create mode 100644 plugins/sentry/src/components/ErrorCell/ErrorCell.test.tsx create mode 100644 plugins/sentry/src/components/ErrorCell/ErrorCell.tsx create mode 100644 plugins/sentry/src/components/ErrorGraph/ErrorGraph.tsx create mode 100644 plugins/sentry/src/components/SentryIssuesTable/SentryIssuesTable.test.tsx create mode 100644 plugins/sentry/src/components/SentryIssuesTable/SentryIssuesTable.tsx create mode 100644 plugins/sentry/src/components/SentryPluginPage/SentryPluginPage.test.tsx create mode 100644 plugins/sentry/src/components/SentryPluginPage/SentryPluginPage.tsx create mode 100644 plugins/sentry/src/components/SentryPluginPage/index.ts create mode 100644 plugins/sentry/src/components/SentryPluginWidget/SentryPluginWidget.tsx create mode 100644 plugins/sentry/src/data/api-factory.ts create mode 100644 plugins/sentry/src/data/mock-api.ts create mode 100644 plugins/sentry/src/data/production-api.ts create mode 100644 plugins/sentry/src/data/sentry-api.ts create mode 100644 plugins/sentry/src/data/sentry-issue-mock.json create mode 100644 plugins/sentry/src/data/sentry-issue.ts create mode 100644 plugins/sentry/src/index.ts create mode 100644 plugins/sentry/src/plugin.test.ts create mode 100644 plugins/sentry/src/plugin.ts create mode 100644 plugins/sentry/src/setupTests.ts create mode 100644 plugins/sentry/tsconfig.json diff --git a/packages/app/package.json b/packages/app/package.json index 58ed20bf42..82b25f58e8 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -15,6 +15,7 @@ "@backstage/plugin-tech-radar": "^0.1.1-alpha.6", "@backstage/plugin-welcome": "^0.1.1-alpha.6", "@backstage/theme": "^0.1.1-alpha.6", + "@backstage/plugin-sentry": "^0.1.1-alpha.5", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", diff --git a/packages/app/src/plugins.ts b/packages/app/src/plugins.ts index 021005a97a..25a5bcfc9d 100644 --- a/packages/app/src/plugins.ts +++ b/packages/app/src/plugins.ts @@ -22,3 +22,4 @@ export { plugin as TechRadar } from '@backstage/plugin-tech-radar'; export { plugin as Explore } from '@backstage/plugin-explore'; export { plugin as Circleci } from '@backstage/plugin-circleci'; export { plugin as RegisterComponent } from '@backstage/plugin-register-component'; +export { plugin as Sentry } from '@backstage/plugin-sentry'; diff --git a/plugins/sentry/.eslintrc.js b/plugins/sentry/.eslintrc.js new file mode 100644 index 0000000000..13573efa9c --- /dev/null +++ b/plugins/sentry/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: [require.resolve('@backstage/cli/config/eslint')], +}; diff --git a/plugins/sentry/README.md b/plugins/sentry/README.md new file mode 100644 index 0000000000..9004fd91c7 --- /dev/null +++ b/plugins/sentry/README.md @@ -0,0 +1,13 @@ +# sentry + +Welcome to the sentry plugin! + +_This plugin was created through the Backstage CLI_ + +## Getting started + +Your plugin has been added to the example app in this repository, meaning you'll be able to access it by running `yarn start` in the root directory, and then navigating to [/sentry](http://localhost:3000/sentry). + +You can also serve the plugin in isolation by running `yarn start` in the plugin directory. +This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads. +It is only meant for local development, and the setup for it can be found inside the [/dev](/dev) directory. diff --git a/plugins/sentry/package.json b/plugins/sentry/package.json new file mode 100644 index 0000000000..dde234e901 --- /dev/null +++ b/plugins/sentry/package.json @@ -0,0 +1,44 @@ +{ + "name": "@backstage/plugin-sentry", + "version": "0.1.1-alpha.5", + "main": "dist/index.esm.js", + "types": "src/index.ts", + "license": "Apache-2.0", + "private": true, + "scripts": { + "build": "backstage-cli plugin:build", + "start": "backstage-cli plugin:serve", + "lint": "backstage-cli lint", + "test": "backstage-cli test", + "diff": "backstage-cli plugin:diff", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", + "clean": "backstage-cli clean" + }, + "dependencies": { + "@backstage/core": "^0.1.1-alpha.5", + "@backstage/theme": "^0.1.1-alpha.5", + "@material-ui/core": "^4.9.1", + "@material-ui/icons": "^4.9.1", + "@material-ui/lab": "4.0.0-alpha.45", + "react": "^16.13.1", + "react-dom": "^16.13.1", + "react-sparklines": "^1.7.0", + "react-use": "^14.2.0", + "timeago.js": "^4.0.2" + }, + "devDependencies": { + "@backstage/cli": "^0.1.1-alpha.5", + "@backstage/dev-utils": "^0.1.1-alpha.5", + "@testing-library/jest-dom": "^4.2.4", + "@testing-library/react": "^9.3.2", + "@testing-library/user-event": "^7.1.2", + "@types/jest": "^25.2.1", + "@types/node": "^12.0.0", + "@types/testing-library__jest-dom": "^5.0.4", + "jest-fetch-mock": "^3.0.3" + }, + "files": [ + "dist/**/*.{js,d.ts}" + ] +} diff --git a/plugins/sentry/src/components/ErrorCell/ErrorCell.test.tsx b/plugins/sentry/src/components/ErrorCell/ErrorCell.test.tsx new file mode 100644 index 0000000000..0daa36f32b --- /dev/null +++ b/plugins/sentry/src/components/ErrorCell/ErrorCell.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 { ErrorCell } from './ErrorCell'; +import React from 'react'; +import { render } from '@testing-library/react'; +import mockIssue from '../../data/sentry-issue-mock.json'; +import { ThemeProvider } from '@material-ui/styles'; +import { lightTheme } from '@backstage/theme'; + +describe('Sentry error cell component', () => { + it('should render a link that lead to Sentry', async () => { + const testIssue = { + ...mockIssue, + metadata: { + type: 'Exception', + value: 'exception was thrown', + }, + count: '1', + userCount: 2, + permalink: 'http://example.com', + }; + const cell = render( + + + , + ); + const errorType = await cell.findByText('Exception'); + expect(errorType.closest('a')).toHaveAttribute( + 'href', + 'http://example.com', + ); + }); +}); diff --git a/plugins/sentry/src/components/ErrorCell/ErrorCell.tsx b/plugins/sentry/src/components/ErrorCell/ErrorCell.tsx new file mode 100644 index 0000000000..42ab27e839 --- /dev/null +++ b/plugins/sentry/src/components/ErrorCell/ErrorCell.tsx @@ -0,0 +1,53 @@ +import React, { FC } from 'react'; +import { SentryIssue } from '../../data/sentry-issue'; +import { Link, Typography } from '@material-ui/core'; +import { makeStyles } from '@material-ui/core/styles'; +import { BackstageTheme } from '@backstage/theme'; + +function stripText(text: string, maxLength: number) { + return text.length > maxLength ? `${text.substr(0, maxLength)}...` : text; +} +const useStyles = makeStyles(theme => ({ + root: { + minWidth: 260, + position: 'relative', + '&::before': { + left: -16, + position: 'absolute', + width: '4px', + height: '100%', + content: '""', + backgroundColor: theme.palette.status.error, + borderRadius: 2, + }, + }, + text: { + marginBottom: 0, + }, +})); + +export const ErrorCell: FC<{ sentryIssue: SentryIssue }> = ({ + sentryIssue, +}) => { + const classes = useStyles(); + return ( +
+ + + {sentryIssue.metadata.type + ? stripText(sentryIssue.metadata.type, 28) + : '[No type]'} + + + + {sentryIssue.metadata.value && + stripText(sentryIssue.metadata.value, 48)} + +
+ ); +}; diff --git a/plugins/sentry/src/components/ErrorGraph/ErrorGraph.tsx b/plugins/sentry/src/components/ErrorGraph/ErrorGraph.tsx new file mode 100644 index 0000000000..7643c5ac6e --- /dev/null +++ b/plugins/sentry/src/components/ErrorGraph/ErrorGraph.tsx @@ -0,0 +1,33 @@ +/* + * 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 { SentryIssue } from '../../data/sentry-issue'; +import { Sparklines, SparklinesBars } from 'react-sparklines'; + +export const ErrorGraph: FC<{ sentryIssue: SentryIssue }> = ({ + sentryIssue, +}) => { + const data = + '12h' in sentryIssue.stats + ? sentryIssue.stats['12h'] + : sentryIssue.stats['24h']; + + return ( + val)} svgHeight={48} margin={4}> + + + ); +}; diff --git a/plugins/sentry/src/components/SentryIssuesTable/SentryIssuesTable.test.tsx b/plugins/sentry/src/components/SentryIssuesTable/SentryIssuesTable.test.tsx new file mode 100644 index 0000000000..08159b16fc --- /dev/null +++ b/plugins/sentry/src/components/SentryIssuesTable/SentryIssuesTable.test.tsx @@ -0,0 +1,71 @@ +/* + * 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 SentryIssuesTable from './SentryIssuesTable'; +import { SentryIssue } from '../../data/sentry-issue'; +import mockIssue from '../../data/sentry-issue-mock.json'; +import { ThemeProvider } from '@material-ui/styles'; +import { lightTheme } from '@backstage/theme'; + +describe('SentryIssuesTable', () => { + it('should render headers in a table', async () => { + const issues: SentryIssue[] = [ + { + ...mockIssue, + metadata: { + type: 'Exception', + value: 'exception was thrown', + }, + count: '1', + userCount: 2, + }, + ]; + const table = await render( + + + , + ); + expect(await table.findByText('Error')).toBeInTheDOM(); + expect(await table.findByText('Graph')).toBeInTheDOM(); + expect(await table.findByText('First seen')).toBeInTheDOM(); + expect(await table.findByText('Last seen')).toBeInTheDOM(); + expect(await table.findByText('Events')).toBeInTheDOM(); + expect(await table.findByText('Users')).toBeInTheDOM(); + }); + it('should render values in a table', async () => { + const issues: SentryIssue[] = [ + { + ...mockIssue, + metadata: { + type: 'Exception', + value: 'exception was thrown', + }, + count: '101', + userCount: 202, + }, + ]; + const table = await render( + + + , + ); + expect(await table.findByText('Exception')).toBeInTheDOM(); + expect(await table.findByText('exception was thrown')).toBeInTheDOM(); + expect(await table.findByText('101')).toBeInTheDOM(); + expect(await table.findByText('202')).toBeInTheDOM(); + }); +}); diff --git a/plugins/sentry/src/components/SentryIssuesTable/SentryIssuesTable.tsx b/plugins/sentry/src/components/SentryIssuesTable/SentryIssuesTable.tsx new file mode 100644 index 0000000000..3e5ed98dba --- /dev/null +++ b/plugins/sentry/src/components/SentryIssuesTable/SentryIssuesTable.tsx @@ -0,0 +1,74 @@ +/* + * 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 { Table, TableColumn } from '@backstage/core'; +import { SentryIssue } from '../../data/sentry-issue'; +import { format } from 'timeago.js'; +import { ErrorCell } from '../ErrorCell/ErrorCell'; +import { ErrorGraph } from '../ErrorGraph/ErrorGraph'; + +const columns: TableColumn[] = [ + { + title: 'Error', + render: (data) => , + }, + { + title: 'Graph', + render: (data) => , + }, + { + title: 'First seen', + field: 'firstSeen', + render: (data) => { + const { firstSeen } = data as SentryIssue; + return format(firstSeen); + }, + }, + { + title: 'Last seen', + field: 'lastSeen', + render: (data) => { + const { lastSeen } = data as SentryIssue; + return format(lastSeen); + }, + }, + { + title: 'Events', + field: 'count', + }, + { + title: 'Users', + field: 'userCount', + }, +]; + +type SentryIssuesTableProps = { + sentryIssues: SentryIssue[]; +}; + +const SentryIssuesTable: FC = ({ sentryIssues }) => { + return ( + + ); +}; + +export default SentryIssuesTable; diff --git a/plugins/sentry/src/components/SentryPluginPage/SentryPluginPage.test.tsx b/plugins/sentry/src/components/SentryPluginPage/SentryPluginPage.test.tsx new file mode 100644 index 0000000000..cb22885861 --- /dev/null +++ b/plugins/sentry/src/components/SentryPluginPage/SentryPluginPage.test.tsx @@ -0,0 +1,36 @@ +/* + * 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 mockFetch from 'jest-fetch-mock'; +import SentryPluginPage from './SentryPluginPage'; +import { ThemeProvider } from '@material-ui/core'; +import { lightTheme } from '@backstage/theme'; + +describe('SentryPluginPage', () => { + it('should render header and time switched', () => { + mockFetch.mockResponse(() => new Promise(() => {})); + const rendered = render( + + + , + ); + expect(rendered.getByText('Sentry issues')).toBeInTheDocument(); + expect(rendered.getByText('24H')).toBeInTheDocument(); + expect(rendered.getByText('12H')).toBeInTheDocument(); + }); +}); diff --git a/plugins/sentry/src/components/SentryPluginPage/SentryPluginPage.tsx b/plugins/sentry/src/components/SentryPluginPage/SentryPluginPage.tsx new file mode 100644 index 0000000000..d7bf731a93 --- /dev/null +++ b/plugins/sentry/src/components/SentryPluginPage/SentryPluginPage.tsx @@ -0,0 +1,70 @@ +/* + * 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, useState } from 'react'; +import { Grid } from '@material-ui/core'; +import { + Header, + Page, + pageTheme, + Content, + ContentHeader, + SupportButton, +} from '@backstage/core'; +import SentryPluginWidget from '../SentryPluginWidget/SentryPluginWidget'; +import { ToggleButton, ToggleButtonGroup } from '@material-ui/lab'; + +const SentryPluginPage: FC<{}> = () => { + const [statsFor, setStatsFor] = useState<'12h' | '24h'>('12h'); + const toggleStatsFor = () => + statsFor === '12h' ? setStatsFor('24h') : setStatsFor('12h'); + return ( + +
+ + + 24H + + + 12H + + +
+ + + + Sentry plugin allows you to preview issues and navigate to sentry. + + + + + + + + +
+ ); +}; + +export default SentryPluginPage; diff --git a/plugins/sentry/src/components/SentryPluginPage/index.ts b/plugins/sentry/src/components/SentryPluginPage/index.ts new file mode 100644 index 0000000000..67b34db517 --- /dev/null +++ b/plugins/sentry/src/components/SentryPluginPage/index.ts @@ -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 { default } from './SentryPluginPage'; diff --git a/plugins/sentry/src/components/SentryPluginWidget/SentryPluginWidget.tsx b/plugins/sentry/src/components/SentryPluginWidget/SentryPluginWidget.tsx new file mode 100644 index 0000000000..9b5131d01e --- /dev/null +++ b/plugins/sentry/src/components/SentryPluginWidget/SentryPluginWidget.tsx @@ -0,0 +1,59 @@ +/* + * 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, useEffect } from 'react'; +import { + ErrorApi, + errorApiRef, + InfoCard, + Progress, + useApi, +} from '@backstage/core'; +import SentryIssuesTable from '../SentryIssuesTable/SentryIssuesTable'; +import { useAsync } from 'react-use'; +import { sentryApiFactory } from '../../data/api-factory'; + +const api = sentryApiFactory('spotify'); + +const SentryPluginWidget: FC<{ + sentryProjectId: string; + statsFor: '24h' | '12h'; +}> = ({ sentryProjectId, statsFor }) => { + const errorApi = useApi(errorApiRef); + + const { loading, value, error } = useAsync( + () => api.fetchIssues(sentryProjectId, statsFor), + [statsFor, sentryProjectId], + ); + + useEffect(() => { + if (error) { + errorApi.post(error); + } + }, [error]); + + if (loading) { + return ( + + + + ); + } + + return ; +}; + +export default SentryPluginWidget; diff --git a/plugins/sentry/src/data/api-factory.ts b/plugins/sentry/src/data/api-factory.ts new file mode 100644 index 0000000000..32ff4bde5c --- /dev/null +++ b/plugins/sentry/src/data/api-factory.ts @@ -0,0 +1,25 @@ +/* + * 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 { SentryApi } from './sentry-api'; +import { MockSentryApi } from './mock-api'; +import { ProductionSentryApi } from './production-api'; + +export function sentryApiFactory(organization: string): SentryApi { + if (process.env.NODE_ENV === 'production') { + return new ProductionSentryApi(organization); + } + return new MockSentryApi(); +} diff --git a/plugins/sentry/src/data/mock-api.ts b/plugins/sentry/src/data/mock-api.ts new file mode 100644 index 0000000000..2eab8e30df --- /dev/null +++ b/plugins/sentry/src/data/mock-api.ts @@ -0,0 +1,41 @@ +/* + * 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 { SentryIssue } from './sentry-issue'; +import { SentryApi } from './sentry-api'; +import mockData from './sentry-issue-mock.json'; +function getMockIssue(): SentryIssue { + const randomizedStats = { + '12h': new Array(12) + .fill(0) + .map(() => [0, Math.floor(Math.random() * 100)]), + }; + return { + ...mockData, + userCount: Math.floor(Math.random() * 1000), + stats: randomizedStats, + }; +} +function getMockIssues(number: number): SentryIssue[] { + return new Array(number).fill(0).map(getMockIssue); +} +export class MockSentryApi implements SentryApi { + fetchIssues(project: string, statsFor: string): Promise { + console.info('Fetching mock responses for', project, statsFor); + return new Promise((resolve) => { + setTimeout(() => resolve(getMockIssues(14)), 800); + }); + } +} diff --git a/plugins/sentry/src/data/production-api.ts b/plugins/sentry/src/data/production-api.ts new file mode 100644 index 0000000000..0e8f22452f --- /dev/null +++ b/plugins/sentry/src/data/production-api.ts @@ -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 { SentryIssue } from './sentry-issue'; +import { SentryApi } from './sentry-api'; + +const API_BASE_URL = 'http://localhost:7000/sentry/api/0/projects/'; + +export class ProductionSentryApi implements SentryApi { + private organization: string; + + constructor(organization: string) { + this.organization = organization; + } + + async fetchIssues(project: string, statsFor: string): Promise { + try { + const response = await fetch( + `${API_BASE_URL}/${this.organization}/${project}/issues/?statsFor=${statsFor}`, + ); + + if (response.status >= 400 && response.status < 600) { + throw new Error('Failed fetching Sentry issues'); + } + + return (await response.json()) as SentryIssue[]; + } catch (exception) { + if (exception.detail) { + return exception; + } + throw new Error('Unknown error'); + } + } +} diff --git a/plugins/sentry/src/data/sentry-api.ts b/plugins/sentry/src/data/sentry-api.ts new file mode 100644 index 0000000000..538900b738 --- /dev/null +++ b/plugins/sentry/src/data/sentry-api.ts @@ -0,0 +1,20 @@ +/* + * 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 { SentryIssue } from './sentry-issue'; + +export interface SentryApi { + fetchIssues(project: string, statsFor: string): Promise; +} diff --git a/plugins/sentry/src/data/sentry-issue-mock.json b/plugins/sentry/src/data/sentry-issue-mock.json new file mode 100644 index 0000000000..115be77b19 --- /dev/null +++ b/plugins/sentry/src/data/sentry-issue-mock.json @@ -0,0 +1,61 @@ +{ + "platform": "javascript", + "lastSeen": "2020-05-15T09:17:17.384804Z", + "numComments": 0, + "userCount": 0, + "stats": { + "12h": [ + [1589450400, 7], + [1589454000, 2], + [1589457600, 6], + [1589461200, 8], + [1589464800, 9], + [1589468400, 11], + [1589472000, 4], + [1589475600, 19], + [1589479200, 3], + [1589482800, 24], + [1589486400, 8], + [1589490000, 5], + [1589493600, 5], + [1589497200, 10], + [1589500800, 16], + [1589504400, 24], + [1589508000, 24], + [1589511600, 54], + [1589515200, 4], + [1589518800, 7], + [1589522400, 4], + [1589526000, 4], + [1589529600, 13], + [1589533200, 1] + ] + }, + "culprit": "https://www.example.com/de/account//", + "title": "TypeError: Failed to fetch", + "id": "991214716", + "assignedTo": null, + "logger": null, + "type": "error", + "annotations": [], + "metadata": { "type": "TypeError", "value": "Failed to fetch" }, + "status": "unresolved", + "subscriptionDetails": null, + "isPublic": false, + "hasSeen": true, + "shortId": "example-slug-21", + "shareId": null, + "firstSeen": "2019-04-18T23:36:40.988000Z", + "count": "169815", + "permalink": "https://sentry.io/organizations/example/issues/99176416/", + "level": "error", + "isSubscribed": false, + "isBookmarked": false, + "project": { + "platform": "javascript-react", + "slug": "example-slug", + "id": "1282343", + "name": "example-slug" + }, + "statusDetails": {} +} diff --git a/plugins/sentry/src/data/sentry-issue.ts b/plugins/sentry/src/data/sentry-issue.ts new file mode 100644 index 0000000000..14621bf629 --- /dev/null +++ b/plugins/sentry/src/data/sentry-issue.ts @@ -0,0 +1,69 @@ +/* + * 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. + */ +type SentryPlatform = 'javascript' | 'javascript-react' | string; + +type EventPoint = number[]; + +type SentryProject = { + platform: SentryPlatform; + slug: string; + id: string; + name: string; +}; + +type SentryIssueMetadata = { + function?: string; + type?: string; + value?: string; + filename?: string; +}; + +export type SentryIssue = { + platform: SentryPlatform; + lastSeen: string; + numComments: number; + userCount: number; + stats: { + '24h'?: EventPoint[]; + '12h'?: EventPoint[]; + }; + culprit: string; + title: string; + id: string; + assignedTo: any; + logger: any; + type: string; + annotations: any[]; + metadata: SentryIssueMetadata; + status: string; + subscriptionDetails: any; + isPublic: boolean; + hasSeen: boolean; + shortId: string; + shareId: string | null; + firstSeen: string; + count: string; + permalink: string; + level: string; + isSubscribed: boolean; + isBookmarked: boolean; + project: SentryProject; + statusDetails: any; +}; + +export type SentryApiError = { + detail: string; +}; diff --git a/plugins/sentry/src/index.ts b/plugins/sentry/src/index.ts new file mode 100644 index 0000000000..350b8fd668 --- /dev/null +++ b/plugins/sentry/src/index.ts @@ -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 { plugin } from './plugin'; +export { default as SentryIssuesWidget } from './components/SentryPluginWidget/SentryPluginWidget'; diff --git a/plugins/sentry/src/plugin.test.ts b/plugins/sentry/src/plugin.test.ts new file mode 100644 index 0000000000..8f24236586 --- /dev/null +++ b/plugins/sentry/src/plugin.test.ts @@ -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('sentry', () => { + it('should export plugin', () => { + expect(plugin).toBeDefined(); + }); +}); diff --git a/plugins/sentry/src/plugin.ts b/plugins/sentry/src/plugin.ts new file mode 100644 index 0000000000..6b4080dd2b --- /dev/null +++ b/plugins/sentry/src/plugin.ts @@ -0,0 +1,25 @@ +/* + * 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 { createPlugin } from '@backstage/core'; +import SentryPluginPage from './components/SentryPluginPage'; + +export const plugin = createPlugin({ + id: 'sentry', + register({ router }) { + router.registerRoute('/sentry', SentryPluginPage); + }, +}); diff --git a/plugins/sentry/src/setupTests.ts b/plugins/sentry/src/setupTests.ts new file mode 100644 index 0000000000..1a907ab8e6 --- /dev/null +++ b/plugins/sentry/src/setupTests.ts @@ -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(); diff --git a/plugins/sentry/tsconfig.json b/plugins/sentry/tsconfig.json new file mode 100644 index 0000000000..b663b01fa2 --- /dev/null +++ b/plugins/sentry/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.json", + "include": ["src", "dev"], + "compilerOptions": {} +} diff --git a/yarn.lock b/yarn.lock index 0d2cda2b05..0e0edd4bf3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19958,6 +19958,11 @@ tildify@2.0.0: resolved "https://registry.npmjs.org/tildify/-/tildify-2.0.0.tgz#f205f3674d677ce698b7067a99e949ce03b4754a" integrity sha512-Cc+OraorugtXNfs50hU9KS369rFXCfgGLpfCfvlc+Ud5u6VWmUQsOAa9HbTvheQdYnrdJqqv1e5oIqXppMYnSw== +timeago.js@^4.0.2: + version "4.0.2" + resolved "https://registry.npmjs.org/timeago.js/-/timeago.js-4.0.2.tgz#724e8c8833e3490676c7bb0a75f5daf20e558028" + integrity sha512-a7wPxPdVlQL7lqvitHGGRsofhdwtkoSXPGATFuSOA2i1ZNQEPLrGnj68vOp2sOJTCFAQVXPeNMX/GctBaO9L2w== + timed-out@^4.0.0: version "4.0.1" resolved "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" From 807febae5aedce18c9bcc890c6bc420268c2ab5f Mon Sep 17 00:00:00 2001 From: Wojciech Adaszynski Date: Mon, 18 May 2020 11:30:02 +0200 Subject: [PATCH 3/7] Provide Sentry token to Sentry API through env variable --- plugins/sentry-backend/src/service/router.ts | 7 ++++++- plugins/sentry-backend/src/service/standaloneServer.ts | 3 +-- plugins/sentry/src/data/api-factory.ts | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/plugins/sentry-backend/src/service/router.ts b/plugins/sentry-backend/src/service/router.ts index c6a0cbd593..3c750bed16 100644 --- a/plugins/sentry-backend/src/service/router.ts +++ b/plugins/sentry-backend/src/service/router.ts @@ -22,7 +22,12 @@ export async function createRouter( rootLogger: Logger, ): Promise { const router = Router(); - const sentryForwarder = new SentryApiForwarder(''); + const SENTRY_TOKEN = process.env.SENTRY_TOKEN; + if (!SENTRY_TOKEN) { + console.error('Sentry token must be provided in env to start the API.'); + process.exit(1); + } + const sentryForwarder = new SentryApiForwarder(SENTRY_TOKEN); const logger = rootLogger.child({ plugin: 'sentry' }); router.get('*', (req, res) => sentryForwarder.fowardRequest(req, res)); diff --git a/plugins/sentry-backend/src/service/standaloneServer.ts b/plugins/sentry-backend/src/service/standaloneServer.ts index 3fc55fb7d0..37b87c5c40 100644 --- a/plugins/sentry-backend/src/service/standaloneServer.ts +++ b/plugins/sentry-backend/src/service/standaloneServer.ts @@ -18,8 +18,6 @@ import { Server } from 'http'; import { Logger } from 'winston'; import { createStandaloneApplication } from './standaloneApplication'; -const PORT = 5009; - export async function startStandaloneServer( parentLogger: Logger, ): Promise { @@ -29,6 +27,7 @@ export async function startStandaloneServer( const app = await createStandaloneApplication(logger); logger.debug('Starting application server...'); + const PORT = parseInt(process.env.PORT || '5001', 10); return await new Promise((resolve, reject) => { const server = app.listen(PORT, (err?: Error) => { if (err) { diff --git a/plugins/sentry/src/data/api-factory.ts b/plugins/sentry/src/data/api-factory.ts index 32ff4bde5c..0b82637cb4 100644 --- a/plugins/sentry/src/data/api-factory.ts +++ b/plugins/sentry/src/data/api-factory.ts @@ -18,7 +18,7 @@ import { MockSentryApi } from './mock-api'; import { ProductionSentryApi } from './production-api'; export function sentryApiFactory(organization: string): SentryApi { - if (process.env.NODE_ENV === 'production') { + if (process.env.NODE_ENV !== 'production') { return new ProductionSentryApi(organization); } return new MockSentryApi(); From 35dec7dc7cda58d830ffc23043afc127c00f2efc Mon Sep 17 00:00:00 2001 From: Wojciech Adaszynski Date: Mon, 18 May 2020 11:31:20 +0200 Subject: [PATCH 4/7] Add Sentry widget to component page --- plugins/catalog/package.json | 1 + .../ComponentMetadataCard.tsx | 6 +++++- .../ComponentPage/ComponentPage.tsx | 20 +++++++++++++++---- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 9a99704da1..fe70b8cc44 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -22,6 +22,7 @@ "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", + "@backstage/plugin-sentry": "^0.1.1-alpha.5", "react": "^16.13.1", "react-dom": "^16.13.1", "react-use": "^14.2.0" diff --git a/plugins/catalog/src/components/ComponentMetadataCard/ComponentMetadataCard.tsx b/plugins/catalog/src/components/ComponentMetadataCard/ComponentMetadataCard.tsx index 9d606c5839..7059709992 100644 --- a/plugins/catalog/src/components/ComponentMetadataCard/ComponentMetadataCard.tsx +++ b/plugins/catalog/src/components/ComponentMetadataCard/ComponentMetadataCard.tsx @@ -26,7 +26,11 @@ const ComponentMetadataCard: FC = ({ component, }) => { if (loading) { - return ; + return ( + + + + ); } if (!component) { return null; diff --git a/plugins/catalog/src/components/ComponentPage/ComponentPage.tsx b/plugins/catalog/src/components/ComponentPage/ComponentPage.tsx index 6348e88c4f..0cf7017173 100644 --- a/plugins/catalog/src/components/ComponentPage/ComponentPage.tsx +++ b/plugins/catalog/src/components/ComponentPage/ComponentPage.tsx @@ -28,6 +28,8 @@ import { } from '@backstage/core'; import ComponentContextMenu from '../ComponentContextMenu/ComponentContextMenu'; import ComponentRemovalDialog from '../ComponentRemovalDialog/ComponentRemovalDialog'; +import { SentryIssuesWidget } from '@backstage/plugin-sentry'; +import { Grid } from '@material-ui/core'; const REDIRECT_DELAY = 1000; @@ -94,10 +96,20 @@ const ComponentPage: FC = ({ /> )} - + + + + + + + + ); From 915167e950b7e28c86f8aa9260d82ae67fdf9525 Mon Sep 17 00:00:00 2001 From: Wojciech Adaszynski Date: Mon, 18 May 2020 11:52:21 +0200 Subject: [PATCH 5/7] Fix Sentry plugin tests --- .../src/service/sentry-api.test.ts | 34 +++++++++++++++++++ .../sentry-backend/src/service/sentry-api.ts | 19 +++++++---- .../SentryPluginPage.test.tsx | 11 ++++-- plugins/sentry/src/data/api-factory.ts | 2 +- 4 files changed, 55 insertions(+), 11 deletions(-) create mode 100644 plugins/sentry-backend/src/service/sentry-api.test.ts diff --git a/plugins/sentry-backend/src/service/sentry-api.test.ts b/plugins/sentry-backend/src/service/sentry-api.test.ts new file mode 100644 index 0000000000..fd4c2581b5 --- /dev/null +++ b/plugins/sentry-backend/src/service/sentry-api.test.ts @@ -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 { SentryApiForwarder } from './sentry-api'; +import axios from 'axios'; + +jest.mock('axios', () => ({ + default: { + post: jest.fn(), + }, +})); +describe('SentryApiForwarder', () => { + it('should generate headers based on token passed in constructor', () => { + const forwarder = new SentryApiForwarder('testtoken'); + + expect(forwarder.getRequestHeaders()).toEqual({ + headers: { + Authorization: `Bearer testtoken`, + }, + }); + }); +}); diff --git a/plugins/sentry-backend/src/service/sentry-api.ts b/plugins/sentry-backend/src/service/sentry-api.ts index f80ad5ea7f..861ad52227 100644 --- a/plugins/sentry-backend/src/service/sentry-api.ts +++ b/plugins/sentry-backend/src/service/sentry-api.ts @@ -18,18 +18,23 @@ import axios from 'axios'; export class SentryApiForwarder { constructor(private token: string) {} + + // public for testing + public getRequestHeaders() { + return { + headers: { + Authorization: `Bearer ${this.token}`, + }, + }; + } public fowardRequest(request: express.Request, response: express.Response) { const sentryUrl = request.path; axios - .get(`https://sentry.io/${sentryUrl}`, { - headers: { - Authorization: `Bearer ${this.token}`, - }, - }) - .then(res => { + .get(`https://sentry.io/${sentryUrl}`, this.getRequestHeaders()) + .then((res) => { response.send(res.data); }) - .catch(err => { + .catch((err) => { return response.status(err.response.status).json({ detail: err.response.statusText, }); diff --git a/plugins/sentry/src/components/SentryPluginPage/SentryPluginPage.test.tsx b/plugins/sentry/src/components/SentryPluginPage/SentryPluginPage.test.tsx index cb22885861..400c75e065 100644 --- a/plugins/sentry/src/components/SentryPluginPage/SentryPluginPage.test.tsx +++ b/plugins/sentry/src/components/SentryPluginPage/SentryPluginPage.test.tsx @@ -20,14 +20,19 @@ import mockFetch from 'jest-fetch-mock'; import SentryPluginPage from './SentryPluginPage'; import { ThemeProvider } from '@material-ui/core'; import { lightTheme } from '@backstage/theme'; +import { ApiProvider, ApiRegistry, errorApiRef } from '@backstage/core'; + +const errorApi = { post: () => {} }; describe('SentryPluginPage', () => { it('should render header and time switched', () => { mockFetch.mockResponse(() => new Promise(() => {})); const rendered = render( - - - , + + + + + , ); expect(rendered.getByText('Sentry issues')).toBeInTheDocument(); expect(rendered.getByText('24H')).toBeInTheDocument(); diff --git a/plugins/sentry/src/data/api-factory.ts b/plugins/sentry/src/data/api-factory.ts index 0b82637cb4..32ff4bde5c 100644 --- a/plugins/sentry/src/data/api-factory.ts +++ b/plugins/sentry/src/data/api-factory.ts @@ -18,7 +18,7 @@ import { MockSentryApi } from './mock-api'; import { ProductionSentryApi } from './production-api'; export function sentryApiFactory(organization: string): SentryApi { - if (process.env.NODE_ENV !== 'production') { + if (process.env.NODE_ENV === 'production') { return new ProductionSentryApi(organization); } return new MockSentryApi(); From 0783e07290e381c1994aad996a6052e525dd79fb Mon Sep 17 00:00:00 2001 From: Wojciech Adaszynski Date: Mon, 18 May 2020 12:08:57 +0200 Subject: [PATCH 6/7] Fix linter errors and warnings in sentry plugin --- .../src/service/sentry-api.test.ts | 6 ------ plugins/sentry/dev/index.tsx | 20 +++++++++++++++++++ .../components/ErrorCell/ErrorCell.test.tsx | 2 +- .../src/components/ErrorCell/ErrorCell.tsx | 17 +++++++++++++++- .../SentryIssuesTable.test.tsx | 2 +- plugins/sentry/src/data/mock-api.ts | 3 +-- 6 files changed, 39 insertions(+), 11 deletions(-) create mode 100644 plugins/sentry/dev/index.tsx diff --git a/plugins/sentry-backend/src/service/sentry-api.test.ts b/plugins/sentry-backend/src/service/sentry-api.test.ts index fd4c2581b5..6473c58a10 100644 --- a/plugins/sentry-backend/src/service/sentry-api.test.ts +++ b/plugins/sentry-backend/src/service/sentry-api.test.ts @@ -14,13 +14,7 @@ * limitations under the License. */ import { SentryApiForwarder } from './sentry-api'; -import axios from 'axios'; -jest.mock('axios', () => ({ - default: { - post: jest.fn(), - }, -})); describe('SentryApiForwarder', () => { it('should generate headers based on token passed in constructor', () => { const forwarder = new SentryApiForwarder('testtoken'); diff --git a/plugins/sentry/dev/index.tsx b/plugins/sentry/dev/index.tsx new file mode 100644 index 0000000000..812a5585d4 --- /dev/null +++ b/plugins/sentry/dev/index.tsx @@ -0,0 +1,20 @@ +/* + * 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 { createDevApp } from '@backstage/dev-utils'; +import { plugin } from '../src/plugin'; + +createDevApp().registerPlugin(plugin).render(); diff --git a/plugins/sentry/src/components/ErrorCell/ErrorCell.test.tsx b/plugins/sentry/src/components/ErrorCell/ErrorCell.test.tsx index 0daa36f32b..74f1501225 100644 --- a/plugins/sentry/src/components/ErrorCell/ErrorCell.test.tsx +++ b/plugins/sentry/src/components/ErrorCell/ErrorCell.test.tsx @@ -17,7 +17,7 @@ import { ErrorCell } from './ErrorCell'; import React from 'react'; import { render } from '@testing-library/react'; import mockIssue from '../../data/sentry-issue-mock.json'; -import { ThemeProvider } from '@material-ui/styles'; +import { ThemeProvider } from '@material-ui/core'; import { lightTheme } from '@backstage/theme'; describe('Sentry error cell component', () => { diff --git a/plugins/sentry/src/components/ErrorCell/ErrorCell.tsx b/plugins/sentry/src/components/ErrorCell/ErrorCell.tsx index 42ab27e839..408014b790 100644 --- a/plugins/sentry/src/components/ErrorCell/ErrorCell.tsx +++ b/plugins/sentry/src/components/ErrorCell/ErrorCell.tsx @@ -1,3 +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 React, { FC } from 'react'; import { SentryIssue } from '../../data/sentry-issue'; import { Link, Typography } from '@material-ui/core'; @@ -7,7 +22,7 @@ import { BackstageTheme } from '@backstage/theme'; function stripText(text: string, maxLength: number) { return text.length > maxLength ? `${text.substr(0, maxLength)}...` : text; } -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles((theme) => ({ root: { minWidth: 260, position: 'relative', diff --git a/plugins/sentry/src/components/SentryIssuesTable/SentryIssuesTable.test.tsx b/plugins/sentry/src/components/SentryIssuesTable/SentryIssuesTable.test.tsx index 08159b16fc..4bd231a1e5 100644 --- a/plugins/sentry/src/components/SentryIssuesTable/SentryIssuesTable.test.tsx +++ b/plugins/sentry/src/components/SentryIssuesTable/SentryIssuesTable.test.tsx @@ -18,7 +18,7 @@ import { render } from '@testing-library/react'; import SentryIssuesTable from './SentryIssuesTable'; import { SentryIssue } from '../../data/sentry-issue'; import mockIssue from '../../data/sentry-issue-mock.json'; -import { ThemeProvider } from '@material-ui/styles'; +import { ThemeProvider } from '@material-ui/core'; import { lightTheme } from '@backstage/theme'; describe('SentryIssuesTable', () => { diff --git a/plugins/sentry/src/data/mock-api.ts b/plugins/sentry/src/data/mock-api.ts index 2eab8e30df..e26cce1762 100644 --- a/plugins/sentry/src/data/mock-api.ts +++ b/plugins/sentry/src/data/mock-api.ts @@ -32,8 +32,7 @@ function getMockIssues(number: number): SentryIssue[] { return new Array(number).fill(0).map(getMockIssue); } export class MockSentryApi implements SentryApi { - fetchIssues(project: string, statsFor: string): Promise { - console.info('Fetching mock responses for', project, statsFor); + fetchIssues(): Promise { return new Promise((resolve) => { setTimeout(() => resolve(getMockIssues(14)), 800); }); From baf8188db7b1d8aa1df2a208a90e98cc3810ebe1 Mon Sep 17 00:00:00 2001 From: Wojciech Adaszynski Date: Wed, 20 May 2020 09:57:32 +0200 Subject: [PATCH 7/7] Sentry plugin refactoring - code review changes --- packages/app/package.json | 2 +- plugins/catalog/package.json | 2 +- plugins/sentry-backend/package.json | 10 +++--- plugins/sentry-backend/src/service/router.ts | 22 +++++------- .../src/service/sentry-api.test.ts | 6 ++-- .../sentry-backend/src/service/sentry-api.ts | 34 +++++++++++-------- plugins/sentry-backend/tsconfig.json | 11 ------ plugins/sentry/package.json | 17 +++++----- .../SentryIssuesTable.test.tsx | 20 +++++------ .../SentryPluginPage.test.tsx | 2 +- .../SentryPluginPage/SentryPluginPage.tsx | 6 ++-- .../SentryPluginWidget/SentryPluginWidget.tsx | 4 +-- plugins/sentry/src/data/production-api.ts | 3 +- plugins/sentry/src/index.ts | 2 +- plugins/sentry/src/setupTests.ts | 2 +- plugins/sentry/tsconfig.json | 5 --- yarn.lock | 10 +++++- 17 files changed, 73 insertions(+), 85 deletions(-) delete mode 100644 plugins/sentry-backend/tsconfig.json delete mode 100644 plugins/sentry/tsconfig.json diff --git a/packages/app/package.json b/packages/app/package.json index 82b25f58e8..0a3d68603b 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -15,7 +15,7 @@ "@backstage/plugin-tech-radar": "^0.1.1-alpha.6", "@backstage/plugin-welcome": "^0.1.1-alpha.6", "@backstage/theme": "^0.1.1-alpha.6", - "@backstage/plugin-sentry": "^0.1.1-alpha.5", + "@backstage/plugin-sentry": "^0.1.1-alpha.6", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index fe70b8cc44..cd8548bdde 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -22,7 +22,7 @@ "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", - "@backstage/plugin-sentry": "^0.1.1-alpha.5", + "@backstage/plugin-sentry": "^0.1.1-alpha.6", "react": "^16.13.1", "react-dom": "^16.13.1", "react-use": "^14.2.0" diff --git a/plugins/sentry-backend/package.json b/plugins/sentry-backend/package.json index e9b7d01bf4..34259cdc7b 100644 --- a/plugins/sentry-backend/package.json +++ b/plugins/sentry-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-sentry-backend", - "version": "0.1.1-alpha.5", + "version": "0.1.1-alpha.6", "main": "dist", "types": "src/index.ts", "license": "Apache-2.0", @@ -13,8 +13,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.1.1-alpha.5", - "@backstage/core": "^0.1.1-alpha.5", + "@backstage/backend-common": "^0.1.1-alpha.6", + "@backstage/core": "^0.1.1-alpha.6", "axios": "^0.19.2", "cors": "^2.8.5", "express": "^4.17.1", @@ -23,8 +23,8 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.5", - "@backstage/dev-utils": "^0.1.1-alpha.5", + "@backstage/cli": "^0.1.1-alpha.6", + "@backstage/dev-utils": "^0.1.1-alpha.6", "@types/jest": "^25.2.1", "@types/node": "^12.0.0" }, diff --git a/plugins/sentry-backend/src/service/router.ts b/plugins/sentry-backend/src/service/router.ts index 3c750bed16..a949f3583f 100644 --- a/plugins/sentry-backend/src/service/router.ts +++ b/plugins/sentry-backend/src/service/router.ts @@ -16,25 +16,19 @@ import { Logger } from 'winston'; import Router from 'express-promise-router'; import express from 'express'; -import { SentryApiForwarder } from './sentry-api'; +import { getSentryApiForwarder } from './sentry-api'; -export async function createRouter( - rootLogger: Logger, -): Promise { +export async function createRouter(logger: Logger): Promise { const router = Router(); const SENTRY_TOKEN = process.env.SENTRY_TOKEN; if (!SENTRY_TOKEN) { - console.error('Sentry token must be provided in env to start the API.'); - process.exit(1); + throw new Error( + 'Sentry token must be provided in SENTRY_TOKEN environment variable to start the API.', + ); } - const sentryForwarder = new SentryApiForwarder(SENTRY_TOKEN); - const logger = rootLogger.child({ plugin: 'sentry' }); + const sentryForwarder = getSentryApiForwarder(SENTRY_TOKEN, logger); - router.get('*', (req, res) => sentryForwarder.fowardRequest(req, res)); + router.use(sentryForwarder); - const app = express(); - app.set('logger', logger); - app.use('/', router); - - return app; + return router; } diff --git a/plugins/sentry-backend/src/service/sentry-api.test.ts b/plugins/sentry-backend/src/service/sentry-api.test.ts index 6473c58a10..f15692861f 100644 --- a/plugins/sentry-backend/src/service/sentry-api.test.ts +++ b/plugins/sentry-backend/src/service/sentry-api.test.ts @@ -13,13 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { SentryApiForwarder } from './sentry-api'; +import { getRequestHeaders } from './sentry-api'; describe('SentryApiForwarder', () => { it('should generate headers based on token passed in constructor', () => { - const forwarder = new SentryApiForwarder('testtoken'); - - expect(forwarder.getRequestHeaders()).toEqual({ + expect(getRequestHeaders('testtoken')).toEqual({ headers: { Authorization: `Bearer testtoken`, }, diff --git a/plugins/sentry-backend/src/service/sentry-api.ts b/plugins/sentry-backend/src/service/sentry-api.ts index 861ad52227..5c8a9f1aa5 100644 --- a/plugins/sentry-backend/src/service/sentry-api.ts +++ b/plugins/sentry-backend/src/service/sentry-api.ts @@ -15,29 +15,33 @@ */ import express from 'express'; import axios from 'axios'; +import { Logger } from 'winston'; -export class SentryApiForwarder { - constructor(private token: string) {} +export function getRequestHeaders(token: string) { + return { + headers: { + Authorization: `Bearer ${token}`, + }, + }; +} - // public for testing - public getRequestHeaders() { - return { - headers: { - Authorization: `Bearer ${this.token}`, - }, - }; - } - public fowardRequest(request: express.Request, response: express.Response) { +export function getSentryApiForwarder(token: string, logger: Logger) { + return function fowardRequest( + request: express.Request, + response: express.Response, + ) { const sentryUrl = request.path; + const effectiveUrl = `https://sentry.io/${sentryUrl}`; + logger.info(`Calling Sentry REST API, ${effectiveUrl}`); axios - .get(`https://sentry.io/${sentryUrl}`, this.getRequestHeaders()) - .then((res) => { + .get(effectiveUrl, getRequestHeaders(token)) + .then(res => { response.send(res.data); }) - .catch((err) => { + .catch(err => { return response.status(err.response.status).json({ detail: err.response.statusText, }); }); - } + }; } diff --git a/plugins/sentry-backend/tsconfig.json b/plugins/sentry-backend/tsconfig.json deleted file mode 100644 index 7d4ea182e2..0000000000 --- a/plugins/sentry-backend/tsconfig.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "extends": "../../packages/backend/tsconfig.json", - "include": [ - "./src" - ], - "compilerOptions": { - "baseUrl": "./src", - "outDir": "./dist", - "skipLibCheck": true - } -} diff --git a/plugins/sentry/package.json b/plugins/sentry/package.json index dde234e901..11decb4087 100644 --- a/plugins/sentry/package.json +++ b/plugins/sentry/package.json @@ -1,7 +1,8 @@ { "name": "@backstage/plugin-sentry", - "version": "0.1.1-alpha.5", + "version": "0.1.1-alpha.6", "main": "dist/index.esm.js", + "main:src": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", "private": true, @@ -16,8 +17,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.1.1-alpha.5", - "@backstage/theme": "^0.1.1-alpha.5", + "@backstage/core": "^0.1.1-alpha.6", + "@backstage/theme": "^0.1.1-alpha.6", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -28,12 +29,12 @@ "timeago.js": "^4.0.2" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.5", - "@backstage/dev-utils": "^0.1.1-alpha.5", - "@testing-library/jest-dom": "^4.2.4", + "@backstage/cli": "^0.1.1-alpha.6", + "@backstage/dev-utils": "^0.1.1-alpha.6", + "@testing-library/jest-dom": "^5.7.0", "@testing-library/react": "^9.3.2", - "@testing-library/user-event": "^7.1.2", - "@types/jest": "^25.2.1", + "@testing-library/user-event": "^10.2.4", + "@types/jest": "^25.2.2", "@types/node": "^12.0.0", "@types/testing-library__jest-dom": "^5.0.4", "jest-fetch-mock": "^3.0.3" diff --git a/plugins/sentry/src/components/SentryIssuesTable/SentryIssuesTable.test.tsx b/plugins/sentry/src/components/SentryIssuesTable/SentryIssuesTable.test.tsx index 4bd231a1e5..6926fdfc2a 100644 --- a/plugins/sentry/src/components/SentryIssuesTable/SentryIssuesTable.test.tsx +++ b/plugins/sentry/src/components/SentryIssuesTable/SentryIssuesTable.test.tsx @@ -39,12 +39,12 @@ describe('SentryIssuesTable', () => { , ); - expect(await table.findByText('Error')).toBeInTheDOM(); - expect(await table.findByText('Graph')).toBeInTheDOM(); - expect(await table.findByText('First seen')).toBeInTheDOM(); - expect(await table.findByText('Last seen')).toBeInTheDOM(); - expect(await table.findByText('Events')).toBeInTheDOM(); - expect(await table.findByText('Users')).toBeInTheDOM(); + expect(await table.findByText('Error')).toBeInTheDocument(); + expect(await table.findByText('Graph')).toBeInTheDocument(); + expect(await table.findByText('First seen')).toBeInTheDocument(); + expect(await table.findByText('Last seen')).toBeInTheDocument(); + expect(await table.findByText('Events')).toBeInTheDocument(); + expect(await table.findByText('Users')).toBeInTheDocument(); }); it('should render values in a table', async () => { const issues: SentryIssue[] = [ @@ -63,9 +63,9 @@ describe('SentryIssuesTable', () => { , ); - expect(await table.findByText('Exception')).toBeInTheDOM(); - expect(await table.findByText('exception was thrown')).toBeInTheDOM(); - expect(await table.findByText('101')).toBeInTheDOM(); - expect(await table.findByText('202')).toBeInTheDOM(); + expect(await table.findByText('Exception')).toBeInTheDocument(); + expect(await table.findByText('exception was thrown')).toBeInTheDocument(); + expect(await table.findByText('101')).toBeInTheDocument(); + expect(await table.findByText('202')).toBeInTheDocument(); }); }); diff --git a/plugins/sentry/src/components/SentryPluginPage/SentryPluginPage.test.tsx b/plugins/sentry/src/components/SentryPluginPage/SentryPluginPage.test.tsx index 400c75e065..b0437ff4f7 100644 --- a/plugins/sentry/src/components/SentryPluginPage/SentryPluginPage.test.tsx +++ b/plugins/sentry/src/components/SentryPluginPage/SentryPluginPage.test.tsx @@ -26,7 +26,7 @@ const errorApi = { post: () => {} }; describe('SentryPluginPage', () => { it('should render header and time switched', () => { - mockFetch.mockResponse(() => new Promise(() => {})); + mockFetch.mockResponse('{}'); const rendered = render( diff --git a/plugins/sentry/src/components/SentryPluginPage/SentryPluginPage.tsx b/plugins/sentry/src/components/SentryPluginPage/SentryPluginPage.tsx index d7bf731a93..d8dc55a800 100644 --- a/plugins/sentry/src/components/SentryPluginPage/SentryPluginPage.tsx +++ b/plugins/sentry/src/components/SentryPluginPage/SentryPluginPage.tsx @@ -24,13 +24,13 @@ import { ContentHeader, SupportButton, } from '@backstage/core'; -import SentryPluginWidget from '../SentryPluginWidget/SentryPluginWidget'; +import { SentryPluginWidget } from '../SentryPluginWidget/SentryPluginWidget'; import { ToggleButton, ToggleButtonGroup } from '@material-ui/lab'; const SentryPluginPage: FC<{}> = () => { const [statsFor, setStatsFor] = useState<'12h' | '24h'>('12h'); - const toggleStatsFor = () => - statsFor === '12h' ? setStatsFor('24h') : setStatsFor('12h'); + const toggleStatsFor = () => setStatsFor(statsFor === '12h' ? '12h' : '24h'); + return (
diff --git a/plugins/sentry/src/components/SentryPluginWidget/SentryPluginWidget.tsx b/plugins/sentry/src/components/SentryPluginWidget/SentryPluginWidget.tsx index 9b5131d01e..5049905966 100644 --- a/plugins/sentry/src/components/SentryPluginWidget/SentryPluginWidget.tsx +++ b/plugins/sentry/src/components/SentryPluginWidget/SentryPluginWidget.tsx @@ -28,7 +28,7 @@ import { sentryApiFactory } from '../../data/api-factory'; const api = sentryApiFactory('spotify'); -const SentryPluginWidget: FC<{ +export const SentryPluginWidget: FC<{ sentryProjectId: string; statsFor: '24h' | '12h'; }> = ({ sentryProjectId, statsFor }) => { @@ -55,5 +55,3 @@ const SentryPluginWidget: FC<{ return ; }; - -export default SentryPluginWidget; diff --git a/plugins/sentry/src/data/production-api.ts b/plugins/sentry/src/data/production-api.ts index 0e8f22452f..ce4bab5aca 100644 --- a/plugins/sentry/src/data/production-api.ts +++ b/plugins/sentry/src/data/production-api.ts @@ -16,7 +16,8 @@ import { SentryIssue } from './sentry-issue'; import { SentryApi } from './sentry-api'; -const API_BASE_URL = 'http://localhost:7000/sentry/api/0/projects/'; +const API_HOST = process.env.API_HOST || 'http://localhost:7000'; +const API_BASE_URL = `${API_HOST}/sentry/api/0/projects/`; export class ProductionSentryApi implements SentryApi { private organization: string; diff --git a/plugins/sentry/src/index.ts b/plugins/sentry/src/index.ts index 350b8fd668..50da801a40 100644 --- a/plugins/sentry/src/index.ts +++ b/plugins/sentry/src/index.ts @@ -15,4 +15,4 @@ */ export { plugin } from './plugin'; -export { default as SentryIssuesWidget } from './components/SentryPluginWidget/SentryPluginWidget'; +export { SentryPluginWidget as SentryIssuesWidget } from './components/SentryPluginWidget/SentryPluginWidget'; diff --git a/plugins/sentry/src/setupTests.ts b/plugins/sentry/src/setupTests.ts index 1a907ab8e6..e34bc46f4b 100644 --- a/plugins/sentry/src/setupTests.ts +++ b/plugins/sentry/src/setupTests.ts @@ -14,5 +14,5 @@ * limitations under the License. */ -import '@testing-library/jest-dom/extend-expect'; +import '@testing-library/jest-dom'; require('jest-fetch-mock').enableMocks(); diff --git a/plugins/sentry/tsconfig.json b/plugins/sentry/tsconfig.json deleted file mode 100644 index b663b01fa2..0000000000 --- a/plugins/sentry/tsconfig.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "include": ["src", "dev"], - "compilerOptions": {} -} diff --git a/yarn.lock b/yarn.lock index 0e0edd4bf3..245e4fb7c9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4015,6 +4015,14 @@ jest-diff "^25.2.1" pretty-format "^25.2.1" +"@types/jest@^25.2.1": + version "25.2.3" + resolved "https://registry.npmjs.org/@types/jest/-/jest-25.2.3.tgz#33d27e4c4716caae4eced355097a47ad363fdcaf" + integrity sha512-JXc1nK/tXHiDhV55dvfzqtmP4S3sy3T3ouV2tkViZgxY/zeUkcpQcQPGRlgF4KmWzWW5oiWYSZwtCB+2RsE4Fw== + dependencies: + jest-diff "^25.2.1" + pretty-format "^25.2.1" + "@types/jest@^25.2.2": version "25.2.2" resolved "https://registry.npmjs.org/@types/jest/-/jest-25.2.2.tgz#6a752e7a00f69c3e790ea00c345029d5cefa92bf" @@ -5322,7 +5330,7 @@ aws4@^1.8.0: resolved "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz#7e33d8f7d449b3f673cd72deb9abdc552dbe528e" integrity sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug== -axios@^0.19.0: +axios@^0.19.0, axios@^0.19.2: version "0.19.2" resolved "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27" integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==