Merge pull request #26849 from drodil/custom_sign_in_error

feat: allow defining custom sign in error element
This commit is contained in:
Fredrik Adelöw
2024-10-14 16:27:24 +02:00
committed by GitHub
5 changed files with 98 additions and 12 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/core-components': patch
---
It is possible to define a custom error element to be shown when sign in fails
+5 -1
View File
@@ -15,6 +15,7 @@ import { CardHeaderProps } from '@material-ui/core/CardHeader';
import { Column } from '@material-table/core';
import { ComponentClass } from 'react';
import { ComponentProps } from 'react';
import { ComponentType } from 'react';
import { default as CSS_2 } from 'csstype';
import { CSSProperties } from 'react';
import { ElementType } from 'react';
@@ -876,6 +877,9 @@ export const ProxiedSignInPage: (
export type ProxiedSignInPageProps = SignInPageProps & {
provider: string;
headers?: HeadersInit | (() => HeadersInit) | (() => Promise<HeadersInit>);
ErrorComponent?: ComponentType<{
error?: Error;
}>;
};
// Warning: (ae-missing-release-tag) "ResponseErrorPanel" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal)
@@ -1748,7 +1752,7 @@ export type WarningPanelClassKey =
// src/layout/Sidebar/config.d.ts:8:1 - (ae-undocumented) Missing documentation for "SubmenuOptions".
// src/layout/Sidebar/config.d.ts:12:22 - (ae-undocumented) Missing documentation for "sidebarConfig".
// src/layout/Sidebar/config.d.ts:52:22 - (ae-undocumented) Missing documentation for "SIDEBAR_INTRO_LOCAL_STORAGE".
// src/layout/SignInPage/SignInPage.d.ts:17:1 - (ae-undocumented) Missing documentation for "SignInPage".
// src/layout/SignInPage/SignInPage.d.ts:26:1 - (ae-undocumented) Missing documentation for "SignInPage".
// src/layout/SignInPage/customProvider.d.ts:3:1 - (ae-undocumented) Missing documentation for "CustomProviderClassKey".
// src/layout/SignInPage/styles.d.ts:2:1 - (ae-undocumented) Missing documentation for "SignInPageClassKey".
// src/layout/SignInPage/types.d.ts:3:1 - (ae-undocumented) Missing documentation for "SignInProviderConfig".
@@ -19,9 +19,9 @@ import { render, screen } from '@testing-library/react';
import { rest } from 'msw';
import { setupServer } from 'msw/node';
import {
TestApiProvider,
mockApis,
registerMswTestHooks,
TestApiProvider,
wrapInTestApp,
} from '@backstage/test-utils';
import { ProxiedSignInPage } from './ProxiedSignInPage';
@@ -99,4 +99,58 @@ describe('ProxiedSignInPage', () => {
screen.findByText('Request failed with 401 Unauthorized'),
).resolves.toBeInTheDocument();
});
it('should allow custom error component', async () => {
const ErrorComponent = ({ error }: { error?: Error }) => (
<>
<h1>Failed to authenticate</h1>
<div>{error?.message}</div>
</>
);
const CustomSubject = wrapInTestApp(<div>authenticated</div>, {
components: {
SignInPage: props => (
<TestApiProvider
apis={[
[
discoveryApiRef,
{
getBaseUrl: async () => 'http://example.com/api/auth',
},
],
]}
>
<ProxiedSignInPage
{...props}
provider="test"
ErrorComponent={ErrorComponent}
/>
</TestApiProvider>
),
},
});
worker.use(
rest.get('http://example.com/api/auth/test/refresh', (_, res, ctx) =>
res(
ctx.status(401),
ctx.set('Content-Type', 'application/json'),
ctx.json({
error: { name: 'Error', message: 'not-displayed' },
}),
),
),
);
render(CustomSubject);
await expect(
screen.findByText('Failed to authenticate'),
).resolves.toBeInTheDocument();
await expect(
screen.findByText('Request failed with 401 Unauthorized'),
).resolves.toBeInTheDocument();
});
});
@@ -19,7 +19,7 @@ import {
SignInPageProps,
useApi,
} from '@backstage/core-plugin-api';
import React from 'react';
import React, { ComponentType } from 'react';
import { useAsync, useMountEffect } from '@react-hookz/web';
import { ErrorPanel } from '../../components/ErrorPanel';
import { Progress } from '../../components/Progress';
@@ -44,6 +44,12 @@ export type ProxiedSignInPageProps = SignInPageProps & {
* underlying provider
*/
headers?: HeadersInit | (() => HeadersInit) | (() => Promise<HeadersInit>);
/**
* Error component to be rendered instead of the default error panel in case
* sign in fails.
*/
ErrorComponent?: ComponentType<{ error?: Error }>;
};
/**
@@ -82,7 +88,11 @@ export const ProxiedSignInPage = (props: ProxiedSignInPageProps) => {
if (status === 'loading') {
return <Progress />;
} else if (error) {
return <ErrorPanel title={t('proxiedSignInPage.title')} error={error} />;
return props.ErrorComponent ? (
<props.ErrorComponent error={error} />
) : (
<ErrorPanel title={t('proxiedSignInPage.title')} error={error} />
);
}
return null;
@@ -24,7 +24,7 @@ import { UserIdentity } from './UserIdentity';
import Button from '@material-ui/core/Button';
import Grid from '@material-ui/core/Grid';
import Typography from '@material-ui/core/Typography';
import React, { ReactNode, useState } from 'react';
import React, { ComponentType, ReactNode, useState } from 'react';
import { useMountEffect } from '@react-hookz/web';
import { Progress } from '../../components/Progress';
import { Content } from '../Content/Content';
@@ -39,14 +39,22 @@ import { coreComponentsTranslationRef } from '../../translation';
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
import { useSearchParams } from 'react-router-dom';
type MultiSignInPageProps = SignInPageProps & {
type CommonSignInPageProps = SignInPageProps & {
/**
* Error component to be rendered instead of the default error panel in case
* sign in fails.
*/
ErrorComponent?: ComponentType<{ error?: Error }>;
};
type MultiSignInPageProps = CommonSignInPageProps & {
providers: IdentityProviders;
title?: string;
titleComponent?: ReactNode;
align?: 'center' | 'left';
};
type SingleSignInPageProps = SignInPageProps & {
type SingleSignInPageProps = CommonSignInPageProps & {
provider: SignInProviderConfig;
auto?: boolean;
};
@@ -102,6 +110,7 @@ export const SingleSignInPage = ({
provider,
auto,
onSignInSuccess,
ErrorComponent,
}: SingleSignInPageProps) => {
const classes = useStyles();
const authApi = useApi(provider.apiRef);
@@ -200,11 +209,15 @@ export const SingleSignInPage = ({
}
>
<Typography variant="body1">{provider.message}</Typography>
{error && error.name !== 'PopupRejectedError' && (
<Typography variant="body1" color="error">
{error.message}
</Typography>
)}
{error &&
error.name !== 'PopupRejectedError' &&
(ErrorComponent ? (
<ErrorComponent error={error} />
) : (
<Typography variant="body1" color="error">
{error.message}
</Typography>
))}
</InfoCard>
</GridItem>
</Grid>