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 {