diff --git a/plugins/airbrake/package.json b/plugins/airbrake/package.json index 7342fdd839..47dc6b31c9 100644 --- a/plugins/airbrake/package.json +++ b/plugins/airbrake/package.json @@ -27,6 +27,7 @@ "@backstage/core-components": "^0.8.10", "@backstage/core-plugin-api": "^0.7.0", "@backstage/dev-utils": "^0.2.23", + "@backstage/errors": "^0.2.2", "@backstage/plugin-catalog-react": "^0.7.0", "@backstage/test-utils": "^0.2.6", "@backstage/theme": "^0.2.15", diff --git a/plugins/airbrake/src/api/AirbrakeApi.ts b/plugins/airbrake/src/api/AirbrakeApi.ts index 672b1f92f4..3abe745b5b 100644 --- a/plugins/airbrake/src/api/AirbrakeApi.ts +++ b/plugins/airbrake/src/api/AirbrakeApi.ts @@ -16,6 +16,7 @@ import { Groups } from './airbrakeGroups'; import { createApiRef } from '@backstage/core-plugin-api'; +import { CustomErrorBase } from '@backstage/errors'; export const airbrakeApiRef = createApiRef({ id: 'plugin.airbrake.service', @@ -25,7 +26,7 @@ export interface AirbrakeApi { fetchGroups(projectId: string): Promise; } -export class NoProjectIdError extends Error { +export class NoProjectIdError extends CustomErrorBase { constructor() { super('Project ID is not present'); } diff --git a/plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.test.tsx b/plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.test.tsx index 64f1f11223..e8d09b5e65 100644 --- a/plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.test.tsx +++ b/plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.test.tsx @@ -18,7 +18,6 @@ import React from 'react'; import { EntityAirbrakeWidget } from './EntityAirbrakeWidget'; import exampleData from '../../api/mock/airbrakeGroupsApiMock.json'; import { - MockErrorApi, renderInTestApp, setupRequestMockHandlers, TestApiProvider, @@ -30,7 +29,6 @@ import { MockAirbrakeApi, ProductionAirbrakeApi, } from '../../api'; -import { errorApiRef } from '@backstage/core-plugin-api'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; @@ -53,14 +51,9 @@ describe('EntityAirbrakeWidget', () => { }); it('states that the annotation is missing if no project ID annotation is provided but does not error', async () => { - const mockErrorApi = new MockErrorApi({ collect: true }); - const widget = await renderInTestApp( , @@ -68,7 +61,9 @@ describe('EntityAirbrakeWidget', () => { await expect( widget.findByText('Missing Annotation'), ).resolves.toBeInTheDocument(); - expect(mockErrorApi.getErrors().length).toBe(0); + expect( + widget.queryByText(/.*Failed fetching Airbrake groups.*/), + ).not.toBeInTheDocument(); }); it('states that an error occurred if the API call fails', async () => { @@ -80,14 +75,10 @@ describe('EntityAirbrakeWidget', () => { }, ), ); - const mockErrorApi = new MockErrorApi({ collect: true }); const widget = await renderInTestApp( , @@ -96,9 +87,10 @@ describe('EntityAirbrakeWidget', () => { await expect( widget.findByText(/.*there was an issue communicating with Airbrake.*/), ).resolves.toBeInTheDocument(); - expect(mockErrorApi.getErrors().length).toBe(1); - expect(mockErrorApi.getErrors()[0].error.message).toStrictEqual( - 'Failed fetching Airbrake groups', - ); + expect( + widget.getByRole('heading', { + name: /.*Failed fetching Airbrake groups.*/, + }), + ).toBeInTheDocument(); }); }); diff --git a/plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.tsx b/plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.tsx index 29260427a9..f235a3e2f5 100644 --- a/plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.tsx +++ b/plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.tsx @@ -14,10 +14,11 @@ * limitations under the License. */ import { Entity } from '@backstage/catalog-model'; -import React, { useEffect, useState } from 'react'; +import React from 'react'; import { Grid, Typography } from '@material-ui/core'; import { EmptyState, + ErrorPanel, InfoCard, MissingAnnotationEmptyState, Progress, @@ -25,7 +26,7 @@ import { 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 { useApi } from '@backstage/core-plugin-api'; import { airbrakeApiRef } from '../../api'; import useAsync from 'react-use/lib/useAsync'; import { AIRBRAKE_PROJECT_ID_ANNOTATION, useProjectId } from '../useProjectId'; @@ -47,33 +48,22 @@ export const EntityAirbrakeWidget = ({ entity }: { entity: Entity }) => { const classes = useStyles(); const projectId = useProjectId(entity); - const errorApi = useApi(errorApiRef); const airbrakeApi = useApi(airbrakeApiRef); - const [componentState, setComponentState] = useState( - ComponentState.Loading, - ); + let componentState = ComponentState.Loading; - const { loading, value } = useAsync(async () => { - try { - const result = await airbrakeApi.fetchGroups(projectId); - setComponentState(ComponentState.Loaded); - return result; - } catch (e) { - if (!projectId) { - setComponentState(ComponentState.NoProjectId); - } else { - setComponentState(ComponentState.Error); - errorApi.post(e); - } - throw e; - } - }, [airbrakeApi, errorApi, projectId]); + const { loading, value, error } = useAsync(() => { + return airbrakeApi.fetchGroups(projectId); + }, [airbrakeApi, projectId]); - useEffect(() => { - if (loading) { - setComponentState(ComponentState.Loading); - } - }, [loading]); + if (loading) { + componentState = ComponentState.Loading; + } else if (!projectId) { + componentState = ComponentState.NoProjectId; + } else if (error) { + componentState = ComponentState.Error; + } else if (value) { + componentState = ComponentState.Loaded; + } switch (componentState) { case ComponentState.Loaded: @@ -103,11 +93,14 @@ export const EntityAirbrakeWidget = ({ entity }: { entity: Entity }) => { case ComponentState.Error: default: return ( - + <> + + + ); } };