@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/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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"}
@@ -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,4 @@
1
+ export { N as NewRelicPage, a as newRelicPlugin, a as plugin } from './esm/index-DhuyBTi3.esm.js';
2
+ import '@backstage/core-plugin-api';
3
+ import 'parse-link-header';
4
+ //# sourceMappingURL=index.esm.js.map
@@ -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
+ }