Merge pull request #18399 from Zaperex/lighthouse
feat(lighthouse): add more verbose components for "no audits found" case
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/plugin-lighthouse': patch
|
||||
---
|
||||
|
||||
Added more verbose components (used to render `null`) when no audits for a website corresponding to the provided url were found.
|
||||
Added `Create New Audit` button for the `AuditListForEntity` component used by `EntityLighthouseContent` and `EmbeddedRouter`.
|
||||
Removed error alert from `errorApi` if error was due to no audits being found for a website (empty database query result).
|
||||
@@ -409,6 +409,7 @@ unregistration
|
||||
untracked
|
||||
upsert
|
||||
upvote
|
||||
url
|
||||
URIs
|
||||
URLs
|
||||
utils
|
||||
|
||||
@@ -26,16 +26,31 @@ import { lighthouseApiRef } from '../../api';
|
||||
import { useWebsiteForEntity } from '../../hooks/useWebsiteForEntity';
|
||||
import * as data from '../../__fixtures__/website-list-response.json';
|
||||
import { AuditListForEntity } from './AuditListForEntity';
|
||||
import { rootRouteRef } from '../../plugin';
|
||||
import { fireEvent, screen } from '@testing-library/react';
|
||||
|
||||
jest.mock('../../hooks/useWebsiteForEntity', () => ({
|
||||
useWebsiteForEntity: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('react-router-dom', () => {
|
||||
const actual = jest.requireActual('react-router-dom');
|
||||
const mockNavigation = jest.fn();
|
||||
return {
|
||||
...actual,
|
||||
useNavigate: jest.fn(() => mockNavigation),
|
||||
};
|
||||
});
|
||||
|
||||
const useWebsiteForEntityMock = useWebsiteForEntity as jest.Mock;
|
||||
|
||||
const websiteListResponse = data as WebsiteListResponse;
|
||||
const entityWebsite = websiteListResponse.items[0];
|
||||
|
||||
const testAppOptions = {
|
||||
mountedRoutes: { '/': rootRouteRef },
|
||||
};
|
||||
|
||||
describe('<AuditListTableForEntity />', () => {
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
@@ -73,39 +88,77 @@ describe('<AuditListTableForEntity />', () => {
|
||||
loading: false,
|
||||
error: null,
|
||||
});
|
||||
const { findByText } = await renderInTestApp(subject());
|
||||
expect(await findByText(entityWebsite.url)).toBeInTheDocument();
|
||||
const rendered = await renderInTestApp(subject(), testAppOptions);
|
||||
const create_audit_button = await rendered.findByText('Create New Audit');
|
||||
const support_button = await rendered.findByText('Support');
|
||||
expect(await rendered.findByText(entityWebsite.url)).toBeInTheDocument();
|
||||
expect(await rendered.findByText('Latest Audit')).toBeInTheDocument();
|
||||
expect(create_audit_button).toBeInTheDocument();
|
||||
expect(support_button).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders a Progress element where the data is loading', async () => {
|
||||
it('renders a Progress element when the data is loading', async () => {
|
||||
useWebsiteForEntityMock.mockReturnValue({
|
||||
value: null,
|
||||
loading: true,
|
||||
error: null,
|
||||
});
|
||||
|
||||
const { findByTestId } = await renderInTestApp(subject());
|
||||
const { findByTestId } = await renderInTestApp(subject(), testAppOptions);
|
||||
expect(await findByTestId('progress')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders nothing where there is an error loading data', async () => {
|
||||
it('renders a WarningPanel when there is an error loading data', async () => {
|
||||
useWebsiteForEntityMock.mockReturnValue({
|
||||
value: null,
|
||||
loading: false,
|
||||
error: 'error',
|
||||
error: { name: 'error', message: 'error loading data' },
|
||||
});
|
||||
const { queryByTestId } = await renderInTestApp(subject());
|
||||
expect(queryByTestId('AuditListTable')).toBeNull();
|
||||
await renderInTestApp(subject(), testAppOptions);
|
||||
const expandIcon = screen.getByText('Error: Could not load audit list.');
|
||||
fireEvent.click(expandIcon);
|
||||
expect(
|
||||
screen.getByText('Error: Could not load audit list.'),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText('error loading data')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders nothing where there is not data', async () => {
|
||||
it('renders an empty table when there is no data', async () => {
|
||||
useWebsiteForEntityMock.mockReturnValue({
|
||||
value: null,
|
||||
loading: false,
|
||||
error: null,
|
||||
});
|
||||
|
||||
const { queryByTestId } = await renderInTestApp(subject());
|
||||
expect(queryByTestId('AuditListTable')).toBeNull();
|
||||
const rendered = await renderInTestApp(subject(), testAppOptions);
|
||||
const create_audit_button = await rendered.findByText('Create New Audit');
|
||||
const support_button = await rendered.findByText('Support');
|
||||
expect(
|
||||
await rendered.findByText('No records to display'),
|
||||
).toBeInTheDocument();
|
||||
expect(await rendered.findByText('Latest Audit')).toBeInTheDocument();
|
||||
expect(create_audit_button).toBeInTheDocument();
|
||||
expect(support_button).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders an empty table when there is no data and error loading data due to empty database query result', async () => {
|
||||
useWebsiteForEntityMock.mockReturnValue({
|
||||
value: null,
|
||||
loading: false,
|
||||
error: {
|
||||
name: 'error',
|
||||
message: 'no audited website found for url unit-test-url',
|
||||
},
|
||||
});
|
||||
|
||||
const rendered = await renderInTestApp(subject(), testAppOptions);
|
||||
const create_audit_button = await rendered.findByText('Create New Audit');
|
||||
const support_button = await rendered.findByText('Support');
|
||||
expect(
|
||||
await rendered.findByText('No records to display'),
|
||||
).toBeInTheDocument();
|
||||
expect(await rendered.findByText('Latest Audit')).toBeInTheDocument();
|
||||
expect(create_audit_button).toBeInTheDocument();
|
||||
expect(support_button).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,19 +13,65 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import React, { ReactNode } from 'react';
|
||||
import { AuditListTable } from './AuditListTable';
|
||||
import { useWebsiteForEntity } from '../../hooks/useWebsiteForEntity';
|
||||
import { Progress } from '@backstage/core-components';
|
||||
import { LIGHTHOUSE_WEBSITE_URL_ANNOTATION } from '../../../constants';
|
||||
import {
|
||||
Content,
|
||||
ContentHeader,
|
||||
InfoCard,
|
||||
Progress,
|
||||
WarningPanel,
|
||||
} from '@backstage/core-components';
|
||||
import { Button } from '@material-ui/core';
|
||||
import { resolvePath, useNavigate } from 'react-router-dom';
|
||||
import { useEntity } from '@backstage/plugin-catalog-react';
|
||||
import { useRouteRef } from '@backstage/core-plugin-api';
|
||||
import { rootRouteRef } from '../../plugin';
|
||||
import LighthouseSupportButton from '../SupportButton';
|
||||
|
||||
export const AuditListForEntity = () => {
|
||||
const { value, loading, error } = useWebsiteForEntity();
|
||||
if (loading) {
|
||||
return <Progress />;
|
||||
}
|
||||
if (error || !value) {
|
||||
return null;
|
||||
const { entity } = useEntity();
|
||||
const navigate = useNavigate();
|
||||
const fromPath = useRouteRef(rootRouteRef)?.() ?? '../';
|
||||
|
||||
let createAuditButtonUrl = 'create-audit';
|
||||
const websiteUrl =
|
||||
entity.metadata.annotations?.[LIGHTHOUSE_WEBSITE_URL_ANNOTATION] ?? '';
|
||||
if (websiteUrl) {
|
||||
createAuditButtonUrl += `?url=${encodeURIComponent(websiteUrl)}`;
|
||||
}
|
||||
|
||||
return <AuditListTable data-test-id="AuditListTable" items={[value]} />;
|
||||
let content: ReactNode = null;
|
||||
content = (
|
||||
<Content>
|
||||
<ContentHeader title="Latest Audit">
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={() => navigate(resolvePath(createAuditButtonUrl, fromPath))}
|
||||
>
|
||||
Create New Audit
|
||||
</Button>
|
||||
<LighthouseSupportButton />
|
||||
</ContentHeader>
|
||||
<AuditListTable items={value ? [value] : []} />
|
||||
</Content>
|
||||
);
|
||||
|
||||
if (loading) {
|
||||
content = <Progress />;
|
||||
}
|
||||
if (error && !error.message.includes('no audited website found for url')) {
|
||||
// We only want to display this warning panel when its caused by an error other than no audits for the website
|
||||
content = (
|
||||
<WarningPanel severity="error" title="Could not load audit list.">
|
||||
{error.message}
|
||||
</WarningPanel>
|
||||
);
|
||||
}
|
||||
|
||||
return <InfoCard noPadding>{content}</InfoCard>;
|
||||
};
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
import { useWebsiteForEntity } from '../../hooks/useWebsiteForEntity';
|
||||
import * as data from '../../__fixtures__/website-list-response.json';
|
||||
import { LastLighthouseAuditCard } from './LastLighthouseAuditCard';
|
||||
import { fireEvent, screen } from '@testing-library/react';
|
||||
|
||||
jest.mock('../../hooks/useWebsiteForEntity', () => ({
|
||||
useWebsiteForEntity: jest.fn(),
|
||||
@@ -147,17 +148,22 @@ describe('<LastLighthouseAuditCard />', () => {
|
||||
(useWebsiteForEntity as jest.Mock).mockReturnValue({
|
||||
value: null,
|
||||
loading: false,
|
||||
error: 'error',
|
||||
error: { name: 'error', message: 'error loading data' },
|
||||
});
|
||||
});
|
||||
|
||||
it('renders nothing', async () => {
|
||||
const { queryByTestId } = await renderInTestApp(
|
||||
it('renders a WarningPanel', async () => {
|
||||
await renderInTestApp(
|
||||
<EntityProvider entity={entity}>
|
||||
<LastLighthouseAuditCard />
|
||||
</EntityProvider>,
|
||||
);
|
||||
expect(queryByTestId('AuditListTable')).toBeNull();
|
||||
const expandIcon = screen.getByText('Error: Could not load audit list.');
|
||||
fireEvent.click(expandIcon);
|
||||
expect(
|
||||
screen.getByText('Error: Could not load audit list.'),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText('error loading data')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -179,4 +185,26 @@ describe('<LastLighthouseAuditCard />', () => {
|
||||
expect(queryByTestId('AuditListTable')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('where error was due to an empty database query result', () => {
|
||||
beforeEach(() => {
|
||||
(useWebsiteForEntity as jest.Mock).mockReturnValue({
|
||||
value: null,
|
||||
loading: false,
|
||||
error: {
|
||||
name: 'error',
|
||||
message: 'no audited website found for url unit-test-url',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('renders EmptyState card', async () => {
|
||||
const rendered = await renderInTestApp(
|
||||
<EntityProvider entity={entity}>
|
||||
<LastLighthouseAuditCard />
|
||||
</EntityProvider>,
|
||||
);
|
||||
expect(rendered.getByText('No Audits Found')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
import { useWebsiteForEntity } from '../../hooks/useWebsiteForEntity';
|
||||
import AuditStatusIcon from '../AuditStatusIcon';
|
||||
import {
|
||||
EmptyState,
|
||||
InfoCard,
|
||||
InfoCardVariants,
|
||||
Progress,
|
||||
@@ -30,6 +31,7 @@ import {
|
||||
StatusOK,
|
||||
StatusWarning,
|
||||
StructuredMetadataTable,
|
||||
WarningPanel,
|
||||
} from '@backstage/core-components';
|
||||
|
||||
const LighthouseCategoryScoreStatus = (props: { score: number }) => {
|
||||
@@ -105,7 +107,14 @@ export const LastLighthouseAuditCard = (props: {
|
||||
content = <Progress />;
|
||||
}
|
||||
if (error) {
|
||||
content = null;
|
||||
// We only want to display this warning panel when its caused by an error other than no audits being found for the website
|
||||
content = error.message.includes('no audited website found for url') ? (
|
||||
<EmptyState title="No Audits Found" missing="data" />
|
||||
) : (
|
||||
<WarningPanel severity="error" title="Could not load audit list.">
|
||||
{error.message}
|
||||
</WarningPanel>
|
||||
);
|
||||
}
|
||||
if (website) {
|
||||
content = (
|
||||
|
||||
@@ -98,4 +98,21 @@ describe('useWebsiteForEntity', () => {
|
||||
expect(mockErrorApi.post).toHaveBeenCalledWith(error);
|
||||
});
|
||||
});
|
||||
|
||||
describe('where there is an error regarding "no audited websites for url"', () => {
|
||||
const error = new Error('no audited website found for url unit-test-url');
|
||||
|
||||
beforeEach(() => {
|
||||
(mockLighthouseApi.getWebsiteByUrl as jest.Mock).mockRejectedValueOnce(
|
||||
error,
|
||||
);
|
||||
});
|
||||
|
||||
it('does not post the error to the error api and returns the error to the caller', async () => {
|
||||
const { result, waitForNextUpdate } = subject();
|
||||
await waitForNextUpdate();
|
||||
expect(result.current?.error).toBe(error);
|
||||
expect(mockErrorApi.post).not.toHaveBeenCalledWith(error);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -31,7 +31,11 @@ export const useWebsiteForEntity = () => {
|
||||
() => lighthouseApi.getWebsiteByUrl(websiteUrl),
|
||||
[websiteUrl],
|
||||
);
|
||||
if (response.error) {
|
||||
// Do not display error alert if its due to no audits found for a website
|
||||
if (
|
||||
response.error &&
|
||||
!response.error.message.includes('no audited website found for url')
|
||||
) {
|
||||
errorApi.post(response.error);
|
||||
}
|
||||
return response;
|
||||
|
||||
Reference in New Issue
Block a user