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:
@@ -13,6 +13,7 @@
|
||||
"@backstage/plugin-graphiql": "^0.1.1-alpha.13",
|
||||
"@backstage/plugin-lighthouse": "^0.1.1-alpha.13",
|
||||
"@backstage/plugin-register-component": "^0.1.1-alpha.13",
|
||||
"@backstage/plugin-rollbar": "^0.1.1-alpha.13",
|
||||
"@backstage/plugin-scaffolder": "^0.1.1-alpha.13",
|
||||
"@backstage/plugin-sentry": "^0.1.1-alpha.13",
|
||||
"@backstage/plugin-tech-radar": "^0.1.1-alpha.13",
|
||||
|
||||
@@ -57,6 +57,8 @@ import {
|
||||
} from '@backstage/plugin-graphiql';
|
||||
import { scaffolderApiRef, ScaffolderApi } from '@backstage/plugin-scaffolder';
|
||||
|
||||
import { rollbarApiRef, RollbarClient } from '@backstage/plugin-rollbar';
|
||||
|
||||
export const apis = (config: ConfigApi) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`Creating APIs for ${config.getString('app.title')}`);
|
||||
@@ -170,5 +172,13 @@ export const apis = (config: ConfigApi) => {
|
||||
]),
|
||||
);
|
||||
|
||||
builder.add(
|
||||
rollbarApiRef,
|
||||
new RollbarClient({
|
||||
apiOrigin: backendUrl,
|
||||
basePath: '/rollbar',
|
||||
}),
|
||||
);
|
||||
|
||||
return builder.build();
|
||||
};
|
||||
|
||||
@@ -26,3 +26,4 @@ export { plugin as GitopsProfiles } from '@backstage/plugin-gitops-profiles';
|
||||
export { plugin as TechDocs } from '@backstage/plugin-techdocs';
|
||||
export { plugin as GraphiQL } from '@backstage/plugin-graphiql';
|
||||
export { plugin as GithubActions } from '@backstage/plugin-github-actions';
|
||||
export { plugin as Rollbar } from '@backstage/plugin-rollbar';
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
"@backstage/plugin-auth-backend": "^0.1.1-alpha.13",
|
||||
"@backstage/plugin-catalog-backend": "^0.1.1-alpha.13",
|
||||
"@backstage/plugin-identity-backend": "^0.1.1-alpha.13",
|
||||
"@backstage/plugin-rollbar-backend": "^0.1.1-alpha.13",
|
||||
"@backstage/plugin-scaffolder-backend": "^0.1.1-alpha.13",
|
||||
"@backstage/plugin-sentry-backend": "^0.1.1-alpha.13",
|
||||
"@backstage/plugin-techdocs-backend": "^0.1.1-alpha.13",
|
||||
|
||||
@@ -33,6 +33,7 @@ import knex from 'knex';
|
||||
import auth from './plugins/auth';
|
||||
import catalog from './plugins/catalog';
|
||||
import identity from './plugins/identity';
|
||||
import rollbar from './plugins/rollbar';
|
||||
import scaffolder from './plugins/scaffolder';
|
||||
import sentry from './plugins/sentry';
|
||||
import techdocs from './plugins/techdocs';
|
||||
@@ -69,6 +70,12 @@ async function main() {
|
||||
const service = createServiceBuilder(module)
|
||||
.loadConfig(configReader)
|
||||
.addRouter('/catalog', await catalog(catalogEnv))
|
||||
.addRouter(
|
||||
'/rollbar',
|
||||
await rollbar(
|
||||
getRootLogger().child({ type: 'plugin', plugin: 'rollbar' }),
|
||||
),
|
||||
)
|
||||
.addRouter('/scaffolder', await scaffolder(scaffolderEnv))
|
||||
.addRouter(
|
||||
'/sentry',
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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 { createRouter } from '@backstage/plugin-rollbar-backend';
|
||||
import { Logger } from 'winston';
|
||||
|
||||
export default async function createPlugin(logger: Logger) {
|
||||
return await createRouter({ logger });
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
extends: [require.resolve('@backstage/cli/config/eslint.backend')],
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
# Rollbar Backend
|
||||
|
||||
Simple plugin that proxies requests to the [Rollbar](https://rollbar.com) API.
|
||||
|
||||
## Setup
|
||||
|
||||
A `ROLLBAR_TOKEN` environment variable must be set to a read access account token.
|
||||
|
||||
## Links
|
||||
|
||||
- (Frontend part of the plugin)[https://github.com/spotify/backstage/tree/master/plugins/rollbar]
|
||||
- (The Backstage homepage)[https://backstage.io]
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"name": "@backstage/plugin-rollbar-backend",
|
||||
"version": "0.1.1-alpha.13",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"main": "dist/index.cjs.js",
|
||||
"types": "dist/index.d.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "backstage-cli backend:dev",
|
||||
"build": "backstage-cli backend:build",
|
||||
"lint": "backstage-cli lint",
|
||||
"test": "backstage-cli test",
|
||||
"prepack": "backstage-cli prepack",
|
||||
"postpack": "backstage-cli postpack",
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.1.1-alpha.13",
|
||||
"@types/express": "^4.17.6",
|
||||
"axios": "^0.19.2",
|
||||
"camelcase-keys": "^6.2.2",
|
||||
"compression": "^1.7.4",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.17.1",
|
||||
"express-promise-router": "^3.0.3",
|
||||
"fs-extra": "^9.0.0",
|
||||
"helmet": "^3.22.0",
|
||||
"lodash": "^4.17.15",
|
||||
"morgan": "^1.10.0",
|
||||
"winston": "^3.2.1",
|
||||
"yn": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.13",
|
||||
"@types/supertest": "^2.0.8",
|
||||
"jest-fetch-mock": "^3.0.3",
|
||||
"supertest": "^4.0.2"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
@@ -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('&');
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
extends: [require.resolve('@backstage/cli/config/eslint')],
|
||||
};
|
||||
@@ -0,0 +1,56 @@
|
||||
# Rollbar Plugin
|
||||
|
||||
Website: [https://rollbar.com/](https://rollbar.com/)
|
||||
|
||||
## Setup
|
||||
|
||||
1. Configure the [rollbar backend plugin](https://github.com/spotify/backstage/tree/master/plugins/rollbar-backend/README.md)
|
||||
|
||||
2. If you have standalone app (you didn't clone this repo), then do
|
||||
|
||||
```bash
|
||||
yarn add @backstage/plugin-rollbar
|
||||
```
|
||||
|
||||
3. Add plugin to the list of plugins:
|
||||
|
||||
```ts
|
||||
// packages/app/src/plugins.ts
|
||||
export { plugin as Rollbar } from '@backstage/plugin-rollbar';
|
||||
```
|
||||
|
||||
4. Add plugin API to your Backstage instance:
|
||||
|
||||
```ts
|
||||
// packages/app/src/api.ts
|
||||
import { RollbarClient, rollbarApiRef } from '@backstage/plugin-rollbar';
|
||||
|
||||
// ...
|
||||
|
||||
builder.add(
|
||||
rollbarApiRef,
|
||||
new RollbarClient({
|
||||
apiOrigin: backendUrl,
|
||||
basePath: '/rollbar',
|
||||
}),
|
||||
);
|
||||
|
||||
// Alternatively you can use the mock client
|
||||
// builder.add(rollbarApiRef, new RollbarMockClient());
|
||||
```
|
||||
|
||||
5. Run app with `yarn start` and navigate to `/rollbar`
|
||||
|
||||
## Features
|
||||
|
||||
- List rollbar projects
|
||||
- View top active items for each project
|
||||
|
||||
## Limitations
|
||||
|
||||
- Rollbar has rate limits per token
|
||||
|
||||
## Links
|
||||
|
||||
- (Backend part of the plugin)[https://github.com/spotify/backstage/tree/master/plugins/rollbar-backend]
|
||||
- (The Backstage homepage)[https://backstage.io]
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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 { createDevApp } from '@backstage/dev-utils';
|
||||
import { plugin } from '../src/plugin';
|
||||
|
||||
createDevApp().registerPlugin(plugin).render();
|
||||
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"name": "@backstage/plugin-rollbar",
|
||||
"version": "0.1.1-alpha.13",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
"private": false,
|
||||
"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": "^0.1.1-alpha.13",
|
||||
"@backstage/theme": "^0.1.1-alpha.13",
|
||||
"@material-ui/core": "^4.9.1",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.45",
|
||||
"lodash": "^4.17.15",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-router": "6.0.0-beta.0",
|
||||
"react-router-dom": "6.0.0-beta.0",
|
||||
"react-sparklines": "^1.7.0",
|
||||
"react-use": "^14.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.13",
|
||||
"@backstage/dev-utils": "^0.1.1-alpha.13",
|
||||
"@backstage/test-utils": "^0.1.1-alpha.13",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
"@testing-library/react-hooks": "^3.3.0",
|
||||
"@testing-library/user-event": "^12.0.7",
|
||||
"@types/jest": "^25.2.2",
|
||||
"@types/node": "^12.0.0",
|
||||
"@types/react": "^16.9",
|
||||
"jest-fetch-mock": "^3.0.3"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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 { createApiRef } from '@backstage/core';
|
||||
import {
|
||||
RollbarItemsResponse,
|
||||
RollbarProject,
|
||||
RollbarTopActiveItem,
|
||||
} from './types';
|
||||
|
||||
export const rollbarApiRef = createApiRef<RollbarApi>({
|
||||
id: 'plugin.rollbar.service',
|
||||
description:
|
||||
'Used by the Rollbar plugin to make requests to accompanying backend',
|
||||
});
|
||||
|
||||
export interface RollbarApi {
|
||||
getAllProjects(): Promise<RollbarProject[]>;
|
||||
getTopActiveItems(
|
||||
project: string,
|
||||
hours?: number,
|
||||
): Promise<RollbarTopActiveItem[]>;
|
||||
getProjectItems(project: string): Promise<RollbarItemsResponse>;
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* 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 { RollbarApi } from './RollbarApi';
|
||||
import {
|
||||
RollbarItemsResponse,
|
||||
RollbarProject,
|
||||
RollbarTopActiveItem,
|
||||
} from './types';
|
||||
|
||||
export class RollbarClient implements RollbarApi {
|
||||
private apiOrigin: string;
|
||||
private basePath: string;
|
||||
|
||||
constructor({
|
||||
apiOrigin,
|
||||
basePath,
|
||||
}: {
|
||||
apiOrigin: string;
|
||||
basePath: string;
|
||||
}) {
|
||||
this.apiOrigin = apiOrigin;
|
||||
this.basePath = basePath;
|
||||
}
|
||||
|
||||
async getAllProjects(): Promise<RollbarProject[]> {
|
||||
const path = `/projects`;
|
||||
|
||||
return await this.get(path);
|
||||
}
|
||||
|
||||
async getTopActiveItems(
|
||||
project: string,
|
||||
hours = 24,
|
||||
environment = 'production',
|
||||
): Promise<RollbarTopActiveItem[]> {
|
||||
const path = `/projects/${project}/top_active_items?environment=${environment}&hours=${hours}`;
|
||||
|
||||
return await this.get(path);
|
||||
}
|
||||
|
||||
async getProjectItems(project: string): Promise<RollbarItemsResponse> {
|
||||
const path = `/projects/${project}/items`;
|
||||
|
||||
return await this.get(path);
|
||||
}
|
||||
|
||||
private async get(path: string): Promise<any> {
|
||||
const url = `${this.apiOrigin}${this.basePath}${path}`;
|
||||
const response = await fetch(url);
|
||||
|
||||
if (!response.ok) {
|
||||
const payload = await response.text();
|
||||
const message = `Request failed with ${response.status} ${response.statusText}, ${payload}`;
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
|
||||
import { RollbarApi } from './RollbarApi';
|
||||
import {
|
||||
RollbarItemsResponse,
|
||||
RollbarProject,
|
||||
RollbarTopActiveItem,
|
||||
} from './types';
|
||||
|
||||
export class RollbarMockClient implements RollbarApi {
|
||||
async getAllProjects(): Promise<RollbarProject[]> {
|
||||
return Promise.resolve([
|
||||
{ id: 123, name: 'project-a', accountId: 1, status: 'enabled' },
|
||||
{ id: 356, name: 'project-b', accountId: 1, status: 'enabled' },
|
||||
{ id: 789, name: 'project-c', accountId: 1, status: 'enabled' },
|
||||
]);
|
||||
}
|
||||
|
||||
async getTopActiveItems(
|
||||
_project: string,
|
||||
_hours = 24,
|
||||
_environment = 'production',
|
||||
): Promise<RollbarTopActiveItem[]> {
|
||||
const createItem = (id: number): RollbarTopActiveItem => ({
|
||||
item: {
|
||||
id,
|
||||
counter: id,
|
||||
environment: 'production',
|
||||
framework: 2,
|
||||
lastOccurrenceTimestamp: new Date().getTime() / 1000,
|
||||
level: 50,
|
||||
occurrences: 100,
|
||||
projectId: 12345,
|
||||
title: `Some error occurred in service - ${id}`,
|
||||
uniqueOccurrences: 10,
|
||||
},
|
||||
counts: Array.from({ length: 168 }, () =>
|
||||
Math.floor(Math.random() * 100),
|
||||
),
|
||||
});
|
||||
|
||||
const items = Array.from({ length: 10 }, (_, i) => createItem(i));
|
||||
|
||||
return Promise.resolve(items);
|
||||
}
|
||||
|
||||
async getProjectItems(_project: string): Promise<RollbarItemsResponse> {
|
||||
return Promise.resolve({
|
||||
items: [],
|
||||
page: 0,
|
||||
totalCount: 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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 shared/dry 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,52 @@
|
||||
/*
|
||||
* 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 React, { ReactNode } from 'react';
|
||||
import {
|
||||
Header,
|
||||
Page,
|
||||
pageTheme,
|
||||
Content,
|
||||
ContentHeader,
|
||||
SupportButton,
|
||||
} from '@backstage/core';
|
||||
import { Grid } from '@material-ui/core';
|
||||
|
||||
type Props = {
|
||||
title?: string;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export const RollbarLayout = ({ title = 'Dashboard', children }: Props) => {
|
||||
return (
|
||||
<Page theme={pageTheme.tool}>
|
||||
<Header
|
||||
title="Rollbar"
|
||||
subtitle="Real-time error tracking & debugging tools for developers"
|
||||
/>
|
||||
<Content>
|
||||
<ContentHeader title={title}>
|
||||
<SupportButton>
|
||||
Rollbar plugin allows you to preview issues and navigate to rollbar.
|
||||
</SupportButton>
|
||||
</ContentHeader>
|
||||
<Grid container spacing={3} direction="column">
|
||||
{children}
|
||||
</Grid>
|
||||
</Content>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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 * as React from 'react';
|
||||
import { ApiProvider, ApiRegistry } from '@backstage/core';
|
||||
import { wrapInTestApp } from '@backstage/test-utils';
|
||||
import { render } from '@testing-library/react';
|
||||
import { RollbarApi, rollbarApiRef } from '../../api/RollbarApi';
|
||||
import { RollbarProject } from '../../api/types';
|
||||
import { RollbarPage } from './RollbarPage';
|
||||
|
||||
describe('RollbarPage component', () => {
|
||||
const projects: RollbarProject[] = [
|
||||
{ id: 123, name: 'abc', accountId: 1, status: 'enabled' },
|
||||
{ id: 456, name: 'xyz', accountId: 1, status: 'enabled' },
|
||||
];
|
||||
const rollbarApi: Partial<RollbarApi> = {
|
||||
getAllProjects: () => Promise.resolve(projects),
|
||||
};
|
||||
|
||||
const renderWrapped = (children: React.ReactNode) =>
|
||||
render(
|
||||
wrapInTestApp(
|
||||
<ApiProvider apis={ApiRegistry.from([[rollbarApiRef, rollbarApi]])}>
|
||||
{children}
|
||||
</ApiProvider>,
|
||||
),
|
||||
);
|
||||
|
||||
it('should render rollbar landing page', async () => {
|
||||
const rendered = renderWrapped(<RollbarPage />);
|
||||
expect(rendered.getByText(/Rollbar/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -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 React from 'react';
|
||||
import { useAsync } from 'react-use';
|
||||
import { useApi } from '@backstage/core';
|
||||
import { rollbarApiRef } from '../../api/RollbarApi';
|
||||
import { RollbarLayout } from '../RollbarLayout/RollbarLayout';
|
||||
import { RollbarProjectTable } from './RollbarProjectTable';
|
||||
|
||||
export const RollbarPage = () => {
|
||||
const rollbarApi = useApi(rollbarApiRef);
|
||||
const { value, loading } = useAsync(() => rollbarApi.getAllProjects());
|
||||
|
||||
return (
|
||||
<RollbarLayout>
|
||||
<RollbarProjectTable loading={loading} projects={value || []} />
|
||||
</RollbarLayout>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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 React from 'react';
|
||||
import { Link as RouterLink } from 'react-router-dom';
|
||||
import { Link } from '@material-ui/core';
|
||||
import { Table, TableColumn } from '@backstage/core';
|
||||
import { RollbarProject } from '../../api/types';
|
||||
|
||||
const columns: TableColumn[] = [
|
||||
{
|
||||
title: 'ID',
|
||||
field: 'id',
|
||||
type: 'numeric',
|
||||
align: 'left',
|
||||
width: '100px',
|
||||
},
|
||||
{
|
||||
title: 'Name',
|
||||
field: 'name',
|
||||
type: 'string',
|
||||
align: 'left',
|
||||
highlight: true,
|
||||
render: (row: Partial<RollbarProject>) => (
|
||||
<Link component={RouterLink} to={`/rollbar/${row.name}`}>
|
||||
{row.name}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Status',
|
||||
field: 'status',
|
||||
type: 'string',
|
||||
align: 'left',
|
||||
},
|
||||
];
|
||||
|
||||
type Props = {
|
||||
projects: RollbarProject[];
|
||||
loading: boolean;
|
||||
};
|
||||
|
||||
export const RollbarProjectTable = ({ projects, loading }: Props) => {
|
||||
return (
|
||||
<Table
|
||||
isLoading={loading}
|
||||
columns={columns}
|
||||
options={{
|
||||
padding: 'dense',
|
||||
paging: true,
|
||||
search: true,
|
||||
pageSize: 10,
|
||||
showEmptyDataSourceMessage: !loading,
|
||||
}}
|
||||
title="Projects"
|
||||
data={projects}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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 * as React from 'react';
|
||||
import { ApiProvider, ApiRegistry } from '@backstage/core';
|
||||
import { wrapInTestApp } from '@backstage/test-utils';
|
||||
import { render } from '@testing-library/react';
|
||||
import { RollbarApi, rollbarApiRef } from '../../api/RollbarApi';
|
||||
import { RollbarTopActiveItem } from '../../api/types';
|
||||
import { RollbarProjectPage } from './RollbarProjectPage';
|
||||
|
||||
describe('RollbarProjectPage component', () => {
|
||||
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],
|
||||
},
|
||||
];
|
||||
const rollbarApi: Partial<RollbarApi> = {
|
||||
getTopActiveItems: () => Promise.resolve(items),
|
||||
};
|
||||
|
||||
const renderWrapped = (children: React.ReactNode) =>
|
||||
render(
|
||||
wrapInTestApp(
|
||||
<ApiProvider apis={ApiRegistry.from([[rollbarApiRef, rollbarApi]])}>
|
||||
{children}
|
||||
</ApiProvider>,
|
||||
),
|
||||
);
|
||||
|
||||
it('should render rollbar project page', async () => {
|
||||
const rendered = renderWrapped(<RollbarProjectPage />);
|
||||
expect(rendered.getByText(/Top Active Items/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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 React from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useAsync } from 'react-use';
|
||||
import { useApi } from '@backstage/core';
|
||||
import { rollbarApiRef } from '../../api/RollbarApi';
|
||||
import { RollbarLayout } from '../RollbarLayout/RollbarLayout';
|
||||
import { RollbarTopItemsTable } from '../RollbarTopItemsTable/RollbarTopItemsTable';
|
||||
|
||||
export const RollbarProjectPage = () => {
|
||||
const rollbarApi = useApi(rollbarApiRef);
|
||||
const { componentId } = useParams() as {
|
||||
componentId: string;
|
||||
};
|
||||
const { value, loading } = useAsync(() =>
|
||||
rollbarApi
|
||||
.getTopActiveItems(componentId, 168)
|
||||
.then(data =>
|
||||
data.sort((a, b) => b.item.occurrences - a.item.occurrences),
|
||||
),
|
||||
);
|
||||
|
||||
return (
|
||||
<RollbarLayout>
|
||||
<RollbarTopItemsTable loading={loading} items={value || []} />
|
||||
</RollbarLayout>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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 { wrapInTestApp } from '@backstage/test-utils';
|
||||
import { render } from '@testing-library/react';
|
||||
import * as React from 'react';
|
||||
import { RollbarTopItemsTable } from './RollbarTopItemsTable';
|
||||
import { RollbarTopActiveItem } from '../../api/types';
|
||||
|
||||
const items: RollbarTopActiveItem[] = [
|
||||
{
|
||||
item: {
|
||||
id: 89898989,
|
||||
counter: 1234,
|
||||
environment: 'production',
|
||||
framework: 0,
|
||||
lastOccurrenceTimestamp: new Date().getTime() / 1000,
|
||||
level: 50,
|
||||
occurrences: 150,
|
||||
title: 'Error in foo',
|
||||
uniqueOccurrences: 40,
|
||||
projectId: 12345,
|
||||
},
|
||||
counts: [10, 20, 30, 40, 50],
|
||||
},
|
||||
];
|
||||
|
||||
describe('RollbarTopItemsTable component', () => {
|
||||
it('should render empty data message when loaded and no data', async () => {
|
||||
const rendered = render(
|
||||
wrapInTestApp(<RollbarTopItemsTable items={[]} loading={false} />),
|
||||
);
|
||||
expect(rendered.getByText(/No records to display/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should display item attributes when loading has finished', async () => {
|
||||
const rendered = render(
|
||||
wrapInTestApp(<RollbarTopItemsTable items={items} loading={false} />),
|
||||
);
|
||||
expect(rendered.getByText(/1234/)).toBeInTheDocument();
|
||||
expect(rendered.getByText(/Error in foo/)).toBeInTheDocument();
|
||||
expect(rendered.getByText(/critical/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* 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 React from 'react';
|
||||
import { Table, TableColumn } from '@backstage/core';
|
||||
import {
|
||||
RollbarFrameworkId,
|
||||
RollbarLevel,
|
||||
RollbarTopActiveItem,
|
||||
} from '../../api/types';
|
||||
import { RollbarTrendGraph } from '../RollbarTrendGraph/RollbarTrendGraph';
|
||||
|
||||
const columns: TableColumn[] = [
|
||||
{
|
||||
title: 'ID',
|
||||
field: 'item.counter',
|
||||
type: 'string',
|
||||
align: 'left',
|
||||
width: '70px',
|
||||
},
|
||||
{
|
||||
title: 'Title',
|
||||
field: 'item.title',
|
||||
type: 'string',
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
title: 'Trend',
|
||||
sorting: false,
|
||||
render: data => (
|
||||
<RollbarTrendGraph counts={(data as RollbarTopActiveItem).counts} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Occurrences',
|
||||
field: 'item.occurrences',
|
||||
type: 'numeric',
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
title: 'Environment',
|
||||
field: 'item.environment',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
title: 'Level',
|
||||
field: 'item.level',
|
||||
type: 'string',
|
||||
render: data => RollbarLevel[(data as RollbarTopActiveItem).item.level],
|
||||
},
|
||||
{
|
||||
title: 'Framework',
|
||||
field: 'item.framework',
|
||||
type: 'string',
|
||||
render: data =>
|
||||
RollbarFrameworkId[(data as RollbarTopActiveItem).item.framework],
|
||||
},
|
||||
{
|
||||
title: 'Last Occurrence',
|
||||
field: 'item.lastOccurrenceTimestamp',
|
||||
type: 'datetime',
|
||||
render: data =>
|
||||
new Date(
|
||||
(data as RollbarTopActiveItem).item.lastOccurrenceTimestamp * 1000,
|
||||
).toLocaleDateString(),
|
||||
},
|
||||
];
|
||||
|
||||
type Props = {
|
||||
items: RollbarTopActiveItem[];
|
||||
loading: boolean;
|
||||
};
|
||||
|
||||
export const RollbarTopItemsTable = ({ items, loading }: Props) => {
|
||||
return (
|
||||
<Table
|
||||
isLoading={loading}
|
||||
columns={columns}
|
||||
options={{
|
||||
padding: 'dense',
|
||||
paging: false,
|
||||
search: true,
|
||||
showEmptyDataSourceMessage: !loading,
|
||||
}}
|
||||
title="Top Active Items"
|
||||
data={items}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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 React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { wrapInTestApp } from '@backstage/test-utils';
|
||||
import { RollbarTrendGraph } from './RollbarTrendGraph';
|
||||
|
||||
describe('RollbarTrendGraph component', () => {
|
||||
it('should render a trend graph sparkline', async () => {
|
||||
const mockCounts = [1, 2, 3, 4];
|
||||
const rendered = render(
|
||||
wrapInTestApp(
|
||||
<RollbarTrendGraph counts={mockCounts} data-testid="graph" />,
|
||||
),
|
||||
);
|
||||
expect(rendered).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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 React from 'react';
|
||||
import { Sparklines, SparklinesBars } from 'react-sparklines';
|
||||
|
||||
type Props = {
|
||||
counts: number[];
|
||||
};
|
||||
|
||||
export const RollbarTrendGraph = ({ counts }: Props) => {
|
||||
return (
|
||||
<Sparklines data={counts} svgHeight={48} min={0} margin={4}>
|
||||
<SparklinesBars barWidth={2} />
|
||||
</Sparklines>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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 { plugin } from './plugin';
|
||||
export * from './api/RollbarApi';
|
||||
export { RollbarClient } from './api/RollbarClient';
|
||||
export { RollbarMockClient } from './api/RollbarMockClient';
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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 { plugin } from './plugin';
|
||||
|
||||
describe('rollbar', () => {
|
||||
it('should export plugin', () => {
|
||||
expect(plugin).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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 { createPlugin } from '@backstage/core';
|
||||
import { RollbarPage } from './components/RollbarPage/RollbarPage';
|
||||
import { RollbarProjectPage } from './components/RollbarProjectPage/RollbarProjectPage';
|
||||
import { rootRoute, rootProjectRoute } from './routes';
|
||||
|
||||
export const plugin = createPlugin({
|
||||
id: 'rollbar',
|
||||
register({ router }) {
|
||||
router.addRoute(rootRoute, RollbarPage);
|
||||
router.addRoute(rootProjectRoute, RollbarProjectPage);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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 { createRouteRef } from '@backstage/core';
|
||||
|
||||
export const rootRoute = createRouteRef({
|
||||
path: '/rollbar',
|
||||
title: 'Rollbar Home',
|
||||
});
|
||||
|
||||
export const rootProjectRoute = createRouteRef({
|
||||
path: '/rollbar/:componentId/*',
|
||||
title: 'Rollbar',
|
||||
});
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
import '@testing-library/jest-dom';
|
||||
|
||||
require('jest-fetch-mock').enableMocks();
|
||||
@@ -5839,6 +5839,15 @@ camelcase-keys@^4.0.0:
|
||||
map-obj "^2.0.0"
|
||||
quick-lru "^1.0.0"
|
||||
|
||||
camelcase-keys@^6.2.2:
|
||||
version "6.2.2"
|
||||
resolved "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz#5e755d6ba51aa223ec7d3d52f25778210f9dc3c0"
|
||||
integrity sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==
|
||||
dependencies:
|
||||
camelcase "^5.3.1"
|
||||
map-obj "^4.0.0"
|
||||
quick-lru "^4.0.1"
|
||||
|
||||
camelcase@^2.0.0:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f"
|
||||
@@ -12499,6 +12508,11 @@ map-obj@^2.0.0:
|
||||
resolved "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz#a65cd29087a92598b8791257a523e021222ac1f9"
|
||||
integrity sha1-plzSkIepJZi4eRJXpSPgISIqwfk=
|
||||
|
||||
map-obj@^4.0.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.npmjs.org/map-obj/-/map-obj-4.1.0.tgz#b91221b542734b9f14256c0132c897c5d7256fd5"
|
||||
integrity sha512-glc9y00wgtwcDmp7GaE/0b0OnxpNJsVf3ael/An6Fe2Q51LLwN1er6sdomLRzz5h0+yMpiYLhWYF5R7HeqVd4g==
|
||||
|
||||
map-or-similar@^1.5.0:
|
||||
version "1.5.0"
|
||||
resolved "https://registry.npmjs.org/map-or-similar/-/map-or-similar-1.5.0.tgz#6de2653174adfb5d9edc33c69d3e92a1b76faf08"
|
||||
@@ -15240,6 +15254,11 @@ quick-lru@^1.0.0:
|
||||
resolved "https://registry.npmjs.org/quick-lru/-/quick-lru-1.1.0.tgz#4360b17c61136ad38078397ff11416e186dcfbb8"
|
||||
integrity sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g=
|
||||
|
||||
quick-lru@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f"
|
||||
integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==
|
||||
|
||||
raf-schd@^4.0.2:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.2.tgz#bd44c708188f2e84c810bf55fcea9231bcaed8a0"
|
||||
|
||||
Reference in New Issue
Block a user