Merge pull request #2904 from spotify/iameap/hacks-newrelic

New Relic Plugin Base Improvements
This commit is contained in:
Fredrik Adelöw
2020-10-15 22:59:08 +02:00
committed by GitHub
6 changed files with 190 additions and 70 deletions
+25
View File
@@ -0,0 +1,25 @@
---
'@backstage/plugin-newrelic': minor
---
The New Relic plugin now uses the Backstage proxy to communicate with New Relic's API.
Please update your `app-config.yaml` as follows:
```yaml
# Old Config
newrelic:
api:
baseUrl: 'https://api.newrelic.com/v2'
key: NEW_RELIC_REST_API_KEY
```
```yaml
# New Config
proxy:
'/newrelic/apm/api':
target: https://api.newrelic.com/v2
headers:
X-Api-Key:
$env: NEW_RELIC_REST_API_KEY
```
+6 -5
View File
@@ -34,6 +34,12 @@ proxy:
$env: TRAVISCI_AUTH_TOKEN
travis-api-version: 3
'/newrelic/apm/api':
target: https://api.newrelic.com/v2
headers:
X-Api-Key:
$env: NEW_RELIC_REST_API_KEY
organization:
name: Spotify
@@ -51,11 +57,6 @@ rollbar:
accountToken:
$env: ROLLBAR_ACCOUNT_TOKEN
newrelic:
api:
baseUrl: 'https://api.newrelic.com/v2'
key: NEW_RELIC_REST_API_KEY
lighthouse:
baseUrl: http://localhost:3003
+28 -6
View File
@@ -7,16 +7,38 @@ Website: [https://newrelic.com](https://newrelic.com)
## Getting Started
Add New Relic REST API Key to `app-config.yaml`
This plugin uses the Backstage proxy to securely communicate with New Relic's
APIs. Add the following to your `app-config.yaml` to enable this configuration:
```yaml
newrelic:
api:
baseUrl: 'https://api.newrelic.com/v2'
key: <NEW_RELIC_REST_API_KEY>
proxy:
'/newrelic/apm/api':
target: https://api.newrelic.com/v2
headers:
X-Api-Key:
$env: NEW_RELIC_REST_API_KEY
```
New Relic Plugin Path: [/newrelic](http://localhost:3000/newrelic)
In your production deployment of Backstage, you would also need to ensure that
you've set the `NEW_RELIC_REST_API_KEY` environment variable before starting
the backend.
While working locally, you may wish to hard-code your API key in your
`app-config.local.yaml` like this:
```yaml
# app-config.local.yaml
proxy:
'/newrelic/apm/api':
headers:
X-Api-Key: NRRA-YourActualApiKey
```
Read more about how to find or generate this key in
[New Relic's Documentation](https://docs.newrelic.com/docs/apis/get-started/intro-apis/types-new-relic-api-keys#rest-api-key).
See if it's working by visiting the New Relic Plugin Path:
[/newrelic](http://localhost:3000/newrelic)
## Features
+110
View File
@@ -0,0 +1,110 @@
/*
* 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, DiscoveryApi } from '@backstage/core';
export type NewRelicApplication = {
id: number;
application_summary: NewRelicApplicationSummary;
name: string;
language: string;
health_status: string;
reporting: boolean;
settings: NewRelicApplicationSettings;
links?: NewRelicApplicationLinks;
};
export type NewRelicApplicationSummary = {
apdex_score: number;
error_rate: number;
host_count: number;
instance_count: number;
response_time: number;
throughput: number;
};
export type NewRelicApplicationSettings = {
app_apdex_threshold: number;
end_user_apdex_threshold: number;
enable_real_user_monitoring: boolean;
use_server_side_config: boolean;
};
export type NewRelicApplicationLinks = {
application_instances: Array<any>;
servers: Array<any>;
application_hosts: Array<any>;
};
export type NewRelicApplications = {
applications: NewRelicApplication[];
};
export const newRelicApiRef = createApiRef<NewRelicApi>({
id: 'plugin.newrelic.service',
description: 'Used by the NewRelic plugin to make requests',
});
const DEFAULT_PROXY_PATH_BASE = '/newrelic';
type Options = {
discoveryApi: DiscoveryApi;
/**
* Path to use for requests via the proxy, defaults to /newrelic
*/
proxyPathBase?: string;
};
export interface NewRelicApi {
getApplications(): Promise<NewRelicApplications>;
}
export class NewRelicClient implements NewRelicApi {
private readonly discoveryApi: DiscoveryApi;
private readonly proxyPathBase: string;
constructor(options: Options) {
this.discoveryApi = options.discoveryApi;
this.proxyPathBase = options.proxyPathBase ?? DEFAULT_PROXY_PATH_BASE;
}
async getApplications(): Promise<NewRelicApplications> {
const url = await this.getApiUrl('apm', 'applications.json');
const response = await fetch(url);
let responseJson;
try {
responseJson = await response.json();
} catch (e) {
responseJson = { applications: [] };
}
if (response.status !== 200) {
throw new Error(
`Error communicating with New Relic: ${
responseJson?.error?.title || response.statusText
}`,
);
}
return responseJson;
}
private async getApiUrl(product: string, path: string) {
const proxyUrl = await this.discoveryApi.getBaseUrl('proxy');
return `${proxyUrl}${this.proxyPathBase}/${product}/api/${path}`;
}
}
@@ -15,52 +15,10 @@
*/
import React, { FC } from 'react';
import {
configApiRef,
Progress,
Table,
TableColumn,
useApi,
} from '@backstage/core';
import { Progress, Table, TableColumn, useApi } from '@backstage/core';
import Alert from '@material-ui/lab/Alert';
import { useAsync } from 'react-use';
type NewRelicApplication = {
id: number;
application_summary: NewRelicApplicationSummary;
name: string;
language: string;
health_status: string;
reporting: boolean;
settings: NewRelicApplicationSettings;
links?: NewRelicApplicationLinks;
};
type NewRelicApplicationSummary = {
apdex_score: number;
error_rate: number;
host_count: number;
instance_count: number;
response_time: number;
throughput: number;
};
type NewRelicApplicationSettings = {
app_apdex_threshold: number;
end_user_apdex_threshold: number;
enable_real_user_monitoring: boolean;
use_server_side_config: boolean;
};
type NewRelicApplicationLinks = {
application_instances: Array<any>;
servers: Array<any>;
application_hosts: Array<any>;
};
type NewRelicApplications = {
applications: NewRelicApplication[];
};
import { newRelicApiRef, NewRelicApplications } from '../../api';
export const NewRelicAPMTable: FC<NewRelicApplications> = ({
applications,
@@ -73,7 +31,7 @@ export const NewRelicAPMTable: FC<NewRelicApplications> = ({
{ title: 'Instance Count', field: 'instanceCount' },
{ title: 'Apdex', field: 'apdexScore' },
];
const data = applications.map((app: NewRelicApplication) => {
const data = applications.map(app => {
const { name, application_summary: applicationSummary } = app;
const {
response_time: responseTime,
@@ -104,20 +62,11 @@ export const NewRelicAPMTable: FC<NewRelicApplications> = ({
};
const NewRelicFetchComponent: FC<{}> = () => {
const configApi = useApi(configApiRef);
const apiBaseUrl = configApi.getString('newrelic.api.baseUrl');
const apiKey = configApi.getString('newrelic.api.key');
const api = useApi(newRelicApiRef);
const { value, loading, error } = useAsync(async (): Promise<
NewRelicApplication[]
> => {
const response = await fetch(`${apiBaseUrl}/applications.json`, {
headers: {
'X-Api-Key': apiKey,
},
});
const data: NewRelicApplications = await response.json();
return data.applications.filter((application: NewRelicApplication) => {
const { value, loading, error } = useAsync(async () => {
const data = await api.getApplications();
return data.applications.filter(application => {
return application.hasOwnProperty('application_summary');
});
}, []);
+14 -1
View File
@@ -14,7 +14,13 @@
* limitations under the License.
*/
import { createPlugin, createRouteRef } from '@backstage/core';
import {
createApiFactory,
createPlugin,
createRouteRef,
discoveryApiRef,
} from '@backstage/core';
import { NewRelicClient, newRelicApiRef } from './api';
import NewRelicComponent from './components/NewRelicComponent';
export const rootRouteRef = createRouteRef({
@@ -24,6 +30,13 @@ export const rootRouteRef = createRouteRef({
export const plugin = createPlugin({
id: 'newrelic',
apis: [
createApiFactory({
api: newRelicApiRef,
deps: { discoveryApi: discoveryApiRef },
factory: ({ discoveryApi }) => new NewRelicClient({ discoveryApi }),
}),
],
register({ router }) {
router.addRoute(rootRouteRef, NewRelicComponent);
},