Merge pull request #26849 from drodil/custom_sign_in_error
feat: allow defining custom sign in error element
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/core-components': patch
|
||||
---
|
||||
|
||||
It is possible to define a custom error element to be shown when sign in fails
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user