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}
+
+ ,
),
];
},