diff --git a/.changeset/modern-planets-fry.md b/.changeset/modern-planets-fry.md new file mode 100644 index 0000000000..2da39473a1 --- /dev/null +++ b/.changeset/modern-planets-fry.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-newrelic-dashboard': patch +--- + +Created a New Plugin , which imports dashboard links and snapshots from New Relic diff --git a/app-config.yaml b/app-config.yaml index 6cd05f44e8..2837d97d2f 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -74,6 +74,11 @@ proxy: headers: X-Api-Key: ${NEW_RELIC_REST_API_KEY} + '/newrelic/api': + target: https://api.newrelic.com + headers: + X-Api-Key: ${NEW_RELIC_USER_KEY} + '/pagerduty': target: https://api.pagerduty.com headers: diff --git a/packages/app/package.json b/packages/app/package.json index a9e8e8ddc8..1f609858f6 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -33,6 +33,7 @@ "@backstage/plugin-kubernetes": "^0.5.1", "@backstage/plugin-lighthouse": "^0.2.31", "@backstage/plugin-newrelic": "^0.3.10", + "@backstage/plugin-newrelic-dashboard": "^0.1.0", "@backstage/plugin-org": "^0.3.31", "@backstage/plugin-pagerduty": "0.3.19", "@backstage/plugin-rollbar": "^0.3.20", diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx index a7de64e89a..455cd188fb 100644 --- a/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/app/src/components/catalog/EntityPage.tsx @@ -124,6 +124,12 @@ import { EntityTravisCIOverviewCard, isTravisciAvailable, } from '@roadiehq/backstage-plugin-travis-ci'; +import { + isNewRelicDashboardAvailable, + EntityNewRelicDashboardContent, + EntityNewRelicDashboardCard, +} from '@backstage/plugin-newrelic-dashboard'; + import React, { ReactNode, useMemo, useState } from 'react'; const customEntityFilterKind = ['Component', 'API', 'System']; @@ -291,6 +297,14 @@ const overviewContent = ( + + + + + + + + @@ -371,6 +385,14 @@ const serviceEntityPage = ( + + + + @@ -429,6 +451,13 @@ const websiteEntityPage = ( + + + diff --git a/plugins/newrelic-dashboard/.eslintrc.js b/plugins/newrelic-dashboard/.eslintrc.js new file mode 100644 index 0000000000..13573efa9c --- /dev/null +++ b/plugins/newrelic-dashboard/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: [require.resolve('@backstage/cli/config/eslint')], +}; diff --git a/plugins/newrelic-dashboard/README.md b/plugins/newrelic-dashboard/README.md new file mode 100644 index 0000000000..a31042b3fe --- /dev/null +++ b/plugins/newrelic-dashboard/README.md @@ -0,0 +1,79 @@ +# New Relic Dashboard Plugin + +Welcome to the newrelic-dashboard plugin! + +## Features + +- Adds New Relic Dashboard Pages Links to Overview section of the catalog +- Shows Snapshots of dashboards in New Relic + +## Getting started + +This plugin uses the Backstage proxy to securely communicate with New Relic's APIs. We use NerdGraph (New Relic's GraphQL API) + +To generate a New Relic API Key , you can visit this [link](https://one.newrelic.com/launcher/api-keys-ui.api-keys-launcher) + +1. Add the following to your app-config.yaml to enable this configuration: + +``` +// app-config.yaml +proxy: + '/newrelic/api': + target: https://api.newrelic.com + headers: + X-Api-Key: ${NEW_RELIC_USER_KEY} +``` + +2. Add the following to `EntityPage.tsx` to display New Relic Dashboard Tab + +``` +// In packages/app/src/components/catalog/EntityPage.tsx +import { + isNewRelicDashboardAvailable, + EntityNewRelicDashboardContent, + EntityNewRelicDashboardCard, +} from '@backstage/plugin-newrelic-dashboard'; + +const serviceEntityPage = ( + + {/* other tabs... */} + + + +``` + +3. Add the following in `EntityPage.tsx` to display dashboard links in overview page + +``` +const overviewContent = ( + {/* other tabs... */} + + + + + + + +``` + +4. Add `newrelic.com/dashboard-guid` annotation in catalog descriptor file + +To Obtain the dashboard's GUID: Click the info icon by the dashboard's name to access the See metadata and manage tags modal and see the dashboard's GUID. + +``` +// catalog-info.yaml +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + # ... + annotations: + newrelic.com/dashboard-guid: +spec: + type: service +``` + +All set , you will be able to see the plugin in action! diff --git a/plugins/newrelic-dashboard/api-report.md b/plugins/newrelic-dashboard/api-report.md new file mode 100644 index 0000000000..ffdd5a2833 --- /dev/null +++ b/plugins/newrelic-dashboard/api-report.md @@ -0,0 +1,38 @@ +## API Report File for "@backstage/plugin-newrelic-dashboard" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +/// + +import { BackstagePlugin } from '@backstage/core-plugin-api'; +import { Entity } from '@backstage/catalog-model'; +import { RouteRef } from '@backstage/core-plugin-api'; + +// Warning: (ae-missing-release-tag) "EntityNewRelicDashboardCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const EntityNewRelicDashboardCard: () => JSX.Element; + +// Warning: (ae-missing-release-tag) "EntityNewRelicDashboardContent" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const EntityNewRelicDashboardContent: () => JSX.Element; + +// Warning: (ae-missing-release-tag) "isNewRelicDashboardAvailable" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const isNewRelicDashboardAvailable: (entity: Entity) => boolean; + +// Warning: (ae-missing-release-tag) "newRelicDashboardPlugin" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const newRelicDashboardPlugin: BackstagePlugin< + { + root: RouteRef; + }, + {} +>; + +// (No @packageDocumentation comment for this package) +``` diff --git a/plugins/newrelic-dashboard/dev/index.tsx b/plugins/newrelic-dashboard/dev/index.tsx new file mode 100644 index 0000000000..d0db8c4d96 --- /dev/null +++ b/plugins/newrelic-dashboard/dev/index.tsx @@ -0,0 +1,19 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 { newRelicDashboardPlugin } from './../src/plugin'; + +createDevApp().registerPlugin(newRelicDashboardPlugin).render(); diff --git a/plugins/newrelic-dashboard/package.json b/plugins/newrelic-dashboard/package.json new file mode 100644 index 0000000000..3da22d1c9e --- /dev/null +++ b/plugins/newrelic-dashboard/package.json @@ -0,0 +1,46 @@ +{ + "name": "@backstage/plugin-newrelic-dashboard", + "version": "0.1.0", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "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/catalog-model": "^0.9.8", + "@backstage/core-components": "^0.8.1", + "@backstage/core-plugin-api": "^0.3.1", + "@backstage/errors": "^0.1.5", + "@backstage/plugin-catalog-react": "^0.6.7", + "@material-ui/core": "^4.12.2", + "@material-ui/icons": "^4.9.1", + "@material-ui/lab": "4.0.0-alpha.57", + "react-use": "^17.2.4" + }, + "devDependencies": { + "@backstage/cli": "^0.10.2", + "@backstage/dev-utils": "^0.2.14", + "@testing-library/jest-dom": "^5.10.1", + "@types/react": "^16.13.1 || ^17.0.0", + "cross-fetch": "^3.0.6" + }, + "files": [ + "dist" + ], + "peerDependencies": { + "react": "^16.13.1 || ^17.0.0" + } +} diff --git a/plugins/newrelic-dashboard/src/Router.tsx b/plugins/newrelic-dashboard/src/Router.tsx new file mode 100644 index 0000000000..24a5a1037f --- /dev/null +++ b/plugins/newrelic-dashboard/src/Router.tsx @@ -0,0 +1,46 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 { Entity } from '@backstage/catalog-model'; +import React from 'react'; +import { MissingAnnotationEmptyState } from '@backstage/core-components'; +import { Button } from '@material-ui/core'; +import { NewRelicDashboard } from './components/NewRelicDashboard'; +import { useEntity } from '@backstage/plugin-catalog-react'; +import { NEWRELIC_GUID_ANNOTATION } from './constants'; + +export const isNewRelicDashboardAvailable = (entity: Entity) => + Boolean(entity?.metadata?.annotations?.[NEWRELIC_GUID_ANNOTATION]); + +export const Router = () => { + const { entity } = useEntity(); + + if (isNewRelicDashboardAvailable(entity)) { + return ; + } + + return ( + <> + + + + ); +}; diff --git a/plugins/newrelic-dashboard/src/api/NewRelicDashboardApi.ts b/plugins/newrelic-dashboard/src/api/NewRelicDashboardApi.ts new file mode 100644 index 0000000000..48c7a44c3a --- /dev/null +++ b/plugins/newrelic-dashboard/src/api/NewRelicDashboardApi.ts @@ -0,0 +1,42 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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-plugin-api'; +import { DashboardEntity } from '../types/DashboardEntity'; +import { DashboardSnapshot } from '../types/DashboardSnapshot'; +import { SnapshotDetails } from '../types/SnapshotDetails'; + +export interface DashboardEntitySummary { + getDashboardEntity: DashboardEntity; +} + +export interface DashboardSnapshotSummary { + getDashboardSnapshot: DashboardSnapshot; +} + +export interface SnapshotDetailsSummary { + getSnapshotDetails: SnapshotDetails[]; +} +export const newRelicDashboardApiRef = createApiRef({ + id: 'plugin.newrelicdashboard.service', +}); + +export type NewRelicDashboardApi = { + getDashboardEntity(guid: string): Promise; + getDashboardSnapshot( + guid: string, + duration: number, + ): Promise; +}; diff --git a/plugins/newrelic-dashboard/src/api/NewRelicDashboardClient.ts b/plugins/newrelic-dashboard/src/api/NewRelicDashboardClient.ts new file mode 100644 index 0000000000..b79cceeeda --- /dev/null +++ b/plugins/newrelic-dashboard/src/api/NewRelicDashboardClient.ts @@ -0,0 +1,99 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 { + DashboardEntitySummary, + DashboardSnapshotSummary, + NewRelicDashboardApi, +} from './NewRelicDashboardApi'; +import { DiscoveryApi } from '@backstage/core-plugin-api'; +import { DashboardEntity } from '../types/DashboardEntity'; +import { DashboardSnapshot } from '../types/DashboardSnapshot'; +import { getDashboardParentGuidQuery } from '../queries/getDashboardParentGuidQuery'; +import { getDashboardSnapshotQuery } from '../queries/getDashboardSnapshotQuery'; +import { ResponseError } from '@backstage/errors'; + +export class NewRelicDashboardClient implements NewRelicDashboardApi { + private readonly discoveryApi: DiscoveryApi; + constructor({ + discoveryApi, + }: { + discoveryApi: DiscoveryApi; + baseUrl?: string; + }) { + this.discoveryApi = discoveryApi; + } + + private async callApi( + query: string, + variables: { [key in string]: string | number }, + ): Promise { + const myHeaders = new Headers(); + myHeaders.append('Content-Type', 'application/json'); + const graphql = JSON.stringify({ + query: query, + variables: variables, + }); + const requestOptions: RequestInit = { + method: 'POST', + headers: myHeaders, + body: graphql, + redirect: 'follow', + }; + + const apiUrl = `${await this.discoveryApi.getBaseUrl( + 'proxy', + )}/newrelic/api/graphql`; + const response = await fetch(apiUrl, requestOptions); + if (response.status === 200) { + return (await response.json()) as T; + } + if (!response.ok) { + throw await ResponseError.fromResponse(response); + } else { + return undefined; + } + } + + async getDashboardEntity( + guid: string, + ): Promise { + const DashboardEntityList = await this.callApi( + getDashboardParentGuidQuery, + { + query: `parentId ='${guid}'`, + }, + ); + return { + getDashboardEntity: DashboardEntityList!, + }; + } + + async getDashboardSnapshot( + guid: string, + duration: number, + ): Promise { + const DashboardSnapshotValue = await this.callApi( + getDashboardSnapshotQuery, + { + guid: guid, + duration: duration, + }, + ); + return { + getDashboardSnapshot: DashboardSnapshotValue!, + }; + } +} diff --git a/plugins/newrelic-dashboard/src/api/index.ts b/plugins/newrelic-dashboard/src/api/index.ts new file mode 100644 index 0000000000..9b14160bcf --- /dev/null +++ b/plugins/newrelic-dashboard/src/api/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 type { NewRelicDashboardApi } from './NewRelicDashboardApi'; +export { newRelicDashboardApiRef } from './NewRelicDashboardApi'; +export { NewRelicDashboardClient } from './NewRelicDashboardClient'; diff --git a/plugins/newrelic-dashboard/src/components/NewRelicDashboard/DashboardEntityList.tsx b/plugins/newrelic-dashboard/src/components/NewRelicDashboard/DashboardEntityList.tsx new file mode 100644 index 0000000000..005bc42151 --- /dev/null +++ b/plugins/newrelic-dashboard/src/components/NewRelicDashboard/DashboardEntityList.tsx @@ -0,0 +1,89 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 { Box, makeStyles, Typography } from '@material-ui/core'; +import { newRelicDashboardApiRef } from '../../api'; +import { useApi } from '@backstage/core-plugin-api'; +import { useAsync } from 'react-use'; +import { + Progress, + InfoCard, + Link, + ErrorPanel, +} from '@backstage/core-components'; +import DesktopMac from '@material-ui/icons/DesktopMac'; +import { DashboardEntitySummary } from '../../api/NewRelicDashboardApi'; +import { ResultEntity } from '../../types/DashboardEntity'; +import { useEntity } from '@backstage/plugin-catalog-react'; +import { NEWRELIC_GUID_ANNOTATION } from './../../constants'; + +const useStyles = makeStyles({ + svgIcon: { + display: 'inline-block', + '& svg': { + display: 'inline-block', + fontSize: 'inherit', + verticalAlign: 'baseline', + }, + }, +}); +export const DashboardEntityList = () => { + const { entity } = useEntity(); + const classes = useStyles(); + const newRelicDashboardAPI = useApi(newRelicDashboardApiRef); + const { value, loading, error } = useAsync(async (): Promise< + DashboardEntitySummary | undefined + > => { + const dashboardObject: Promise = + newRelicDashboardAPI.getDashboardEntity( + String(entity.metadata.annotations?.[NEWRELIC_GUID_ANNOTATION]), + ); + return dashboardObject; + }, [entity.metadata.annotations?.[NEWRELIC_GUID_ANNOTATION]]); + if (loading) { + return ; + } + if (error) { + return ; + } + return ( + + {value?.getDashboardEntity === undefined && + 'Unauthorized Request , please check API Key'} + {value?.getDashboardEntity !== undefined && + value?.getDashboardEntity?.data.actor.entitySearch.results?.entities + ?.length <= 0 && ( + <>No Dashboard Pages found with the specified Dashboard GUID + )} + {value?.getDashboardEntity?.data.actor.entitySearch.results.entities?.map( + (entityResult: ResultEntity) => { + return ( + + + + + + + + {entityResult.name} + + + ); + }, + )} + + ); +}; diff --git a/plugins/newrelic-dashboard/src/components/NewRelicDashboard/DashboardSnapshotList/DashboardSnapshot.tsx b/plugins/newrelic-dashboard/src/components/NewRelicDashboard/DashboardSnapshotList/DashboardSnapshot.tsx new file mode 100644 index 0000000000..95de46df2c --- /dev/null +++ b/plugins/newrelic-dashboard/src/components/NewRelicDashboard/DashboardSnapshotList/DashboardSnapshot.tsx @@ -0,0 +1,82 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 { Box, Grid } from '@material-ui/core'; +import { useApi } from '@backstage/core-plugin-api'; +import { useAsync } from 'react-use'; +import { + InfoCard, + Progress, + ErrorPanel, + Link, +} from '@backstage/core-components'; +import { newRelicDashboardApiRef } from '../../../api'; +import { DashboardSnapshotSummary } from './../../../api/NewRelicDashboardApi'; + +type Props = { + guid: string; + name: string; + permalink: string; + duration: number; +}; + +export const DashboardSnapshot = ({ + guid, + name, + permalink, + duration, +}: Props) => { + const newRelicDashboardAPI = useApi(newRelicDashboardApiRef); + const { value, loading, error } = useAsync(async (): Promise< + DashboardSnapshotSummary | undefined + > => { + const dashboardObject: Promise = + newRelicDashboardAPI.getDashboardSnapshot(guid, duration); + return dashboardObject; + }, [guid, duration]); + if (loading) { + return ; + } + if (error) { + return ; + } + const url = + value?.getDashboardSnapshot?.data?.dashboardCreateSnapshotUrl?.replace( + /\pdf$/i, + 'png', + ); + return ( + + + + + + {url ? ( + {`${name} + ) : ( + 'Dashboard loading... , click here to open if it didnt render correctly' + )} + + + + + + ); +}; diff --git a/plugins/newrelic-dashboard/src/components/NewRelicDashboard/DashboardSnapshotList/DashboardSnapshotList.tsx b/plugins/newrelic-dashboard/src/components/NewRelicDashboard/DashboardSnapshotList/DashboardSnapshotList.tsx new file mode 100644 index 0000000000..c839a43ac2 --- /dev/null +++ b/plugins/newrelic-dashboard/src/components/NewRelicDashboard/DashboardSnapshotList/DashboardSnapshotList.tsx @@ -0,0 +1,148 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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, { useState } from 'react'; +import { Grid, Tab, Tabs, makeStyles } from '@material-ui/core'; +import { newRelicDashboardApiRef } from '../../../api'; +import { useApi } from '@backstage/core-plugin-api'; +import { useAsync } from 'react-use'; +import { Progress, ErrorPanel } from '@backstage/core-components'; +import { DashboardSnapshot } from './DashboardSnapshot'; +import { DashboardEntitySummary } from '../../../api/NewRelicDashboardApi'; +import { ResultEntity } from '../../../types/DashboardEntity'; + +interface TabPanelProps { + children?: React.ReactNode; + index: number; + value1: number; +} + +function TabPanel(props: TabPanelProps) { + const { children, value1, index, ...other } = props; + + return ( + + ); +} +function a11yProps(index: number) { + return { + id: `simple-tab-${index}`, + 'aria-controls': `simple-tabpanel-${index}`, + }; +} +type Props = { + guid: string; +}; +const useStyles = makeStyles( + theme => ({ + tabsWrapper: { + gridArea: 'pageSubheader', + backgroundColor: theme.palette.background.paper, + paddingLeft: theme.spacing(3), + }, + defaultTab: { + padding: theme.spacing(3, 3), + ...theme.typography.caption, + textTransform: 'uppercase', + fontWeight: 'bold', + color: theme.palette.text.secondary, + }, + selected: { + color: theme.palette.text.primary, + }, + tabRoot: { + '&:hover': { + backgroundColor: theme.palette.background.default, + color: theme.palette.text.primary, + }, + }, + }), + { name: 'DashboardHeaderTabs' }, +); +export const DashboardSnapshotList = ({ guid }: Props) => { + const styles = useStyles(); + const newRelicDashboardAPI = useApi(newRelicDashboardApiRef); + const { value, loading, error } = useAsync(async (): Promise< + DashboardEntitySummary | undefined + > => { + const dashboardObject: Promise = + newRelicDashboardAPI.getDashboardEntity(guid); + return dashboardObject; + }, [guid]); + const [value1, setValue1] = useState(0); + const handleChange = ({}: React.ChangeEvent<{}>, newValue: number) => { + setValue1(newValue); + }; + + if (loading) { + return ; + } + if (error) { + return ; + } + return ( + + + {value?.getDashboardEntity?.data?.actor.entitySearch.results.entities?.map( + (Entity: ResultEntity, index: number) => { + return ( + + ); + }, + )} + + {value?.getDashboardEntity?.data?.actor.entitySearch.results.entities?.map( + (Entity: ResultEntity, index: number) => { + return ( + + + + ); + }, + )} + + ); +}; diff --git a/plugins/newrelic-dashboard/src/components/NewRelicDashboard/DashboardSnapshotList/index.ts b/plugins/newrelic-dashboard/src/components/NewRelicDashboard/DashboardSnapshotList/index.ts new file mode 100644 index 0000000000..6b17582515 --- /dev/null +++ b/plugins/newrelic-dashboard/src/components/NewRelicDashboard/DashboardSnapshotList/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 { DashboardSnapshot } from './DashboardSnapshot'; +export { DashboardSnapshotList } from './DashboardSnapshotList'; diff --git a/plugins/newrelic-dashboard/src/components/NewRelicDashboard/NewRelicDashboard.tsx b/plugins/newrelic-dashboard/src/components/NewRelicDashboard/NewRelicDashboard.tsx new file mode 100644 index 0000000000..c9cc9bf611 --- /dev/null +++ b/plugins/newrelic-dashboard/src/components/NewRelicDashboard/NewRelicDashboard.tsx @@ -0,0 +1,44 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 { Grid } from '@material-ui/core'; +import { Page, Content } from '@backstage/core-components'; +import { DashboardEntityList } from './DashboardEntityList'; +import { DashboardSnapshotList } from './DashboardSnapshotList'; +import { useEntity } from '@backstage/plugin-catalog-react'; +import { NEWRELIC_GUID_ANNOTATION } from '../../constants'; + +export const NewRelicDashboard = () => { + const { entity } = useEntity(); + return ( + + + + + + + + + + + + + ); +}; diff --git a/plugins/newrelic-dashboard/src/components/NewRelicDashboard/index.ts b/plugins/newrelic-dashboard/src/components/NewRelicDashboard/index.ts new file mode 100644 index 0000000000..668d76a059 --- /dev/null +++ b/plugins/newrelic-dashboard/src/components/NewRelicDashboard/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 { NewRelicDashboard } from './NewRelicDashboard'; diff --git a/plugins/newrelic-dashboard/src/constants.ts b/plugins/newrelic-dashboard/src/constants.ts new file mode 100644 index 0000000000..dd0dcff2d8 --- /dev/null +++ b/plugins/newrelic-dashboard/src/constants.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 const NEWRELIC_GUID_ANNOTATION = 'newrelic.com/dashboard-guid'; diff --git a/plugins/newrelic-dashboard/src/index.ts b/plugins/newrelic-dashboard/src/index.ts new file mode 100644 index 0000000000..821ccd4bda --- /dev/null +++ b/plugins/newrelic-dashboard/src/index.ts @@ -0,0 +1,21 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 { + newRelicDashboardPlugin, + EntityNewRelicDashboardCard, + EntityNewRelicDashboardContent, +} from './plugin'; +export { isNewRelicDashboardAvailable } from './Router'; diff --git a/plugins/newrelic-dashboard/src/plugin.test.ts b/plugins/newrelic-dashboard/src/plugin.test.ts new file mode 100644 index 0000000000..e5eccd930b --- /dev/null +++ b/plugins/newrelic-dashboard/src/plugin.test.ts @@ -0,0 +1,22 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 { newRelicDashboardPlugin } from './plugin'; + +describe('new-relic-dashboard', () => { + it('should export plugin', () => { + expect(newRelicDashboardPlugin).toBeDefined(); + }); +}); diff --git a/plugins/newrelic-dashboard/src/plugin.ts b/plugins/newrelic-dashboard/src/plugin.ts new file mode 100644 index 0000000000..18397902f7 --- /dev/null +++ b/plugins/newrelic-dashboard/src/plugin.ts @@ -0,0 +1,62 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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, + configApiRef, + createApiFactory, + discoveryApiRef, + createComponentExtension, +} from '@backstage/core-plugin-api'; +import { newRelicDashboardApiRef, NewRelicDashboardClient } from './api'; +import { rootRouteRef } from './routes'; + +export const newRelicDashboardPlugin = createPlugin({ + id: 'new-relic-dashboard', + routes: { + root: rootRouteRef, + }, + apis: [ + createApiFactory({ + api: newRelicDashboardApiRef, + deps: { configApi: configApiRef, discoveryApi: discoveryApiRef }, + factory: ({ configApi, discoveryApi }) => + new NewRelicDashboardClient({ + discoveryApi, + baseUrl: configApi.getOptionalString('newrelicdashboard.baseUrl'), + }), + }), + ], +}); +export const EntityNewRelicDashboardContent = newRelicDashboardPlugin.provide( + createComponentExtension({ + name: 'EntityNewRelicDashboardPage', + component: { + lazy: () => import('./Router').then(m => m.Router), + }, + }), +); + +export const EntityNewRelicDashboardCard = newRelicDashboardPlugin.provide( + createComponentExtension({ + name: 'EntityNewRelicDashboardListComponent', + component: { + lazy: () => + import('./components/NewRelicDashboard/DashboardEntityList').then( + m => m.DashboardEntityList, + ), + }, + }), +); diff --git a/plugins/newrelic-dashboard/src/queries/getDashboardParentGuidQuery.ts b/plugins/newrelic-dashboard/src/queries/getDashboardParentGuidQuery.ts new file mode 100644 index 0000000000..3a06384539 --- /dev/null +++ b/plugins/newrelic-dashboard/src/queries/getDashboardParentGuidQuery.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 const getDashboardParentGuidQuery = + 'query ($query: String) {\n actor {\n entitySearch(query: $query) {\n results {\n entities {\n name\n ... on DashboardEntityOutline {\n name\n guid\n }\n permalink\n }\n }\n }\n }\n}\n'; diff --git a/plugins/newrelic-dashboard/src/queries/getDashboardSnapshotQuery.ts b/plugins/newrelic-dashboard/src/queries/getDashboardSnapshotQuery.ts new file mode 100644 index 0000000000..be683ecd7b --- /dev/null +++ b/plugins/newrelic-dashboard/src/queries/getDashboardSnapshotQuery.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 const getDashboardSnapshotQuery = + 'mutation($guid: EntityGuid! , ,$duration: Milliseconds) {\n dashboardCreateSnapshotUrl(guid: $guid , params: {timeWindow: {duration: $duration}})\n}'; diff --git a/plugins/newrelic-dashboard/src/routes.ts b/plugins/newrelic-dashboard/src/routes.ts new file mode 100644 index 0000000000..5bc8b24457 --- /dev/null +++ b/plugins/newrelic-dashboard/src/routes.ts @@ -0,0 +1,20 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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-plugin-api'; + +export const rootRouteRef = createRouteRef({ + id: 'new-relic-dashboard', +}); diff --git a/plugins/newrelic-dashboard/src/setupTests.ts b/plugins/newrelic-dashboard/src/setupTests.ts new file mode 100644 index 0000000000..fc6dbd98f8 --- /dev/null +++ b/plugins/newrelic-dashboard/src/setupTests.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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'; +import 'cross-fetch/polyfill'; diff --git a/plugins/newrelic-dashboard/src/types/DashboardEntity.ts b/plugins/newrelic-dashboard/src/types/DashboardEntity.ts new file mode 100644 index 0000000000..78a1b7913d --- /dev/null +++ b/plugins/newrelic-dashboard/src/types/DashboardEntity.ts @@ -0,0 +1,33 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 type DashboardEntity = { + data: { + actor: { + entitySearch: { + results: { + entities: Array; + }; + }; + }; + }; +}; + +export type ResultEntity = { + guid: string; + permalink: string; + name: string; +}; diff --git a/plugins/newrelic-dashboard/src/types/DashboardSnapshot.ts b/plugins/newrelic-dashboard/src/types/DashboardSnapshot.ts new file mode 100644 index 0000000000..6c11c6f6d7 --- /dev/null +++ b/plugins/newrelic-dashboard/src/types/DashboardSnapshot.ts @@ -0,0 +1,21 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 type DashboardSnapshot = { + data: { + dashboardCreateSnapshotUrl: string; + }; +}; diff --git a/plugins/newrelic-dashboard/src/types/SnapshotDetails.ts b/plugins/newrelic-dashboard/src/types/SnapshotDetails.ts new file mode 100644 index 0000000000..e189ac52c4 --- /dev/null +++ b/plugins/newrelic-dashboard/src/types/SnapshotDetails.ts @@ -0,0 +1,20 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 type SnapshotDetails = { + dashboardSnapshotUrl: string; + permalink: string; +};