Merge pull request #9605 from moltenice-forks/airbrake-connect
Make the Airbrake plugin useable
This commit is contained in:
@@ -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.
|
||||
@@ -3,6 +3,8 @@ accessors
|
||||
addon
|
||||
addons
|
||||
Airbrake
|
||||
Airbrakes
|
||||
airbrake
|
||||
Anddddd
|
||||
Apdex
|
||||
api
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<express.Router>;
|
||||
|
||||
// @public
|
||||
export function extractAirbrakeConfig(config: Config): AirbrakeConfig;
|
||||
export function extractAirbrakeConfig(
|
||||
config: Config,
|
||||
logger: winston.Logger,
|
||||
): AirbrakeConfig;
|
||||
|
||||
// @public
|
||||
export interface RouterOptions {
|
||||
|
||||
Vendored
+1
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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 };
|
||||
});
|
||||
});
|
||||
@@ -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: '',
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -32,9 +32,9 @@ export interface ServerOptions {
|
||||
export async function startStandaloneServer(
|
||||
options: ServerOptions,
|
||||
): Promise<Server> {
|
||||
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 => {
|
||||
|
||||
@@ -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 = (
|
||||
<EntityLayoutWrapper>
|
||||
<EntityLayout.Route path="/airbrake" title="Airbrake">
|
||||
<EntityAirbrakeContent />
|
||||
</EntityLayout.Route>
|
||||
</EntityLayoutWrapper>
|
||||
);
|
||||
```
|
||||
|
||||
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).
|
||||
|
||||
@@ -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<undefined>;
|
||||
@@ -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)
|
||||
```
|
||||
|
||||
@@ -71,12 +71,6 @@ export const ApiBar = () => {
|
||||
value.setProjectId?.(parseInt(e.target.value, 10) || undefined)
|
||||
}
|
||||
/>
|
||||
<TextField
|
||||
label="API Key"
|
||||
variant="outlined"
|
||||
defaultValue={value.apiKey}
|
||||
onChange={e => value.setApiKey?.(e.target.value)}
|
||||
/>
|
||||
</MuiThemeProvider>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -23,23 +23,18 @@ import React, {
|
||||
interface ContextInterface {
|
||||
projectId?: number;
|
||||
setProjectId?: Dispatch<SetStateAction<number | undefined>>;
|
||||
apiKey?: string;
|
||||
setApiKey?: Dispatch<SetStateAction<string>>;
|
||||
}
|
||||
|
||||
export const Context = React.createContext<ContextInterface>({});
|
||||
|
||||
export const ContextProvider = ({ children }: PropsWithChildren<{}>) => {
|
||||
const [projectId, setProjectId] = useState<number>();
|
||||
const [apiKey, setApiKey] = useState<string>('');
|
||||
|
||||
return (
|
||||
<Context.Provider
|
||||
value={{
|
||||
projectId,
|
||||
setProjectId,
|
||||
apiKey,
|
||||
setApiKey,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -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: (
|
||||
<ContextProvider>
|
||||
<Page themeId="tool">
|
||||
<Header title="Airbrake demo application" subtitle="Real API">
|
||||
<Header
|
||||
title="Airbrake demo application"
|
||||
subtitle="Real API (The Airbrake backend plugin must be running for this to work)"
|
||||
>
|
||||
<ApiBar />
|
||||
</Header>
|
||||
<Content>
|
||||
@@ -61,7 +65,10 @@ createDevApp()
|
||||
{value => (
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[airbrakeApiRef, new ProductionAirbrakeApi(value.apiKey)],
|
||||
[
|
||||
airbrakeApiRef,
|
||||
new ProductionAirbrakeApi(localDiscoveryApi),
|
||||
],
|
||||
]}
|
||||
>
|
||||
<EntityProvider entity={createEntity(value.projectId)}>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
},
|
||||
|
||||
@@ -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<Groups> {
|
||||
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);
|
||||
|
||||
|
||||
@@ -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<string> {
|
||||
return `http://localhost:7007/api/${pluginId}`;
|
||||
},
|
||||
};
|
||||
@@ -14,4 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { MockAirbrakeApi } from './MockApi';
|
||||
export * from './MockApi';
|
||||
export * from './MockEntity';
|
||||
export * from './LocalDiscoveryApi';
|
||||
|
||||
@@ -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(
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[airbrakeApiRef, new ProductionAirbrakeApi('fakeApiKey')],
|
||||
[airbrakeApiRef, new ProductionAirbrakeApi(localDiscoveryApi)],
|
||||
[errorApiRef, mockErrorApi],
|
||||
]}
|
||||
>
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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: {
|
||||
|
||||
Reference in New Issue
Block a user