@backstage-community/plugin-newrelic 0.3.50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1256 -0
- package/README.md +105 -0
- package/dist/esm/index-C0wl3iLB.esm.js +95 -0
- package/dist/esm/index-C0wl3iLB.esm.js.map +1 -0
- package/dist/esm/index-DhuyBTi3.esm.js +92 -0
- package/dist/esm/index-DhuyBTi3.esm.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.esm.js +4 -0
- package/dist/index.esm.js.map +1 -0
- package/package.json +69 -0
package/README.md
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# New Relic Plugin (Alpha)
|
|
2
|
+
|
|
3
|
+
Website: [https://newrelic.com](https://newrelic.com)
|
|
4
|
+
|
|
5
|
+
<img src="./src/assets/img/newrelic-plugin-apm.png" alt="New Relic Plugin APM" />
|
|
6
|
+
<img src="./src/assets/img/newrelic-plugin-tools.png" alt="New Relic Plugin Tools" />
|
|
7
|
+
|
|
8
|
+
## Getting Started
|
|
9
|
+
|
|
10
|
+
This plugin uses the Backstage proxy to securely communicate with New Relic's
|
|
11
|
+
APIs.
|
|
12
|
+
|
|
13
|
+
1. Add the following to your `app-config.yaml` to enable this configuration:
|
|
14
|
+
|
|
15
|
+
```yaml
|
|
16
|
+
proxy:
|
|
17
|
+
'/newrelic/apm/api':
|
|
18
|
+
target: https://api.newrelic.com/v2
|
|
19
|
+
headers:
|
|
20
|
+
X-Api-Key: ${NEW_RELIC_REST_API_KEY}
|
|
21
|
+
allowedHeaders:
|
|
22
|
+
- link
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
There is some types of api key on new relic, to this use must be `User` type of key, In your production deployment of Backstage, you would also need to ensure that
|
|
26
|
+
you've set the `NEW_RELIC_REST_API_KEY` environment variable before starting
|
|
27
|
+
the backend.
|
|
28
|
+
|
|
29
|
+
While working locally, you may wish to hard-code your API key in your
|
|
30
|
+
`app-config.local.yaml` like this:
|
|
31
|
+
|
|
32
|
+
```yaml
|
|
33
|
+
# app-config.local.yaml
|
|
34
|
+
proxy:
|
|
35
|
+
'/newrelic/apm/api':
|
|
36
|
+
headers:
|
|
37
|
+
X-Api-Key: NRRA-YourActualApiKey
|
|
38
|
+
allowedHeaders:
|
|
39
|
+
- link
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Read more about how to find or generate this key in
|
|
43
|
+
[New Relic's Documentation](https://docs.newrelic.com/docs/apis/get-started/intro-apis/types-new-relic-api-keys#rest-api-key).
|
|
44
|
+
|
|
45
|
+
See if it's working by visiting the New Relic Plugin Path:
|
|
46
|
+
[/newrelic](http://localhost:3000/newrelic)
|
|
47
|
+
|
|
48
|
+
2. Add a dependency to your `packages/app/package.json`:
|
|
49
|
+
```sh
|
|
50
|
+
# From your Backstage root directory
|
|
51
|
+
yarn --cwd packages/app add @backstage-community/plugin-newrelic
|
|
52
|
+
```
|
|
53
|
+
3. Add the `NewRelicPage` to your `packages/app/src/App.tsx`:
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
<FlatRoutes>
|
|
57
|
+
…
|
|
58
|
+
<Route path="/newrelic" element={<NewRelicPage />} />
|
|
59
|
+
</FlatRoutes>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
4. Add link to New Relic to your sidebar
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
// packages/app/src/components/Root/Root.tsx
|
|
66
|
+
import ExtensionIcon from '@material-ui/icons/ExtensionOutlined';
|
|
67
|
+
|
|
68
|
+
...
|
|
69
|
+
|
|
70
|
+
export const Root = ({ children }: PropsWithChildren<{}>) => (
|
|
71
|
+
<SidebarPage>
|
|
72
|
+
<Sidebar>
|
|
73
|
+
...
|
|
74
|
+
<SidebarItem icon={ExtensionIcon} to="newrelic" text="New Relic" />
|
|
75
|
+
...
|
|
76
|
+
</Sidebar>
|
|
77
|
+
</SidebarPage>
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
5. Navigate to your.domain.com/newrelic.
|
|
83
|
+
|
|
84
|
+
At this step you must be able to see a page like that
|
|
85
|
+
<img src="./src/assets/img/newrelic-plugin-apm.png" alt="New Relic Plugin APM" />
|
|
86
|
+
|
|
87
|
+
## Features
|
|
88
|
+
|
|
89
|
+
- View New Relic Application Performance Monitoring (APM) data such as:
|
|
90
|
+
- Application Name
|
|
91
|
+
- Response Time (ms)
|
|
92
|
+
- Throughput (rpm)
|
|
93
|
+
- Error Rate
|
|
94
|
+
- Instance Count
|
|
95
|
+
- Apdex Score
|
|
96
|
+
|
|
97
|
+
## Limitations
|
|
98
|
+
|
|
99
|
+
- Currently only supports New Relic APM data
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
You can also serve the plugin in isolation by running `yarn start` in the plugin directory.
|
|
104
|
+
This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads.
|
|
105
|
+
It is only meant for local development, and the setup for it can be found inside the [/dev](./dev) directory.
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import Grid from '@material-ui/core/Grid';
|
|
3
|
+
import Alert from '@material-ui/lab/Alert';
|
|
4
|
+
import useAsync from 'react-use/esm/useAsync';
|
|
5
|
+
import { n as newRelicApiRef } from './index-DhuyBTi3.esm.js';
|
|
6
|
+
import { Progress, Table, Page, Header, HeaderLabel, Content, ContentHeader, SupportButton } from '@backstage/core-components';
|
|
7
|
+
import { useApi } from '@backstage/core-plugin-api';
|
|
8
|
+
import 'parse-link-header';
|
|
9
|
+
|
|
10
|
+
const sortNumeric = (field) => (a, b) => {
|
|
11
|
+
return a[field] - b[field];
|
|
12
|
+
};
|
|
13
|
+
const NewRelicAPMTable = ({ applications }) => {
|
|
14
|
+
const columns = [
|
|
15
|
+
{ title: "Application", field: "name", searchable: true },
|
|
16
|
+
{
|
|
17
|
+
title: "Response Time (ms)",
|
|
18
|
+
field: "responseTime",
|
|
19
|
+
customSort: sortNumeric("responseTime"),
|
|
20
|
+
searchable: false
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
title: "Throughput (rpm)",
|
|
24
|
+
field: "throughput",
|
|
25
|
+
customSort: sortNumeric("throughput"),
|
|
26
|
+
searchable: false
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
title: "Error Rate (%)",
|
|
30
|
+
field: "errorRate",
|
|
31
|
+
customSort: sortNumeric("errorRate"),
|
|
32
|
+
searchable: false
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
title: "Instance Count",
|
|
36
|
+
field: "instanceCount",
|
|
37
|
+
customSort: sortNumeric("instanceCount"),
|
|
38
|
+
searchable: false
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
title: "Apdex",
|
|
42
|
+
field: "apdexScore",
|
|
43
|
+
customSort: sortNumeric("apdexScore"),
|
|
44
|
+
searchable: false
|
|
45
|
+
}
|
|
46
|
+
];
|
|
47
|
+
const data = applications.map((app) => {
|
|
48
|
+
const { name, application_summary: applicationSummary } = app;
|
|
49
|
+
const {
|
|
50
|
+
response_time: responseTime,
|
|
51
|
+
throughput,
|
|
52
|
+
error_rate: errorRate,
|
|
53
|
+
instance_count: instanceCount,
|
|
54
|
+
apdex_score: apdexScore
|
|
55
|
+
} = applicationSummary;
|
|
56
|
+
return {
|
|
57
|
+
name,
|
|
58
|
+
responseTime,
|
|
59
|
+
throughput,
|
|
60
|
+
errorRate,
|
|
61
|
+
instanceCount,
|
|
62
|
+
apdexScore
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
return /* @__PURE__ */ React.createElement(
|
|
66
|
+
Table,
|
|
67
|
+
{
|
|
68
|
+
title: "Application Performance Monitoring",
|
|
69
|
+
options: { search: true, paging: true },
|
|
70
|
+
columns,
|
|
71
|
+
data
|
|
72
|
+
}
|
|
73
|
+
);
|
|
74
|
+
};
|
|
75
|
+
const NewRelicFetchComponent = () => {
|
|
76
|
+
const api = useApi(newRelicApiRef);
|
|
77
|
+
const { value, loading, error } = useAsync(async () => {
|
|
78
|
+
const data = await api.getApplications();
|
|
79
|
+
return data.applications.filter((application) => {
|
|
80
|
+
return application.hasOwnProperty("application_summary");
|
|
81
|
+
});
|
|
82
|
+
}, []);
|
|
83
|
+
if (loading) {
|
|
84
|
+
return /* @__PURE__ */ React.createElement(Progress, null);
|
|
85
|
+
} else if (error) {
|
|
86
|
+
return /* @__PURE__ */ React.createElement(Alert, { severity: "error" }, error.message);
|
|
87
|
+
}
|
|
88
|
+
return /* @__PURE__ */ React.createElement(NewRelicAPMTable, { applications: value || [] });
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const NewRelicComponent = () => /* @__PURE__ */ React.createElement(Page, { themeId: "tool" }, /* @__PURE__ */ React.createElement(Header, { title: "New Relic" }, /* @__PURE__ */ React.createElement(HeaderLabel, { label: "Owner", value: "Engineering" })), /* @__PURE__ */ React.createElement(Content, null, /* @__PURE__ */ React.createElement(ContentHeader, { title: "New Relic" }, /* @__PURE__ */ React.createElement(SupportButton, null, "New Relic Application Performance Monitoring")), /* @__PURE__ */ React.createElement(Grid, { container: true, spacing: 3, direction: "column" }, /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(NewRelicFetchComponent, null)))));
|
|
92
|
+
var NewRelicComponent$1 = NewRelicComponent;
|
|
93
|
+
|
|
94
|
+
export { NewRelicComponent$1 as default };
|
|
95
|
+
//# sourceMappingURL=index-C0wl3iLB.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-C0wl3iLB.esm.js","sources":["../../src/components/NewRelicFetchComponent/NewRelicFetchComponent.tsx","../../src/components/NewRelicComponent/NewRelicComponent.tsx"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport React from 'react';\nimport Alert from '@material-ui/lab/Alert';\nimport useAsync from 'react-use/esm/useAsync';\nimport { newRelicApiRef, NewRelicApplications } from '../../api';\n\nimport { Progress, Table, TableColumn } from '@backstage/core-components';\nimport { useApi } from '@backstage/core-plugin-api';\n\nconst sortNumeric =\n <F extends string>(field: F) =>\n (a: { [key in F]: number }, b: { [key in F]: number }) => {\n return a[field] - b[field];\n };\n\ntype NewRelicTableData = {\n name: string;\n responseTime: number;\n throughput: number;\n errorRate: number;\n instanceCount: number;\n apdexScore: number;\n};\n\nexport const NewRelicAPMTable = ({ applications }: NewRelicApplications) => {\n const columns: TableColumn<NewRelicTableData>[] = [\n { title: 'Application', field: 'name', searchable: true },\n {\n title: 'Response Time (ms)',\n field: 'responseTime',\n customSort: sortNumeric('responseTime'),\n searchable: false,\n },\n {\n title: 'Throughput (rpm)',\n field: 'throughput',\n customSort: sortNumeric('throughput'),\n searchable: false,\n },\n {\n title: 'Error Rate (%)',\n field: 'errorRate',\n customSort: sortNumeric('errorRate'),\n searchable: false,\n },\n {\n title: 'Instance Count',\n field: 'instanceCount',\n customSort: sortNumeric('instanceCount'),\n searchable: false,\n },\n {\n title: 'Apdex',\n field: 'apdexScore',\n customSort: sortNumeric('apdexScore'),\n searchable: false,\n },\n ];\n const data: Array<NewRelicTableData> = applications.map(app => {\n const { name, application_summary: applicationSummary } = app;\n const {\n response_time: responseTime,\n throughput,\n error_rate: errorRate,\n instance_count: instanceCount,\n apdex_score: apdexScore,\n } = applicationSummary;\n\n return {\n name,\n responseTime,\n throughput,\n errorRate,\n instanceCount,\n apdexScore,\n };\n });\n\n return (\n <Table\n title=\"Application Performance Monitoring\"\n options={{ search: true, paging: true }}\n columns={columns}\n data={data}\n />\n );\n};\n\nconst NewRelicFetchComponent = () => {\n const api = useApi(newRelicApiRef);\n\n const { value, loading, error } = useAsync(async () => {\n const data = await api.getApplications();\n return data.applications.filter(application => {\n return application.hasOwnProperty('application_summary');\n });\n }, []);\n\n if (loading) {\n return <Progress />;\n } else if (error) {\n return <Alert severity=\"error\">{error.message}</Alert>;\n }\n\n return <NewRelicAPMTable applications={value || []} />;\n};\n\nexport default NewRelicFetchComponent;\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport React from 'react';\nimport Grid from '@material-ui/core/Grid';\nimport NewRelicFetchComponent from '../NewRelicFetchComponent';\nimport {\n Header,\n Page,\n Content,\n ContentHeader,\n HeaderLabel,\n SupportButton,\n} from '@backstage/core-components';\n\nconst NewRelicComponent = () => (\n <Page themeId=\"tool\">\n <Header title=\"New Relic\">\n <HeaderLabel label=\"Owner\" value=\"Engineering\" />\n </Header>\n <Content>\n <ContentHeader title=\"New Relic\">\n <SupportButton>\n New Relic Application Performance Monitoring\n </SupportButton>\n </ContentHeader>\n <Grid container spacing={3} direction=\"column\">\n <Grid item>\n <NewRelicFetchComponent />\n </Grid>\n </Grid>\n </Content>\n </Page>\n);\n\nexport default NewRelicComponent;\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { createApiRef, createRouteRef, createPlugin, createApiFactory, discoveryApiRef, fetchApiRef, createRoutableExtension } from '@backstage/core-plugin-api';
|
|
2
|
+
import parseLinkHeader from 'parse-link-header';
|
|
3
|
+
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
6
|
+
var __publicField = (obj, key, value) => {
|
|
7
|
+
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
8
|
+
return value;
|
|
9
|
+
};
|
|
10
|
+
const newRelicApiRef = createApiRef({
|
|
11
|
+
id: "plugin.newrelic.service"
|
|
12
|
+
});
|
|
13
|
+
const DEFAULT_PROXY_PATH_BASE = "/newrelic";
|
|
14
|
+
class NewRelicClient {
|
|
15
|
+
constructor(options) {
|
|
16
|
+
__publicField(this, "discoveryApi");
|
|
17
|
+
__publicField(this, "fetchApi");
|
|
18
|
+
__publicField(this, "proxyPathBase");
|
|
19
|
+
__publicField(this, "baseUrl");
|
|
20
|
+
var _a;
|
|
21
|
+
this.discoveryApi = options.discoveryApi;
|
|
22
|
+
this.fetchApi = options.fetchApi;
|
|
23
|
+
this.proxyPathBase = (_a = options.proxyPathBase) != null ? _a : DEFAULT_PROXY_PATH_BASE;
|
|
24
|
+
this.baseUrl = "";
|
|
25
|
+
}
|
|
26
|
+
async getApplications() {
|
|
27
|
+
if (!this.baseUrl) {
|
|
28
|
+
const proxyUrl = await this.discoveryApi.getBaseUrl("proxy");
|
|
29
|
+
this.baseUrl = `${proxyUrl}${this.proxyPathBase}/apm/api/applications.json`;
|
|
30
|
+
}
|
|
31
|
+
const applications = [];
|
|
32
|
+
let targetUrl = this.baseUrl;
|
|
33
|
+
do {
|
|
34
|
+
const { nextPageUrl, applicationsFromReadPage } = await this.fetchNewRelic(targetUrl);
|
|
35
|
+
targetUrl = nextPageUrl != null ? nextPageUrl : "";
|
|
36
|
+
applications.push(...applicationsFromReadPage);
|
|
37
|
+
} while (!!targetUrl);
|
|
38
|
+
return { applications };
|
|
39
|
+
}
|
|
40
|
+
async fetchNewRelic(targetUrl) {
|
|
41
|
+
var _a, _b, _c;
|
|
42
|
+
const response = await this.fetchApi.fetch(targetUrl);
|
|
43
|
+
if (!response.ok) {
|
|
44
|
+
let specificErrorTitle = void 0;
|
|
45
|
+
try {
|
|
46
|
+
specificErrorTitle = (_b = (_a = await response.json()) == null ? void 0 : _a.error) == null ? void 0 : _b.title;
|
|
47
|
+
} catch (e) {
|
|
48
|
+
}
|
|
49
|
+
throw new Error(
|
|
50
|
+
`Error communicating with New Relic: ${specificErrorTitle || response.statusText}`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
const readResponse = await response.json();
|
|
54
|
+
const linkHeader = response.headers.get("link");
|
|
55
|
+
const parseResult = parseLinkHeader(linkHeader);
|
|
56
|
+
const nextPageNumber = (_c = parseResult == null ? void 0 : parseResult.next) == null ? void 0 : _c.page;
|
|
57
|
+
return {
|
|
58
|
+
nextPageUrl: nextPageNumber && `${this.baseUrl}?page=${nextPageNumber}`,
|
|
59
|
+
applicationsFromReadPage: readResponse.applications
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const rootRouteRef = createRouteRef({
|
|
65
|
+
id: "newrelic"
|
|
66
|
+
});
|
|
67
|
+
const newRelicPlugin = createPlugin({
|
|
68
|
+
id: "newrelic",
|
|
69
|
+
apis: [
|
|
70
|
+
createApiFactory({
|
|
71
|
+
api: newRelicApiRef,
|
|
72
|
+
deps: {
|
|
73
|
+
discoveryApi: discoveryApiRef,
|
|
74
|
+
fetchApi: fetchApiRef
|
|
75
|
+
},
|
|
76
|
+
factory: ({ discoveryApi, fetchApi }) => new NewRelicClient({ discoveryApi, fetchApi })
|
|
77
|
+
})
|
|
78
|
+
],
|
|
79
|
+
routes: {
|
|
80
|
+
root: rootRouteRef
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
const NewRelicPage = newRelicPlugin.provide(
|
|
84
|
+
createRoutableExtension({
|
|
85
|
+
name: "NewRelicPage",
|
|
86
|
+
component: () => import('./index-C0wl3iLB.esm.js').then((m) => m.default),
|
|
87
|
+
mountPoint: rootRouteRef
|
|
88
|
+
})
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
export { NewRelicPage as N, newRelicPlugin as a, newRelicApiRef as n };
|
|
92
|
+
//# sourceMappingURL=index-DhuyBTi3.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-DhuyBTi3.esm.js","sources":["../../src/api/index.ts","../../src/plugin.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n createApiRef,\n DiscoveryApi,\n FetchApi,\n} from '@backstage/core-plugin-api';\n\nimport parseLinkHeader from 'parse-link-header';\n\nexport type NewRelicApplication = {\n id: number;\n application_summary: NewRelicApplicationSummary;\n name: string;\n language: string;\n health_status: string;\n reporting: boolean;\n settings: NewRelicApplicationSettings;\n links?: NewRelicApplicationLinks;\n};\n\nexport type NewRelicApplicationSummary = {\n apdex_score: number;\n error_rate: number;\n host_count: number;\n instance_count: number;\n response_time: number;\n throughput: number;\n};\n\nexport type NewRelicApplicationSettings = {\n app_apdex_threshold: number;\n end_user_apdex_threshold: number;\n enable_real_user_monitoring: boolean;\n use_server_side_config: boolean;\n};\n\nexport type NewRelicApplicationLinks = {\n application_instances: Array<any>;\n servers: Array<any>;\n application_hosts: Array<any>;\n};\n\nexport type NewRelicApplications = {\n applications: NewRelicApplication[];\n};\n\nexport const newRelicApiRef = createApiRef<NewRelicApi>({\n id: 'plugin.newrelic.service',\n});\n\nconst DEFAULT_PROXY_PATH_BASE = '/newrelic';\n\ntype Options = {\n discoveryApi: DiscoveryApi;\n fetchApi: FetchApi;\n /**\n * Path to use for requests via the proxy, defaults to /newrelic\n */\n proxyPathBase?: string;\n};\n\nexport interface NewRelicApi {\n getApplications(): Promise<NewRelicApplications>;\n}\n\ninterface NewRelicPageReadResult {\n nextPageUrl: string | undefined;\n applicationsFromReadPage: NewRelicApplication[];\n}\n\nexport class NewRelicClient implements NewRelicApi {\n private readonly discoveryApi: DiscoveryApi;\n private readonly fetchApi: FetchApi;\n private readonly proxyPathBase: string;\n private baseUrl: string;\n\n constructor(options: Options) {\n this.discoveryApi = options.discoveryApi;\n this.fetchApi = options.fetchApi;\n this.proxyPathBase = options.proxyPathBase ?? DEFAULT_PROXY_PATH_BASE;\n this.baseUrl = '';\n }\n\n async getApplications(): Promise<NewRelicApplications> {\n if (!this.baseUrl) {\n const proxyUrl = await this.discoveryApi.getBaseUrl('proxy');\n this.baseUrl = `${proxyUrl}${this.proxyPathBase}/apm/api/applications.json`;\n }\n\n const applications: NewRelicApplication[] = [];\n let targetUrl = this.baseUrl;\n\n do {\n const { nextPageUrl, applicationsFromReadPage } =\n await this.fetchNewRelic(targetUrl);\n\n targetUrl = nextPageUrl ?? '';\n applications.push(...applicationsFromReadPage);\n } while (!!targetUrl);\n\n return { applications };\n }\n\n private async fetchNewRelic(\n targetUrl: string,\n ): Promise<NewRelicPageReadResult> {\n const response = await this.fetchApi.fetch(targetUrl);\n\n if (!response.ok) {\n let specificErrorTitle = undefined;\n try {\n specificErrorTitle = (await response.json())?.error?.title;\n } catch (e) {\n /* empty */\n }\n\n throw new Error(\n `Error communicating with New Relic: ${\n specificErrorTitle || response.statusText\n }`,\n );\n }\n\n const readResponse = (await response.json()) as NewRelicApplications;\n const linkHeader = response.headers.get('link');\n const parseResult = parseLinkHeader(linkHeader);\n const nextPageNumber = parseResult?.next?.page;\n\n return {\n nextPageUrl: nextPageNumber && `${this.baseUrl}?page=${nextPageNumber}`,\n applicationsFromReadPage: readResponse.applications,\n };\n }\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { NewRelicClient, newRelicApiRef } from './api';\nimport {\n createApiFactory,\n createPlugin,\n createRouteRef,\n discoveryApiRef,\n fetchApiRef,\n createRoutableExtension,\n} from '@backstage/core-plugin-api';\n\nexport const rootRouteRef = createRouteRef({\n id: 'newrelic',\n});\n\n/** @public */\nexport const newRelicPlugin = createPlugin({\n id: 'newrelic',\n apis: [\n createApiFactory({\n api: newRelicApiRef,\n deps: {\n discoveryApi: discoveryApiRef,\n fetchApi: fetchApiRef,\n },\n factory: ({ discoveryApi, fetchApi }) =>\n new NewRelicClient({ discoveryApi, fetchApi }),\n }),\n ],\n routes: {\n root: rootRouteRef,\n },\n});\n\n/** @public */\nexport const NewRelicPage = newRelicPlugin.provide(\n createRoutableExtension({\n name: 'NewRelicPage',\n component: () =>\n import('./components/NewRelicComponent').then(m => m.default),\n mountPoint: rootRouteRef,\n }),\n);\n"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import * as react from 'react';
|
|
3
|
+
import * as _backstage_core_plugin_api from '@backstage/core-plugin-api';
|
|
4
|
+
|
|
5
|
+
/** @public */
|
|
6
|
+
declare const newRelicPlugin: _backstage_core_plugin_api.BackstagePlugin<{
|
|
7
|
+
root: _backstage_core_plugin_api.RouteRef<undefined>;
|
|
8
|
+
}, {}, {}>;
|
|
9
|
+
/** @public */
|
|
10
|
+
declare const NewRelicPage: () => react.JSX.Element;
|
|
11
|
+
|
|
12
|
+
export { NewRelicPage, newRelicPlugin, newRelicPlugin as plugin };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;"}
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@backstage-community/plugin-newrelic",
|
|
3
|
+
"version": "0.3.50",
|
|
4
|
+
"description": "A Backstage plugin that integrates towards New Relic",
|
|
5
|
+
"backstage": {
|
|
6
|
+
"role": "frontend-plugin"
|
|
7
|
+
},
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"access": "public",
|
|
10
|
+
"main": "dist/index.esm.js",
|
|
11
|
+
"types": "dist/index.d.ts"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"backstage",
|
|
15
|
+
"newrelic"
|
|
16
|
+
],
|
|
17
|
+
"homepage": "https://backstage.io",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "https://github.com/backstage/community-plugins",
|
|
21
|
+
"directory": "workspaces/newrelic/plugins/newrelic"
|
|
22
|
+
},
|
|
23
|
+
"license": "Apache-2.0",
|
|
24
|
+
"sideEffects": false,
|
|
25
|
+
"main": "dist/index.esm.js",
|
|
26
|
+
"types": "dist/index.d.ts",
|
|
27
|
+
"files": [
|
|
28
|
+
"dist"
|
|
29
|
+
],
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "backstage-cli package build",
|
|
32
|
+
"clean": "backstage-cli package clean",
|
|
33
|
+
"lint": "backstage-cli package lint",
|
|
34
|
+
"prepack": "backstage-cli package prepack",
|
|
35
|
+
"postpack": "backstage-cli package postpack",
|
|
36
|
+
"start": "backstage-cli package start",
|
|
37
|
+
"test": "backstage-cli package test"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@backstage/core-components": "^0.14.4",
|
|
41
|
+
"@backstage/core-plugin-api": "^1.9.2",
|
|
42
|
+
"@material-ui/core": "^4.12.2",
|
|
43
|
+
"@material-ui/lab": "4.0.0-alpha.61",
|
|
44
|
+
"@types/react": "^16.13.1 || ^17.0.0 || ^18.0.0",
|
|
45
|
+
"parse-link-header": "^2.0.0",
|
|
46
|
+
"react-use": "^17.2.4"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@backstage/cli": "^0.26.3",
|
|
50
|
+
"@backstage/dev-utils": "^1.0.31",
|
|
51
|
+
"@backstage/test-utils": "^1.5.4",
|
|
52
|
+
"@testing-library/dom": "^10.0.0",
|
|
53
|
+
"@testing-library/jest-dom": "^6.0.0",
|
|
54
|
+
"@testing-library/react": "^15.0.0",
|
|
55
|
+
"@types/parse-link-header": "^2.0.1",
|
|
56
|
+
"@types/react-dom": "^18.2.19",
|
|
57
|
+
"canvas": "^2.11.2",
|
|
58
|
+
"msw": "^1.2.3",
|
|
59
|
+
"react": "^16.13.1 || ^17.0.0 || ^18.0.0",
|
|
60
|
+
"react-dom": "^16.13.1 || ^17.0.0 || ^18.0.0",
|
|
61
|
+
"react-router-dom": "6.0.0-beta.0 || ^6.3.0"
|
|
62
|
+
},
|
|
63
|
+
"peerDependencies": {
|
|
64
|
+
"react": "^16.13.1 || ^17.0.0 || ^18.0.0",
|
|
65
|
+
"react-dom": "^16.13.1 || ^17.0.0 || ^18.0.0",
|
|
66
|
+
"react-router-dom": "6.0.0-beta.0 || ^6.3.0"
|
|
67
|
+
},
|
|
68
|
+
"module": "./dist/index.esm.js"
|
|
69
|
+
}
|