adding rollbar plugin & backend
This is the first commit of the rollbar plugin. It is fully functional but will require some additional features to make it more usable and feature complete. It currently includes a backend wraps the rollbar REST API and provides data for the frontend plugin.
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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 { getRequestHeaders } from './RollbarApi';
|
||||
|
||||
describe('RollbarApi', () => {
|
||||
describe('getRequestHeaders', () => {
|
||||
it('should generate headers based on token passed in constructor', () => {
|
||||
expect(getRequestHeaders('testtoken')).toEqual({
|
||||
headers: {
|
||||
'X-Rollbar-Access-Token': `testtoken`,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* 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 axios from 'axios';
|
||||
import { Logger } from 'winston';
|
||||
import camelcaseKeys from 'camelcase-keys';
|
||||
import { buildQuery } from '../util';
|
||||
import {
|
||||
RollbarItemCount,
|
||||
RollbarItemsResponse,
|
||||
RollbarProject,
|
||||
RollbarProjectAccessToken,
|
||||
RollbarTopActiveItem,
|
||||
} from './types';
|
||||
|
||||
const baseUrl = 'https://api.rollbar.com/api/1';
|
||||
|
||||
const buildUrl = (url: string) => `${baseUrl}${url}`;
|
||||
|
||||
export class RollbarApi {
|
||||
private projectMap: ProjectMetadataMap | undefined;
|
||||
|
||||
constructor(
|
||||
private readonly accessToken: string,
|
||||
private readonly logger: Logger,
|
||||
) {}
|
||||
|
||||
async getAllProjects() {
|
||||
return this.get<RollbarProject[]>('/projects').then(projects =>
|
||||
projects.filter(p => p.name),
|
||||
);
|
||||
}
|
||||
|
||||
async getProject(projectName: string) {
|
||||
return this.getForProject<RollbarProject>(
|
||||
`/project/:projectId`,
|
||||
projectName,
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
async getProjectItems(projectName: string) {
|
||||
return this.getForProject<RollbarItemsResponse>(
|
||||
`/items`,
|
||||
projectName,
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
async getTopActiveItems(
|
||||
projectName: string,
|
||||
options: { hours: number; environment: string } = {
|
||||
hours: 24,
|
||||
environment: 'production',
|
||||
},
|
||||
) {
|
||||
return this.getForProject<RollbarTopActiveItem[]>(
|
||||
`/reports/top_active_items?${buildQuery(options)}`,
|
||||
projectName,
|
||||
);
|
||||
}
|
||||
|
||||
async getOccuranceCounts(
|
||||
projectName: string,
|
||||
options: { environment: string; item_id?: number } = {
|
||||
environment: 'production',
|
||||
},
|
||||
) {
|
||||
return this.getForProject<RollbarItemCount[]>(
|
||||
`/reports/occurrence_counts?${buildQuery(options as any)}`,
|
||||
projectName,
|
||||
);
|
||||
}
|
||||
|
||||
async getActivatedCounts(
|
||||
projectName: string,
|
||||
options: { environment: string; item_id?: number } = {
|
||||
environment: 'production',
|
||||
},
|
||||
) {
|
||||
return this.getForProject<RollbarItemCount[]>(
|
||||
`/reports/activated_counts?${buildQuery(options as any)}`,
|
||||
projectName,
|
||||
);
|
||||
}
|
||||
|
||||
private async getProjectAccessTokens(projectId: number) {
|
||||
return this.get<RollbarProjectAccessToken[]>(
|
||||
`/project/${projectId}/access_tokens`,
|
||||
);
|
||||
}
|
||||
|
||||
private async get<T>(url: string, accessToken?: string) {
|
||||
const fullUrl = buildUrl(url);
|
||||
|
||||
if (this.logger) {
|
||||
this.logger.info(`Calling Rollbar REST API, ${fullUrl}`);
|
||||
}
|
||||
|
||||
return axios
|
||||
.get(fullUrl, getRequestHeaders(accessToken || this.accessToken || ''))
|
||||
.then(response =>
|
||||
camelcaseKeys<T>(response?.data?.result, { deep: true }),
|
||||
);
|
||||
}
|
||||
|
||||
private async getForProject<T>(
|
||||
url: string,
|
||||
projectName: string,
|
||||
useProjectToken = true,
|
||||
) {
|
||||
const project = await this.getProjectMetadata(projectName);
|
||||
const resolvedUrl = url.replace(':projectId', project.id.toString());
|
||||
return this.get<T>(resolvedUrl, useProjectToken ? project.accessToken : '');
|
||||
}
|
||||
|
||||
private async getProjectMetadata(name: string) {
|
||||
const projectMap = await this.getProjectMap();
|
||||
const project = projectMap[name];
|
||||
|
||||
if (!project) {
|
||||
throw Error(`Invalid project: '${name}'`);
|
||||
}
|
||||
|
||||
if (!project.accessToken) {
|
||||
const tokens = await this.getProjectAccessTokens(project.id);
|
||||
const token = tokens.find(t => t.scopes.includes('read'));
|
||||
project.accessToken = token ? token.accessToken : undefined;
|
||||
}
|
||||
|
||||
if (!project.accessToken) {
|
||||
throw Error(`Could not find project read access token for '${name}'`);
|
||||
}
|
||||
|
||||
return project;
|
||||
}
|
||||
|
||||
private async getProjectMap() {
|
||||
if (this.projectMap) {
|
||||
return this.projectMap;
|
||||
}
|
||||
|
||||
const projects = await this.getAllProjects();
|
||||
|
||||
this.projectMap = projects.reduce((accum: ProjectMetadataMap, i) => {
|
||||
accum[i.name] = { id: i.id, name: i.name };
|
||||
return accum;
|
||||
}, {});
|
||||
|
||||
return this.projectMap;
|
||||
}
|
||||
}
|
||||
|
||||
export function getRequestHeaders(token: string) {
|
||||
return {
|
||||
headers: {
|
||||
'X-Rollbar-Access-Token': `${token}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
type ProjectMetadata = {
|
||||
name: string;
|
||||
id: number;
|
||||
accessToken?: string | undefined;
|
||||
};
|
||||
|
||||
interface ProjectMetadataMap {
|
||||
[name: string]: ProjectMetadata;
|
||||
}
|
||||
@@ -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 { RollbarApi, getRequestHeaders } from './RollbarApi';
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// TODO: Make this re-usable with backend
|
||||
|
||||
export type RollbarProjectAccessTokenScope = 'read' | 'write';
|
||||
export type RollbarEnvironment = 'production' | string;
|
||||
|
||||
export enum RollbarLevel {
|
||||
debug = 10,
|
||||
info = 20,
|
||||
warning = 30,
|
||||
error = 40,
|
||||
critical = 50,
|
||||
}
|
||||
|
||||
export enum RollbarFrameworkId {
|
||||
'unknown' = 0,
|
||||
'rails' = 1,
|
||||
'django' = 2,
|
||||
'pyramid' = 3,
|
||||
'node-js' = 4,
|
||||
'pylons' = 5,
|
||||
'php' = 6,
|
||||
'browser-js' = 7,
|
||||
'rollbar-system' = 8,
|
||||
'android' = 9,
|
||||
'ios' = 10,
|
||||
'mailgun' = 11,
|
||||
'logentries' = 12,
|
||||
'python' = 13,
|
||||
'ruby' = 14,
|
||||
'sidekiq' = 15,
|
||||
'flask' = 16,
|
||||
'celery' = 17,
|
||||
'rq' = 18,
|
||||
}
|
||||
|
||||
export enum RollbarPlatformId {
|
||||
'unknown' = 0,
|
||||
'browser' = 1,
|
||||
'flash' = 2,
|
||||
'android' = 3,
|
||||
'ios' = 4,
|
||||
'heroku' = 5,
|
||||
'google-app-engine' = 6,
|
||||
'client' = 7,
|
||||
}
|
||||
|
||||
export type RollbarProject = {
|
||||
id: number;
|
||||
name: string;
|
||||
accountId: number;
|
||||
status: 'enabled' | string;
|
||||
};
|
||||
|
||||
export type RollbarProjectAccessToken = {
|
||||
projectId: number;
|
||||
name: string;
|
||||
scopes: RollbarProjectAccessTokenScope[];
|
||||
accessToken: string;
|
||||
status: 'enabled' | string;
|
||||
};
|
||||
|
||||
export type RollbarItem = {
|
||||
publicItemId: number;
|
||||
integrationsData: null;
|
||||
levelLock: number;
|
||||
controllingId: number;
|
||||
lastActivatedTimestamp: number;
|
||||
assignedUserId: number;
|
||||
groupStatus: number;
|
||||
hash: string;
|
||||
id: number;
|
||||
environment: RollbarEnvironment;
|
||||
titleLock: number;
|
||||
title: string;
|
||||
lastOccurrenceId: number;
|
||||
lastOccurrenceTimestamp: number;
|
||||
platform: RollbarPlatformId;
|
||||
firstOccurrenceTimestamp: number;
|
||||
project_id: number;
|
||||
resolvedInVersion: string;
|
||||
status: 'enabled' | string;
|
||||
uniqueOccurrences: number;
|
||||
groupItemId: number;
|
||||
framework: RollbarFrameworkId;
|
||||
totalOccurrences: number;
|
||||
level: RollbarLevel;
|
||||
counter: number;
|
||||
lastModifiedBy: number;
|
||||
firstOccurrenceId: number;
|
||||
activatingOccurrenceId: number;
|
||||
lastResolvedTimestamp: number;
|
||||
};
|
||||
|
||||
export type RollbarItemsResponse = {
|
||||
items: RollbarItem[];
|
||||
page: number;
|
||||
totalCount: number;
|
||||
};
|
||||
|
||||
export type RollbarItemCount = {
|
||||
timestamp: number;
|
||||
count: number;
|
||||
};
|
||||
|
||||
export type RollbarTopActiveItem = {
|
||||
item: {
|
||||
id: number;
|
||||
counter: number;
|
||||
environment: RollbarEnvironment;
|
||||
framework: RollbarFrameworkId;
|
||||
lastOccurrenceTimestamp: number;
|
||||
level: number;
|
||||
occurrences: number;
|
||||
projectId: number;
|
||||
title: string;
|
||||
uniqueOccurrences: number;
|
||||
};
|
||||
counts: number[];
|
||||
};
|
||||
@@ -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 * from './api';
|
||||
export * from './service/router';
|
||||
@@ -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 { getRootLogger } from '@backstage/backend-common';
|
||||
import yn from 'yn';
|
||||
import { startStandaloneServer } from './service/standaloneServer';
|
||||
|
||||
const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7000;
|
||||
const enableCors = yn(process.env.PLUGIN_CORS, { default: false });
|
||||
const logger = getRootLogger();
|
||||
|
||||
startStandaloneServer({ port, enableCors, logger }).catch(err => {
|
||||
logger.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
logger.info('CTRL+C pressed; exiting.');
|
||||
process.exit(0);
|
||||
});
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* 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 { getVoidLogger } from '@backstage/backend-common';
|
||||
import express from 'express';
|
||||
import request from 'supertest';
|
||||
import { RollbarApi } from '../api';
|
||||
import { createRouter } from './router';
|
||||
import { RollbarProject, RollbarTopActiveItem } from '../api/types';
|
||||
|
||||
describe('createRouter', () => {
|
||||
let rollbarApi: jest.Mocked<RollbarApi>;
|
||||
let app: express.Express;
|
||||
|
||||
beforeAll(async () => {
|
||||
rollbarApi = {
|
||||
getAllProjects: jest.fn(),
|
||||
getProject: jest.fn(),
|
||||
getProjectItems: jest.fn(),
|
||||
getTopActiveItems: jest.fn(),
|
||||
getOccuranceCounts: jest.fn(),
|
||||
getActivatedCounts: jest.fn(),
|
||||
} as any;
|
||||
const router = await createRouter({
|
||||
rollbarApi,
|
||||
logger: getVoidLogger(),
|
||||
});
|
||||
app = express().use(router);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('GET /projects', () => {
|
||||
it('lists projects', async () => {
|
||||
const projects: RollbarProject[] = [
|
||||
{ id: 123, name: 'abc', accountId: 1, status: 'enabled' },
|
||||
{ id: 456, name: 'xyz', accountId: 1, status: 'enabled' },
|
||||
];
|
||||
|
||||
rollbarApi.getAllProjects.mockResolvedValueOnce(projects);
|
||||
|
||||
const response = await request(app).get('/projects');
|
||||
|
||||
expect(response.status).toEqual(200);
|
||||
expect(response.body).toEqual(projects);
|
||||
});
|
||||
|
||||
it('throws an error', async () => {
|
||||
rollbarApi.getAllProjects.mockImplementationOnce(() => {
|
||||
throw Error('error');
|
||||
});
|
||||
|
||||
const response = await request(app).get('/projects');
|
||||
|
||||
expect(response.status).toEqual(500);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /projects/:id', () => {
|
||||
it('fetches a single project', async () => {
|
||||
const project: RollbarProject = {
|
||||
id: 123,
|
||||
name: 'abc',
|
||||
accountId: 1,
|
||||
status: 'enabled',
|
||||
};
|
||||
|
||||
rollbarApi.getProject.mockResolvedValueOnce(project);
|
||||
|
||||
const response = await request(app).get(`/projects/${123}`);
|
||||
|
||||
expect(response.status).toEqual(200);
|
||||
expect(response.body).toEqual(project);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /projects/:id/top_active_items', () => {
|
||||
it('fetches a single project', async () => {
|
||||
const items: RollbarTopActiveItem[] = [
|
||||
{
|
||||
item: {
|
||||
id: 9898989,
|
||||
counter: 1234,
|
||||
environment: 'production',
|
||||
framework: 2,
|
||||
lastOccurrenceTimestamp: new Date().getTime() / 1000,
|
||||
level: 50,
|
||||
occurrences: 100,
|
||||
projectId: 12345,
|
||||
title: 'error occured',
|
||||
uniqueOccurrences: 10,
|
||||
},
|
||||
counts: [10, 10, 10, 10, 10, 50],
|
||||
},
|
||||
];
|
||||
|
||||
rollbarApi.getTopActiveItems.mockResolvedValueOnce(items);
|
||||
|
||||
const response = await request(app).get(
|
||||
`/projects/${123}/top_active_items`,
|
||||
);
|
||||
|
||||
expect(response.status).toEqual(200);
|
||||
expect(response.body).toEqual(items);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* 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 } from '@backstage/backend-common';
|
||||
import { Logger } from 'winston';
|
||||
import Router from 'express-promise-router';
|
||||
import express from 'express';
|
||||
import { RollbarApi } from '../api';
|
||||
|
||||
export interface RouterOptions {
|
||||
rollbarApi?: RollbarApi;
|
||||
logger: Logger;
|
||||
}
|
||||
|
||||
export async function createRouter(
|
||||
options: RouterOptions,
|
||||
): Promise<express.Router> {
|
||||
const router = Router();
|
||||
const logger = options.logger.child({ plugin: 'rollbar' });
|
||||
const accessToken = !options.rollbarApi ? getRollbarToken(logger) : '';
|
||||
|
||||
if (options.rollbarApi || accessToken) {
|
||||
const rollbarApi =
|
||||
options.rollbarApi || new RollbarApi(accessToken, logger);
|
||||
|
||||
router.use(express.json());
|
||||
|
||||
const runAsync = createRunAsyncWrapper(logger);
|
||||
|
||||
router.get(
|
||||
'/projects',
|
||||
runAsync(async (_req, res) => {
|
||||
const projects = await rollbarApi.getAllProjects();
|
||||
res.status(200).header('').send(projects);
|
||||
}),
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/projects/:id',
|
||||
runAsync(async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const projects = await rollbarApi.getProject(id);
|
||||
res.status(200).send(projects);
|
||||
}),
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/projects/:id/items',
|
||||
runAsync(async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const projects = await rollbarApi.getProjectItems(id);
|
||||
res.status(200).send(projects);
|
||||
}),
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/projects/:id/top_active_items',
|
||||
runAsync(async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const query = req.query;
|
||||
const items = await rollbarApi.getTopActiveItems(id, query as any);
|
||||
res.status(200).send(items);
|
||||
}),
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/projects/:id/occurance_counts',
|
||||
runAsync(async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const query = req.query;
|
||||
const items = await rollbarApi.getOccuranceCounts(id, query as any);
|
||||
res.status(200).send(items);
|
||||
}),
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/projects/:id/activated_item_counts',
|
||||
runAsync(async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const query = req.query;
|
||||
const items = await rollbarApi.getActivatedCounts(id, query as any);
|
||||
res.status(200).send(items);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
router.use(errorHandler());
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
function createRunAsyncWrapper(logger: Logger) {
|
||||
return function runAsyncWrapper(callback: express.RequestHandler) {
|
||||
return function runAsync(
|
||||
req: express.Request,
|
||||
res: express.Response,
|
||||
next: express.NextFunction,
|
||||
) {
|
||||
return Promise.resolve(callback(req, res, next)).catch(error => {
|
||||
logger.error(error);
|
||||
next(error);
|
||||
});
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
function getRollbarToken(logger: Logger) {
|
||||
const token = process.env.ROLLBAR_TOKEN || '';
|
||||
|
||||
if (!token) {
|
||||
if (process.env.NODE_ENV !== 'development') {
|
||||
throw new Error(
|
||||
'Rollbar token must be provided in ROLLBAR_TOKEN environment variable to start the API.',
|
||||
);
|
||||
}
|
||||
logger.warn(
|
||||
'Failed to initialize rollbar backend, set ROLLBAR_TOKEN environment variable to start the API.',
|
||||
);
|
||||
}
|
||||
|
||||
return token;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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 { createServiceBuilder } from '@backstage/backend-common';
|
||||
import { Server } from 'http';
|
||||
import { Logger } from 'winston';
|
||||
import { createRouter } from './router';
|
||||
|
||||
export interface ServerOptions {
|
||||
port: number;
|
||||
enableCors: boolean;
|
||||
logger: Logger;
|
||||
}
|
||||
export async function startStandaloneServer(
|
||||
options: ServerOptions,
|
||||
): Promise<Server> {
|
||||
const logger = options.logger.child({ service: 'rollbar-backend' });
|
||||
|
||||
logger.debug('Creating application...');
|
||||
|
||||
const router = await createRouter({ logger });
|
||||
|
||||
const service = createServiceBuilder(module)
|
||||
.enableCors({ origin: 'http://localhost:3000' })
|
||||
.addRouter('/catalog', router);
|
||||
|
||||
return await service.start().catch(err => {
|
||||
logger.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
require('jest-fetch-mock').enableMocks();
|
||||
|
||||
export {};
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
interface PrimitiveMap {
|
||||
[name: string]: number | string | boolean;
|
||||
}
|
||||
|
||||
export const buildQuery = (obj: PrimitiveMap) => {
|
||||
return Object.entries(obj)
|
||||
.map(pair => pair.map(encodeURIComponent).join('='))
|
||||
.join('&');
|
||||
};
|
||||
Reference in New Issue
Block a user