This is an automated email from the ASF dual-hosted git repository.

rfellows pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git


The following commit(s) were added to refs/heads/main by this push:
     new 921155bf2c8 NIFI-15695: Hanlding the error case when resolving the 
root process group fails. (#10990)
921155bf2c8 is described below

commit 921155bf2c81ecfdd9e34fd9f2a075ee9cf7fd12
Author: Matt Gilman <[email protected]>
AuthorDate: Tue Mar 10 13:28:19 2026 -0400

    NIFI-15695: Hanlding the error case when resolving the root process group 
fails. (#10990)
    
    - Ensuring the splash screen doesn't remain visible when there is an 
unhandled error in the guard sequence.
---
 .../frontend/apps/nifi/src/app/app.component.ts    |   9 +-
 .../ui/root/guard/root-group.guard.spec.ts         | 128 +++++++++++++++++++--
 .../ui/root/guard/root-group.guard.ts              |   9 +-
 3 files changed, 136 insertions(+), 10 deletions(-)

diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/app.component.ts 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/app.component.ts
index 8871ddb3833..8f7147b3ae0 100644
--- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/app.component.ts
+++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/app.component.ts
@@ -20,8 +20,9 @@ import {
     GuardsCheckEnd,
     GuardsCheckStart,
     NavigationCancel,
-    NavigationStart,
     NavigationEnd,
+    NavigationError,
+    NavigationStart,
     Router
 } from '@angular/router';
 import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@@ -76,7 +77,11 @@ export class AppComponent implements OnDestroy {
                     if (event instanceof GuardsCheckStart) {
                         this.guardLoading = true;
                     }
-                    if (event instanceof GuardsCheckEnd || event instanceof 
NavigationCancel) {
+                    if (
+                        event instanceof GuardsCheckEnd ||
+                        event instanceof NavigationCancel ||
+                        event instanceof NavigationError
+                    ) {
                         this.guardLoading = false;
                     }
                 }),
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/root/guard/root-group.guard.spec.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/root/guard/root-group.guard.spec.ts
index f1b8906b1ac..4bc23762a36 100644
--- 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/root/guard/root-group.guard.spec.ts
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/root/guard/root-group.guard.spec.ts
@@ -16,19 +16,133 @@
  */
 
 import { TestBed } from '@angular/core/testing';
-import { CanActivateFn } from '@angular/router';
+import { Router } from '@angular/router';
+import { MockStore, provideMockStore } from '@ngrx/store/testing';
+import { of, throwError } from 'rxjs';
+import { HttpErrorResponse } from '@angular/common/http';
 
 import { rootGroupGuard } from './root-group.guard';
+import { FlowService } from '../../../service/flow.service';
+import { ErrorHelper } from '../../../../../service/error-helper.service';
+import { selectCurrentProcessGroupId } from 
'../../../state/flow/flow.selectors';
+import { fullScreenError } from '../../../../../state/error/error.actions';
 
-describe('rootGroupGuard', () => {
-    const executeGuard: CanActivateFn = (...guardParameters) =>
-        TestBed.runInInjectionContext(() => 
rootGroupGuard(...guardParameters));
+interface SetupOptions {
+    currentProcessGroupId?: string;
+    statusResponse?: any;
+    statusError?: HttpErrorResponse;
+}
+
+function createMockStatusResponse(id = 'abc-123') {
+    return { processGroupStatus: { id } };
+}
+
+async function setup(options: SetupOptions = {}) {
+    const { currentProcessGroupId = 'root', statusResponse, statusError } = 
options;
+
+    const mockFlowService = {
+        getProcessGroupStatus: jest.fn()
+    };
+
+    if (statusError) {
+        mockFlowService.getProcessGroupStatus.mockReturnValue(throwError(() => 
statusError));
+    } else if (statusResponse) {
+        
mockFlowService.getProcessGroupStatus.mockReturnValue(of(statusResponse));
+    }
+
+    const mockRouter = {
+        navigate: jest.fn().mockReturnValue(Promise.resolve(true))
+    };
+
+    const mockErrorHelper = {
+        fullScreenError: jest.fn().mockImplementation((error: 
HttpErrorResponse) =>
+            fullScreenError({
+                errorDetail: {
+                    title: 'Insufficient Permissions',
+                    message: error.message
+                }
+            })
+        )
+    };
+
+    await TestBed.configureTestingModule({
+        providers: [
+            provideMockStore(),
+            { provide: FlowService, useValue: mockFlowService },
+            { provide: Router, useValue: mockRouter },
+            { provide: ErrorHelper, useValue: mockErrorHelper }
+        ]
+    }).compileComponents();
+
+    const store = TestBed.inject(MockStore);
+    store.overrideSelector(selectCurrentProcessGroupId, currentProcessGroupId);
+    const dispatchSpy = jest.spyOn(store, 'dispatch');
+
+    return { store, mockFlowService, mockRouter, mockErrorHelper, dispatchSpy 
};
+}
 
+describe('rootGroupGuard', () => {
     beforeEach(() => {
-        TestBed.configureTestingModule({});
+        jest.clearAllMocks();
+    });
+
+    describe('when process group id is initial state (root)', () => {
+        it('should navigate to the root process group on successful status 
response', async () => {
+            const statusResponse = createMockStatusResponse('real-pg-id');
+            const { mockRouter, mockFlowService } = await setup({
+                currentProcessGroupId: 'root',
+                statusResponse
+            });
+
+            const result = await new Promise((resolve) => {
+                const guard$ = TestBed.runInInjectionContext(() => 
rootGroupGuard({} as any, {} as any));
+                (guard$ as any).subscribe((val: any) => resolve(val));
+            });
+
+            expect(mockFlowService.getProcessGroupStatus).toHaveBeenCalled();
+            
expect(mockRouter.navigate).toHaveBeenCalledWith(['/process-groups', 
'real-pg-id']);
+            expect(result).toBe(true);
+        });
+
+        it('should dispatch fullScreenError and return false on HTTP error', 
async () => {
+            const statusError = new HttpErrorResponse({
+                status: 403,
+                statusText: 'Forbidden',
+                error: { message: 'Access is denied' },
+                url: '/nifi-api/flow/process-groups/root/status'
+            });
+            const { mockErrorHelper, dispatchSpy, mockRouter } = await setup({
+                currentProcessGroupId: 'root',
+                statusError
+            });
+
+            const result = await new Promise((resolve) => {
+                const guard$ = TestBed.runInInjectionContext(() => 
rootGroupGuard({} as any, {} as any));
+                (guard$ as any).subscribe((val: any) => resolve(val));
+            });
+
+            expect(result).toBe(false);
+            
expect(mockErrorHelper.fullScreenError).toHaveBeenCalledWith(statusError);
+            expect(dispatchSpy).toHaveBeenCalled();
+            expect(mockRouter.navigate).not.toHaveBeenCalled();
+        });
     });
 
-    it('should be created', () => {
-        expect(executeGuard).toBeTruthy();
+    describe('when process group id is already set', () => {
+        it('should navigate directly to the existing process group', async () 
=> {
+            const existingPgId = 'existing-pg-id';
+            const { mockRouter, mockFlowService } = await setup({
+                currentProcessGroupId: existingPgId
+            });
+
+            const result = await new Promise((resolve) => {
+                const guard$ = TestBed.runInInjectionContext(() => 
rootGroupGuard({} as any, {} as any));
+                (guard$ as any).subscribe((val: any) => resolve(val));
+            });
+
+            
expect(mockFlowService.getProcessGroupStatus).not.toHaveBeenCalled();
+            
expect(mockRouter.navigate).toHaveBeenCalledWith(['/process-groups', 
existingPgId]);
+            expect(result).toBe(true);
+        });
     });
 });
diff --git 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/root/guard/root-group.guard.ts
 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/root/guard/root-group.guard.ts
index a275bdc76e5..9ceff75e791 100644
--- 
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/root/guard/root-group.guard.ts
+++ 
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/flow-designer/ui/root/guard/root-group.guard.ts
@@ -18,17 +18,20 @@
 import { CanActivateFn, Router } from '@angular/router';
 import { FlowService } from '../../../service/flow.service';
 import { inject } from '@angular/core';
-import { switchMap, take } from 'rxjs';
+import { catchError, of, switchMap, take } from 'rxjs';
 import { Store } from '@ngrx/store';
 import { CurrentUserState } from '../../../../../state/current-user';
 import { FlowState } from '../../../state/flow';
 import { selectCurrentProcessGroupId } from 
'../../../state/flow/flow.selectors';
 import { initialState } from '../../../state/flow/flow.reducer';
+import { HttpErrorResponse } from '@angular/common/http';
+import { ErrorHelper } from '../../../../../service/error-helper.service';
 
 export const rootGroupGuard: CanActivateFn = () => {
     const router: Router = inject(Router);
     const flowService: FlowService = inject(FlowService);
     const store: Store<CurrentUserState> = inject(Store<FlowState>);
+    const errorHelper: ErrorHelper = inject(ErrorHelper);
 
     return store.select(selectCurrentProcessGroupId).pipe(
         take(1),
@@ -38,6 +41,10 @@ export const rootGroupGuard: CanActivateFn = () => {
                     take(1),
                     switchMap((rootGroupStatus: any) => {
                         return router.navigate(['/process-groups', 
rootGroupStatus.processGroupStatus.id]);
+                    }),
+                    catchError((error: HttpErrorResponse) => {
+                        store.dispatch(errorHelper.fullScreenError(error));
+                        return of(false);
                     })
                 );
             } else {

Reply via email to