Add a discovery API

Signed-off-by: Karan Shah <karan.shah@simplybusiness.co.uk>
This commit is contained in:
Karan Shah
2022-02-11 14:44:05 +00:00
parent d9a4750d26
commit cb672b0502
11 changed files with 53 additions and 32 deletions
@@ -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}
+6 -2
View File
@@ -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';
@@ -61,7 +62,10 @@ createDevApp()
{value => (
<TestApiProvider
apis={[
[airbrakeApiRef, new ProductionAirbrakeApi(value.apiKey)],
[
airbrakeApiRef,
new ProductionAirbrakeApi(localDiscoveryApi),
],
]}
>
<EntityProvider entity={createEntity(value.projectId)}>
+1 -1
View File
@@ -22,6 +22,7 @@
"dependencies": {
"@backstage/catalog-model": "^0.9.10",
"@backstage/core-components": "^0.8.8",
"@backstage/core-app-api": "^0.5.2",
"@backstage/core-plugin-api": "^0.6.0",
"@backstage/dev-utils": "^0.2.21",
"@backstage/plugin-catalog-react": "^0.6.14",
@@ -39,7 +40,6 @@
"devDependencies": {
"@backstage/app-defaults": "^0.1.7",
"@backstage/cli": "^0.13.2",
"@backstage/core-app-api": "^0.5.2",
"@backstage/dev-utils": "^0.2.21",
"@backstage/test-utils": "^0.2.4",
"@testing-library/jest-dom": "^5.10.1",
@@ -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));
},
+4 -2
View File
@@ -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,21 @@
/*
* 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 { UrlPatternDiscovery } from '@backstage/core-app-api';
export const localDiscoveryApi = UrlPatternDiscovery.compile(
'http://localhost:7007/api/{{ pluginId }}',
);
+3 -1
View File
@@ -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],
]}
>
+1 -1
View File
@@ -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', () => {
+7 -3
View File
@@ -13,7 +13,11 @@
* 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';
@@ -23,8 +27,8 @@ export const airbrakePlugin = createPlugin({
apis: [
createApiFactory({
api: airbrakeApiRef,
deps: {},
factory: () => new ProductionAirbrakeApi(),
deps: { discoveryApi: discoveryApiRef },
factory: ({ discoveryApi }) => new ProductionAirbrakeApi(discoveryApi),
}),
],
routes: {