Merge pull request #9876 from simplybusiness/airbrake-no-projectid-no-error

Airbrake Plugin: No API calls made or errors created if there is no project ID
This commit is contained in:
Fredrik Adelöw
2022-03-09 19:49:07 +01:00
committed by GitHub
3 changed files with 59 additions and 58 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-airbrake': patch
---
Fix a bug where API calls were being made and errors were being added to the snack bar when no project ID was present. This is a common use case for components that haven't added the Airbrake plugin annotation to their `catalog-info.yaml`.
@@ -18,23 +18,21 @@ import React from 'react';
import { EntityAirbrakeWidget } from './EntityAirbrakeWidget';
import exampleData from '../../api/mock/airbrakeGroupsApiMock.json';
import {
MockErrorApi,
renderInTestApp,
setupRequestMockHandlers,
TestApiProvider,
} from '@backstage/test-utils';
import { createEntity } from '../../api';
import {
airbrakeApiRef,
createEntity,
localDiscoveryApi,
MockAirbrakeApi,
ProductionAirbrakeApi,
} from '../../api';
import { errorApiRef } from '@backstage/core-plugin-api';
import { rest } from 'msw';
import { setupServer } from 'msw/node';
describe('EntityAirbrakeContent', () => {
describe('EntityAirbrakeWidget', () => {
const worker = setupServer();
setupRequestMockHandlers(worker);
@@ -52,15 +50,20 @@ describe('EntityAirbrakeContent', () => {
}
});
it('states that the annotation is missing if no project ID annotation is provided', async () => {
it('states that the annotation is missing if no project ID annotation is provided but does not error', async () => {
const widget = await renderInTestApp(
<TestApiProvider apis={[[airbrakeApiRef, new MockAirbrakeApi()]]}>
<TestApiProvider
apis={[[airbrakeApiRef, new ProductionAirbrakeApi(localDiscoveryApi)]]}
>
<EntityAirbrakeWidget entity={createEntity()} />
</TestApiProvider>,
);
await expect(
widget.findByText('Missing Annotation'),
).resolves.toBeInTheDocument();
expect(
widget.queryByText(/.*Failed fetching Airbrake groups.*/),
).not.toBeInTheDocument();
});
it('states that an error occurred if the API call fails', async () => {
@@ -72,14 +75,10 @@ describe('EntityAirbrakeContent', () => {
},
),
);
const mockErrorApi = new MockErrorApi({ collect: true });
const widget = await renderInTestApp(
<TestApiProvider
apis={[
[airbrakeApiRef, new ProductionAirbrakeApi(localDiscoveryApi)],
[errorApiRef, mockErrorApi],
]}
apis={[[airbrakeApiRef, new ProductionAirbrakeApi(localDiscoveryApi)]]}
>
<EntityAirbrakeWidget entity={createEntity(123)} />
</TestApiProvider>,
@@ -88,9 +87,10 @@ describe('EntityAirbrakeContent', () => {
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();
});
});
@@ -14,10 +14,11 @@
* limitations under the License.
*/
import { Entity } from '@backstage/catalog-model';
import React, { useEffect } 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';
@@ -40,55 +41,50 @@ export const EntityAirbrakeWidget = ({ entity }: { entity: Entity }) => {
const classes = useStyles();
const projectId = useProjectId(entity);
const errorApi = useApi<ErrorApi>(errorApiRef);
const airbrakeApi = useApi(airbrakeApiRef);
const { loading, value, error } = useAsync(
() => airbrakeApi.fetchGroups(projectId),
[airbrakeApi, projectId],
);
useEffect(() => {
if (error) {
errorApi.post(error);
const { loading, value, error } = useAsync(() => {
if (!projectId) {
return Promise.resolve(undefined);
}
}, [error, errorApi]);
if (loading || !projectId || error) {
return airbrakeApi.fetchGroups(projectId);
}, [airbrakeApi, projectId]);
if (!projectId) {
return (
<InfoCard title="Airbrake groups" variant="gridItem">
{loading && <Progress />}
{!loading && !projectId && (
<MissingAnnotationEmptyState
annotation={AIRBRAKE_PROJECT_ID_ANNOTATION}
/>
)}
{!loading && error && (
<EmptyState
missing="info"
title="No information to display"
description={`There is no Airbrake project with id '${projectId}' or there was an issue communicating with Airbrake.`}
/>
)}
</InfoCard>
<MissingAnnotationEmptyState
annotation={AIRBRAKE_PROJECT_ID_ANNOTATION}
/>
);
} else if (loading) {
return <Progress />;
} else if (value) {
return (
<Grid container spacing={3} direction="column">
{value.groups?.map(group => (
<Grid item key={group.id}>
{group.errors?.map(groupError => (
<InfoCard title={groupError.type} key={hash(groupError)}>
<Typography variant="body1" className={classes.multilineText}>
{groupError.message}
</Typography>
</InfoCard>
))}
</Grid>
))}
</Grid>
);
}
return (
<Grid container spacing={3} direction="column">
{value?.groups?.map(group => (
<Grid item key={group.id}>
{group.errors?.map(groupError => (
<InfoCard title={groupError.type} key={hash(groupError)}>
<Typography variant="body1" className={classes.multilineText}>
{groupError.message}
</Typography>
</InfoCard>
))}
</Grid>
))}
</Grid>
<>
{error && <ErrorPanel error={error} />}
<EmptyState
missing="info"
title="No information to display"
description={`There is no Airbrake project with id '${projectId}' or there was an issue communicating with Airbrake.`}
/>
</>
);
};