Merge pull request #6586 from ntgussoni/feature/show-catalog-errors
Show catalog-parsing related errors in entity page
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
---
|
||||
'@backstage/core-components': patch
|
||||
'@backstage/plugin-catalog': patch
|
||||
---
|
||||
|
||||
Added an EntityProcessingErrorsPanel component to show any errors that occurred when refreshing an entity from its source location. If upgrading, this should be added to EntityPage in your Backstage application:
|
||||
|
||||
```diff
|
||||
// packages/app/src/components/catalog/EntityPage.tsx
|
||||
|
||||
const overviewContent = (
|
||||
...
|
||||
<EntityOrphanWarning />
|
||||
</Grid>
|
||||
</EntitySwitch.Case>
|
||||
</EntitySwitch>
|
||||
+ <EntitySwitch>
|
||||
+ <EntitySwitch.Case if={hasCatalogProcessingErrors}>
|
||||
+ <Grid item xs={12}>
|
||||
+ <EntityProcessingErrorsPanel />
|
||||
+ </Grid>
|
||||
+ </EntitySwitch.Case>
|
||||
+ </EntitySwitch>
|
||||
|
||||
```
|
||||
|
||||
Additionally, WarningPanel now changes color based on the provided severity.
|
||||
@@ -40,6 +40,8 @@ import {
|
||||
isKind,
|
||||
EntityHasResourcesCard,
|
||||
EntityOrphanWarning,
|
||||
EntityProcessingErrorsPanel,
|
||||
hasCatalogProcessingErrors,
|
||||
isOrphan,
|
||||
} from '@backstage/plugin-catalog';
|
||||
import {
|
||||
@@ -231,6 +233,14 @@ const overviewContent = (
|
||||
</EntitySwitch.Case>
|
||||
</EntitySwitch>
|
||||
|
||||
<EntitySwitch>
|
||||
<EntitySwitch.Case if={hasCatalogProcessingErrors}>
|
||||
<Grid item xs={12}>
|
||||
<EntityProcessingErrorsPanel />
|
||||
</Grid>
|
||||
</EntitySwitch.Case>
|
||||
</EntitySwitch>
|
||||
|
||||
<Grid item md={8} xs={12}>
|
||||
<EntityAboutCard variant="gridItem" />
|
||||
</Grid>
|
||||
|
||||
@@ -80,7 +80,7 @@ export const Avatar: ({
|
||||
// Warning: (ae-missing-release-tag) "Breadcrumbs" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const Breadcrumbs: ({ children, ...props }: Props_25) => JSX.Element;
|
||||
export const Breadcrumbs: ({ children, ...props }: Props_24) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "BrokenImageIcon" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
@@ -441,7 +441,7 @@ export const Content: ({
|
||||
noPadding,
|
||||
children,
|
||||
...props
|
||||
}: PropsWithChildren<Props_18>) => JSX.Element;
|
||||
}: PropsWithChildren<Props_17>) => JSX.Element;
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "ContentHeaderProps" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-missing-release-tag) "ContentHeader" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
@@ -695,7 +695,7 @@ export const Header: ({
|
||||
tooltip,
|
||||
type,
|
||||
typeLink,
|
||||
}: PropsWithChildren<Props_19>) => JSX.Element;
|
||||
}: PropsWithChildren<Props_18>) => JSX.Element;
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-missing-release-tag) "HeaderIconLinkRow" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
@@ -777,7 +777,7 @@ export const InfoCard: ({
|
||||
className,
|
||||
noPadding,
|
||||
titleTypographyProps,
|
||||
}: Props_20) => JSX.Element;
|
||||
}: Props_19) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "InfoCardVariants" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
@@ -1200,7 +1200,7 @@ export const OverflowTooltip: (props: Props_12) => JSX.Element;
|
||||
export const Page: ({
|
||||
themeId,
|
||||
children,
|
||||
}: PropsWithChildren<Props_21>) => JSX.Element;
|
||||
}: PropsWithChildren<Props_20>) => JSX.Element;
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "PageWithHeaderProps" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-missing-release-tag) "PageWithHeader" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
@@ -1291,7 +1291,7 @@ export const Sidebar: ({
|
||||
openDelayMs,
|
||||
closeDelayMs,
|
||||
children,
|
||||
}: PropsWithChildren<Props_22>) => JSX.Element;
|
||||
}: PropsWithChildren<Props_21>) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "SIDEBAR_INTRO_LOCAL_STORAGE" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
@@ -2448,7 +2448,7 @@ export const SidebarSpacer: React_2.ComponentType<
|
||||
// Warning: (ae-missing-release-tag) "SignInPage" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const SignInPage: (props: Props_23) => JSX.Element;
|
||||
export const SignInPage: (props: Props_22) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "SignInProviderConfig" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
@@ -2592,7 +2592,7 @@ export const TabbedCard: ({
|
||||
deepLink,
|
||||
value,
|
||||
onChange,
|
||||
}: PropsWithChildren<Props_24>) => JSX.Element;
|
||||
}: PropsWithChildren<Props_23>) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "TabbedLayout" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
@@ -2715,11 +2715,17 @@ export const WarningIcon: IconComponent;
|
||||
// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
|
||||
// Warning: (tsdoc-param-tag-with-invalid-optional-name) The @param should not include a JSDoc-style optional name; it must not be enclosed in '[ ]' brackets.
|
||||
// Warning: (tsdoc-param-tag-with-invalid-type) The @param block should not include a JSDoc-style '{type}'
|
||||
// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-forgotten-export) The symbol "WarningProps" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-missing-release-tag) "WarningPanel" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public
|
||||
export const WarningPanel: (props: Props_17) => JSX.Element;
|
||||
export const WarningPanel: ({
|
||||
severity,
|
||||
title,
|
||||
message,
|
||||
children,
|
||||
defaultExpanded,
|
||||
}: WarningProps) => JSX.Element;
|
||||
|
||||
// Warnings were encountered during analysis:
|
||||
//
|
||||
|
||||
@@ -100,6 +100,7 @@ export const ErrorPanel = ({
|
||||
}: PropsWithChildren<ErrorPanelProps>) => {
|
||||
return (
|
||||
<WarningPanel
|
||||
severity="error"
|
||||
title={title ?? error.message}
|
||||
defaultExpanded={defaultExpanded}
|
||||
>
|
||||
|
||||
@@ -19,11 +19,19 @@ import { fireEvent, screen } from '@testing-library/react';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
import { Typography } from '@material-ui/core';
|
||||
|
||||
import { WarningPanel } from './WarningPanel';
|
||||
import { WarningPanel, WarningProps } from './WarningPanel';
|
||||
|
||||
const propsTitle = { title: 'Mock title' };
|
||||
const propsTitleMessage = { title: 'Mock title', message: 'Some more info' };
|
||||
const propsMessage = { message: 'Some more info' };
|
||||
const propsTitle: WarningProps = { title: 'Mock title' };
|
||||
const propsTitleMessage: WarningProps = {
|
||||
title: 'Mock title',
|
||||
message: 'Some more info',
|
||||
};
|
||||
const propsMessage: WarningProps = { message: 'Some more info' };
|
||||
const propsErrorMessage: WarningProps = {
|
||||
severity: 'error',
|
||||
title: 'Mock title',
|
||||
message: 'Some more info',
|
||||
};
|
||||
|
||||
describe('<WarningPanel />', () => {
|
||||
it('renders without exploding', async () => {
|
||||
@@ -64,4 +72,8 @@ describe('<WarningPanel />', () => {
|
||||
expect(screen.getByText('Some more info')).toBeInTheDocument();
|
||||
expect(screen.getByText('Java stacktrace')).toBeInTheDocument();
|
||||
});
|
||||
it('renders message using severity', async () => {
|
||||
await renderInTestApp(<WarningPanel {...propsErrorMessage} />);
|
||||
expect(screen.getByText('Error: Mock title')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { BackstageTheme } from '@backstage/theme';
|
||||
import {
|
||||
Accordion,
|
||||
@@ -22,30 +21,61 @@ import {
|
||||
Grid,
|
||||
makeStyles,
|
||||
Typography,
|
||||
darken,
|
||||
lighten,
|
||||
} from '@material-ui/core';
|
||||
import ErrorOutline from '@material-ui/icons/ErrorOutline';
|
||||
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
|
||||
import React from 'react';
|
||||
|
||||
const getWarningTextColor = (
|
||||
severity: NonNullable<WarningProps['severity']>,
|
||||
theme: BackstageTheme,
|
||||
) => {
|
||||
const getColor = theme.palette.type === 'light' ? darken : lighten;
|
||||
return getColor(theme.palette[severity].light, 0.6);
|
||||
};
|
||||
|
||||
const getWarningBackgroundColor = (
|
||||
severity: NonNullable<WarningProps['severity']>,
|
||||
theme: BackstageTheme,
|
||||
) => {
|
||||
const getBackgroundColor = theme.palette.type === 'light' ? lighten : darken;
|
||||
return getBackgroundColor(theme.palette[severity].light, 0.9);
|
||||
};
|
||||
|
||||
const useErrorOutlineStyles = makeStyles<BackstageTheme>(theme => ({
|
||||
root: {
|
||||
marginRight: theme.spacing(1),
|
||||
fill: theme.palette.warningText,
|
||||
fill: ({ severity }: WarningProps) =>
|
||||
getWarningTextColor(
|
||||
severity as NonNullable<WarningProps['severity']>,
|
||||
theme,
|
||||
),
|
||||
},
|
||||
}));
|
||||
const ErrorOutlineStyled = () => {
|
||||
const classes = useErrorOutlineStyles();
|
||||
|
||||
const ErrorOutlineStyled = ({ severity }: Pick<WarningProps, 'severity'>) => {
|
||||
const classes = useErrorOutlineStyles({ severity });
|
||||
return <ErrorOutline classes={classes} />;
|
||||
};
|
||||
const ExpandMoreIconStyled = () => {
|
||||
const classes = useErrorOutlineStyles();
|
||||
const ExpandMoreIconStyled = ({ severity }: Pick<WarningProps, 'severity'>) => {
|
||||
const classes = useErrorOutlineStyles({ severity });
|
||||
return <ExpandMoreIcon classes={classes} />;
|
||||
};
|
||||
|
||||
const useStyles = makeStyles<BackstageTheme>(theme => ({
|
||||
panel: {
|
||||
backgroundColor: theme.palette.warningBackground,
|
||||
color: theme.palette.warningText,
|
||||
backgroundColor: ({ severity }: WarningProps) =>
|
||||
getWarningBackgroundColor(
|
||||
severity as NonNullable<WarningProps['severity']>,
|
||||
theme,
|
||||
),
|
||||
color: ({ severity }: WarningProps) =>
|
||||
getWarningTextColor(
|
||||
severity as NonNullable<WarningProps['severity']>,
|
||||
theme,
|
||||
),
|
||||
verticalAlign: 'middle',
|
||||
},
|
||||
summary: {
|
||||
@@ -53,14 +83,26 @@ const useStyles = makeStyles<BackstageTheme>(theme => ({
|
||||
flexDirection: 'row',
|
||||
},
|
||||
summaryText: {
|
||||
color: theme.palette.warningText,
|
||||
color: ({ severity }: WarningProps) =>
|
||||
getWarningTextColor(
|
||||
severity as NonNullable<WarningProps['severity']>,
|
||||
theme,
|
||||
),
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
message: {
|
||||
width: '100%',
|
||||
display: 'block',
|
||||
color: theme.palette.warningText,
|
||||
backgroundColor: theme.palette.warningBackground,
|
||||
color: ({ severity }: WarningProps) =>
|
||||
getWarningTextColor(
|
||||
severity as NonNullable<WarningProps['severity']>,
|
||||
theme,
|
||||
),
|
||||
backgroundColor: ({ severity }: WarningProps) =>
|
||||
getWarningBackgroundColor(
|
||||
severity as NonNullable<WarningProps['severity']>,
|
||||
theme,
|
||||
),
|
||||
},
|
||||
details: {
|
||||
width: '100%',
|
||||
@@ -73,7 +115,7 @@ const useStyles = makeStyles<BackstageTheme>(theme => ({
|
||||
},
|
||||
}));
|
||||
|
||||
type Props = {
|
||||
export type WarningProps = {
|
||||
title?: string;
|
||||
severity?: 'warning' | 'error' | 'info';
|
||||
message?: React.ReactNode;
|
||||
@@ -89,19 +131,23 @@ const capitalize = (s: string) => {
|
||||
* WarningPanel. Show a user friendly error message to a user similar to ErrorPanel except that the warning panel
|
||||
* only shows the warning message to the user.
|
||||
*
|
||||
* @param {string} [severity=warning] Ability to change the severity of the alert. Not fully implemented. (error, warning, info)
|
||||
* @param {string} [severity=warning] Ability to change the severity of the alert.
|
||||
* @param {string} [title] A title for the warning. If not supplied, "Warning" will be used.
|
||||
* @param {Object} [message] Optional more detailed user-friendly message elaborating on the cause of the error.
|
||||
* @param {Object} [children] Objects to provide context, such as a stack trace or detailed error reporting.
|
||||
* Will be available inside an unfolded accordion.
|
||||
*/
|
||||
export const WarningPanel = (props: Props) => {
|
||||
const classes = useStyles(props);
|
||||
const { severity, title, message, children, defaultExpanded } = props;
|
||||
export const WarningPanel = ({
|
||||
severity = 'warning',
|
||||
title,
|
||||
message,
|
||||
children,
|
||||
defaultExpanded,
|
||||
}: WarningProps) => {
|
||||
const classes = useStyles({ severity });
|
||||
|
||||
// If no severity or title provided, the heading will read simply "Warning"
|
||||
const subTitle =
|
||||
(severity ? capitalize(severity) : 'Warning') + (title ? `: ${title}` : '');
|
||||
const subTitle = capitalize(severity) + (title ? `: ${title}` : '');
|
||||
|
||||
return (
|
||||
<Accordion
|
||||
@@ -110,10 +156,10 @@ export const WarningPanel = (props: Props) => {
|
||||
role="alert"
|
||||
>
|
||||
<AccordionSummary
|
||||
expandIcon={<ExpandMoreIconStyled />}
|
||||
expandIcon={<ExpandMoreIconStyled severity={severity} />}
|
||||
className={classes.summary}
|
||||
>
|
||||
<ErrorOutlineStyled />
|
||||
<ErrorOutlineStyled severity={severity} />
|
||||
<Typography className={classes.summaryText} variant="subtitle1">
|
||||
{subTitle}
|
||||
</Typography>
|
||||
|
||||
+20
@@ -35,8 +35,12 @@ import {
|
||||
EntityLayout,
|
||||
EntityLinksCard,
|
||||
EntitySwitch,
|
||||
EntityOrphanWarning,
|
||||
EntityProcessingErrorsPanel,
|
||||
isComponentType,
|
||||
isKind,
|
||||
hasCatalogProcessingErrors,
|
||||
isOrphan,
|
||||
} from '@backstage/plugin-catalog';
|
||||
import {
|
||||
isGithubActionsAvailable,
|
||||
@@ -80,6 +84,22 @@ const cicdContent = (
|
||||
|
||||
const overviewContent = (
|
||||
<Grid container spacing={3} alignItems="stretch">
|
||||
<EntitySwitch>
|
||||
<EntitySwitch.Case if={isOrphan}>
|
||||
<Grid item xs={12}>
|
||||
<EntityOrphanWarning />
|
||||
</Grid>
|
||||
</EntitySwitch.Case>
|
||||
</EntitySwitch>
|
||||
|
||||
<EntitySwitch>
|
||||
<EntitySwitch.Case if={hasCatalogProcessingErrors}>
|
||||
<Grid item xs={12}>
|
||||
<EntityProcessingErrorsPanel />
|
||||
</Grid>
|
||||
</EntitySwitch.Case>
|
||||
</EntitySwitch>
|
||||
|
||||
<Grid item md={6}>
|
||||
<EntityAboutCard variant="gridItem" />
|
||||
</Grid>
|
||||
|
||||
@@ -327,6 +327,11 @@ export const EntityPageLayout: {
|
||||
}) => null;
|
||||
};
|
||||
|
||||
// Warning: (ae-missing-release-tag) "EntityProcessingErrorsPanel" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public
|
||||
export const EntityProcessingErrorsPanel: () => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "EntitySwitch" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
@@ -358,6 +363,11 @@ export const FilteredEntityLayout: ({
|
||||
children,
|
||||
}: PropsWithChildren<{}>) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "hasCatalogProcessingErrors" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const hasCatalogProcessingErrors: (entity: Entity) => boolean;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "isComponentType" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* 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 { EntityProvider } from '@backstage/plugin-catalog-react';
|
||||
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
import React from 'react';
|
||||
import { EntityProcessingErrorsPanel } from './EntityProcessingErrorsPanel';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
|
||||
describe('<EntityProcessErrors />', () => {
|
||||
it('renders EntityProcessErrors if the entity has errors', async () => {
|
||||
const entity: Entity = {
|
||||
apiVersion: 'v1',
|
||||
kind: 'Component',
|
||||
metadata: {
|
||||
name: 'software',
|
||||
description: 'This is the description',
|
||||
},
|
||||
|
||||
spec: {
|
||||
owner: 'guest',
|
||||
type: 'service',
|
||||
lifecycle: 'production',
|
||||
},
|
||||
status: {
|
||||
items: [
|
||||
{
|
||||
type: 'backstage.io/catalog-processing',
|
||||
level: 'error',
|
||||
message:
|
||||
'InputError: Policy check failed; caused by Error: Malformed envelope, /metadata/labels should be object',
|
||||
error: {
|
||||
name: 'InputError',
|
||||
message:
|
||||
'Policy check failed; caused by Error: Malformed envelope, /metadata/labels should be object',
|
||||
cause: {
|
||||
name: 'Error',
|
||||
message:
|
||||
'Malformed envelope, /metadata/labels should be object',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'foo',
|
||||
level: 'error',
|
||||
message: 'InputError: This should not be rendered',
|
||||
error: {
|
||||
name: 'InputError',
|
||||
message: 'Foo',
|
||||
cause: {
|
||||
name: 'Error',
|
||||
message:
|
||||
'Malformed envelope, /metadata/labels should be object',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'backstage.io/catalog-processing',
|
||||
level: 'error',
|
||||
message: 'InputError: Foo',
|
||||
error: {
|
||||
name: 'InputError',
|
||||
message: 'Foo',
|
||||
cause: {
|
||||
name: 'Error',
|
||||
message:
|
||||
'Malformed envelope, /metadata/labels should be object',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const { getByText, queryByText } = await renderInTestApp(
|
||||
<EntityProvider entity={entity}>
|
||||
<EntityProcessingErrorsPanel />
|
||||
</EntityProvider>,
|
||||
);
|
||||
|
||||
expect(
|
||||
getByText(
|
||||
'Error: Policy check failed; caused by Error: Malformed envelope, /metadata/labels should be object',
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
expect(getByText('Error: Foo')).toBeInTheDocument();
|
||||
expect(queryByText('Error: This should not be rendered')).toBeNull();
|
||||
});
|
||||
});
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Entity, UNSTABLE_EntityStatusItem } from '@backstage/catalog-model';
|
||||
import { useEntity } from '@backstage/plugin-catalog-react';
|
||||
import { Box } from '@material-ui/core';
|
||||
import React from 'react';
|
||||
import { ResponseErrorPanel } from '@backstage/core-components';
|
||||
import { ENTITY_STATUS_CATALOG_PROCESSING_TYPE } from '@backstage/catalog-client';
|
||||
|
||||
const errorfilter = (i: UNSTABLE_EntityStatusItem) =>
|
||||
i.error &&
|
||||
i.level === 'error' &&
|
||||
i.type === ENTITY_STATUS_CATALOG_PROCESSING_TYPE;
|
||||
|
||||
export const hasCatalogProcessingErrors = (entity: Entity) =>
|
||||
entity?.status?.items?.filter(errorfilter).length! > 0;
|
||||
|
||||
/**
|
||||
* Displays a list of errors if the entity is invalid.
|
||||
*/
|
||||
export const EntityProcessingErrorsPanel = () => {
|
||||
const { entity } = useEntity();
|
||||
const catalogProcessingErrors =
|
||||
(entity?.status?.items?.filter(
|
||||
errorfilter,
|
||||
) as Required<UNSTABLE_EntityStatusItem>[]) || [];
|
||||
|
||||
return (
|
||||
<>
|
||||
{catalogProcessingErrors.map(({ error }, index) => (
|
||||
<Box key={index} mb={1}>
|
||||
<ResponseErrorPanel error={error} />
|
||||
</Box>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export {
|
||||
EntityProcessingErrorsPanel,
|
||||
hasCatalogProcessingErrors,
|
||||
} from './EntityProcessingErrorsPanel';
|
||||
@@ -23,6 +23,7 @@ export * from './components/CatalogTable/columns';
|
||||
export * from './components/CreateComponentButton';
|
||||
export * from './components/EntityLayout';
|
||||
export * from './components/EntityOrphanWarning';
|
||||
export * from './components/EntityProcessingErrorsPanel';
|
||||
export * from './components/EntityPageLayout';
|
||||
export * from './components/EntitySwitch';
|
||||
export * from './components/FilteredEntityLayout';
|
||||
|
||||
@@ -100,7 +100,7 @@ describe('<GroupsExplorerContent />', () => {
|
||||
const catalogError = new Error('Network timeout');
|
||||
catalogApi.getEntities.mockRejectedValueOnce(catalogError);
|
||||
|
||||
const { getByText } = await renderInTestApp(
|
||||
const { getAllByText } = await renderInTestApp(
|
||||
<Wrapper>
|
||||
<GroupsExplorerContent />
|
||||
</Wrapper>,
|
||||
@@ -108,7 +108,7 @@ describe('<GroupsExplorerContent />', () => {
|
||||
);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(getByText(/Warning: Network timeout/)).toBeInTheDocument(),
|
||||
expect(getAllByText(/Error: Network timeout/).length).not.toBe(0),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -74,7 +74,7 @@ describe('<FossaCard />', () => {
|
||||
|
||||
fossaApi.getFindingSummary.mockRejectedValue(new Error('My Error'));
|
||||
|
||||
const { getByText } = await renderInTestApp(
|
||||
const { getAllByText } = await renderInTestApp(
|
||||
<Wrapper>
|
||||
<EntityProvider entity={entity}>
|
||||
<FossaCard />
|
||||
@@ -82,7 +82,7 @@ describe('<FossaCard />', () => {
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
expect(getByText(/Warning: My Error/i)).toBeInTheDocument();
|
||||
expect(getAllByText(/Error: My Error/i).length).not.toBe(0);
|
||||
});
|
||||
|
||||
it('shows empty', async () => {
|
||||
|
||||
@@ -311,7 +311,7 @@ describe('github-deployments', () => {
|
||||
|
||||
expect(
|
||||
await rendered.findByText(
|
||||
'Warning: No apiBaseUrl available for host my-github-3.com, please check your integrations config',
|
||||
'Error: No apiBaseUrl available for host my-github-3.com, please check your integrations config',
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
@@ -336,7 +336,7 @@ describe('github-deployments', () => {
|
||||
|
||||
expect(
|
||||
await rendered.findByText(
|
||||
'Warning: No matching GitHub integration configuration for host my-github-unknown.com, please check your integrations config',
|
||||
'Error: No matching GitHub integration configuration for host my-github-unknown.com, please check your integrations config',
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user