diff --git a/packages/frontend-defaults/src/createApp.test.tsx b/packages/frontend-defaults/src/createApp.test.tsx index 540fb5e41c..9b3733b5af 100644 --- a/packages/frontend-defaults/src/createApp.test.tsx +++ b/packages/frontend-defaults/src/createApp.test.tsx @@ -187,6 +187,86 @@ describe('createApp', () => { ).resolves.toBeInTheDocument(); }); + it('should surface sign-in bootstrap errors through the app root boundary', async () => { + const identityApi = { + getProfileInfo: async () => ({ displayName: 'Test User' }), + getBackstageIdentity: async () => ({ + type: 'user' as const, + userEntityRef: 'user:default/test-user', + ownershipEntityRefs: ['user:default/test-user'], + }), + getCredentials: async () => ({ token: 'token' }), + signOut: async () => {}, + }; + const featureFlagsApi = { + isActive: jest.fn(() => { + throw new Error('sign-in bootstrap failed'); + }), + registerFlag: jest.fn(), + getRegisteredFlags: () => [], + save: jest.fn(), + } as unknown as typeof featureFlagsApiRef.T; + + const app = createApp({ + advanced: { + configLoader: async () => ({ config: mockApis.config() }), + }, + features: [ + appPluginOriginal, + createFrontendModule({ + pluginId: 'app', + extensions: [ + ApiBlueprint.make({ + params: defineParams => + defineParams({ + api: featureFlagsApiRef, + deps: {}, + factory: () => featureFlagsApi, + }), + }), + appPluginOriginal.getExtension('sign-in-page:app').override({ + factory: () => { + function SignInPage(props: { + onSignInSuccess(identity: IdentityApi): void; + }) { + useEffect(() => { + props.onSignInSuccess(identityApi); + }, [props]); + + return
Custom Sign In
; + } + + return [signInPageComponentDataRef(SignInPage)]; + }, + }), + ], + }), + createFrontendPlugin({ + pluginId: 'test', + featureFlags: [{ name: 'test-flag' }], + extensions: [ + PageBlueprint.make({ + if: { featureFlags: { $contains: 'test-flag' } }, + params: { + path: '/', + loader: async () =>
Flagged Page
, + }, + }), + ], + }), + ], + }); + + await renderWithEffects(app.createRoot()); + + await expect( + screen.findByText(/Error in app/), + ).resolves.toBeInTheDocument(); + await expect( + screen.findByText('sign-in bootstrap failed'), + ).resolves.toBeInTheDocument(); + }); + it('should deduplicate features keeping the last received one', async () => { const duplicatedFeatureId = 'test'; const app = createApp({ diff --git a/packages/frontend-defaults/src/createApp.tsx b/packages/frontend-defaults/src/createApp.tsx index 38eadf6509..310cd48f17 100644 --- a/packages/frontend-defaults/src/createApp.tsx +++ b/packages/frontend-defaults/src/createApp.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { JSX, lazy, ReactNode, Suspense, useReducer, useState } from 'react'; +import { JSX, lazy, ReactNode, Suspense, useReducer } from 'react'; import { ConfigApi, coreExtensionData, @@ -151,13 +151,8 @@ function PreparedAppRoot(props: { }): JSX.Element { const signIn = props.preparedApp.getSignIn(); const SignIn = signIn.Component; - const [finalizeError, setFinalizeError] = useState(); const [, triggerRerender] = useReducer((count: number) => count + 1, 0); - if (finalizeError) { - throw finalizeError; - } - const finalizedApp: FinalizedSpecializedApp | undefined = props.preparedApp.tryFinalize(); @@ -167,7 +162,6 @@ function PreparedAppRoot(props: { onReady={() => { triggerRerender(); }} - onError={setFinalizeError} /> ); } diff --git a/plugins/app/src/extensions/AppRoot.tsx b/plugins/app/src/extensions/AppRoot.tsx index a970ae4463..af6b134fc1 100644 --- a/plugins/app/src/extensions/AppRoot.tsx +++ b/plugins/app/src/extensions/AppRoot.tsx @@ -22,6 +22,7 @@ import { JSX, } from 'react'; import { + ExtensionBoundary, coreExtensionData, discoveryApiRef, fetchApiRef, @@ -84,7 +85,7 @@ export const AppRoot = createExtension({ ), }, output: [coreExtensionData.reactElement], - factory({ inputs, apis }) { + factory({ inputs, apis, node }) { if (isProtectedApp()) { const identityApi = apis.get(identityApiRef); if (!identityApi) { @@ -123,19 +124,21 @@ export const AppRoot = createExtension({ return [ coreExtensionData.reactElement( - - el.get(coreExtensionData.reactElement), - )} - > - {content} - , + + + el.get(coreExtensionData.reactElement), + )} + > + {content} + + , ), ]; },