diff --git a/.changeset/thin-buckets-juggle.md b/.changeset/thin-buckets-juggle.md new file mode 100644 index 0000000000..a95f8a86ad --- /dev/null +++ b/.changeset/thin-buckets-juggle.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-airbrake': minor +--- + +A plugin for Airbrake (https://airbrake.io/) has been created diff --git a/.github/styles/vocab.txt b/.github/styles/vocab.txt index 8199855c9e..d3ea8c1c27 100644 --- a/.github/styles/vocab.txt +++ b/.github/styles/vocab.txt @@ -1,5 +1,6 @@ abc accessors +Airbrake Anddddd Apdex api diff --git a/microsite/data/plugins/airbrake.yaml b/microsite/data/plugins/airbrake.yaml new file mode 100644 index 0000000000..96de75c002 --- /dev/null +++ b/microsite/data/plugins/airbrake.yaml @@ -0,0 +1,9 @@ +--- +title: Airbrake +author: Simply Business +authorUrl: https://github.com/simplybusiness/ +category: Monitoring +description: Access Airbrake error monitoring and other integrations from within Backstage +documentation: https://github.com/backstage/backstage/blob/master/plugins/airbrake/README.md +iconUrl: https://wp-assets.airbrake.io/wp-content/uploads/2020/10/05222904/Square-white-A-on-Orange.png +npmPackageName: '@backstage/plugin-airbrake' diff --git a/packages/app/package.json b/packages/app/package.json index 54e0760ecc..bbfd903484 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -11,6 +11,7 @@ "@backstage/core-components": "^0.8.3", "@backstage/core-plugin-api": "^0.4.1", "@backstage/integration-react": "^0.1.16", + "@backstage/plugin-airbrake": "^0.0.0", "@backstage/plugin-api-docs": "^0.6.21", "@backstage/plugin-azure-devops": "^0.1.9", "@backstage/plugin-apache-airflow": "^0.1.2", diff --git a/plugins/airbrake/.eslintrc.js b/plugins/airbrake/.eslintrc.js new file mode 100644 index 0000000000..13573efa9c --- /dev/null +++ b/plugins/airbrake/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: [require.resolve('@backstage/cli/config/eslint')], +}; diff --git a/plugins/airbrake/README.md b/plugins/airbrake/README.md new file mode 100644 index 0000000000..de041fb3ec --- /dev/null +++ b/plugins/airbrake/README.md @@ -0,0 +1,13 @@ +# Airbrake + +Welcome to the Airbrake plugin! + +This is a plugin providing connectivity between Backstage and Airbrake (https://airbrake.io/). + +_This plugin is currently not fit for use as it is work in progress_ + +## Getting started + +You can 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/airbrake/api-report.md b/plugins/airbrake/api-report.md new file mode 100644 index 0000000000..c01aa938ae --- /dev/null +++ b/plugins/airbrake/api-report.md @@ -0,0 +1,27 @@ +## API Report File for "@backstage/plugin-airbrake" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +/// + +import { BackstagePlugin } from '@backstage/core-plugin-api'; +import { RouteRef } from '@backstage/core-plugin-api'; + +// Warning: (ae-missing-release-tag) "airbrakePlugin" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const airbrakePlugin: BackstagePlugin< + { + root: RouteRef; + }, + {} +>; + +// Warning: (ae-missing-release-tag) "EntityAirbrakeContent" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const EntityAirbrakeContent: () => JSX.Element; + +// (No @packageDocumentation comment for this package) +``` diff --git a/plugins/airbrake/dev/index.tsx b/plugins/airbrake/dev/index.tsx new file mode 100644 index 0000000000..e74ce68ee5 --- /dev/null +++ b/plugins/airbrake/dev/index.tsx @@ -0,0 +1,53 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 { createDevApp } from '@backstage/dev-utils'; +import { EntityAirbrakeContent, airbrakePlugin } from '../src/plugin'; +import { + Content, + ContentHeader, + Header, + HeaderLabel, + Page, + SupportButton, +} from '@backstage/core-components'; + +createDevApp() + .registerPlugin(airbrakePlugin) + .addPage({ + element: ( + +
+ + +
+ + + + A description of your plugin goes here. + + + + +
+ ), + title: 'Root Page', + path: '/airbrake', + }) + .render(); diff --git a/plugins/airbrake/package.json b/plugins/airbrake/package.json new file mode 100644 index 0000000000..7294c97413 --- /dev/null +++ b/plugins/airbrake/package.json @@ -0,0 +1,63 @@ +{ + "name": "@backstage/plugin-airbrake", + "version": "0.0.0", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "publishConfig": { + "access": "public", + "main": "dist/index.esm.js", + "types": "dist/index.d.ts" + }, + "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-components": "^0.8.3", + "@backstage/core-plugin-api": "^0.4.1", + "@backstage/theme": "^0.2.14", + "@material-ui/core": "^4.12.2", + "@material-ui/icons": "^4.9.1", + "@material-ui/lab": "4.0.0-alpha.57", + "react-use": "^17.2.4", + "object-hash": "^2.2.0" + }, + "peerDependencies": { + "react": "^16.13.1 || ^17.0.0" + }, + "devDependencies": { + "@types/object-hash": "^2.2.1", + "@backstage/app-defaults": "^0.1.2", + "@backstage/cli": "^0.10.5", + "@backstage/core-app-api": "^0.3.1", + "@backstage/dev-utils": "^0.2.16", + "@backstage/test-utils": "^0.2.1", + "@testing-library/jest-dom": "^5.10.1", + "@testing-library/react": "^11.2.5", + "@testing-library/user-event": "^13.1.8", + "@types/jest": "^26.0.7", + "@types/node": "^14.14.32", + "msw": "^0.35.0", + "cross-fetch": "^3.0.6", + "react-router": "6.0.0-beta.0" + }, + "files": [ + "dist" + ], + "jest": { + "coverageThreshold": { + "global": { + "functions": 100, + "lines": 100, + "statements": 100 + } + } + } +} diff --git a/plugins/airbrake/src/EntityAirbrakeContent.test.tsx b/plugins/airbrake/src/EntityAirbrakeContent.test.tsx new file mode 100644 index 0000000000..f0afef425f --- /dev/null +++ b/plugins/airbrake/src/EntityAirbrakeContent.test.tsx @@ -0,0 +1,25 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 { EntityAirbrakeContent } from './plugin'; +import { renderInTestApp } from '@backstage/test-utils'; + +describe('The Airbrake entity', () => { + it('should render the content properly', async () => { + const rendered = await renderInTestApp(); + expect(rendered.getByText('ChunkLoadError')).toBeInTheDocument(); + }); +}); diff --git a/plugins/airbrake/src/components/EntityAirbrakeContent/EntityAirbrakeContent.test.tsx b/plugins/airbrake/src/components/EntityAirbrakeContent/EntityAirbrakeContent.test.tsx new file mode 100644 index 0000000000..a621eb281c --- /dev/null +++ b/plugins/airbrake/src/components/EntityAirbrakeContent/EntityAirbrakeContent.test.tsx @@ -0,0 +1,32 @@ +/* + * Copyright 2020 The Backstage Authors + * + * 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 { EntityAirbrakeContent } from './EntityAirbrakeContent'; +import exampleData from './example-data.json'; +import { renderInTestApp } from '@backstage/test-utils'; + +describe('EntityAirbrakeContent', () => { + it('renders all errors sent from Airbrake', async () => { + const table = await renderInTestApp(); + expect(exampleData.groups.length).toBeGreaterThan(0); + for (const group of exampleData.groups) { + expect( + await table.getByText(group.errors[0].message), + ).toBeInTheDocument(); + } + }); +}); diff --git a/plugins/airbrake/src/components/EntityAirbrakeContent/EntityAirbrakeContent.tsx b/plugins/airbrake/src/components/EntityAirbrakeContent/EntityAirbrakeContent.tsx new file mode 100644 index 0000000000..062d9e898a --- /dev/null +++ b/plugins/airbrake/src/components/EntityAirbrakeContent/EntityAirbrakeContent.tsx @@ -0,0 +1,48 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { Grid, Typography } from '@material-ui/core'; +import { InfoCard } from '@backstage/core-components'; +import exampleData from './example-data.json'; +import hash from 'object-hash'; +import { makeStyles } from '@material-ui/core/styles'; +import { BackstageTheme } from '@backstage/theme'; + +const useStyles = makeStyles(() => ({ + multilineText: { + whiteSpace: 'pre-wrap', + }, +})); + +export const EntityAirbrakeContent = () => { + const classes = useStyles(); + + return ( + + {exampleData.groups.map(group => ( + + {group.errors.map(error => ( + + + {error.message} + + + ))} + + ))} + + ); +}; diff --git a/plugins/airbrake/src/components/EntityAirbrakeContent/example-data.json b/plugins/airbrake/src/components/EntityAirbrakeContent/example-data.json new file mode 100644 index 0000000000..fa654ec50b --- /dev/null +++ b/plugins/airbrake/src/components/EntityAirbrakeContent/example-data.json @@ -0,0 +1,85 @@ +{ + "count": 2, + "end": "", + "groups": [ + { + "id": "1", + "projectId": 123, + "resolved": false, + "muted": false, + "mutedBy": 0, + "mutedAt": null, + "errors": [ + { + "type": "Error", + "message": "useSearch must be used within a SearchContextProvider", + "backtrace": [ + { + "file": "webpack-internal:///../../node_modules/@backstage/plugin-search/dist/esm/index-893ec2f5.esm.js", + "function": "useSearch", + "line": 303, + "column": 11, + "code": null + } + ] + } + ], + "attributes": null, + "context": { + "action": "", + "component": "", + "environment": "local", + "severity": "error" + }, + "lastDeployId": "0", + "lastDeployAt": null, + "lastNoticeId": "234", + "lastNoticeAt": "2021-12-19T09:59:00.124Z", + "noticeCount": 5, + "noticeTotalCount": 5, + "commentCount": 0, + "createdAt": "2021-12-19T09:44:30.067447Z" + }, + { + "id": "2", + "projectId": 123, + "resolved": true, + "muted": false, + "mutedBy": 0, + "mutedAt": null, + "errors": [ + { + "type": "ChunkLoadError", + "message": "Loading chunk 7764 failed.", + "backtrace": [ + { + "file": "/PROJECT_ROOT/static/runtime.069c874d.js", + "function": "Object._.f.j", + "line": 1, + "column": 19465, + "code": null + } + ] + } + ], + "attributes": null, + "context": { + "action": "", + "component": "", + "environment": "local", + "severity": "error" + }, + "lastDeployId": "0", + "lastDeployAt": null, + "lastNoticeId": "345", + "lastNoticeAt": "2021-12-15T17:16:38.419Z", + "noticeCount": 1, + "noticeTotalCount": 1, + "commentCount": 0, + "createdAt": "2021-12-15T17:16:38.41983Z" + } + ], + "page": 1, + "resolvedCount": 1, + "unresolvedCount": 1 +} diff --git a/plugins/airbrake/src/index.test.ts b/plugins/airbrake/src/index.test.ts new file mode 100644 index 0000000000..3aea2edf66 --- /dev/null +++ b/plugins/airbrake/src/index.test.ts @@ -0,0 +1,23 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 { EntityAirbrakeContent, airbrakePlugin } from './index'; + +describe('The Airbrake plugin index file', () => { + it('exports the plugin and content', async () => { + expect(EntityAirbrakeContent).toBeTruthy(); + expect(airbrakePlugin).toBeTruthy(); + }); +}); diff --git a/plugins/airbrake/src/index.ts b/plugins/airbrake/src/index.ts new file mode 100644 index 0000000000..8f319e02fd --- /dev/null +++ b/plugins/airbrake/src/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 { airbrakePlugin, EntityAirbrakeContent } from './plugin'; diff --git a/plugins/airbrake/src/plugin.test.ts b/plugins/airbrake/src/plugin.test.ts new file mode 100644 index 0000000000..c8e9c30e83 --- /dev/null +++ b/plugins/airbrake/src/plugin.test.ts @@ -0,0 +1,23 @@ +/* + * Copyright 2020 The Backstage Authors + * + * 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 { airbrakePlugin } from './plugin'; + +describe('catalog', () => { + it('should export plugin', () => { + expect(airbrakePlugin).toBeDefined(); + }); +}); diff --git a/plugins/airbrake/src/plugin.ts b/plugins/airbrake/src/plugin.ts new file mode 100644 index 0000000000..0a3679baa4 --- /dev/null +++ b/plugins/airbrake/src/plugin.ts @@ -0,0 +1,39 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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, + createRoutableExtension, +} from '@backstage/core-plugin-api'; + +import { rootRouteRef } from './routes'; + +export const airbrakePlugin = createPlugin({ + id: 'airbrake', + routes: { + root: rootRouteRef, + }, +}); + +export const EntityAirbrakeContent = airbrakePlugin.provide( + createRoutableExtension({ + name: 'EntityAirbrakeContent', + component: () => + import('./components/EntityAirbrakeContent/EntityAirbrakeContent').then( + m => m.EntityAirbrakeContent, + ), + mountPoint: rootRouteRef, + }), +); diff --git a/plugins/airbrake/src/routes.ts b/plugins/airbrake/src/routes.ts new file mode 100644 index 0000000000..52672267fe --- /dev/null +++ b/plugins/airbrake/src/routes.ts @@ -0,0 +1,20 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 { createRouteRef } from '@backstage/core-plugin-api'; + +export const rootRouteRef = createRouteRef({ + id: 'airbrake', +}); diff --git a/plugins/airbrake/src/setupTests.ts b/plugins/airbrake/src/setupTests.ts new file mode 100644 index 0000000000..fc6dbd98f8 --- /dev/null +++ b/plugins/airbrake/src/setupTests.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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'; +import 'cross-fetch/polyfill'; diff --git a/yarn.lock b/yarn.lock index 2eec99eab6..1c1762383d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7879,6 +7879,11 @@ dependencies: "@types/node" "*" +"@types/object-hash@^2.2.1": + version "2.2.1" + resolved "https://registry.npmjs.org/@types/object-hash/-/object-hash-2.2.1.tgz#67c169f8f033e0b62abbf81df2d00f4598d540b9" + integrity sha512-i/rtaJFCsPljrZvP/akBqEwUP2y5cZLOmvO+JaYnz01aPknrQ+hB5MRcO7iqCUsFaYfTG8kGfKUyboA07xeDHQ== + "@types/overlayscrollbars@^1.12.0": version "1.12.0" resolved "https://registry.npmjs.org/@types/overlayscrollbars/-/overlayscrollbars-1.12.0.tgz#98456caceca8ad73bd5bb572632a585074e70764" @@ -21790,7 +21795,7 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" -object-hash@^2.0.1, object-hash@^2.1.1: +object-hash@^2.0.1, object-hash@^2.1.1, object-hash@^2.2.0: version "2.2.0" resolved "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5" integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==