From 135df6accc042471807536659e79cec942212fb8 Mon Sep 17 00:00:00 2001 From: Karan Shah Date: Mon, 10 Jan 2022 10:52:02 +0000 Subject: [PATCH 001/344] Setup the API code Signed-off-by: Karan Shah --- plugins/airbrake/src/api/airbrake-api.ts | 26 ++++++ plugins/airbrake/src/api/airbrake-groups.d.ts | 65 ++++++++++++++ plugins/airbrake/src/api/index.ts | 21 +++++ .../api/mock/airbrake-groups-api-mock.json | 85 +++++++++++++++++++ plugins/airbrake/src/api/mock/index.ts | 17 ++++ plugins/airbrake/src/api/mock/mock-api.ts | 27 ++++++ plugins/airbrake/src/api/production-api.ts | 37 ++++++++ 7 files changed, 278 insertions(+) create mode 100644 plugins/airbrake/src/api/airbrake-api.ts create mode 100644 plugins/airbrake/src/api/airbrake-groups.d.ts create mode 100644 plugins/airbrake/src/api/index.ts create mode 100644 plugins/airbrake/src/api/mock/airbrake-groups-api-mock.json create mode 100644 plugins/airbrake/src/api/mock/index.ts create mode 100644 plugins/airbrake/src/api/mock/mock-api.ts create mode 100644 plugins/airbrake/src/api/production-api.ts diff --git a/plugins/airbrake/src/api/airbrake-api.ts b/plugins/airbrake/src/api/airbrake-api.ts new file mode 100644 index 0000000000..1a3cfa72e4 --- /dev/null +++ b/plugins/airbrake/src/api/airbrake-api.ts @@ -0,0 +1,26 @@ +/* + * Copyright 2020 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 { Groups } from './airbrake-groups'; +import { createApiRef } from '@backstage/core-plugin-api'; + +export const airbrakeApiRef = createApiRef({ + id: 'plugin.airbrake.service', +}); + +export interface AirbrakeApi { + fetchGroups(project: string): Promise; +} diff --git a/plugins/airbrake/src/api/airbrake-groups.d.ts b/plugins/airbrake/src/api/airbrake-groups.d.ts new file mode 100644 index 0000000000..df4c5de120 --- /dev/null +++ b/plugins/airbrake/src/api/airbrake-groups.d.ts @@ -0,0 +1,65 @@ +/* + * Copyright 2022 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 interface Groups { + count: number; + end: string; + groups?: Group[] | null; + page: number; + resolvedCount: number; + unresolvedCount: number; +} + +export interface Group { + id: string; + projectId: number; + resolved: boolean; + muted: boolean; + mutedBy: number; + mutedAt?: null; + errors?: Error[] | null; + attributes?: null; + context: Context; + lastDeployId: string; + lastDeployAt?: null; + lastNoticeId: string; + lastNoticeAt: string; + noticeCount: number; + noticeTotalCount: number; + commentCount: number; + createdAt: string; +} + +export interface Error { + type: string; + message: string; + backtrace?: Backtrace[] | null; +} + +export interface Backtrace { + file: string; + function: string; + line: number; + column: number; + code?: null; +} + +export interface Context { + action: string; + component: string; + environment: string; + severity: string; +} diff --git a/plugins/airbrake/src/api/index.ts b/plugins/airbrake/src/api/index.ts new file mode 100644 index 0000000000..7bad94e298 --- /dev/null +++ b/plugins/airbrake/src/api/index.ts @@ -0,0 +1,21 @@ +/* + * Copyright 2020 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 * from './mock'; +export type { AirbrakeApi } from './airbrake-api'; +export { airbrakeApiRef } from './airbrake-api'; +export type { Groups } from './airbrake-groups'; +export { ProductionAirbrakeApi } from './production-api'; diff --git a/plugins/airbrake/src/api/mock/airbrake-groups-api-mock.json b/plugins/airbrake/src/api/mock/airbrake-groups-api-mock.json new file mode 100644 index 0000000000..fa654ec50b --- /dev/null +++ b/plugins/airbrake/src/api/mock/airbrake-groups-api-mock.json @@ -0,0 +1,85 @@ +{ + "count": 2, + "end": "", + "groups": [ + { + "id": "1", + "projectId": 123, + "resolved": false, + "muted": false, + "mutedBy": 0, + "mutedAt": null, + "errors": [ + { + "type": "Error", + "message": "useSearch must be used within a SearchContextProvider", + "backtrace": [ + { + "file": "webpack-internal:///../../node_modules/@backstage/plugin-search/dist/esm/index-893ec2f5.esm.js", + "function": "useSearch", + "line": 303, + "column": 11, + "code": null + } + ] + } + ], + "attributes": null, + "context": { + "action": "", + "component": "", + "environment": "local", + "severity": "error" + }, + "lastDeployId": "0", + "lastDeployAt": null, + "lastNoticeId": "234", + "lastNoticeAt": "2021-12-19T09:59:00.124Z", + "noticeCount": 5, + "noticeTotalCount": 5, + "commentCount": 0, + "createdAt": "2021-12-19T09:44:30.067447Z" + }, + { + "id": "2", + "projectId": 123, + "resolved": true, + "muted": false, + "mutedBy": 0, + "mutedAt": null, + "errors": [ + { + "type": "ChunkLoadError", + "message": "Loading chunk 7764 failed.", + "backtrace": [ + { + "file": "/PROJECT_ROOT/static/runtime.069c874d.js", + "function": "Object._.f.j", + "line": 1, + "column": 19465, + "code": null + } + ] + } + ], + "attributes": null, + "context": { + "action": "", + "component": "", + "environment": "local", + "severity": "error" + }, + "lastDeployId": "0", + "lastDeployAt": null, + "lastNoticeId": "345", + "lastNoticeAt": "2021-12-15T17:16:38.419Z", + "noticeCount": 1, + "noticeTotalCount": 1, + "commentCount": 0, + "createdAt": "2021-12-15T17:16:38.41983Z" + } + ], + "page": 1, + "resolvedCount": 1, + "unresolvedCount": 1 +} diff --git a/plugins/airbrake/src/api/mock/index.ts b/plugins/airbrake/src/api/mock/index.ts new file mode 100644 index 0000000000..f084268951 --- /dev/null +++ b/plugins/airbrake/src/api/mock/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2020 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 { MockAirbrakeApi } from './mock-api'; diff --git a/plugins/airbrake/src/api/mock/mock-api.ts b/plugins/airbrake/src/api/mock/mock-api.ts new file mode 100644 index 0000000000..df0293da7c --- /dev/null +++ b/plugins/airbrake/src/api/mock/mock-api.ts @@ -0,0 +1,27 @@ +/* + * Copyright 2020 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 { Groups } from '../airbrake-groups'; +import { AirbrakeApi } from '../airbrake-api'; +import mockGroupsData from './airbrake-groups-api-mock.json'; + +export class MockAirbrakeApi implements AirbrakeApi { + fetchGroups(): Promise { + return new Promise(resolve => { + setTimeout(() => resolve(mockGroupsData), 800); + }); + } +} diff --git a/plugins/airbrake/src/api/production-api.ts b/plugins/airbrake/src/api/production-api.ts new file mode 100644 index 0000000000..37ac938752 --- /dev/null +++ b/plugins/airbrake/src/api/production-api.ts @@ -0,0 +1,37 @@ +/* + * Copyright 2020 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 { Groups } from './airbrake-groups'; +import { AirbrakeApi } from './airbrake-api'; +import { DiscoveryApi } from '@backstage/core-plugin-api'; + +export class ProductionAirbrakeApi implements AirbrakeApi { + constructor(private readonly discoveryApi: DiscoveryApi) {} + + async fetchGroups(project: string): Promise { + const apiUrl = `${await this.discoveryApi.getBaseUrl( + 'proxy', + )}/airbrake/api`; + + const response = await fetch(`${apiUrl}/v4/projects/${project}/groups`); + + if (response.status >= 400 && response.status < 600) { + throw new Error('Failed fetching Airbrake issues'); + } + + return (await response.json()) as Groups; + } +} From f3d7baaefa248681c8db3e0829f95cf02af40934 Mon Sep 17 00:00:00 2001 From: Karan Shah Date: Mon, 10 Jan 2022 11:49:23 +0000 Subject: [PATCH 002/344] Connect the API code to the components Signed-off-by: Karan Shah --- plugins/airbrake/package.json | 1 + plugins/airbrake/src/api/airbrake-groups.d.ts | 12 +-- .../EntityAirbrakeContent.tsx | 65 ++++++++++++-- .../EntityAirbrakeContent/example-data.json | 85 ------------------- .../airbrake/src/components/useProjectSlug.ts | 23 +++++ 5 files changed, 87 insertions(+), 99 deletions(-) delete mode 100644 plugins/airbrake/src/components/EntityAirbrakeContent/example-data.json create mode 100644 plugins/airbrake/src/components/useProjectSlug.ts diff --git a/plugins/airbrake/package.json b/plugins/airbrake/package.json index 7294c97413..7f96f0bc61 100644 --- a/plugins/airbrake/package.json +++ b/plugins/airbrake/package.json @@ -20,6 +20,7 @@ "clean": "backstage-cli clean" }, "dependencies": { + "@backstage/catalog-model": "^0.9.7", "@backstage/core-components": "^0.8.3", "@backstage/core-plugin-api": "^0.4.1", "@backstage/theme": "^0.2.14", diff --git a/plugins/airbrake/src/api/airbrake-groups.d.ts b/plugins/airbrake/src/api/airbrake-groups.d.ts index df4c5de120..e1887eca9b 100644 --- a/plugins/airbrake/src/api/airbrake-groups.d.ts +++ b/plugins/airbrake/src/api/airbrake-groups.d.ts @@ -29,12 +29,12 @@ export interface Group { resolved: boolean; muted: boolean; mutedBy: number; - mutedAt?: null; - errors?: Error[] | null; - attributes?: null; + mutedAt?: string | null; + errors?: Error[]; + attributes?: string | null; context: Context; lastDeployId: string; - lastDeployAt?: null; + lastDeployAt?: string | null; lastNoticeId: string; lastNoticeAt: string; noticeCount: number; @@ -46,7 +46,7 @@ export interface Group { export interface Error { type: string; message: string; - backtrace?: Backtrace[] | null; + backtrace?: Backtrace[]; } export interface Backtrace { @@ -54,7 +54,7 @@ export interface Backtrace { function: string; line: number; column: number; - code?: null; + code?: string | null; } export interface Context { diff --git a/plugins/airbrake/src/components/EntityAirbrakeContent/EntityAirbrakeContent.tsx b/plugins/airbrake/src/components/EntityAirbrakeContent/EntityAirbrakeContent.tsx index 062d9e898a..2bd3917443 100644 --- a/plugins/airbrake/src/components/EntityAirbrakeContent/EntityAirbrakeContent.tsx +++ b/plugins/airbrake/src/components/EntityAirbrakeContent/EntityAirbrakeContent.tsx @@ -13,13 +13,25 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; +import { Entity } from '@backstage/catalog-model'; +import React, { useEffect } from 'react'; import { Grid, Typography } from '@material-ui/core'; -import { InfoCard } from '@backstage/core-components'; -import exampleData from './example-data.json'; +import { + EmptyState, + InfoCard, + MissingAnnotationEmptyState, + Progress, +} from '@backstage/core-components'; import hash from 'object-hash'; import { makeStyles } from '@material-ui/core/styles'; import { BackstageTheme } from '@backstage/theme'; +import { ErrorApi, errorApiRef, useApi } from '@backstage/core-plugin-api'; +import { airbrakeApiRef } from '../../api'; +import useAsync from 'react-use/lib/useAsync'; +import { + AIRBRAKE_PROJECT_SLUG_ANNOTATION, + useProjectSlug, +} from '../useProjectSlug'; const useStyles = makeStyles(() => ({ multilineText: { @@ -27,17 +39,54 @@ const useStyles = makeStyles(() => ({ }, })); -export const EntityAirbrakeContent = () => { +export const EntityAirbrakeContent = ({ entity }: { entity: Entity }) => { const classes = useStyles(); + const projectId = useProjectSlug(entity); + const errorApi = useApi(errorApiRef); + const airbrakeApi = useApi(airbrakeApiRef); + + const { loading, value, error } = useAsync( + () => airbrakeApi.fetchGroups(projectId), + [projectId], + ); + + useEffect(() => { + if (error) { + errorApi.post(error); + } + }, [error, errorApi]); + + if (loading || !projectId || error) { + return ( + + {loading && } + + {!loading && !projectId && ( + + )} + + {!loading && error && ( + + )} + + ); + } + return ( - {exampleData.groups.map(group => ( + {value?.groups?.map(group => ( - {group.errors.map(error => ( - + {group.errors?.map(groupError => ( + - {error.message} + {groupError.message} ))} diff --git a/plugins/airbrake/src/components/EntityAirbrakeContent/example-data.json b/plugins/airbrake/src/components/EntityAirbrakeContent/example-data.json deleted file mode 100644 index fa654ec50b..0000000000 --- a/plugins/airbrake/src/components/EntityAirbrakeContent/example-data.json +++ /dev/null @@ -1,85 +0,0 @@ -{ - "count": 2, - "end": "", - "groups": [ - { - "id": "1", - "projectId": 123, - "resolved": false, - "muted": false, - "mutedBy": 0, - "mutedAt": null, - "errors": [ - { - "type": "Error", - "message": "useSearch must be used within a SearchContextProvider", - "backtrace": [ - { - "file": "webpack-internal:///../../node_modules/@backstage/plugin-search/dist/esm/index-893ec2f5.esm.js", - "function": "useSearch", - "line": 303, - "column": 11, - "code": null - } - ] - } - ], - "attributes": null, - "context": { - "action": "", - "component": "", - "environment": "local", - "severity": "error" - }, - "lastDeployId": "0", - "lastDeployAt": null, - "lastNoticeId": "234", - "lastNoticeAt": "2021-12-19T09:59:00.124Z", - "noticeCount": 5, - "noticeTotalCount": 5, - "commentCount": 0, - "createdAt": "2021-12-19T09:44:30.067447Z" - }, - { - "id": "2", - "projectId": 123, - "resolved": true, - "muted": false, - "mutedBy": 0, - "mutedAt": null, - "errors": [ - { - "type": "ChunkLoadError", - "message": "Loading chunk 7764 failed.", - "backtrace": [ - { - "file": "/PROJECT_ROOT/static/runtime.069c874d.js", - "function": "Object._.f.j", - "line": 1, - "column": 19465, - "code": null - } - ] - } - ], - "attributes": null, - "context": { - "action": "", - "component": "", - "environment": "local", - "severity": "error" - }, - "lastDeployId": "0", - "lastDeployAt": null, - "lastNoticeId": "345", - "lastNoticeAt": "2021-12-15T17:16:38.419Z", - "noticeCount": 1, - "noticeTotalCount": 1, - "commentCount": 0, - "createdAt": "2021-12-15T17:16:38.41983Z" - } - ], - "page": 1, - "resolvedCount": 1, - "unresolvedCount": 1 -} diff --git a/plugins/airbrake/src/components/useProjectSlug.ts b/plugins/airbrake/src/components/useProjectSlug.ts new file mode 100644 index 0000000000..4c6337b49e --- /dev/null +++ b/plugins/airbrake/src/components/useProjectSlug.ts @@ -0,0 +1,23 @@ +/* + * Copyright 2020 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'; + +export const AIRBRAKE_PROJECT_SLUG_ANNOTATION = 'airbrake.io/project-slug'; + +export const useProjectSlug = (entity: Entity) => { + return entity?.metadata.annotations?.[AIRBRAKE_PROJECT_SLUG_ANNOTATION] ?? ''; +}; From b7e049377029e2fb699d1d2c9fd6be111e7bb89a Mon Sep 17 00:00:00 2001 From: Karan Shah Date: Mon, 10 Jan 2022 13:51:33 +0000 Subject: [PATCH 003/344] Documentation should just point to the plugin folder, not the README file directly. Signed-off-by: Karan Shah --- microsite/data/plugins/airbrake.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/microsite/data/plugins/airbrake.yaml b/microsite/data/plugins/airbrake.yaml index 96de75c002..29c1f394d7 100644 --- a/microsite/data/plugins/airbrake.yaml +++ b/microsite/data/plugins/airbrake.yaml @@ -4,6 +4,6 @@ author: Simply Business authorUrl: https://github.com/simplybusiness/ category: Monitoring description: Access Airbrake error monitoring and other integrations from within Backstage -documentation: https://github.com/backstage/backstage/blob/master/plugins/airbrake/README.md +documentation: https://github.com/backstage/backstage/blob/master/plugins/airbrake iconUrl: https://wp-assets.airbrake.io/wp-content/uploads/2020/10/05222904/Square-white-A-on-Orange.png npmPackageName: '@backstage/plugin-airbrake' From d16c977381980614e529a65a28bc6b5dc5a2e352 Mon Sep 17 00:00:00 2001 From: Karan Shah Date: Mon, 10 Jan 2022 16:27:46 +0000 Subject: [PATCH 004/344] Incorporate the API changes to the rest of the code. Signed-off-by: Karan Shah --- plugins/airbrake/dev/index.tsx | 2 +- plugins/airbrake/package.json | 1 + .../EntityAirbrakeWidget.test.tsx} | 22 +++++++++-- .../EntityAirbrakeWidget.tsx} | 2 +- .../components/EntityAirbrakeWidget/index.ts | 17 +++++++++ ...keContent.test.tsx => extensions.test.tsx} | 2 +- plugins/airbrake/src/extensions.tsx | 38 +++++++++++++++++++ plugins/airbrake/src/index.ts | 3 +- plugins/airbrake/src/plugin.ts | 28 ++++++++------ 9 files changed, 96 insertions(+), 19 deletions(-) rename plugins/airbrake/src/components/{EntityAirbrakeContent/EntityAirbrakeContent.test.tsx => EntityAirbrakeWidget/EntityAirbrakeWidget.test.tsx} (61%) rename plugins/airbrake/src/components/{EntityAirbrakeContent/EntityAirbrakeContent.tsx => EntityAirbrakeWidget/EntityAirbrakeWidget.tsx} (97%) create mode 100644 plugins/airbrake/src/components/EntityAirbrakeWidget/index.ts rename plugins/airbrake/src/{EntityAirbrakeContent.test.tsx => extensions.test.tsx} (94%) create mode 100644 plugins/airbrake/src/extensions.tsx diff --git a/plugins/airbrake/dev/index.tsx b/plugins/airbrake/dev/index.tsx index e74ce68ee5..4c52e27094 100644 --- a/plugins/airbrake/dev/index.tsx +++ b/plugins/airbrake/dev/index.tsx @@ -15,7 +15,7 @@ */ import React from 'react'; import { createDevApp } from '@backstage/dev-utils'; -import { EntityAirbrakeContent, airbrakePlugin } from '../src/plugin'; +import { EntityAirbrakeContent, airbrakePlugin } from '../src'; import { Content, ContentHeader, diff --git a/plugins/airbrake/package.json b/plugins/airbrake/package.json index 7f96f0bc61..5ebfae651f 100644 --- a/plugins/airbrake/package.json +++ b/plugins/airbrake/package.json @@ -23,6 +23,7 @@ "@backstage/catalog-model": "^0.9.7", "@backstage/core-components": "^0.8.3", "@backstage/core-plugin-api": "^0.4.1", + "@backstage/plugin-catalog-react": "^0.6.10", "@backstage/theme": "^0.2.14", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", diff --git a/plugins/airbrake/src/components/EntityAirbrakeContent/EntityAirbrakeContent.test.tsx b/plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.test.tsx similarity index 61% rename from plugins/airbrake/src/components/EntityAirbrakeContent/EntityAirbrakeContent.test.tsx rename to plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.test.tsx index a621eb281c..d93c642e6c 100644 --- a/plugins/airbrake/src/components/EntityAirbrakeContent/EntityAirbrakeContent.test.tsx +++ b/plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.test.tsx @@ -15,13 +15,29 @@ */ import React from 'react'; -import { EntityAirbrakeContent } from './EntityAirbrakeContent'; -import exampleData from './example-data.json'; +import { EntityAirbrakeWidget } from './EntityAirbrakeWidget'; +import exampleData from '../../api/mock/airbrake-groups-api-mock.json'; import { renderInTestApp } from '@backstage/test-utils'; +import { AIRBRAKE_PROJECT_SLUG_ANNOTATION } from '../useProjectSlug'; +import { Entity } from '@backstage/catalog-model'; describe('EntityAirbrakeContent', () => { + const entity = (name?: string) => + ({ + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + annotations: { + [AIRBRAKE_PROJECT_SLUG_ANNOTATION]: name, + }, + name: name, + }, + } as Entity); + it('renders all errors sent from Airbrake', async () => { - const table = await renderInTestApp(); + const table = await renderInTestApp( + , + ); expect(exampleData.groups.length).toBeGreaterThan(0); for (const group of exampleData.groups) { expect( diff --git a/plugins/airbrake/src/components/EntityAirbrakeContent/EntityAirbrakeContent.tsx b/plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.tsx similarity index 97% rename from plugins/airbrake/src/components/EntityAirbrakeContent/EntityAirbrakeContent.tsx rename to plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.tsx index 2bd3917443..53f7d63f13 100644 --- a/plugins/airbrake/src/components/EntityAirbrakeContent/EntityAirbrakeContent.tsx +++ b/plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.tsx @@ -39,7 +39,7 @@ const useStyles = makeStyles(() => ({ }, })); -export const EntityAirbrakeContent = ({ entity }: { entity: Entity }) => { +export const EntityAirbrakeWidget = ({ entity }: { entity: Entity }) => { const classes = useStyles(); const projectId = useProjectSlug(entity); diff --git a/plugins/airbrake/src/components/EntityAirbrakeWidget/index.ts b/plugins/airbrake/src/components/EntityAirbrakeWidget/index.ts new file mode 100644 index 0000000000..0a85263c5f --- /dev/null +++ b/plugins/airbrake/src/components/EntityAirbrakeWidget/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2020 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 { EntityAirbrakeWidget } from './EntityAirbrakeWidget'; diff --git a/plugins/airbrake/src/EntityAirbrakeContent.test.tsx b/plugins/airbrake/src/extensions.test.tsx similarity index 94% rename from plugins/airbrake/src/EntityAirbrakeContent.test.tsx rename to plugins/airbrake/src/extensions.test.tsx index f0afef425f..db8670ac50 100644 --- a/plugins/airbrake/src/EntityAirbrakeContent.test.tsx +++ b/plugins/airbrake/src/extensions.test.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ import React from 'react'; -import { EntityAirbrakeContent } from './plugin'; +import { EntityAirbrakeContent } from './extensions'; import { renderInTestApp } from '@backstage/test-utils'; describe('The Airbrake entity', () => { diff --git a/plugins/airbrake/src/extensions.tsx b/plugins/airbrake/src/extensions.tsx new file mode 100644 index 0000000000..582088b250 --- /dev/null +++ b/plugins/airbrake/src/extensions.tsx @@ -0,0 +1,38 @@ +/* + * Copyright 2020 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 { useEntity } from '@backstage/plugin-catalog-react'; +import React from 'react'; +import { airbrakePlugin } from './plugin'; +import { createRoutableExtension } from '@backstage/core-plugin-api'; +import { rootRouteRef } from './routes'; + +export const EntityAirbrakeContent = airbrakePlugin.provide( + createRoutableExtension({ + name: 'EntityAirbrakeContent', + mountPoint: rootRouteRef, + component: () => + import('./components/EntityAirbrakeWidget').then( + ({ EntityAirbrakeWidget }) => { + const AirbrakePage = () => { + const { entity } = useEntity(); + return ; + }; + return AirbrakePage; + }, + ), + }), +); diff --git a/plugins/airbrake/src/index.ts b/plugins/airbrake/src/index.ts index 8f319e02fd..d5aedd9ba6 100644 --- a/plugins/airbrake/src/index.ts +++ b/plugins/airbrake/src/index.ts @@ -13,4 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { airbrakePlugin, EntityAirbrakeContent } from './plugin'; +export { airbrakePlugin } from './plugin'; +export { EntityAirbrakeContent } from './extensions'; diff --git a/plugins/airbrake/src/plugin.ts b/plugins/airbrake/src/plugin.ts index 0a3679baa4..30dc6749c5 100644 --- a/plugins/airbrake/src/plugin.ts +++ b/plugins/airbrake/src/plugin.ts @@ -14,26 +14,30 @@ * limitations under the License. */ import { + configApiRef, + createApiFactory, createPlugin, - createRoutableExtension, + discoveryApiRef, + identityApiRef, } from '@backstage/core-plugin-api'; import { rootRouteRef } from './routes'; +import { airbrakeApiRef, ProductionAirbrakeApi } from './api'; export const airbrakePlugin = createPlugin({ id: 'airbrake', + apis: [ + createApiFactory({ + api: airbrakeApiRef, + deps: { + configApi: configApiRef, + discoveryApi: discoveryApiRef, + identityApi: identityApiRef, + }, + factory: ({ discoveryApi }) => new ProductionAirbrakeApi(discoveryApi), + }), + ], routes: { root: rootRouteRef, }, }); - -export const EntityAirbrakeContent = airbrakePlugin.provide( - createRoutableExtension({ - name: 'EntityAirbrakeContent', - component: () => - import('./components/EntityAirbrakeContent/EntityAirbrakeContent').then( - m => m.EntityAirbrakeContent, - ), - mountPoint: rootRouteRef, - }), -); From 32ab08a4b14e4a2ef1b538fee7a5f58d368367cd Mon Sep 17 00:00:00 2001 From: Karan Shah Date: Tue, 11 Jan 2022 09:35:22 +0000 Subject: [PATCH 005/344] Register the mock API for the dev app Signed-off-by: Karan Shah --- plugins/airbrake/dev/index.tsx | 6 ++++++ plugins/airbrake/src/plugin.ts | 4 ---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/plugins/airbrake/dev/index.tsx b/plugins/airbrake/dev/index.tsx index 4c52e27094..ec6c4fd0b6 100644 --- a/plugins/airbrake/dev/index.tsx +++ b/plugins/airbrake/dev/index.tsx @@ -24,9 +24,15 @@ import { Page, SupportButton, } from '@backstage/core-components'; +import { airbrakeApiRef, MockAirbrakeApi } from '../src/api'; createDevApp() .registerPlugin(airbrakePlugin) + .registerApi({ + api: airbrakeApiRef, + deps: {}, + factory: () => new MockAirbrakeApi(), + }) .addPage({ element: ( diff --git a/plugins/airbrake/src/plugin.ts b/plugins/airbrake/src/plugin.ts index 30dc6749c5..5a58f5b4b3 100644 --- a/plugins/airbrake/src/plugin.ts +++ b/plugins/airbrake/src/plugin.ts @@ -14,11 +14,9 @@ * limitations under the License. */ import { - configApiRef, createApiFactory, createPlugin, discoveryApiRef, - identityApiRef, } from '@backstage/core-plugin-api'; import { rootRouteRef } from './routes'; @@ -30,9 +28,7 @@ export const airbrakePlugin = createPlugin({ createApiFactory({ api: airbrakeApiRef, deps: { - configApi: configApiRef, discoveryApi: discoveryApiRef, - identityApi: identityApiRef, }, factory: ({ discoveryApi }) => new ProductionAirbrakeApi(discoveryApi), }), From 87ffa4698ddc6bd1a8b3c655a831ea8a1db36567 Mon Sep 17 00:00:00 2001 From: Karan Shah Date: Tue, 11 Jan 2022 10:14:21 +0000 Subject: [PATCH 006/344] Create a mock entity Signed-off-by: Karan Shah --- plugins/airbrake/dev/index.tsx | 8 +++-- plugins/airbrake/src/api/mock/mock-entity.ts | 29 +++++++++++++++++++ .../EntityAirbrakeWidget.test.tsx | 15 +--------- plugins/airbrake/src/extensions.tsx | 3 +- 4 files changed, 37 insertions(+), 18 deletions(-) create mode 100644 plugins/airbrake/src/api/mock/mock-entity.ts diff --git a/plugins/airbrake/dev/index.tsx b/plugins/airbrake/dev/index.tsx index ec6c4fd0b6..cd980408ae 100644 --- a/plugins/airbrake/dev/index.tsx +++ b/plugins/airbrake/dev/index.tsx @@ -15,7 +15,7 @@ */ import React from 'react'; import { createDevApp } from '@backstage/dev-utils'; -import { EntityAirbrakeContent, airbrakePlugin } from '../src'; +import { airbrakePlugin, EntityAirbrakeContent } from '../src'; import { Content, ContentHeader, @@ -25,6 +25,8 @@ import { SupportButton, } from '@backstage/core-components'; import { airbrakeApiRef, MockAirbrakeApi } from '../src/api'; +import { EntityProvider } from '@backstage/plugin-catalog-react'; +import { entity } from '../src/api/mock/mock-entity'; createDevApp() .registerPlugin(airbrakePlugin) @@ -49,7 +51,9 @@ createDevApp() A description of your plugin goes here. - + + + ), diff --git a/plugins/airbrake/src/api/mock/mock-entity.ts b/plugins/airbrake/src/api/mock/mock-entity.ts new file mode 100644 index 0000000000..7e5b897cca --- /dev/null +++ b/plugins/airbrake/src/api/mock/mock-entity.ts @@ -0,0 +1,29 @@ +/* + * Copyright 2022 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 { AIRBRAKE_PROJECT_SLUG_ANNOTATION } from '../../components/useProjectSlug'; +import { Entity } from '@backstage/catalog-model'; + +export const entity = (name?: string) => + ({ + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + annotations: { + [AIRBRAKE_PROJECT_SLUG_ANNOTATION]: name, + }, + name: name, + }, + } as Entity); diff --git a/plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.test.tsx b/plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.test.tsx index d93c642e6c..000bd8f230 100644 --- a/plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.test.tsx +++ b/plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.test.tsx @@ -18,22 +18,9 @@ import React from 'react'; import { EntityAirbrakeWidget } from './EntityAirbrakeWidget'; import exampleData from '../../api/mock/airbrake-groups-api-mock.json'; import { renderInTestApp } from '@backstage/test-utils'; -import { AIRBRAKE_PROJECT_SLUG_ANNOTATION } from '../useProjectSlug'; -import { Entity } from '@backstage/catalog-model'; +import { entity } from '../../api/mock/mock-entity'; describe('EntityAirbrakeContent', () => { - const entity = (name?: string) => - ({ - apiVersion: 'backstage.io/v1alpha1', - kind: 'Component', - metadata: { - annotations: { - [AIRBRAKE_PROJECT_SLUG_ANNOTATION]: name, - }, - name: name, - }, - } as Entity); - it('renders all errors sent from Airbrake', async () => { const table = await renderInTestApp( , diff --git a/plugins/airbrake/src/extensions.tsx b/plugins/airbrake/src/extensions.tsx index 582088b250..6fbea68409 100644 --- a/plugins/airbrake/src/extensions.tsx +++ b/plugins/airbrake/src/extensions.tsx @@ -27,11 +27,10 @@ export const EntityAirbrakeContent = airbrakePlugin.provide( component: () => import('./components/EntityAirbrakeWidget').then( ({ EntityAirbrakeWidget }) => { - const AirbrakePage = () => { + return () => { const { entity } = useEntity(); return ; }; - return AirbrakePage; }, ), }), From 35ab9befbaddb8aa8832df66613a16f7091d96c9 Mon Sep 17 00:00:00 2001 From: Karan Shah Date: Tue, 11 Jan 2022 10:45:29 +0000 Subject: [PATCH 007/344] Rename entity() to createEntity() Signed-off-by: Karan Shah --- plugins/airbrake/dev/index.tsx | 4 ++-- plugins/airbrake/src/api/mock/mock-entity.ts | 2 +- .../EntityAirbrakeWidget/EntityAirbrakeWidget.test.tsx | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/airbrake/dev/index.tsx b/plugins/airbrake/dev/index.tsx index cd980408ae..8c3dd2f643 100644 --- a/plugins/airbrake/dev/index.tsx +++ b/plugins/airbrake/dev/index.tsx @@ -26,7 +26,7 @@ import { } from '@backstage/core-components'; import { airbrakeApiRef, MockAirbrakeApi } from '../src/api'; import { EntityProvider } from '@backstage/plugin-catalog-react'; -import { entity } from '../src/api/mock/mock-entity'; +import { createEntity } from '../src/api/mock/mock-entity'; createDevApp() .registerPlugin(airbrakePlugin) @@ -51,7 +51,7 @@ createDevApp() A description of your plugin goes here. - + diff --git a/plugins/airbrake/src/api/mock/mock-entity.ts b/plugins/airbrake/src/api/mock/mock-entity.ts index 7e5b897cca..b169938004 100644 --- a/plugins/airbrake/src/api/mock/mock-entity.ts +++ b/plugins/airbrake/src/api/mock/mock-entity.ts @@ -16,7 +16,7 @@ import { AIRBRAKE_PROJECT_SLUG_ANNOTATION } from '../../components/useProjectSlug'; import { Entity } from '@backstage/catalog-model'; -export const entity = (name?: string) => +export const createEntity = (name?: string) => ({ apiVersion: 'backstage.io/v1alpha1', kind: 'Component', diff --git a/plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.test.tsx b/plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.test.tsx index 000bd8f230..3c63f84f19 100644 --- a/plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.test.tsx +++ b/plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.test.tsx @@ -18,12 +18,12 @@ import React from 'react'; import { EntityAirbrakeWidget } from './EntityAirbrakeWidget'; import exampleData from '../../api/mock/airbrake-groups-api-mock.json'; import { renderInTestApp } from '@backstage/test-utils'; -import { entity } from '../../api/mock/mock-entity'; +import { createEntity } from '../../api/mock/mock-entity'; describe('EntityAirbrakeContent', () => { it('renders all errors sent from Airbrake', async () => { const table = await renderInTestApp( - , + , ); expect(exampleData.groups.length).toBeGreaterThan(0); for (const group of exampleData.groups) { From 475f1926528c43b29fed62e13d84e33d98a63496 Mon Sep 17 00:00:00 2001 From: Eoghan McIlwaine Date: Mon, 24 Jan 2022 16:27:12 +0100 Subject: [PATCH 008/344] Replace deprecated component on docs page Signed-off-by: Eoghan McIlwaine --- docs/plugins/integrating-plugin-into-software-catalog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/plugins/integrating-plugin-into-software-catalog.md b/docs/plugins/integrating-plugin-into-software-catalog.md index 71430e25a0..97efa56ac3 100644 --- a/docs/plugins/integrating-plugin-into-software-catalog.md +++ b/docs/plugins/integrating-plugin-into-software-catalog.md @@ -107,7 +107,7 @@ const systemPage = ( - + {/* Adding a new tab to the system view */} From e7dd277232d2417e687b003c6a1e204a9e32dead Mon Sep 17 00:00:00 2001 From: Eoghan McIlwaine Date: Mon, 24 Jan 2022 16:29:24 +0100 Subject: [PATCH 009/344] Remove EntitySystemDiagramCard code Signed-off-by: Eoghan McIlwaine --- plugins/catalog/src/index.ts | 1 - plugins/catalog/src/plugin.ts | 14 -------------- 2 files changed, 15 deletions(-) diff --git a/plugins/catalog/src/index.ts b/plugins/catalog/src/index.ts index 57dd911b2d..abe34c29b9 100644 --- a/plugins/catalog/src/index.ts +++ b/plugins/catalog/src/index.ts @@ -49,7 +49,6 @@ export { EntityHasSubcomponentsCard, EntityHasSystemsCard, EntityLinksCard, - EntitySystemDiagramCard, RelatedEntitiesCard, } from './plugin'; diff --git a/plugins/catalog/src/plugin.ts b/plugins/catalog/src/plugin.ts index 5a94cd2a09..2e4ebe9570 100644 --- a/plugins/catalog/src/plugin.ts +++ b/plugins/catalog/src/plugin.ts @@ -177,20 +177,6 @@ export const EntityDependsOnResourcesCard = catalogPlugin.provide( }), ); -/** - * @deprecated This component is replaced by EntityCatalogGraphCard which is imported from `@backstage/plugin-catalog-graph`. This component will be removed in an - * upcoming release - */ -export const EntitySystemDiagramCard = catalogPlugin.provide( - createComponentExtension({ - name: 'EntitySystemDiagramCard', - component: { - lazy: () => - import('./components/SystemDiagramCard').then(m => m.SystemDiagramCard), - }, - }), -); - export const RelatedEntitiesCard = catalogPlugin.provide( createComponentExtension({ name: 'RelatedEntitiesCard', From cbe4ab0d204149ac4e6d0019d0a931c81d6b6412 Mon Sep 17 00:00:00 2001 From: Eoghan McIlwaine Date: Mon, 24 Jan 2022 16:33:21 +0100 Subject: [PATCH 010/344] Remove EntitySystemDiagramCard from api report Signed-off-by: Eoghan McIlwaine --- plugins/catalog/api-report.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/plugins/catalog/api-report.md b/plugins/catalog/api-report.md index ad391b594f..bc29d9ee14 100644 --- a/plugins/catalog/api-report.md +++ b/plugins/catalog/api-report.md @@ -388,12 +388,6 @@ export const EntitySwitch: { }) => null; }; -// Warning: (ae-forgotten-export) The symbol "SystemDiagramCard" needs to be exported by the entry point index.d.ts -// Warning: (ae-missing-release-tag) "EntitySystemDiagramCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public @deprecated (undocumented) -export const EntitySystemDiagramCard: SystemDiagramCard; - // Warning: (ae-missing-release-tag) "FilterContainer" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) From 37092662cb39757d1ad0c1c7aa1c0d24096e3689 Mon Sep 17 00:00:00 2001 From: Eoghan McIlwaine Date: Mon, 24 Jan 2022 16:56:35 +0100 Subject: [PATCH 011/344] Add changeset for the code removal Signed-off-by: Eoghan McIlwaine --- .changeset/pink-onions-ring.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/pink-onions-ring.md diff --git a/.changeset/pink-onions-ring.md b/.changeset/pink-onions-ring.md new file mode 100644 index 0000000000..c7131721f5 --- /dev/null +++ b/.changeset/pink-onions-ring.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog': minor +--- + +**BREAKING** Completely removed the `EntitySystemDiagramCard` component which was previously deprecated. `EntityCatalogGraphCard` should be used instead, as references to `EntitySystemDiagramCard` will now break. From 76b74230bb9869228885db503a7f6caf9e21fb40 Mon Sep 17 00:00:00 2001 From: Eoghan McIlwaine Date: Mon, 24 Jan 2022 18:34:28 +0100 Subject: [PATCH 012/344] Updated changeset after review comment Signed-off-by: Eoghan McIlwaine --- .changeset/pink-onions-ring.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/pink-onions-ring.md b/.changeset/pink-onions-ring.md index c7131721f5..9ab8f1ae11 100644 --- a/.changeset/pink-onions-ring.md +++ b/.changeset/pink-onions-ring.md @@ -2,4 +2,4 @@ '@backstage/plugin-catalog': minor --- -**BREAKING** Completely removed the `EntitySystemDiagramCard` component which was previously deprecated. `EntityCatalogGraphCard` should be used instead, as references to `EntitySystemDiagramCard` will now break. +**BREAKING** Completely removed the `EntitySystemDiagramCard` component which was deprecated in a previous release. Any remaining references to this component are now broken and should be replaced with `EntityCatalogGraphCard`, which can be imported from package `@backstage/plugin-catalog-graph`. \ No newline at end of file From 973229906208d9c0e9bbb03c9ee83edecc211fab Mon Sep 17 00:00:00 2001 From: Eoghan McIlwaine Date: Tue, 25 Jan 2022 09:58:13 +0100 Subject: [PATCH 013/344] Run prettier on changeset Signed-off-by: Eoghan McIlwaine --- .changeset/pink-onions-ring.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/pink-onions-ring.md b/.changeset/pink-onions-ring.md index 9ab8f1ae11..2b81ad4b43 100644 --- a/.changeset/pink-onions-ring.md +++ b/.changeset/pink-onions-ring.md @@ -2,4 +2,4 @@ '@backstage/plugin-catalog': minor --- -**BREAKING** Completely removed the `EntitySystemDiagramCard` component which was deprecated in a previous release. Any remaining references to this component are now broken and should be replaced with `EntityCatalogGraphCard`, which can be imported from package `@backstage/plugin-catalog-graph`. \ No newline at end of file +**BREAKING** Completely removed the `EntitySystemDiagramCard` component which was deprecated in a previous release. Any remaining references to this component are now broken and should be replaced with `EntityCatalogGraphCard`, which can be imported from package `@backstage/plugin-catalog-graph`. From 4b794662c184fe1af07e3b413e33cc32cc8dd301 Mon Sep 17 00:00:00 2001 From: Karan Shah Date: Tue, 25 Jan 2022 10:40:40 +0000 Subject: [PATCH 014/344] Added the UI ability to change APIs Signed-off-by: Karan Shah --- .../airbrake/dev/components/ApiBar/ApiBar.tsx | 62 +++++++++++++++++++ .../airbrake/dev/components/ApiBar/index.ts | 17 +++++ plugins/airbrake/dev/index.tsx | 35 +++++------ 3 files changed, 94 insertions(+), 20 deletions(-) create mode 100644 plugins/airbrake/dev/components/ApiBar/ApiBar.tsx create mode 100644 plugins/airbrake/dev/components/ApiBar/index.ts diff --git a/plugins/airbrake/dev/components/ApiBar/ApiBar.tsx b/plugins/airbrake/dev/components/ApiBar/ApiBar.tsx new file mode 100644 index 0000000000..f86cb348ba --- /dev/null +++ b/plugins/airbrake/dev/components/ApiBar/ApiBar.tsx @@ -0,0 +1,62 @@ +/* + * Copyright 2022 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 { Select, SelectItem } from '@backstage/core-components'; +import { makeStyles, TextField } from '@material-ui/core'; + +const useStyles = makeStyles({ + root: { + display: 'flex', + gap: '1em', + flexWrap: 'wrap', + }, +}); + +export const ApiBar = () => { + const classes = useStyles(); + const apiOptions: SelectItem[] = [ + { label: 'Fake', value: 'fake' }, + { label: 'Real', value: 'real' }, + ]; + const [api, setApi] = useState('fake'); + const [projectId, setProjectId] = useState(); + const [apiKey, setApiKey] = useState(''); + + return ( +
+ setApi(newValue as string)} - /> + value={api} + onChange={e => setApi(e.target.value)} + > + {apiOptions.map(option => ( + + {option.label} + + ))} + {api === 'real' && ( <> Date: Wed, 26 Jan 2022 10:27:54 +0000 Subject: [PATCH 016/344] Start moving towards 2 pages Signed-off-by: Karan Shah --- plugins/airbrake/dev/index.tsx | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/plugins/airbrake/dev/index.tsx b/plugins/airbrake/dev/index.tsx index 17bbcf3d18..36f271a995 100644 --- a/plugins/airbrake/dev/index.tsx +++ b/plugins/airbrake/dev/index.tsx @@ -21,6 +21,8 @@ import { ApiBar } from './components/ApiBar'; import { Content, Header, Page } from '@backstage/core-components'; import { EntityProvider } from '@backstage/plugin-catalog-react'; import { createEntity } from '../src/api/mock/mock-entity'; +import AirplanemodeActiveIcon from '@material-ui/icons/AirplanemodeActive'; +import AirplanemodeInactiveIcon from '@material-ui/icons/AirplanemodeInactive'; createDevApp() .registerPlugin(airbrakePlugin) @@ -29,6 +31,32 @@ createDevApp() deps: {}, factory: () => new MockAirbrakeApi(), }) + .addPage({ + element: ( + +
+ +
+ + + +
+
+ + ), + title: 'Mock API', + path: '/airbrake-mock-api', + icon: AirplanemodeActiveIcon, + }) .addPage({ element: ( @@ -52,7 +80,8 @@ createDevApp() ), - title: 'Root Page', - path: '/airbrake', + title: 'Real API', + path: '/airbrake-real-api', + icon: AirplanemodeInactiveIcon, }) .render(); From de5133035009e4d7b3f05f40566315b80802442d Mon Sep 17 00:00:00 2001 From: Karan Shah Date: Wed, 26 Jan 2022 10:42:20 +0000 Subject: [PATCH 017/344] Added in FlexColumn.tsx Signed-off-by: Karan Shah --- .../airbrake/dev/components/ApiBar/ApiBar.tsx | 41 +++++-------------- .../dev/components/FlexColumn/FlexColumn.tsx | 34 +++++++++++++++ .../dev/components/FlexColumn/index.ts | 18 ++++++++ plugins/airbrake/dev/index.tsx | 21 +++------- 4 files changed, 67 insertions(+), 47 deletions(-) create mode 100644 plugins/airbrake/dev/components/FlexColumn/FlexColumn.tsx create mode 100644 plugins/airbrake/dev/components/FlexColumn/index.ts diff --git a/plugins/airbrake/dev/components/ApiBar/ApiBar.tsx b/plugins/airbrake/dev/components/ApiBar/ApiBar.tsx index d782113dc0..5c2b71ca14 100644 --- a/plugins/airbrake/dev/components/ApiBar/ApiBar.tsx +++ b/plugins/airbrake/dev/components/ApiBar/ApiBar.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ import React, { useState } from 'react'; -import { makeStyles, MenuItem, TextField } from '@material-ui/core'; +import { makeStyles, TextField } from '@material-ui/core'; const useStyles = makeStyles({ root: { @@ -26,42 +26,21 @@ const useStyles = makeStyles({ export const ApiBar = () => { const classes = useStyles(); - const apiOptions = [ - { label: 'Fake', value: 'fake' }, - { label: 'Real', value: 'real' }, - ]; - const [api, setApi] = useState('fake'); const [projectId, setProjectId] = useState(); const [apiKey, setApiKey] = useState(''); return (
setApi(e.target.value)} - > - {apiOptions.map(option => ( - - {option.label} - - ))} - - {api === 'real' && ( - <> - setProjectId(parseInt(e.target.value, 10))} - /> - setApiKey(e.target.value)} - /> - - )} + label="Project ID" + value={projectId} + onChange={e => setProjectId(parseInt(e.target.value, 10))} + /> + setApiKey(e.target.value)} + />
); }; diff --git a/plugins/airbrake/dev/components/FlexColumn/FlexColumn.tsx b/plugins/airbrake/dev/components/FlexColumn/FlexColumn.tsx new file mode 100644 index 0000000000..aaf64d3513 --- /dev/null +++ b/plugins/airbrake/dev/components/FlexColumn/FlexColumn.tsx @@ -0,0 +1,34 @@ +/* + * Copyright 2022 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, { PropsWithChildren, ReactNode } from 'react'; + +export declare type FlexColumnProps = { + children?: ReactNode; +}; + +export const FlexColumn = (props: PropsWithChildren) => { + return ( +
+ {props.children} +
+ ); +}; diff --git a/plugins/airbrake/dev/components/FlexColumn/index.ts b/plugins/airbrake/dev/components/FlexColumn/index.ts new file mode 100644 index 0000000000..5926c3cb28 --- /dev/null +++ b/plugins/airbrake/dev/components/FlexColumn/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2020 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 { FlexColumn } from './FlexColumn'; +export type { FlexColumnProps } from './FlexColumn'; diff --git a/plugins/airbrake/dev/index.tsx b/plugins/airbrake/dev/index.tsx index 36f271a995..ba520892cb 100644 --- a/plugins/airbrake/dev/index.tsx +++ b/plugins/airbrake/dev/index.tsx @@ -23,6 +23,7 @@ import { EntityProvider } from '@backstage/plugin-catalog-react'; import { createEntity } from '../src/api/mock/mock-entity'; import AirplanemodeActiveIcon from '@material-ui/icons/AirplanemodeActive'; import AirplanemodeInactiveIcon from '@material-ui/icons/AirplanemodeInactive'; +import { FlexColumn } from './components/FlexColumn'; createDevApp() .registerPlugin(airbrakePlugin) @@ -39,17 +40,11 @@ createDevApp() subtitle="This uses a fake API" /> -
+ -
+
), @@ -65,18 +60,12 @@ createDevApp() subtitle="Test the plugin below" /> -
+ -
+
), From 39a5bfb958f74f2a6d891c1946a1ec77decaa747 Mon Sep 17 00:00:00 2001 From: Karan Shah Date: Wed, 26 Jan 2022 10:53:42 +0000 Subject: [PATCH 018/344] Improved the icons Signed-off-by: Karan Shah --- .../dev/components/FlexColumn/FlexColumn.tsx | 34 ----------------- .../dev/components/FlexColumn/index.ts | 18 --------- plugins/airbrake/dev/index.tsx | 38 +++++++------------ 3 files changed, 14 insertions(+), 76 deletions(-) delete mode 100644 plugins/airbrake/dev/components/FlexColumn/FlexColumn.tsx delete mode 100644 plugins/airbrake/dev/components/FlexColumn/index.ts diff --git a/plugins/airbrake/dev/components/FlexColumn/FlexColumn.tsx b/plugins/airbrake/dev/components/FlexColumn/FlexColumn.tsx deleted file mode 100644 index aaf64d3513..0000000000 --- a/plugins/airbrake/dev/components/FlexColumn/FlexColumn.tsx +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2022 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, { PropsWithChildren, ReactNode } from 'react'; - -export declare type FlexColumnProps = { - children?: ReactNode; -}; - -export const FlexColumn = (props: PropsWithChildren) => { - return ( -
- {props.children} -
- ); -}; diff --git a/plugins/airbrake/dev/components/FlexColumn/index.ts b/plugins/airbrake/dev/components/FlexColumn/index.ts deleted file mode 100644 index 5926c3cb28..0000000000 --- a/plugins/airbrake/dev/components/FlexColumn/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright 2020 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 { FlexColumn } from './FlexColumn'; -export type { FlexColumnProps } from './FlexColumn'; diff --git a/plugins/airbrake/dev/index.tsx b/plugins/airbrake/dev/index.tsx index ba520892cb..4163590069 100644 --- a/plugins/airbrake/dev/index.tsx +++ b/plugins/airbrake/dev/index.tsx @@ -21,9 +21,8 @@ import { ApiBar } from './components/ApiBar'; import { Content, Header, Page } from '@backstage/core-components'; import { EntityProvider } from '@backstage/plugin-catalog-react'; import { createEntity } from '../src/api/mock/mock-entity'; -import AirplanemodeActiveIcon from '@material-ui/icons/AirplanemodeActive'; -import AirplanemodeInactiveIcon from '@material-ui/icons/AirplanemodeInactive'; -import { FlexColumn } from './components/FlexColumn'; +import CloudOffIcon from '@material-ui/icons/CloudOff'; +import CloudIcon from '@material-ui/icons/Cloud'; createDevApp() .registerPlugin(airbrakePlugin) @@ -35,42 +34,33 @@ createDevApp() .addPage({ element: ( -
+
- - - - - + + + ), title: 'Mock API', path: '/airbrake-mock-api', - icon: AirplanemodeActiveIcon, + icon: CloudOffIcon, }) .addPage({ element: ( -
+
+ +
- - - - - - + + + ), title: 'Real API', path: '/airbrake-real-api', - icon: AirplanemodeInactiveIcon, + icon: CloudIcon, }) .render(); From 1c2ba8ccdeeb83298aa601878ea7d246bb1837a2 Mon Sep 17 00:00:00 2001 From: Karan Shah Date: Thu, 27 Jan 2022 10:47:41 +0000 Subject: [PATCH 019/344] Added state to the top level for the real API Signed-off-by: Karan Shah --- .../airbrake/dev/components/ApiBar/ApiBar.tsx | 33 +++++++------ .../ContextProvider/ContextProvider.tsx | 48 +++++++++++++++++++ .../dev/components/ContextProvider/index.ts | 17 +++++++ plugins/airbrake/dev/index.tsx | 29 +++++++---- 4 files changed, 102 insertions(+), 25 deletions(-) create mode 100644 plugins/airbrake/dev/components/ContextProvider/ContextProvider.tsx create mode 100644 plugins/airbrake/dev/components/ContextProvider/index.ts diff --git a/plugins/airbrake/dev/components/ApiBar/ApiBar.tsx b/plugins/airbrake/dev/components/ApiBar/ApiBar.tsx index 5c2b71ca14..bd8079420f 100644 --- a/plugins/airbrake/dev/components/ApiBar/ApiBar.tsx +++ b/plugins/airbrake/dev/components/ApiBar/ApiBar.tsx @@ -13,8 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { useState } from 'react'; +import React from 'react'; import { makeStyles, TextField } from '@material-ui/core'; +import { Context } from '../ContextProvider'; const useStyles = makeStyles({ root: { @@ -26,21 +27,23 @@ const useStyles = makeStyles({ export const ApiBar = () => { const classes = useStyles(); - const [projectId, setProjectId] = useState(); - const [apiKey, setApiKey] = useState(''); return ( -
- setProjectId(parseInt(e.target.value, 10))} - /> - setApiKey(e.target.value)} - /> -
+ + {value => ( +
+ value.setProjectId?.(parseInt(e.target.value, 10))} + /> + value.setApiKey?.(e.target.value)} + /> +
+ )} +
); }; diff --git a/plugins/airbrake/dev/components/ContextProvider/ContextProvider.tsx b/plugins/airbrake/dev/components/ContextProvider/ContextProvider.tsx new file mode 100644 index 0000000000..bbf72f3b99 --- /dev/null +++ b/plugins/airbrake/dev/components/ContextProvider/ContextProvider.tsx @@ -0,0 +1,48 @@ +/* + * Copyright 2022 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, { + Dispatch, + PropsWithChildren, + SetStateAction, + useState, +} from 'react'; + +interface ContextInterface { + projectId?: number; + setProjectId?: Dispatch>; + apiKey?: String; + setApiKey?: Dispatch>; +} + +export const Context = React.createContext({}); + +export const ContextProvider = ({ children }: PropsWithChildren<{}>) => { + const [projectId, setProjectId] = useState(); + const [apiKey, setApiKey] = useState(''); + + return ( + + {children} + + ); +}; diff --git a/plugins/airbrake/dev/components/ContextProvider/index.ts b/plugins/airbrake/dev/components/ContextProvider/index.ts new file mode 100644 index 0000000000..f1e350868a --- /dev/null +++ b/plugins/airbrake/dev/components/ContextProvider/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2020 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 { ContextProvider, Context } from './ContextProvider'; diff --git a/plugins/airbrake/dev/index.tsx b/plugins/airbrake/dev/index.tsx index 4163590069..37b1810142 100644 --- a/plugins/airbrake/dev/index.tsx +++ b/plugins/airbrake/dev/index.tsx @@ -23,6 +23,7 @@ import { EntityProvider } from '@backstage/plugin-catalog-react'; import { createEntity } from '../src/api/mock/mock-entity'; import CloudOffIcon from '@material-ui/icons/CloudOff'; import CloudIcon from '@material-ui/icons/Cloud'; +import { ContextProvider, Context } from './components/ContextProvider'; createDevApp() .registerPlugin(airbrakePlugin) @@ -48,16 +49,24 @@ createDevApp() }) .addPage({ element: ( - -
- -
- - - - - -
+ + +
+ +
+ + + {value => ( + + + + )} + + +
+
), title: 'Real API', path: '/airbrake-real-api', From f3238a1a6c69f04a778bd9aef445c5eedf022ef9 Mon Sep 17 00:00:00 2001 From: Karan Shah Date: Thu, 27 Jan 2022 11:07:21 +0000 Subject: [PATCH 020/344] Make the project slug into a project ID Signed-off-by: Karan Shah --- plugins/airbrake/dev/components/ApiBar/ApiBar.tsx | 4 +++- plugins/airbrake/dev/index.tsx | 6 ++---- plugins/airbrake/src/api/mock/mock-entity.ts | 15 +++++++++------ .../EntityAirbrakeWidget.test.tsx | 2 +- .../EntityAirbrakeWidget/EntityAirbrakeWidget.tsx | 4 ++-- plugins/airbrake/src/components/useProjectSlug.ts | 4 ++-- 6 files changed, 19 insertions(+), 16 deletions(-) diff --git a/plugins/airbrake/dev/components/ApiBar/ApiBar.tsx b/plugins/airbrake/dev/components/ApiBar/ApiBar.tsx index bd8079420f..8437559baf 100644 --- a/plugins/airbrake/dev/components/ApiBar/ApiBar.tsx +++ b/plugins/airbrake/dev/components/ApiBar/ApiBar.tsx @@ -35,7 +35,9 @@ export const ApiBar = () => { value.setProjectId?.(parseInt(e.target.value, 10))} + onChange={e => + value.setProjectId?.(parseInt(e.target.value, 10) || undefined) + } />
- + @@ -57,9 +57,7 @@ createDevApp() {value => ( - + )} diff --git a/plugins/airbrake/src/api/mock/mock-entity.ts b/plugins/airbrake/src/api/mock/mock-entity.ts index b169938004..7891478f51 100644 --- a/plugins/airbrake/src/api/mock/mock-entity.ts +++ b/plugins/airbrake/src/api/mock/mock-entity.ts @@ -13,17 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { AIRBRAKE_PROJECT_SLUG_ANNOTATION } from '../../components/useProjectSlug'; +import { AIRBRAKE_PROJECT_ID_ANNOTATION } from '../../components/useProjectSlug'; import { Entity } from '@backstage/catalog-model'; -export const createEntity = (name?: string) => - ({ +export const createEntity = (projectId?: number) => { + const projectIdString = projectId?.toString(); + + return { apiVersion: 'backstage.io/v1alpha1', kind: 'Component', metadata: { annotations: { - [AIRBRAKE_PROJECT_SLUG_ANNOTATION]: name, + [AIRBRAKE_PROJECT_ID_ANNOTATION]: projectIdString, }, - name: name, + name: projectIdString, }, - } as Entity); + } as Entity; +}; diff --git a/plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.test.tsx b/plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.test.tsx index 3c63f84f19..5fd1aa9bf5 100644 --- a/plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.test.tsx +++ b/plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.test.tsx @@ -23,7 +23,7 @@ import { createEntity } from '../../api/mock/mock-entity'; describe('EntityAirbrakeContent', () => { it('renders all errors sent from Airbrake', async () => { const table = await renderInTestApp( - , + , ); expect(exampleData.groups.length).toBeGreaterThan(0); for (const group of exampleData.groups) { diff --git a/plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.tsx b/plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.tsx index 53f7d63f13..543ebbefc0 100644 --- a/plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.tsx +++ b/plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.tsx @@ -29,7 +29,7 @@ import { ErrorApi, errorApiRef, useApi } from '@backstage/core-plugin-api'; import { airbrakeApiRef } from '../../api'; import useAsync from 'react-use/lib/useAsync'; import { - AIRBRAKE_PROJECT_SLUG_ANNOTATION, + AIRBRAKE_PROJECT_ID_ANNOTATION, useProjectSlug, } from '../useProjectSlug'; @@ -64,7 +64,7 @@ export const EntityAirbrakeWidget = ({ entity }: { entity: Entity }) => { {!loading && !projectId && ( )} diff --git a/plugins/airbrake/src/components/useProjectSlug.ts b/plugins/airbrake/src/components/useProjectSlug.ts index 4c6337b49e..07ba527c00 100644 --- a/plugins/airbrake/src/components/useProjectSlug.ts +++ b/plugins/airbrake/src/components/useProjectSlug.ts @@ -16,8 +16,8 @@ import { Entity } from '@backstage/catalog-model'; -export const AIRBRAKE_PROJECT_SLUG_ANNOTATION = 'airbrake.io/project-slug'; +export const AIRBRAKE_PROJECT_ID_ANNOTATION = 'airbrake.io/project-id'; export const useProjectSlug = (entity: Entity) => { - return entity?.metadata.annotations?.[AIRBRAKE_PROJECT_SLUG_ANNOTATION] ?? ''; + return entity?.metadata.annotations?.[AIRBRAKE_PROJECT_ID_ANNOTATION] ?? ''; }; From 6b5b9f2f1ba0c30a3344b687b9160e41fcb2c8aa Mon Sep 17 00:00:00 2001 From: Karan Shah Date: Thu, 27 Jan 2022 14:21:27 +0000 Subject: [PATCH 021/344] Rename useProjectSlug.ts to useProjectId.ts Signed-off-by: Karan Shah --- plugins/airbrake/src/api/mock/mock-entity.ts | 2 +- .../EntityAirbrakeWidget/EntityAirbrakeWidget.tsx | 7 ++----- .../src/components/{useProjectSlug.ts => useProjectId.ts} | 2 +- 3 files changed, 4 insertions(+), 7 deletions(-) rename plugins/airbrake/src/components/{useProjectSlug.ts => useProjectId.ts} (93%) diff --git a/plugins/airbrake/src/api/mock/mock-entity.ts b/plugins/airbrake/src/api/mock/mock-entity.ts index 7891478f51..13534f2458 100644 --- a/plugins/airbrake/src/api/mock/mock-entity.ts +++ b/plugins/airbrake/src/api/mock/mock-entity.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { AIRBRAKE_PROJECT_ID_ANNOTATION } from '../../components/useProjectSlug'; +import { AIRBRAKE_PROJECT_ID_ANNOTATION } from '../../components/useProjectId'; import { Entity } from '@backstage/catalog-model'; export const createEntity = (projectId?: number) => { diff --git a/plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.tsx b/plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.tsx index 543ebbefc0..4564af6079 100644 --- a/plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.tsx +++ b/plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.tsx @@ -28,10 +28,7 @@ import { BackstageTheme } from '@backstage/theme'; import { ErrorApi, errorApiRef, useApi } from '@backstage/core-plugin-api'; import { airbrakeApiRef } from '../../api'; import useAsync from 'react-use/lib/useAsync'; -import { - AIRBRAKE_PROJECT_ID_ANNOTATION, - useProjectSlug, -} from '../useProjectSlug'; +import { AIRBRAKE_PROJECT_ID_ANNOTATION, useProjectId } from '../useProjectId'; const useStyles = makeStyles(() => ({ multilineText: { @@ -42,7 +39,7 @@ const useStyles = makeStyles(() => ({ export const EntityAirbrakeWidget = ({ entity }: { entity: Entity }) => { const classes = useStyles(); - const projectId = useProjectSlug(entity); + const projectId = useProjectId(entity); const errorApi = useApi(errorApiRef); const airbrakeApi = useApi(airbrakeApiRef); diff --git a/plugins/airbrake/src/components/useProjectSlug.ts b/plugins/airbrake/src/components/useProjectId.ts similarity index 93% rename from plugins/airbrake/src/components/useProjectSlug.ts rename to plugins/airbrake/src/components/useProjectId.ts index 07ba527c00..3402778b7b 100644 --- a/plugins/airbrake/src/components/useProjectSlug.ts +++ b/plugins/airbrake/src/components/useProjectId.ts @@ -18,6 +18,6 @@ import { Entity } from '@backstage/catalog-model'; export const AIRBRAKE_PROJECT_ID_ANNOTATION = 'airbrake.io/project-id'; -export const useProjectSlug = (entity: Entity) => { +export const useProjectId = (entity: Entity) => { return entity?.metadata.annotations?.[AIRBRAKE_PROJECT_ID_ANNOTATION] ?? ''; }; From f99d31d38526990abce378b7a1e61d2b8dbb158e Mon Sep 17 00:00:00 2001 From: Karan Shah Date: Thu, 27 Jan 2022 15:23:42 +0000 Subject: [PATCH 022/344] Improved how the API Bar looks Signed-off-by: Karan Shah --- .../airbrake/dev/components/ApiBar/ApiBar.tsx | 60 +++++++++++++++---- 1 file changed, 47 insertions(+), 13 deletions(-) diff --git a/plugins/airbrake/dev/components/ApiBar/ApiBar.tsx b/plugins/airbrake/dev/components/ApiBar/ApiBar.tsx index 8437559baf..294e4506d1 100644 --- a/plugins/airbrake/dev/components/ApiBar/ApiBar.tsx +++ b/plugins/airbrake/dev/components/ApiBar/ApiBar.tsx @@ -14,7 +14,12 @@ * limitations under the License. */ import React from 'react'; -import { makeStyles, TextField } from '@material-ui/core'; +import { + createTheme, + makeStyles, + MuiThemeProvider, + TextField, +} from '@material-ui/core'; import { Context } from '../ContextProvider'; const useStyles = makeStyles({ @@ -25,6 +30,31 @@ const useStyles = makeStyles({ }, }); +const textFieldTheme = createTheme({ + palette: { + type: 'dark', + primary: { + light: '#fff', + main: '#fff', + dark: '#fff', + contrastText: '#fff', + }, + secondary: { + light: '#fff', + main: '#fff', + dark: '#fff', + contrastText: '#fff', + }, + action: { + disabled: '#fff', + }, + text: { + primary: '#fff', + secondary: '#fff', + }, + }, +}); + export const ApiBar = () => { const classes = useStyles(); @@ -32,18 +62,22 @@ export const ApiBar = () => { {value => (
- - value.setProjectId?.(parseInt(e.target.value, 10) || undefined) - } - /> - value.setApiKey?.(e.target.value)} - /> + + + value.setProjectId?.(parseInt(e.target.value, 10) || undefined) + } + /> + value.setApiKey?.(e.target.value)} + /> +
)}
From 899b8d748fa414bbe1d0c5d1ea71cafda1a18431 Mon Sep 17 00:00:00 2001 From: Karan Shah Date: Mon, 31 Jan 2022 16:39:49 +0000 Subject: [PATCH 023/344] Connect up the production API Signed-off-by: Karan Shah --- .../airbrake/dev/components/ApiBar/ApiBar.tsx | 4 +-- .../ContextProvider/ContextProvider.tsx | 2 +- plugins/airbrake/dev/index.tsx | 34 ++++++++++++------- plugins/airbrake/src/api/airbrake-api.ts | 2 +- plugins/airbrake/src/api/production-api.ts | 13 +++---- .../EntityAirbrakeWidget.tsx | 4 +-- 6 files changed, 32 insertions(+), 27 deletions(-) diff --git a/plugins/airbrake/dev/components/ApiBar/ApiBar.tsx b/plugins/airbrake/dev/components/ApiBar/ApiBar.tsx index 294e4506d1..eae00c2d32 100644 --- a/plugins/airbrake/dev/components/ApiBar/ApiBar.tsx +++ b/plugins/airbrake/dev/components/ApiBar/ApiBar.tsx @@ -66,7 +66,7 @@ export const ApiBar = () => { value.setProjectId?.(parseInt(e.target.value, 10) || undefined) } @@ -74,7 +74,7 @@ export const ApiBar = () => { value.setApiKey?.(e.target.value)} /> diff --git a/plugins/airbrake/dev/components/ContextProvider/ContextProvider.tsx b/plugins/airbrake/dev/components/ContextProvider/ContextProvider.tsx index bbf72f3b99..6d7fe0f7f2 100644 --- a/plugins/airbrake/dev/components/ContextProvider/ContextProvider.tsx +++ b/plugins/airbrake/dev/components/ContextProvider/ContextProvider.tsx @@ -23,7 +23,7 @@ import React, { interface ContextInterface { projectId?: number; setProjectId?: Dispatch>; - apiKey?: String; + apiKey?: string; setApiKey?: Dispatch>; } diff --git a/plugins/airbrake/dev/index.tsx b/plugins/airbrake/dev/index.tsx index c0a7c59fd9..7f32d30b4a 100644 --- a/plugins/airbrake/dev/index.tsx +++ b/plugins/airbrake/dev/index.tsx @@ -15,31 +15,33 @@ */ import React from 'react'; import { createDevApp } from '@backstage/dev-utils'; +import { TestApiProvider } from '@backstage/test-utils'; import { airbrakePlugin, EntityAirbrakeContent } from '../src'; -import { airbrakeApiRef, MockAirbrakeApi } from '../src/api'; +import { + airbrakeApiRef, + MockAirbrakeApi, + ProductionAirbrakeApi, +} from '../src/api'; import { ApiBar } from './components/ApiBar'; import { Content, Header, Page } from '@backstage/core-components'; import { EntityProvider } from '@backstage/plugin-catalog-react'; import { createEntity } from '../src/api/mock/mock-entity'; import CloudOffIcon from '@material-ui/icons/CloudOff'; import CloudIcon from '@material-ui/icons/Cloud'; -import { ContextProvider, Context } from './components/ContextProvider'; +import { Context, ContextProvider } from './components/ContextProvider'; createDevApp() .registerPlugin(airbrakePlugin) - .registerApi({ - api: airbrakeApiRef, - deps: {}, - factory: () => new MockAirbrakeApi(), - }) .addPage({ element: (
- - - + + + + + ), @@ -57,9 +59,15 @@ createDevApp() {value => ( - - - + + + + + )} diff --git a/plugins/airbrake/src/api/airbrake-api.ts b/plugins/airbrake/src/api/airbrake-api.ts index 1a3cfa72e4..3e3fe123b9 100644 --- a/plugins/airbrake/src/api/airbrake-api.ts +++ b/plugins/airbrake/src/api/airbrake-api.ts @@ -22,5 +22,5 @@ export const airbrakeApiRef = createApiRef({ }); export interface AirbrakeApi { - fetchGroups(project: string): Promise; + fetchGroups(projectId: string): Promise; } diff --git a/plugins/airbrake/src/api/production-api.ts b/plugins/airbrake/src/api/production-api.ts index 37ac938752..cd8d2f95bf 100644 --- a/plugins/airbrake/src/api/production-api.ts +++ b/plugins/airbrake/src/api/production-api.ts @@ -16,20 +16,17 @@ import { Groups } from './airbrake-groups'; import { AirbrakeApi } from './airbrake-api'; -import { DiscoveryApi } from '@backstage/core-plugin-api'; export class ProductionAirbrakeApi implements AirbrakeApi { - constructor(private readonly discoveryApi: DiscoveryApi) {} + constructor(private readonly apiKey?: string) {} - async fetchGroups(project: string): Promise { - const apiUrl = `${await this.discoveryApi.getBaseUrl( - 'proxy', - )}/airbrake/api`; + async fetchGroups(projectId: string): Promise { + const apiUrl = `https://api.airbrake.io/api/v4/projects/${projectId}/groups?key=${this.apiKey}`; - const response = await fetch(`${apiUrl}/v4/projects/${project}/groups`); + const response = await fetch(apiUrl); if (response.status >= 400 && response.status < 600) { - throw new Error('Failed fetching Airbrake issues'); + throw new Error('Failed fetching Airbrake groups'); } return (await response.json()) as Groups; diff --git a/plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.tsx b/plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.tsx index 4564af6079..b11116aeef 100644 --- a/plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.tsx +++ b/plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.tsx @@ -45,7 +45,7 @@ export const EntityAirbrakeWidget = ({ entity }: { entity: Entity }) => { const { loading, value, error } = useAsync( () => airbrakeApi.fetchGroups(projectId), - [projectId], + [airbrakeApi, projectId], ); useEffect(() => { @@ -69,7 +69,7 @@ export const EntityAirbrakeWidget = ({ entity }: { entity: Entity }) => { )} From 3193d9d3bb1bdb1212726442bb0bdbf8de068684 Mon Sep 17 00:00:00 2001 From: Karan Shah Date: Tue, 1 Feb 2022 11:03:33 +0000 Subject: [PATCH 024/344] Add back catalog model and catalog plugin react Signed-off-by: Karan Shah --- plugins/airbrake/package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/airbrake/package.json b/plugins/airbrake/package.json index e037e38009..2ccb7f31b8 100644 --- a/plugins/airbrake/package.json +++ b/plugins/airbrake/package.json @@ -20,8 +20,10 @@ "clean": "backstage-cli clean" }, "dependencies": { + "@backstage/catalog-model": "^0.9.10", "@backstage/core-components": "^0.8.6", "@backstage/core-plugin-api": "^0.6.0", + "@backstage/plugin-catalog-react": "^0.6.12", "@backstage/theme": "^0.2.14", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", From 6c3a7ad51fef61c550f7e47f4bb6a3b087ee6783 Mon Sep 17 00:00:00 2001 From: Karan Shah Date: Tue, 1 Feb 2022 15:24:25 +0000 Subject: [PATCH 025/344] Remove Discovery API Ref Signed-off-by: Karan Shah --- plugins/airbrake/src/plugin.ts | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/plugins/airbrake/src/plugin.ts b/plugins/airbrake/src/plugin.ts index 5a58f5b4b3..f008b48ad4 100644 --- a/plugins/airbrake/src/plugin.ts +++ b/plugins/airbrake/src/plugin.ts @@ -13,11 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { - createApiFactory, - createPlugin, - discoveryApiRef, -} from '@backstage/core-plugin-api'; +import { createApiFactory, createPlugin } from '@backstage/core-plugin-api'; import { rootRouteRef } from './routes'; import { airbrakeApiRef, ProductionAirbrakeApi } from './api'; @@ -27,10 +23,8 @@ export const airbrakePlugin = createPlugin({ apis: [ createApiFactory({ api: airbrakeApiRef, - deps: { - discoveryApi: discoveryApiRef, - }, - factory: ({ discoveryApi }) => new ProductionAirbrakeApi(discoveryApi), + deps: {}, + factory: () => new ProductionAirbrakeApi(), }), ], routes: { From 9e505d20a340879289384684ccbdf24b0edb7464 Mon Sep 17 00:00:00 2001 From: Karan Shah Date: Tue, 1 Feb 2022 15:56:46 +0000 Subject: [PATCH 026/344] Added a changeset Signed-off-by: Karan Shah --- .changeset/two-elephants-arrive.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/two-elephants-arrive.md diff --git a/.changeset/two-elephants-arrive.md b/.changeset/two-elephants-arrive.md new file mode 100644 index 0000000000..6a1e713602 --- /dev/null +++ b/.changeset/two-elephants-arrive.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-airbrake': minor +--- + +API connectivity has added, but currently will only work by running it standalone on a browser with CORS disabled. From 499f8f547eb613efc24d221f83b4bba0229d1871 Mon Sep 17 00:00:00 2001 From: Karan Shah Date: Tue, 1 Feb 2022 17:18:22 +0000 Subject: [PATCH 027/344] Add some routes to the extensions test Signed-off-by: Karan Shah --- plugins/airbrake/src/extensions.test.tsx | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/plugins/airbrake/src/extensions.test.tsx b/plugins/airbrake/src/extensions.test.tsx index db8670ac50..5532621848 100644 --- a/plugins/airbrake/src/extensions.test.tsx +++ b/plugins/airbrake/src/extensions.test.tsx @@ -15,11 +15,23 @@ */ import React from 'react'; import { EntityAirbrakeContent } from './extensions'; -import { renderInTestApp } from '@backstage/test-utils'; +import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; +import { airbrakeApiRef, MockAirbrakeApi } from './api'; +import { rootRouteRef } from './routes'; describe('The Airbrake entity', () => { it('should render the content properly', async () => { - const rendered = await renderInTestApp(); + const rendered = await renderInTestApp( + + + , + { + mountedRoutes: { + '/': rootRouteRef, + }, + routeEntries: ['/'], + }, + ); expect(rendered.getByText('ChunkLoadError')).toBeInTheDocument(); }); }); From 2a8fee1a512e02f93983499ae24995f9466ec6a8 Mon Sep 17 00:00:00 2001 From: Karan Shah Date: Wed, 2 Feb 2022 14:33:50 +0000 Subject: [PATCH 028/344] Initial commit of the airbrake-backend plugin Signed-off-by: Karan Shah --- packages/backend/package.json | 11 ++-- plugins/airbrake-backend/.eslintrc.js | 3 ++ plugins/airbrake-backend/README.md | 16 ++++++ plugins/airbrake-backend/package.json | 50 +++++++++++++++++++ plugins/airbrake-backend/src/index.ts | 17 +++++++ plugins/airbrake-backend/src/run.ts | 33 ++++++++++++ .../src/service/router.test.ts | 45 +++++++++++++++++ .../airbrake-backend/src/service/router.ts | 40 +++++++++++++++ .../src/service/standaloneServer.ts | 50 +++++++++++++++++++ plugins/airbrake-backend/src/setupTests.ts | 17 +++++++ yarn.lock | 41 ++++++++++++--- 11 files changed, 312 insertions(+), 11 deletions(-) create mode 100644 plugins/airbrake-backend/.eslintrc.js create mode 100644 plugins/airbrake-backend/README.md create mode 100644 plugins/airbrake-backend/package.json create mode 100644 plugins/airbrake-backend/src/index.ts create mode 100644 plugins/airbrake-backend/src/run.ts create mode 100644 plugins/airbrake-backend/src/service/router.test.ts create mode 100644 plugins/airbrake-backend/src/service/router.ts create mode 100644 plugins/airbrake-backend/src/service/standaloneServer.ts create mode 100644 plugins/airbrake-backend/src/setupTests.ts diff --git a/packages/backend/package.json b/packages/backend/package.json index c9e1c9d7c3..f3e150522c 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -38,8 +38,8 @@ "@backstage/plugin-code-coverage-backend": "^0.1.20", "@backstage/plugin-graphql-backend": "^0.1.12", "@backstage/plugin-jenkins-backend": "^0.1.11", - "@backstage/plugin-kubernetes-backend": "^0.4.5", "@backstage/plugin-kafka-backend": "^0.2.15", + "@backstage/plugin-kubernetes-backend": "^0.4.5", "@backstage/plugin-permission-backend": "^0.4.2-next.0", "@backstage/plugin-permission-common": "^0.4.0", "@backstage/plugin-permission-node": "^0.4.2-next.0", @@ -48,22 +48,23 @@ "@backstage/plugin-scaffolder-backend": "^0.15.23-next.0", "@backstage/plugin-scaffolder-backend-module-rails": "^0.2.5-next.0", "@backstage/plugin-search-backend": "^0.4.1-next.0", - "@backstage/plugin-search-backend-node": "^0.4.5", "@backstage/plugin-search-backend-module-elasticsearch": "^0.0.8", "@backstage/plugin-search-backend-module-pg": "^0.2.4", - "@backstage/plugin-techdocs-backend": "^0.13.1", + "@backstage/plugin-search-backend-node": "^0.4.5", "@backstage/plugin-tech-insights-backend": "^0.2.2", - "@backstage/plugin-tech-insights-node": "^0.2.0", "@backstage/plugin-tech-insights-backend-module-jsonfc": "^0.1.6", + "@backstage/plugin-tech-insights-node": "^0.2.0", + "@backstage/plugin-techdocs-backend": "^0.13.1", "@backstage/plugin-todo-backend": "^0.1.19", "@gitbeaker/node": "^35.1.0", "@octokit/rest": "^18.5.3", "azure-devops-node-api": "^11.0.1", + "backstage-plugin-airbrake-backend": "^0.0.0", "dockerode": "^3.3.1", "example-app": "file:../app", "express": "^4.17.1", - "express-promise-router": "^4.1.0", "express-prom-bundle": "^6.3.6", + "express-promise-router": "^4.1.0", "knex": "^0.95.1", "pg": "^8.3.0", "pg-connection-string": "^2.3.0", diff --git a/plugins/airbrake-backend/.eslintrc.js b/plugins/airbrake-backend/.eslintrc.js new file mode 100644 index 0000000000..16a033dbc6 --- /dev/null +++ b/plugins/airbrake-backend/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: [require.resolve('@backstage/cli/config/eslint.backend')], +}; diff --git a/plugins/airbrake-backend/README.md b/plugins/airbrake-backend/README.md new file mode 100644 index 0000000000..42369524d6 --- /dev/null +++ b/plugins/airbrake-backend/README.md @@ -0,0 +1,16 @@ +# airbrake-backend + +Welcome to the airbrake-backend backend plugin! + +_This plugin was created through the Backstage CLI_ + +## Getting started + +Your plugin has been added to the example app in this repository, meaning you'll be able to access it by running `yarn start` in the root directory, and then navigating to [/airbrake-backend](http://localhost:7007/airbrake-backend). + +Here is an example endpoint: http://localhost:7007/airbrake-backend/health + +You can also serve the plugin in isolation by running `yarn start` in the plugin directory. +This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads. + +It is only meant for local development. diff --git a/plugins/airbrake-backend/package.json b/plugins/airbrake-backend/package.json new file mode 100644 index 0000000000..01cca714b5 --- /dev/null +++ b/plugins/airbrake-backend/package.json @@ -0,0 +1,50 @@ +{ + "name": "backstage-plugin-airbrake-backend", + "version": "0.0.0", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "private": true, + "publishConfig": { + "access": "public", + "main": "dist/index.cjs.js", + "types": "dist/index.d.ts" + }, + "scripts": { + "start": "backstage-cli backend:dev", + "build": "backstage-cli backend:build", + "lint": "backstage-cli lint", + "test": "backstage-cli test", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", + "clean": "backstage-cli clean" + }, + "dependencies": { + "@backstage/backend-common": "^0.10.5", + "@backstage/config": "^0.1.13", + "@types/express": "*", + "express": "^4.17.1", + "express-promise-router": "^4.1.0", + "winston": "^3.2.1", + "cross-fetch": "^3.0.6", + "yn": "^4.0.0" + }, + "devDependencies": { + "@backstage/cli": "^0.13.1-next.0", + "@types/supertest": "^2.0.8", + "supertest": "^4.0.2", + "msw": "^0.35.0" + }, + "files": [ + "dist" + ], + "jest": { + "coverageThreshold": { + "global": { + "functions": 100, + "lines": 100, + "statements": 100 + } + } + } +} diff --git a/plugins/airbrake-backend/src/index.ts b/plugins/airbrake-backend/src/index.ts new file mode 100644 index 0000000000..ca73cb27ba --- /dev/null +++ b/plugins/airbrake-backend/src/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2020 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 * from './service/router'; diff --git a/plugins/airbrake-backend/src/run.ts b/plugins/airbrake-backend/src/run.ts new file mode 100644 index 0000000000..0a3ed2b7f0 --- /dev/null +++ b/plugins/airbrake-backend/src/run.ts @@ -0,0 +1,33 @@ +/* + * Copyright 2020 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 { getRootLogger } from '@backstage/backend-common'; +import yn from 'yn'; +import { startStandaloneServer } from './service/standaloneServer'; + +const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7007; +const enableCors = yn(process.env.PLUGIN_CORS, { default: false }); +const logger = getRootLogger(); + +startStandaloneServer({ port, enableCors, logger }).catch(err => { + logger.error(err); + process.exit(1); +}); + +process.on('SIGINT', () => { + logger.info('CTRL+C pressed; exiting.'); + process.exit(0); +}); diff --git a/plugins/airbrake-backend/src/service/router.test.ts b/plugins/airbrake-backend/src/service/router.test.ts new file mode 100644 index 0000000000..8b77a04348 --- /dev/null +++ b/plugins/airbrake-backend/src/service/router.test.ts @@ -0,0 +1,45 @@ +/* + * Copyright 2020 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 { getVoidLogger } from '@backstage/backend-common'; +import express from 'express'; +import request from 'supertest'; + +import { createRouter } from './router'; + +describe('createRouter', () => { + let app: express.Express; + + beforeAll(async () => { + const router = await createRouter({ + logger: getVoidLogger(), + }); + app = express().use(router); + }); + + beforeEach(() => { + jest.resetAllMocks(); + }); + + describe('GET /health', () => { + it('returns ok', async () => { + const response = await request(app).get('/health'); + + expect(response.status).toEqual(200); + expect(response.body).toEqual({ status: 'ok' }); + }); + }); +}); diff --git a/plugins/airbrake-backend/src/service/router.ts b/plugins/airbrake-backend/src/service/router.ts new file mode 100644 index 0000000000..9ceaa47627 --- /dev/null +++ b/plugins/airbrake-backend/src/service/router.ts @@ -0,0 +1,40 @@ +/* + * Copyright 2020 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 { errorHandler } from '@backstage/backend-common'; +import express from 'express'; +import Router from 'express-promise-router'; +import { Logger } from 'winston'; + +export interface RouterOptions { + logger: Logger; +} + +export async function createRouter( + options: RouterOptions, +): Promise { + const { logger } = options; + + const router = Router(); + router.use(express.json()); + + router.get('/health', (_, response) => { + logger.info('PONG!'); + response.send({ status: 'ok' }); + }); + router.use(errorHandler()); + return router; +} diff --git a/plugins/airbrake-backend/src/service/standaloneServer.ts b/plugins/airbrake-backend/src/service/standaloneServer.ts new file mode 100644 index 0000000000..6287bf9f97 --- /dev/null +++ b/plugins/airbrake-backend/src/service/standaloneServer.ts @@ -0,0 +1,50 @@ +/* + * Copyright 2020 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 { createServiceBuilder } from '@backstage/backend-common'; +import { Server } from 'http'; +import { Logger } from 'winston'; +import { createRouter } from './router'; + +export interface ServerOptions { + port: number; + enableCors: boolean; + logger: Logger; +} + +export async function startStandaloneServer( + options: ServerOptions, +): Promise { + const logger = options.logger.child({ service: 'airbrake-backend-backend' }); + logger.debug('Starting application server...'); + const router = await createRouter({ + logger, + }); + + let service = createServiceBuilder(module) + .setPort(options.port) + .addRouter('/airbrake-backend', router); + if (options.enableCors) { + service = service.enableCors({ origin: 'http://localhost:3000' }); + } + + return await service.start().catch(err => { + logger.error(err); + process.exit(1); + }); +} + +module.hot?.accept(); diff --git a/plugins/airbrake-backend/src/setupTests.ts b/plugins/airbrake-backend/src/setupTests.ts new file mode 100644 index 0000000000..d3232290a7 --- /dev/null +++ b/plugins/airbrake-backend/src/setupTests.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2020 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 {}; diff --git a/yarn.lock b/yarn.lock index 65307c3d71..a716334c57 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9026,7 +9026,7 @@ compare-func@^2.0.0: array-ify "^1.0.0" dot-prop "^5.1.0" -component-emitter@^1.2.1, component-emitter@^1.3.0: +component-emitter@^1.2.0, component-emitter@^1.2.1, component-emitter@^1.3.0: version "1.3.0" resolved "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== @@ -9307,7 +9307,7 @@ cookie@0.4.1, cookie@^0.4.1, cookie@~0.4.1: resolved "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1" integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA== -cookiejar@^2.1.3: +cookiejar@^2.1.0, cookiejar@^2.1.3: version "2.1.3" resolved "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.3.tgz#fc7a6216e408e74414b90230050842dacda75acc" integrity sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ== @@ -12344,7 +12344,7 @@ form-data@4.0.0, form-data@^4.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" -form-data@^2.3.2, form-data@^2.5.0: +form-data@^2.3.1, form-data@^2.3.2, form-data@^2.5.0: version "2.5.1" resolved "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4" integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA== @@ -12392,6 +12392,11 @@ formdata-node@^4.3.1: node-domexception "1.0.0" web-streams-polyfill "4.0.0-beta.1" +formidable@^1.2.0: + version "1.2.6" + resolved "https://registry.npmjs.org/formidable/-/formidable-1.2.6.tgz#d2a51d60162bbc9b4a055d8457a7c75315d1a168" + integrity sha512-KcpbcpuLNOwrEjnbpMC0gS+X8ciDoZE1kkqzat4a8vrprf+s9pKNQ/QIwWfbfs4ltgmFl3MD177SNTkve3BwGQ== + formidable@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/formidable/-/formidable-2.0.1.tgz#4310bc7965d185536f9565184dee74fbb75557ff" @@ -16969,7 +16974,7 @@ meros@1.1.4, meros@^1.1.4: resolved "https://registry.npmjs.org/meros/-/meros-1.1.4.tgz#c17994d3133db8b23807f62bec7f0cb276cfd948" integrity sha512-E9ZXfK9iQfG9s73ars9qvvvbSIkJZF5yOo9j4tcwM5tN8mUKfj/EKN5PzOr3ZH0y5wL7dLAHw3RVEfpQV9Q7VQ== -methods@^1.0.0, methods@^1.1.2, methods@~1.1.2: +methods@^1.0.0, methods@^1.1.1, methods@^1.1.2, methods@~1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= @@ -17324,7 +17329,7 @@ mime-types@^2.0.8, mime-types@^2.1.12, mime-types@^2.1.27, mime-types@^2.1.31, m dependencies: mime-db "1.51.0" -mime@1.6.0: +mime@1.6.0, mime@^1.4.1: version "1.6.0" resolved "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== @@ -20105,7 +20110,7 @@ qs@6.9.6: resolved "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz#26ed3c8243a431b2924aca84cc90471f35d5a0ee" integrity sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ== -qs@^6.10.1, qs@^6.10.2, qs@^6.9.1, qs@^6.9.4, qs@^6.9.6: +qs@^6.10.1, qs@^6.10.2, qs@^6.5.1, qs@^6.9.1, qs@^6.9.4, qs@^6.9.6: version "6.10.3" resolved "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz#d6cde1b2ffca87b5aa57889816c5f81535e22e8e" integrity sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ== @@ -22855,6 +22860,22 @@ sucrase@^3.18.0, sucrase@^3.20.2: pirates "^4.0.1" ts-interface-checker "^0.1.9" +superagent@^3.8.3: + version "3.8.3" + resolved "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz#460ea0dbdb7d5b11bc4f78deba565f86a178e128" + integrity sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA== + dependencies: + component-emitter "^1.2.0" + cookiejar "^2.1.0" + debug "^3.1.0" + extend "^3.0.0" + form-data "^2.3.1" + formidable "^1.2.0" + methods "^1.1.1" + mime "^1.4.1" + qs "^6.5.1" + readable-stream "^2.3.5" + superagent@^7.1.0: version "7.1.1" resolved "https://registry.npmjs.org/superagent/-/superagent-7.1.1.tgz#2ab187d38c3078c31c3771c0b751f10163a27136" @@ -22872,6 +22893,14 @@ superagent@^7.1.0: readable-stream "^3.6.0" semver "^7.3.5" +supertest@^4.0.2: + version "4.0.2" + resolved "https://registry.npmjs.org/supertest/-/supertest-4.0.2.tgz#c2234dbdd6dc79b6f15b99c8d6577b90e4ce3f36" + integrity sha512-1BAbvrOZsGA3YTCWqbmh14L0YEq0EGICX/nBnfkfVJn7SrxQV1I3pMYjSzG9y/7ZU2V9dWqyqk2POwxlb09duQ== + dependencies: + methods "^1.1.2" + superagent "^3.8.3" + supertest@^6.1.3, supertest@^6.1.6: version "6.2.2" resolved "https://registry.npmjs.org/supertest/-/supertest-6.2.2.tgz#04a5998fd3efaff187cb69f07a169755d655b001" From 58c213f0afcad25f1a045c164e22dabce7c5defe Mon Sep 17 00:00:00 2001 From: Karan Shah Date: Wed, 2 Feb 2022 15:21:24 +0000 Subject: [PATCH 029/344] Ignore generated code Signed-off-by: Karan Shah --- plugins/airbrake-backend/package.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/plugins/airbrake-backend/package.json b/plugins/airbrake-backend/package.json index 01cca714b5..7fa5e3d464 100644 --- a/plugins/airbrake-backend/package.json +++ b/plugins/airbrake-backend/package.json @@ -45,6 +45,11 @@ "lines": 100, "statements": 100 } - } + }, + "coveragePathIgnorePatterns": [ + "standaloneServer.ts", + "index.ts", + "run.ts" + ] } } From 64045884baaddc0e297fe2461270ee85f68d8113 Mon Sep 17 00:00:00 2001 From: Karan Shah Date: Thu, 3 Feb 2022 10:54:18 +0000 Subject: [PATCH 030/344] Added: - Config management - path rewrite Signed-off-by: Karan Shah --- plugins/airbrake-backend/package.json | 2 ++ .../src/config/ExtractAirbrakeConfig.ts | 26 +++++++++++++++++++ plugins/airbrake-backend/src/config/index.ts | 18 +++++++++++++ .../src/service/router.test.ts | 13 +++++++++- .../airbrake-backend/src/service/router.ts | 23 ++++++++++++++++ .../src/service/standaloneServer.ts | 9 ++++++- 6 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 plugins/airbrake-backend/src/config/ExtractAirbrakeConfig.ts create mode 100644 plugins/airbrake-backend/src/config/index.ts diff --git a/plugins/airbrake-backend/package.json b/plugins/airbrake-backend/package.json index 7fa5e3d464..3f174caee4 100644 --- a/plugins/airbrake-backend/package.json +++ b/plugins/airbrake-backend/package.json @@ -25,12 +25,14 @@ "@types/express": "*", "express": "^4.17.1", "express-promise-router": "^4.1.0", + "http-proxy-middleware": "^2.0.0", "winston": "^3.2.1", "cross-fetch": "^3.0.6", "yn": "^4.0.0" }, "devDependencies": { "@backstage/cli": "^0.13.1-next.0", + "@types/http-proxy-middleware": "^0.19.3", "@types/supertest": "^2.0.8", "supertest": "^4.0.2", "msw": "^0.35.0" diff --git a/plugins/airbrake-backend/src/config/ExtractAirbrakeConfig.ts b/plugins/airbrake-backend/src/config/ExtractAirbrakeConfig.ts new file mode 100644 index 0000000000..06f5e46e85 --- /dev/null +++ b/plugins/airbrake-backend/src/config/ExtractAirbrakeConfig.ts @@ -0,0 +1,26 @@ +/* + * Copyright 2022 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 { Config } from '@backstage/config'; + +export type AirbrakeConfig = { + apiKey: string; +}; + +export function extractAirbrakeConfig(config: Config): AirbrakeConfig { + return { + apiKey: config.getString('integrations.airbrake.apiKey'), + }; +} diff --git a/plugins/airbrake-backend/src/config/index.ts b/plugins/airbrake-backend/src/config/index.ts new file mode 100644 index 0000000000..6200baca53 --- /dev/null +++ b/plugins/airbrake-backend/src/config/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2022 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 { extractAirbrakeConfig } from './ExtractAirbrakeConfig'; +export type { AirbrakeConfig } from './ExtractAirbrakeConfig'; diff --git a/plugins/airbrake-backend/src/service/router.test.ts b/plugins/airbrake-backend/src/service/router.test.ts index 8b77a04348..ec21a340a3 100644 --- a/plugins/airbrake-backend/src/service/router.test.ts +++ b/plugins/airbrake-backend/src/service/router.test.ts @@ -17,15 +17,26 @@ import { getVoidLogger } from '@backstage/backend-common'; import express from 'express'; import request from 'supertest'; - +import { ConfigReader } from '@backstage/config'; import { createRouter } from './router'; +import { extractAirbrakeConfig } from '../config'; describe('createRouter', () => { let app: express.Express; beforeAll(async () => { + const config = new ConfigReader({ + integrations: { + airbrake: { + apiKey: 'fakeApiKey', + }, + }, + }); + const airbrakeConfig = extractAirbrakeConfig(config); + const router = await createRouter({ logger: getVoidLogger(), + airbrakeConfig, }); app = express().use(router); }); diff --git a/plugins/airbrake-backend/src/service/router.ts b/plugins/airbrake-backend/src/service/router.ts index 9ceaa47627..77b115f088 100644 --- a/plugins/airbrake-backend/src/service/router.ts +++ b/plugins/airbrake-backend/src/service/router.ts @@ -18,9 +18,12 @@ import { errorHandler } from '@backstage/backend-common'; import express from 'express'; import Router from 'express-promise-router'; import { Logger } from 'winston'; +import { createProxyMiddleware } from 'http-proxy-middleware'; +import { AirbrakeConfig } from '../config'; export interface RouterOptions { logger: Logger; + airbrakeConfig: AirbrakeConfig; } export async function createRouter( @@ -35,6 +38,26 @@ export async function createRouter( logger.info('PONG!'); response.send({ status: 'ok' }); }); + + router.use( + '/api', + createProxyMiddleware({ + target: 'https://api.airbrake.io/api', + changeOrigin: true, + pathRewrite: path => { + const apiKey = options.airbrakeConfig.apiKey; + + let newPath = path.replace(/.+?(\/api)/, ''); + if (newPath.includes('?')) { + newPath += `&key=${apiKey}`; + } else { + newPath += `?key=${apiKey}`; + } + return newPath; + }, + }), + ); + router.use(errorHandler()); return router; } diff --git a/plugins/airbrake-backend/src/service/standaloneServer.ts b/plugins/airbrake-backend/src/service/standaloneServer.ts index 6287bf9f97..5957dd0b7f 100644 --- a/plugins/airbrake-backend/src/service/standaloneServer.ts +++ b/plugins/airbrake-backend/src/service/standaloneServer.ts @@ -14,10 +14,14 @@ * limitations under the License. */ -import { createServiceBuilder } from '@backstage/backend-common'; +import { + createServiceBuilder, + loadBackendConfig, +} from '@backstage/backend-common'; import { Server } from 'http'; import { Logger } from 'winston'; import { createRouter } from './router'; +import { extractAirbrakeConfig } from '../config'; export interface ServerOptions { port: number; @@ -29,9 +33,12 @@ export async function startStandaloneServer( options: ServerOptions, ): Promise { const logger = options.logger.child({ service: 'airbrake-backend-backend' }); + const config = await loadBackendConfig({ logger, argv: process.argv }); + const airbrakeConfig = extractAirbrakeConfig(config); logger.debug('Starting application server...'); const router = await createRouter({ logger, + airbrakeConfig, }); let service = createServiceBuilder(module) From 811c710a21d65eef0ec0b10eba61b21edf8b8722 Mon Sep 17 00:00:00 2001 From: Jussi Hallila Date: Thu, 3 Feb 2022 12:15:12 +0100 Subject: [PATCH 031/344] Fix tech docs inline search on Postgres Fix bug where tech docs collator stores search indices with wrong entity ref casing. Make the collator to conform legacyPathCasing configuration option. Signed-off-by: Jussi Hallila --- .changeset/hip-carrots-suffer.md | 5 +++++ .../techdocs-backend/src/search/DefaultTechDocsCollator.ts | 4 +--- 2 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 .changeset/hip-carrots-suffer.md diff --git a/.changeset/hip-carrots-suffer.md b/.changeset/hip-carrots-suffer.md new file mode 100644 index 0000000000..3823cf982f --- /dev/null +++ b/.changeset/hip-carrots-suffer.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs-backend': patch +--- + +Fix bug where tech docs collator stores search indices with wrong entity ref casing. Make the collator to conform legacyPathCasing configuration option. diff --git a/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.ts b/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.ts index 5149fe6c09..987e8c6b15 100644 --- a/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.ts +++ b/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.ts @@ -145,9 +145,7 @@ export class DefaultTechDocsCollator implements DocumentCollator { path: doc.location, }), path: doc.location, - kind: entity.kind, - namespace: entity.metadata.namespace || 'default', - name: entity.metadata.name, + ...entityInfo, entityTitle: entity.metadata.title, componentType: entity.spec?.type?.toString() || 'other', lifecycle: (entity.spec?.lifecycle as string) || '', From d1eff621051b27472d0cecf3ae9a93f2829b4be5 Mon Sep 17 00:00:00 2001 From: Jussi Hallila Date: Thu, 3 Feb 2022 12:21:54 +0100 Subject: [PATCH 032/344] Fix tests. Signed-off-by: Jussi Hallila --- .../src/search/DefaultTechDocsCollator.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.test.ts b/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.test.ts index 7e7545ea1f..e81fc1e7a9 100644 --- a/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.test.ts +++ b/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.test.ts @@ -198,8 +198,8 @@ describe('DefaultTechDocsCollator', () => { componentType: entity!.spec!.type, lifecycle: entity!.spec!.lifecycle, owner: '', - kind: entity.kind, - name: entity.metadata.name, + kind: entity.kind.toLocaleLowerCase('en-US'), + name: entity.metadata.name.toLocaleLowerCase('en-US'), authorization: { resourceRef: `component:default/${entity.metadata.name}`, }, From a3dd1a8b6da0e0f258f27025bd699c612bace159 Mon Sep 17 00:00:00 2001 From: Karan Shah Date: Thu, 3 Feb 2022 11:51:47 +0000 Subject: [PATCH 033/344] Add a way to access the Airbrake secret from env Signed-off-by: Karan Shah --- app-config.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app-config.yaml b/app-config.yaml index b025064c74..1f03105cde 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -193,6 +193,8 @@ integrations: - host: amazonaws.com accessKeyId: ${AWS_ACCESS_KEY_ID} secretAccessKey: ${AWS_SECRET_ACCESS_KEY} + airbrake: + apiKey: ${AIRBRAKE_API_KEY} catalog: import: From 9723a4a96a3a6465a8125cb70feee343ea8ac1e5 Mon Sep 17 00:00:00 2001 From: Karan Shah Date: Fri, 4 Feb 2022 10:27:01 +0000 Subject: [PATCH 034/344] Write a test for pathRewrite Signed-off-by: Karan Shah --- .../src/service/router.test.ts | 35 ++++++++++++++----- .../airbrake-backend/src/service/router.ts | 29 +++++++++------ 2 files changed, 45 insertions(+), 19 deletions(-) diff --git a/plugins/airbrake-backend/src/service/router.test.ts b/plugins/airbrake-backend/src/service/router.test.ts index ec21a340a3..f36d36d6f1 100644 --- a/plugins/airbrake-backend/src/service/router.test.ts +++ b/plugins/airbrake-backend/src/service/router.test.ts @@ -18,13 +18,20 @@ import { getVoidLogger } from '@backstage/backend-common'; import express from 'express'; import request from 'supertest'; import { ConfigReader } from '@backstage/config'; -import { createRouter } from './router'; -import { extractAirbrakeConfig } from '../config'; +import { + createRouter, + generateAirbrakePathRewrite, + RouterOptions, +} from './router'; +import { AirbrakeConfig, extractAirbrakeConfig } from '../config'; describe('createRouter', () => { let app: express.Express; + let airbrakeConfig: AirbrakeConfig; + + beforeEach(async () => { + jest.resetAllMocks(); - beforeAll(async () => { const config = new ConfigReader({ integrations: { airbrake: { @@ -32,7 +39,7 @@ describe('createRouter', () => { }, }, }); - const airbrakeConfig = extractAirbrakeConfig(config); + airbrakeConfig = extractAirbrakeConfig(config); const router = await createRouter({ logger: getVoidLogger(), @@ -41,10 +48,6 @@ describe('createRouter', () => { app = express().use(router); }); - beforeEach(() => { - jest.resetAllMocks(); - }); - describe('GET /health', () => { it('returns ok', async () => { const response = await request(app).get('/health'); @@ -53,4 +56,20 @@ describe('createRouter', () => { expect(response.body).toEqual({ status: 'ok' }); }); }); + + describe('GET /api', () => { + it('appends the API Key properly', () => { + const options: RouterOptions = { + logger: getVoidLogger(), + airbrakeConfig, + }; + const pathRewrite = generateAirbrakePathRewrite(options) as ( + path: string, + ) => string; + + expect(pathRewrite('/airbrake-backend/api/random/endpoint')).toBe( + '/random/endpoint?key=fakeApiKey', + ); + }); + }); }); diff --git a/plugins/airbrake-backend/src/service/router.ts b/plugins/airbrake-backend/src/service/router.ts index 77b115f088..670a98eb78 100644 --- a/plugins/airbrake-backend/src/service/router.ts +++ b/plugins/airbrake-backend/src/service/router.ts @@ -20,12 +20,29 @@ import Router from 'express-promise-router'; import { Logger } from 'winston'; import { createProxyMiddleware } from 'http-proxy-middleware'; import { AirbrakeConfig } from '../config'; +import { Options } from 'http-proxy-middleware/dist/types'; export interface RouterOptions { logger: Logger; airbrakeConfig: AirbrakeConfig; } +export const generateAirbrakePathRewrite = ( + options: RouterOptions, +): Options['pathRewrite'] => { + const apiKey = options.airbrakeConfig.apiKey; + + return path => { + let newPath = path.replace(/.+?(\/api)/, ''); + if (newPath.includes('?')) { + newPath += `&key=${apiKey}`; + } else { + newPath += `?key=${apiKey}`; + } + return newPath; + }; +}; + export async function createRouter( options: RouterOptions, ): Promise { @@ -44,17 +61,7 @@ export async function createRouter( createProxyMiddleware({ target: 'https://api.airbrake.io/api', changeOrigin: true, - pathRewrite: path => { - const apiKey = options.airbrakeConfig.apiKey; - - let newPath = path.replace(/.+?(\/api)/, ''); - if (newPath.includes('?')) { - newPath += `&key=${apiKey}`; - } else { - newPath += `?key=${apiKey}`; - } - return newPath; - }, + pathRewrite: generateAirbrakePathRewrite(options), }), ); From c0d61279af21ff72d5da138aed829a6916e3f743 Mon Sep 17 00:00:00 2001 From: Karan Shah Date: Fri, 4 Feb 2022 10:50:50 +0000 Subject: [PATCH 035/344] Fix extensions.test.tsx Signed-off-by: Karan Shah --- plugins/airbrake/dev/index.tsx | 2 +- plugins/airbrake/src/api/mock/mock-api.ts | 8 +++++++- plugins/airbrake/src/extensions.test.tsx | 18 +++++++++--------- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/plugins/airbrake/dev/index.tsx b/plugins/airbrake/dev/index.tsx index 7f32d30b4a..8fc43a73a1 100644 --- a/plugins/airbrake/dev/index.tsx +++ b/plugins/airbrake/dev/index.tsx @@ -37,7 +37,7 @@ createDevApp()
- + diff --git a/plugins/airbrake/src/api/mock/mock-api.ts b/plugins/airbrake/src/api/mock/mock-api.ts index df0293da7c..4acfa51778 100644 --- a/plugins/airbrake/src/api/mock/mock-api.ts +++ b/plugins/airbrake/src/api/mock/mock-api.ts @@ -19,9 +19,15 @@ import { AirbrakeApi } from '../airbrake-api'; import mockGroupsData from './airbrake-groups-api-mock.json'; export class MockAirbrakeApi implements AirbrakeApi { + waitTimeInMillis: number; + + constructor(waitTimeInMillis = 10) { + this.waitTimeInMillis = waitTimeInMillis; + } + fetchGroups(): Promise { return new Promise(resolve => { - setTimeout(() => resolve(mockGroupsData), 800); + setTimeout(() => resolve(mockGroupsData), this.waitTimeInMillis); }); } } diff --git a/plugins/airbrake/src/extensions.test.tsx b/plugins/airbrake/src/extensions.test.tsx index 5532621848..3b2d1927d9 100644 --- a/plugins/airbrake/src/extensions.test.tsx +++ b/plugins/airbrake/src/extensions.test.tsx @@ -15,23 +15,23 @@ */ import React from 'react'; import { EntityAirbrakeContent } from './extensions'; +import { Route } from 'react-router'; import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; import { airbrakeApiRef, MockAirbrakeApi } from './api'; -import { rootRouteRef } from './routes'; +import { createEntity } from './api/mock/mock-entity'; +import { EntityProvider } from '@backstage/plugin-catalog-react'; describe('The Airbrake entity', () => { it('should render the content properly', async () => { const rendered = await renderInTestApp( - + + } /> + , - { - mountedRoutes: { - '/': rootRouteRef, - }, - routeEntries: ['/'], - }, ); - expect(rendered.getByText('ChunkLoadError')).toBeInTheDocument(); + await expect( + rendered.findByText('ChunkLoadError'), + ).resolves.toBeInTheDocument(); }); }); From f7ad6f0795c7f20c53856b9027aa72881cfe3796 Mon Sep 17 00:00:00 2001 From: Karan Shah Date: Fri, 4 Feb 2022 11:59:40 +0000 Subject: [PATCH 036/344] Fix EntityAirbrakeWidget.test.tsx Signed-off-by: Karan Shah --- .../EntityAirbrakeWidget/EntityAirbrakeWidget.test.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.test.tsx b/plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.test.tsx index 5fd1aa9bf5..7b3bd6595b 100644 --- a/plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.test.tsx +++ b/plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.test.tsx @@ -17,13 +17,16 @@ import React from 'react'; import { EntityAirbrakeWidget } from './EntityAirbrakeWidget'; import exampleData from '../../api/mock/airbrake-groups-api-mock.json'; -import { renderInTestApp } from '@backstage/test-utils'; +import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; import { createEntity } from '../../api/mock/mock-entity'; +import { airbrakeApiRef, MockAirbrakeApi } from '../../api'; describe('EntityAirbrakeContent', () => { it('renders all errors sent from Airbrake', async () => { const table = await renderInTestApp( - , + + + , ); expect(exampleData.groups.length).toBeGreaterThan(0); for (const group of exampleData.groups) { From e8731ffbc6ddd19d61901ffe1a417bde6f8f7157 Mon Sep 17 00:00:00 2001 From: Karan Shah Date: Fri, 4 Feb 2022 14:06:18 +0000 Subject: [PATCH 037/344] Added a test for the annotation missing component Signed-off-by: Karan Shah --- .../EntityAirbrakeWidget.test.tsx | 12 ++++++++++++ plugins/airbrake/src/plugin.test.ts | 7 +++++++ 2 files changed, 19 insertions(+) diff --git a/plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.test.tsx b/plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.test.tsx index 7b3bd6595b..5baabe92f6 100644 --- a/plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.test.tsx +++ b/plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.test.tsx @@ -35,4 +35,16 @@ describe('EntityAirbrakeContent', () => { ).toBeInTheDocument(); } }); + + it('states that the annotation is missing if no annotation is provided', async () => { + const table = await renderInTestApp( + + + , + ); + expect(exampleData.groups.length).toBeGreaterThan(0); + await expect( + table.findByText('Missing Annotation'), + ).resolves.toBeInTheDocument(); + }); }); diff --git a/plugins/airbrake/src/plugin.test.ts b/plugins/airbrake/src/plugin.test.ts index c8e9c30e83..ad7865713f 100644 --- a/plugins/airbrake/src/plugin.test.ts +++ b/plugins/airbrake/src/plugin.test.ts @@ -15,9 +15,16 @@ */ import { airbrakePlugin } from './plugin'; +import { ProductionAirbrakeApi } from './api'; describe('catalog', () => { it('should export plugin', () => { expect(airbrakePlugin).toBeDefined(); }); + + it('should have at least one API, the production API', () => { + const apiFactories = Array.from(airbrakePlugin.getApis()); + expect(apiFactories.length).toBe(1); + expect(apiFactories[0].factory({})).toBeInstanceOf(ProductionAirbrakeApi); + }); }); From d5470ec47f5191ce637226cb1390efde70be98a3 Mon Sep 17 00:00:00 2001 From: Karan Shah Date: Fri, 4 Feb 2022 14:24:03 +0000 Subject: [PATCH 038/344] Added a test for appending the API key to a URL parameter Signed-off-by: Karan Shah --- .../src/service/router.test.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/plugins/airbrake-backend/src/service/router.test.ts b/plugins/airbrake-backend/src/service/router.test.ts index f36d36d6f1..7fae43fe8c 100644 --- a/plugins/airbrake-backend/src/service/router.test.ts +++ b/plugins/airbrake-backend/src/service/router.test.ts @@ -58,7 +58,7 @@ describe('createRouter', () => { }); describe('GET /api', () => { - it('appends the API Key properly', () => { + it('appends the API Key properly with no other url parameters', () => { const options: RouterOptions = { logger: getVoidLogger(), airbrakeConfig, @@ -71,5 +71,21 @@ describe('createRouter', () => { '/random/endpoint?key=fakeApiKey', ); }); + + it('appends the API Key properly despite there being other URL parameters', () => { + const options: RouterOptions = { + logger: getVoidLogger(), + airbrakeConfig, + }; + const pathRewrite = generateAirbrakePathRewrite(options) as ( + path: string, + ) => string; + + expect( + pathRewrite( + '/airbrake-backend/api/random/endpoint?param1=123¶m2=abc', + ), + ).toBe('/random/endpoint?param1=123¶m2=abc&key=fakeApiKey'); + }); }); }); From 761b6ead75ca584ace781d13e4d4545a47b9973c Mon Sep 17 00:00:00 2001 From: Karan Shah Date: Fri, 4 Feb 2022 14:29:16 +0000 Subject: [PATCH 039/344] Added /v4 to tests to better explain what it's trying to do Signed-off-by: Karan Shah --- plugins/airbrake-backend/src/service/router.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/airbrake-backend/src/service/router.test.ts b/plugins/airbrake-backend/src/service/router.test.ts index 7fae43fe8c..d4d86722a8 100644 --- a/plugins/airbrake-backend/src/service/router.test.ts +++ b/plugins/airbrake-backend/src/service/router.test.ts @@ -67,8 +67,8 @@ describe('createRouter', () => { path: string, ) => string; - expect(pathRewrite('/airbrake-backend/api/random/endpoint')).toBe( - '/random/endpoint?key=fakeApiKey', + expect(pathRewrite('/airbrake-backend/api/v4/random/endpoint')).toBe( + '/v4/random/endpoint?key=fakeApiKey', ); }); @@ -83,9 +83,9 @@ describe('createRouter', () => { expect( pathRewrite( - '/airbrake-backend/api/random/endpoint?param1=123¶m2=abc', + '/airbrake-backend/api/v4/random/endpoint?param1=123¶m2=abc', ), - ).toBe('/random/endpoint?param1=123¶m2=abc&key=fakeApiKey'); + ).toBe('/v4/random/endpoint?param1=123¶m2=abc&key=fakeApiKey'); }); }); }); From d408d210c21b1e638ed16af30b343276035b4d9b Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Fri, 4 Feb 2022 14:17:36 +0100 Subject: [PATCH 040/344] Remove duplicated SearchContextProvider Signed-off-by: Vincenzo Scamporlino --- packages/app/src/components/Root/Root.tsx | 9 ++------- packages/core-app-api/src/apis/system/ApiProvider.tsx | 7 ++++++- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx index 57f1fa536f..2441ace27b 100644 --- a/packages/app/src/components/Root/Root.tsx +++ b/packages/app/src/components/Root/Root.tsx @@ -34,10 +34,7 @@ import { Settings as SidebarSettings, UserSettingsSignInAvatar, } from '@backstage/plugin-user-settings'; -import { - SidebarSearchModal, - SearchContextProvider, -} from '@backstage/plugin-search'; +import { SidebarSearchModal } from '@backstage/plugin-search'; import { Shortcuts } from '@backstage/plugin-shortcuts'; import { Sidebar, @@ -89,9 +86,7 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( } to="/search"> - - - + }> diff --git a/packages/core-app-api/src/apis/system/ApiProvider.tsx b/packages/core-app-api/src/apis/system/ApiProvider.tsx index c75f883a51..f1ebf38d71 100644 --- a/packages/core-app-api/src/apis/system/ApiProvider.tsx +++ b/packages/core-app-api/src/apis/system/ApiProvider.tsx @@ -14,7 +14,12 @@ * limitations under the License. */ -import React, { useContext, ReactNode, PropsWithChildren } from 'react'; +import React, { + useContext, + ReactNode, + PropsWithChildren, + useEffect, +} from 'react'; import PropTypes from 'prop-types'; import { ApiHolder } from '@backstage/core-plugin-api'; import { ApiAggregator } from './ApiAggregator'; From 4a4ab7c6034837a59b9781e9f0592c68715d6d4d Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Fri, 4 Feb 2022 14:22:29 +0100 Subject: [PATCH 041/344] Invoke search only when SearchModal is visible Signed-off-by: Vincenzo Scamporlino --- .../src/apis/system/ApiProvider.tsx | 7 +-- .../src/components/SearchBar/SearchBar.tsx | 17 ++++--- .../SearchModal/SearchModal.test.tsx | 32 +++++++++++--- .../components/SearchModal/SearchModal.tsx | 44 ++++++++++++------- .../SidebarSearchModal/SidebarSearchModal.tsx | 22 +++++++--- 5 files changed, 81 insertions(+), 41 deletions(-) diff --git a/packages/core-app-api/src/apis/system/ApiProvider.tsx b/packages/core-app-api/src/apis/system/ApiProvider.tsx index f1ebf38d71..c75f883a51 100644 --- a/packages/core-app-api/src/apis/system/ApiProvider.tsx +++ b/packages/core-app-api/src/apis/system/ApiProvider.tsx @@ -14,12 +14,7 @@ * limitations under the License. */ -import React, { - useContext, - ReactNode, - PropsWithChildren, - useEffect, -} from 'react'; +import React, { useContext, ReactNode, PropsWithChildren } from 'react'; import PropTypes from 'prop-types'; import { ApiHolder } from '@backstage/core-plugin-api'; import { ApiAggregator } from './ApiAggregator'; diff --git a/plugins/search/src/components/SearchBar/SearchBar.tsx b/plugins/search/src/components/SearchBar/SearchBar.tsx index 2ddec22294..a203d27c93 100644 --- a/plugins/search/src/components/SearchBar/SearchBar.tsx +++ b/plugins/search/src/components/SearchBar/SearchBar.tsx @@ -169,13 +169,16 @@ export type SearchBarProps = Partial; export const SearchBar = ({ onChange, ...props }: SearchBarProps) => { const { term, setTerm } = useSearch(); - const handleChange = (newValue: string) => { - if (onChange) { - onChange(newValue); - } else { - setTerm(newValue); - } - }; + const handleChange = useCallback( + (newValue: string) => { + if (onChange) { + onChange(newValue); + } else { + setTerm(newValue); + } + }, + [onChange, setTerm], + ); return ; }; diff --git a/plugins/search/src/components/SearchModal/SearchModal.test.tsx b/plugins/search/src/components/SearchModal/SearchModal.test.tsx index b59be710de..d3b2a825e2 100644 --- a/plugins/search/src/components/SearchModal/SearchModal.test.tsx +++ b/plugins/search/src/components/SearchModal/SearchModal.test.tsx @@ -24,7 +24,6 @@ import { rootRouteRef } from '../../plugin'; import { searchApiRef } from '../../apis'; import { SearchModal } from './SearchModal'; -import { SearchContextProvider } from '../SearchContext'; describe('SearchModal', () => { const query = jest.fn().mockResolvedValue({ results: [] }); @@ -34,14 +33,16 @@ describe('SearchModal', () => { [searchApiRef, { query }], ); + beforeEach(() => { + query.mockClear(); + }); + const toggleModal = jest.fn(); it('Should render the Modal correctly', async () => { await renderInTestApp( - - - + , { mountedRoutes: { @@ -51,14 +52,13 @@ describe('SearchModal', () => { ); expect(screen.getByRole('dialog')).toBeInTheDocument(); + expect(query).toHaveBeenCalledTimes(1); }); it('Calls toggleModal handler', async () => { await renderInTestApp( - - - + , { mountedRoutes: { @@ -66,7 +66,25 @@ describe('SearchModal', () => { }, }, ); + + expect(query).toHaveBeenCalledTimes(1); userEvent.keyboard('{esc}'); expect(toggleModal).toHaveBeenCalledTimes(1); }); + + it('should render SearchModal hiding its content', async () => { + const { getByTestId } = await renderInTestApp( + + , + { + mountedRoutes: { + '/search': rootRouteRef, + }, + }, + ); + + expect(getByTestId('search-bar-next')).toBeInTheDocument(); + expect(getByTestId('search-bar-next')).not.toBeVisible(); + }); }); diff --git a/plugins/search/src/components/SearchModal/SearchModal.tsx b/plugins/search/src/components/SearchModal/SearchModal.tsx index 1f0a879f96..ce3f51dac3 100644 --- a/plugins/search/src/components/SearchModal/SearchModal.tsx +++ b/plugins/search/src/components/SearchModal/SearchModal.tsx @@ -39,6 +39,7 @@ import { rootRouteRef } from '../../plugin'; export interface SearchModalProps { open?: boolean; + hidden?: boolean; toggleModal: () => void; } @@ -57,7 +58,7 @@ const useStyles = makeStyles(theme => ({ viewResultsLink: { verticalAlign: '0.5em' }, })); -export const Modal = ({ open = true, toggleModal }: SearchModalProps) => { +export const Modal = ({ toggleModal }: SearchModalProps) => { const getSearchLink = useRouteRef(rootRouteRef); const classes = useStyles(); @@ -75,16 +76,7 @@ export const Modal = ({ open = true, toggleModal }: SearchModalProps) => { }; return ( - + <> @@ -139,14 +131,34 @@ export const Modal = ({ open = true, toggleModal }: SearchModalProps) => { - + ); }; -export const SearchModal = ({ open = true, toggleModal }: SearchModalProps) => { +export const SearchModal = ({ + open = true, + hidden, + toggleModal, +}: SearchModalProps) => { + const classes = useStyles(); + return ( - - - + ); }; diff --git a/plugins/search/src/components/SidebarSearchModal/SidebarSearchModal.tsx b/plugins/search/src/components/SidebarSearchModal/SidebarSearchModal.tsx index b8f20de67d..077ad1016e 100644 --- a/plugins/search/src/components/SidebarSearchModal/SidebarSearchModal.tsx +++ b/plugins/search/src/components/SidebarSearchModal/SidebarSearchModal.tsx @@ -13,30 +13,42 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; +import React, { useCallback, useState } from 'react'; import SearchIcon from '@material-ui/icons/Search'; import { SidebarItem } from '@backstage/core-components'; import { IconComponent } from '@backstage/core-plugin-api'; import { SearchModal } from '../SearchModal'; -import { useSearch } from '../SearchContext'; export type SidebarSearchModalProps = { icon?: IconComponent; }; export const SidebarSearchModal = (props: SidebarSearchModalProps) => { - const { open, toggleModal } = useSearch(); + const [state, setState] = useState({ hidden: true, opened: false }); const Icon = props.icon ? props.icon : SearchIcon; + const handleToggleModal = useCallback( + () => + setState(previousState => ({ + opened: true, + hidden: !previousState.hidden, + })), + [], + ); + return ( <> +