diff --git a/.changeset/heavy-scissors-run.md b/.changeset/heavy-scissors-run.md new file mode 100644 index 0000000000..c2982a509c --- /dev/null +++ b/.changeset/heavy-scissors-run.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-airbrake': minor +'@backstage/plugin-airbrake-backend': minor +--- + +This marks the first release where the Airbrake plugin is useable. Airbrake frontend and Airbrake backend work with each other. Currently just a summary of the latest Airbrakes is shown on Backstage. diff --git a/.github/styles/vocab.txt b/.github/styles/vocab.txt index 408b84903c..fea996b03b 100644 --- a/.github/styles/vocab.txt +++ b/.github/styles/vocab.txt @@ -3,6 +3,8 @@ accessors addon addons Airbrake +Airbrakes +airbrake Anddddd Apdex api diff --git a/plugins/airbrake-backend/README.md b/plugins/airbrake-backend/README.md index 42369524d6..7ded50c85a 100644 --- a/plugins/airbrake-backend/README.md +++ b/plugins/airbrake-backend/README.md @@ -1,16 +1,21 @@ # airbrake-backend -Welcome to the airbrake-backend backend plugin! +The Airbrake backend plugin provides a simple proxy to the Airbrake API while hiding away the secret API key from the frontend. -_This plugin was created through the Backstage CLI_ +## How to use -## Getting started +See the [Airbrake plugin instructions](../airbrake/README.md#how-to-use). -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 [/airbrake-backend](http://localhost:7007/airbrake-backend). +## Local Development -Here is an example endpoint: http://localhost:7007/airbrake-backend/health - -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. +1. Set the environment variable `AIRBRAKE_API_KEY` with your [API key](https://airbrake.io/docs/api/#authentication). +2. Run this plugin in standalone mode by running `yarn start`. The configuration is already setup in the root [`app-config.yaml`](../../app-config.yaml) to pick up your API key from the environment variable above. + +Access it from http://localhost:7007/api/airbrake. Or use the Airbrake plugin which will talk to it automatically. + +Here are some example endpoints: + +- http://localhost:7007/api/airbrake/health +- http://localhost:7007/api/airbrake/api/v4/projects diff --git a/plugins/airbrake-backend/api-report.md b/plugins/airbrake-backend/api-report.md index b9e9a50e0b..58f299fb4c 100644 --- a/plugins/airbrake-backend/api-report.md +++ b/plugins/airbrake-backend/api-report.md @@ -6,6 +6,7 @@ import { Config } from '@backstage/config'; import express from 'express'; import { Logger as Logger_2 } from 'winston'; +import * as winston from 'winston'; // @public export interface AirbrakeConfig { @@ -16,7 +17,10 @@ export interface AirbrakeConfig { export function createRouter(options: RouterOptions): Promise; // @public -export function extractAirbrakeConfig(config: Config): AirbrakeConfig; +export function extractAirbrakeConfig( + config: Config, + logger: winston.Logger, +): AirbrakeConfig; // @public export interface RouterOptions { diff --git a/plugins/airbrake-backend/config.d.ts b/plugins/airbrake-backend/config.d.ts index ab6a7ef537..af28d7e73e 100644 --- a/plugins/airbrake-backend/config.d.ts +++ b/plugins/airbrake-backend/config.d.ts @@ -19,6 +19,7 @@ export interface Config { airbrake: { /** * The API Key to authenticate requests. More details on how to get this here: https://airbrake.io/docs/api/#authentication + * @visibility secret */ apiKey: string; }; diff --git a/plugins/airbrake-backend/package.json b/plugins/airbrake-backend/package.json index f24ae2931d..decdeef3dc 100644 --- a/plugins/airbrake-backend/package.json +++ b/plugins/airbrake-backend/package.json @@ -40,19 +40,5 @@ "dist", "config.d.ts" ], - "configSchema": "config.d.ts", - "jest": { - "coverageThreshold": { - "global": { - "functions": 100, - "lines": 100, - "statements": 100 - } - }, - "coveragePathIgnorePatterns": [ - "standaloneServer.ts", - "index.ts", - "run.ts" - ] - } + "configSchema": "config.d.ts" } diff --git a/plugins/airbrake-backend/src/config/ExtractAirbrakeConfig.test.ts b/plugins/airbrake-backend/src/config/ExtractAirbrakeConfig.test.ts new file mode 100644 index 0000000000..d94746c8ef --- /dev/null +++ b/plugins/airbrake-backend/src/config/ExtractAirbrakeConfig.test.ts @@ -0,0 +1,67 @@ +/* + * Copyright 2022 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 { getVoidLogger } from '@backstage/backend-common'; +import { ConfigReader } from '@backstage/config'; +import { extractAirbrakeConfig } from './ExtractAirbrakeConfig'; +import * as winston from 'winston'; + +describe('ExtractAirbrakeConfig', () => { + let oldProcessEnv: NodeJS.ProcessEnv; + let voidLogger: winston.Logger; + + beforeEach(() => { + oldProcessEnv = process.env; + voidLogger = getVoidLogger(); + }); + + it('gets the API key', () => { + const config = new ConfigReader({ + airbrake: { + apiKey: 'fakeApiKey', + }, + }); + + const airbrakeConfig = extractAirbrakeConfig(config, voidLogger); + expect(airbrakeConfig.apiKey).toStrictEqual('fakeApiKey'); + }); + + it('does not fail in development', () => { + process.env = { + ...oldProcessEnv, + NODE_ENV: 'development', + }; + + const config = new ConfigReader({}); + + expect(() => extractAirbrakeConfig(config, voidLogger)).not.toThrow(); + const airbrakeConfig = extractAirbrakeConfig(config, voidLogger); + expect(airbrakeConfig.apiKey).toStrictEqual(''); + }); + + it('fails in production', () => { + process.env = { + ...oldProcessEnv, + NODE_ENV: 'production', + }; + + const config = new ConfigReader({}); + expect(() => extractAirbrakeConfig(config, voidLogger)).toThrow(); + }); + + afterEach(() => { + process.env = { ...oldProcessEnv }; + }); +}); diff --git a/plugins/airbrake-backend/src/config/ExtractAirbrakeConfig.ts b/plugins/airbrake-backend/src/config/ExtractAirbrakeConfig.ts index ea39a27d11..20542ccbde 100644 --- a/plugins/airbrake-backend/src/config/ExtractAirbrakeConfig.ts +++ b/plugins/airbrake-backend/src/config/ExtractAirbrakeConfig.ts @@ -14,6 +14,7 @@ * limitations under the License. */ import { Config } from '@backstage/config'; +import * as winston from 'winston'; /** * The configuration needed for the airbrake-backend plugin @@ -33,9 +34,27 @@ export interface AirbrakeConfig { * @public * * @param config - The config object to extract from + * @param logger - THe logger object */ -export function extractAirbrakeConfig(config: Config): AirbrakeConfig { - return { - apiKey: config.getString('airbrake.apiKey'), - }; +export function extractAirbrakeConfig( + config: Config, + logger: winston.Logger, +): AirbrakeConfig { + try { + return { + apiKey: config.getString('airbrake.apiKey'), + }; + } catch (e) { + if (process.env.NODE_ENV !== 'development') { + throw e; + } else { + logger.warn( + 'Airbrake config missing, Airbrake plugin will probably not work', + e, + ); + return { + apiKey: '', + }; + } + } } diff --git a/plugins/airbrake-backend/src/service/router.test.ts b/plugins/airbrake-backend/src/service/router.test.ts index 60c1f52126..05c9308d7f 100644 --- a/plugins/airbrake-backend/src/service/router.test.ts +++ b/plugins/airbrake-backend/src/service/router.test.ts @@ -24,23 +24,26 @@ import { RouterOptions, } from './router'; import { AirbrakeConfig, extractAirbrakeConfig } from '../config'; +import * as winston from 'winston'; describe('createRouter', () => { let app: express.Express; let airbrakeConfig: AirbrakeConfig; + let voidLogger: winston.Logger; beforeEach(async () => { jest.resetAllMocks(); + voidLogger = getVoidLogger(); const config = new ConfigReader({ airbrake: { apiKey: 'fakeApiKey', }, }); - airbrakeConfig = extractAirbrakeConfig(config); + airbrakeConfig = extractAirbrakeConfig(config, voidLogger); const router = await createRouter({ - logger: getVoidLogger(), + logger: voidLogger, airbrakeConfig, }); app = express().use(router); @@ -58,7 +61,7 @@ describe('createRouter', () => { describe('GET /api', () => { it('appends the API Key properly with no other url parameters', () => { const options: RouterOptions = { - logger: getVoidLogger(), + logger: voidLogger, airbrakeConfig, }; const pathRewrite = generateAirbrakePathRewrite(options) as ( @@ -72,7 +75,7 @@ describe('createRouter', () => { it('appends the API Key properly despite there being other URL parameters', () => { const options: RouterOptions = { - logger: getVoidLogger(), + logger: voidLogger, airbrakeConfig, }; const pathRewrite = generateAirbrakePathRewrite(options) as ( diff --git a/plugins/airbrake-backend/src/service/standaloneServer.ts b/plugins/airbrake-backend/src/service/standaloneServer.ts index 5957dd0b7f..2f2de71fa1 100644 --- a/plugins/airbrake-backend/src/service/standaloneServer.ts +++ b/plugins/airbrake-backend/src/service/standaloneServer.ts @@ -32,9 +32,9 @@ export interface ServerOptions { export async function startStandaloneServer( options: ServerOptions, ): Promise { - const logger = options.logger.child({ service: 'airbrake-backend-backend' }); + const logger = options.logger.child({ service: 'airbrake-backend' }); const config = await loadBackendConfig({ logger, argv: process.argv }); - const airbrakeConfig = extractAirbrakeConfig(config); + const airbrakeConfig = extractAirbrakeConfig(config, logger); logger.debug('Starting application server...'); const router = await createRouter({ logger, @@ -43,9 +43,13 @@ export async function startStandaloneServer( let service = createServiceBuilder(module) .setPort(options.port) - .addRouter('/airbrake-backend', router); + .addRouter('/api/airbrake', router); if (options.enableCors) { + logger.info('CORS is enabled, limiting to localhost with port 3000'); service = service.enableCors({ origin: 'http://localhost:3000' }); + } else { + logger.info('CORS is disabled, allowing all origins'); + service = service.enableCors({ origin: '*' }); } return await service.start().catch(err => { diff --git a/plugins/airbrake/README.md b/plugins/airbrake/README.md index de041fb3ec..2fb5dbd2fc 100644 --- a/plugins/airbrake/README.md +++ b/plugins/airbrake/README.md @@ -1,13 +1,75 @@ # Airbrake -Welcome to the Airbrake plugin! +The Airbrake plugin provides connectivity between Backstage and Airbrake (https://airbrake.io/). -This is a plugin providing connectivity between Backstage and Airbrake (https://airbrake.io/). +## How to use -_This plugin is currently not fit for use as it is work in progress_ +1. Install the Frontend plugin: -## Getting started + ```bash + # From your Backstage root directory + cd packages/app + yarn add @backstage/plugin-airbrake + ``` -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. +2. Install the Backend plugin: + + ```bash + # From your Backstage root directory + cd packages/backend + yarn add @backstage/plugin-airbrake-backend + ``` + +3. Add the `EntityAirbrakeContent` to `packages/app/src/components/catalog/EntityPage.tsx`: + + ```typescript jsx + import { EntityAirbrakeContent } from '@backstage/plugin-airbrake'; + + const serviceEntityPage = ( + + + + + + ); + ``` + +4. Setup the Backend code in `packages/backend/src/index.ts`: + + ```typescript + import { + createRouter as createAirbrakeRouter, + extractAirbrakeConfig, + } from '@backstage/plugin-airbrake-backend'; + + async function main() { + //... After const config = await loadBackendConfig({ ... + + const airbrakeRouter = await createAirbrakeRouter({ + logger, + airbrakeConfig: extractAirbrakeConfig(config), + }); + + const service = createServiceBuilder(module) + // ... Add the airbrakeRouter here + .addRouter('/api/airbrake', airbrakeRouter); + } + ``` + +5. Add this config as a top level section in your `app-config.yaml`: + + ```yaml + airbrake: + apiKey: ${AIRBRAKE_API_KEY} + ``` + +6. Set an environment variable `AIRBRAKE_API_KEY` with your [API key](https://airbrake.io/docs/api/#authentication) + before starting Backstage backend. + +## Local Development + +Start this plugin in standalone mode by running `yarn start`. 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. + +> A mock API will be used to run it in standalone. If you want to talk to the real API [follow the instructions to start up Airbrake Backend in standalone](../airbrake-backend/README.md#local-development). diff --git a/plugins/airbrake/api-report.md b/plugins/airbrake/api-report.md index c01aa938ae..bd274e604d 100644 --- a/plugins/airbrake/api-report.md +++ b/plugins/airbrake/api-report.md @@ -8,9 +8,7 @@ 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) +// @public export const airbrakePlugin: BackstagePlugin< { root: RouteRef; @@ -18,10 +16,6 @@ export const airbrakePlugin: BackstagePlugin< {} >; -// 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) +// @public export const EntityAirbrakeContent: () => JSX.Element; - -// (No @packageDocumentation comment for this package) ``` diff --git a/plugins/airbrake/dev/components/ApiBar/ApiBar.tsx b/plugins/airbrake/dev/components/ApiBar/ApiBar.tsx index eae00c2d32..7a4ee42f14 100644 --- a/plugins/airbrake/dev/components/ApiBar/ApiBar.tsx +++ b/plugins/airbrake/dev/components/ApiBar/ApiBar.tsx @@ -71,12 +71,6 @@ export const ApiBar = () => { value.setProjectId?.(parseInt(e.target.value, 10) || undefined) } /> - value.setApiKey?.(e.target.value)} - /> )} diff --git a/plugins/airbrake/dev/components/ContextProvider/ContextProvider.tsx b/plugins/airbrake/dev/components/ContextProvider/ContextProvider.tsx index 6d7fe0f7f2..d46366d8f6 100644 --- a/plugins/airbrake/dev/components/ContextProvider/ContextProvider.tsx +++ b/plugins/airbrake/dev/components/ContextProvider/ContextProvider.tsx @@ -23,23 +23,18 @@ import React, { interface ContextInterface { projectId?: number; setProjectId?: Dispatch>; - apiKey?: string; - setApiKey?: Dispatch>; } export const Context = React.createContext({}); export const ContextProvider = ({ children }: PropsWithChildren<{}>) => { const [projectId, setProjectId] = useState(); - const [apiKey, setApiKey] = useState(''); return ( {children} diff --git a/plugins/airbrake/dev/index.tsx b/plugins/airbrake/dev/index.tsx index cd0d29301f..a844dfdfdc 100644 --- a/plugins/airbrake/dev/index.tsx +++ b/plugins/airbrake/dev/index.tsx @@ -19,13 +19,14 @@ import { TestApiProvider } from '@backstage/test-utils'; import { airbrakePlugin, EntityAirbrakeContent } from '../src'; import { airbrakeApiRef, + localDiscoveryApi, MockAirbrakeApi, ProductionAirbrakeApi, } from '../src/api'; import { ApiBar } from './components/ApiBar'; import { Content, Header, Page } from '@backstage/core-components'; import { EntityProvider } from '@backstage/plugin-catalog-react'; -import { createEntity } from '../src/api/mock/MockEntity'; +import { createEntity } from '../src/api'; import CloudOffIcon from '@material-ui/icons/CloudOff'; import CloudIcon from '@material-ui/icons/Cloud'; import { Context, ContextProvider } from './components/ContextProvider'; @@ -53,7 +54,10 @@ createDevApp() element: ( -
+
@@ -61,7 +65,10 @@ createDevApp() {value => ( diff --git a/plugins/airbrake/package.json b/plugins/airbrake/package.json index 1912e59f66..128373881c 100644 --- a/plugins/airbrake/package.json +++ b/plugins/airbrake/package.json @@ -42,7 +42,6 @@ "devDependencies": { "@backstage/app-defaults": "^0.1.8", "@backstage/cli": "^0.14.0", - "@backstage/core-app-api": "^0.5.3", "@backstage/dev-utils": "^0.2.22", "@backstage/test-utils": "^0.2.5", "@testing-library/jest-dom": "^5.10.1", @@ -57,14 +56,5 @@ }, "files": [ "dist" - ], - "jest": { - "coverageThreshold": { - "global": { - "functions": 100, - "lines": 100, - "statements": 100 - } - } - } + ] } diff --git a/plugins/airbrake/src/api/ProductionApi.test.ts b/plugins/airbrake/src/api/ProductionApi.test.ts index 04bb73caf7..f09127b4c2 100644 --- a/plugins/airbrake/src/api/ProductionApi.test.ts +++ b/plugins/airbrake/src/api/ProductionApi.test.ts @@ -19,21 +19,19 @@ import { rest } from 'msw'; import mockGroupsData from './mock/airbrakeGroupsApiMock.json'; import { setupServer } from 'msw/node'; import { setupRequestMockHandlers } from '@backstage/test-utils'; +import { localDiscoveryApi } from './mock'; describe('The production Airbrake API', () => { - const productionApi = new ProductionAirbrakeApi('fakeApiKey'); + const productionApi = new ProductionAirbrakeApi(localDiscoveryApi); const worker = setupServer(); setupRequestMockHandlers(worker); it('fetches groups using the provided project ID', async () => { worker.use( rest.get( - 'https://api.airbrake.io/api/v4/projects/123456/groups', - (req, res, ctx) => { - if (req.url.searchParams.get('key') === 'fakeApiKey') { - return res(ctx.status(200), ctx.json(mockGroupsData)); - } - return res(ctx.status(401)); + 'http://localhost:7007/api/airbrake/api/v4/projects/123456/groups', + (_, res, ctx) => { + return res(ctx.status(200), ctx.json(mockGroupsData)); }, ), ); @@ -46,7 +44,7 @@ describe('The production Airbrake API', () => { it('throws if fetching groups was unsuccessful', async () => { worker.use( rest.get( - 'https://api.airbrake.io/api/v4/projects/123456/groups', + 'http://localhost:7007/api/airbrake/api/v4/projects/123456/groups', (_, res, ctx) => { return res(ctx.status(500)); }, diff --git a/plugins/airbrake/src/api/ProductionApi.ts b/plugins/airbrake/src/api/ProductionApi.ts index 76416bc173..21ca19ee7d 100644 --- a/plugins/airbrake/src/api/ProductionApi.ts +++ b/plugins/airbrake/src/api/ProductionApi.ts @@ -16,12 +16,14 @@ import { Groups } from './airbrakeGroups'; import { AirbrakeApi } from './AirbrakeApi'; +import { DiscoveryApi } from '@backstage/core-plugin-api'; export class ProductionAirbrakeApi implements AirbrakeApi { - constructor(private readonly apiKey?: string) {} + constructor(private readonly discoveryApi: DiscoveryApi) {} async fetchGroups(projectId: string): Promise { - const apiUrl = `https://api.airbrake.io/api/v4/projects/${projectId}/groups?key=${this.apiKey}`; + const baseUrl = await this.discoveryApi.getBaseUrl('airbrake'); + const apiUrl = `${baseUrl}/api/v4/projects/${projectId}/groups`; const response = await fetch(apiUrl); diff --git a/plugins/airbrake/src/api/mock/LocalDiscoveryApi.ts b/plugins/airbrake/src/api/mock/LocalDiscoveryApi.ts new file mode 100644 index 0000000000..c3080a8104 --- /dev/null +++ b/plugins/airbrake/src/api/mock/LocalDiscoveryApi.ts @@ -0,0 +1,22 @@ +/* + * Copyright 2022 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 { DiscoveryApi } from '@backstage/core-plugin-api'; + +export const localDiscoveryApi: DiscoveryApi = { + async getBaseUrl(pluginId: string): Promise { + return `http://localhost:7007/api/${pluginId}`; + }, +}; diff --git a/plugins/airbrake/src/api/mock/index.ts b/plugins/airbrake/src/api/mock/index.ts index 0588d45fa6..4e0ab5fabf 100644 --- a/plugins/airbrake/src/api/mock/index.ts +++ b/plugins/airbrake/src/api/mock/index.ts @@ -14,4 +14,6 @@ * limitations under the License. */ -export { MockAirbrakeApi } from './MockApi'; +export * from './MockApi'; +export * from './MockEntity'; +export * from './LocalDiscoveryApi'; diff --git a/plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.test.tsx b/plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.test.tsx index ef1ac229c9..ee14a17f9a 100644 --- a/plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.test.tsx +++ b/plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.test.tsx @@ -23,9 +23,10 @@ import { setupRequestMockHandlers, TestApiProvider, } from '@backstage/test-utils'; -import { createEntity } from '../../api/mock/MockEntity'; +import { createEntity } from '../../api'; import { airbrakeApiRef, + localDiscoveryApi, MockAirbrakeApi, ProductionAirbrakeApi, } from '../../api'; @@ -65,7 +66,7 @@ describe('EntityAirbrakeContent', () => { it('states that an error occurred if the API call fails', async () => { worker.use( rest.get( - 'https://api.airbrake.io/api/v4/projects/123/groups', + 'http://localhost:7007/api/airbrake/api/v4/projects/123/groups', (_, res, ctx) => { return res(ctx.status(500)); }, @@ -76,7 +77,7 @@ describe('EntityAirbrakeContent', () => { const widget = await renderInTestApp( diff --git a/plugins/airbrake/src/extensions.test.tsx b/plugins/airbrake/src/extensions.test.tsx index d677d939a4..05afe8f1e3 100644 --- a/plugins/airbrake/src/extensions.test.tsx +++ b/plugins/airbrake/src/extensions.test.tsx @@ -18,7 +18,7 @@ import { EntityAirbrakeContent } from './extensions'; import { Route } from 'react-router'; import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; import { airbrakeApiRef, MockAirbrakeApi } from './api'; -import { createEntity } from './api/mock/MockEntity'; +import { createEntity } from './api'; import { EntityProvider } from '@backstage/plugin-catalog-react'; describe('The Airbrake entity', () => { diff --git a/plugins/airbrake/src/extensions.tsx b/plugins/airbrake/src/extensions.tsx index 6fbea68409..d4f890f44a 100644 --- a/plugins/airbrake/src/extensions.tsx +++ b/plugins/airbrake/src/extensions.tsx @@ -20,6 +20,11 @@ import { airbrakePlugin } from './plugin'; import { createRoutableExtension } from '@backstage/core-plugin-api'; import { rootRouteRef } from './routes'; +/** + * This is the widget that shows up on a component page + * + * @public + */ export const EntityAirbrakeContent = airbrakePlugin.provide( createRoutableExtension({ name: 'EntityAirbrakeContent', diff --git a/plugins/airbrake/src/index.ts b/plugins/airbrake/src/index.ts index d5aedd9ba6..2b2406776b 100644 --- a/plugins/airbrake/src/index.ts +++ b/plugins/airbrake/src/index.ts @@ -13,5 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +/** + * The Airbrake plugin provides connectivity between Backstage and Airbrake (https://airbrake.io/). + * + * @packageDocumentation + */ + export { airbrakePlugin } from './plugin'; export { EntityAirbrakeContent } from './extensions'; diff --git a/plugins/airbrake/src/plugin.ts b/plugins/airbrake/src/plugin.ts index f008b48ad4..56f65ba110 100644 --- a/plugins/airbrake/src/plugin.ts +++ b/plugins/airbrake/src/plugin.ts @@ -13,18 +13,27 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { createApiFactory, createPlugin } from '@backstage/core-plugin-api'; +import { + createApiFactory, + createPlugin, + discoveryApiRef, +} from '@backstage/core-plugin-api'; import { rootRouteRef } from './routes'; import { airbrakeApiRef, ProductionAirbrakeApi } from './api'; +/** + * The Airbrake plugin instance + * + * @public + */ export const airbrakePlugin = createPlugin({ id: 'airbrake', apis: [ createApiFactory({ api: airbrakeApiRef, - deps: {}, - factory: () => new ProductionAirbrakeApi(), + deps: { discoveryApi: discoveryApiRef }, + factory: ({ discoveryApi }) => new ProductionAirbrakeApi(discoveryApi), }), ], routes: {