diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/management-controller-services/index.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/management-controller-services/index.ts
index b1725d338c..6c1be51d41 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/management-controller-services/index.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/management-controller-services/index.ts
@@ -57,6 +57,5 @@ export interface ManagementControllerServicesState {
controllerServices: ControllerServiceEntity[];
saving: boolean;
loadedTimestamp: string;
- error: string | null;
- status: 'pending' | 'loading' | 'error' | 'success';
+ status: 'pending' | 'loading' | 'success';
}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/management-controller-services/management-controller-services.actions.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/management-controller-services/management-controller-services.actions.ts
index 0ecaca9f51..483b481bf4 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/management-controller-services/management-controller-services.actions.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/management-controller-services/management-controller-services.actions.ts
@@ -45,8 +45,8 @@ export const loadManagementControllerServicesSuccess = createAction(
props<{ response: LoadManagementControllerServicesResponse }>()
);
-export const managementControllerServicesApiError = createAction(
- '[Management Controller Services] Load Management Controller Services Error',
+export const managementControllerServicesBannerApiError = createAction(
+ '[Management Controller Services] Management Controller Services Banner Api Error',
props<{ error: string }>()
);
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/management-controller-services/management-controller-services.effects.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/management-controller-services/management-controller-services.effects.ts
index 62abf521cb..b94109c269 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/management-controller-services/management-controller-services.effects.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/management-controller-services/management-controller-services.effects.ts
@@ -18,7 +18,8 @@
import { Injectable } from '@angular/core';
import { Actions, concatLatestFrom, createEffect, ofType } from '@ngrx/effects';
import * as ManagementControllerServicesActions from './management-controller-services.actions';
-import { catchError, from, map, NEVER, Observable, of, switchMap, take, takeUntil, tap } from 'rxjs';
+import * as ErrorActions from '../../../../state/error/error.actions';
+import { catchError, from, map, of, switchMap, take, takeUntil, tap } from 'rxjs';
import { MatDialog } from '@angular/material/dialog';
import { ManagementControllerServiceService } from '../../service/management-controller-service.service';
import { Store } from '@ngrx/store';
@@ -31,17 +32,15 @@ import { EditControllerService } from '../../../../ui/common/controller-service/
import {
ComponentType,
ControllerServiceReferencingComponent,
- InlineServiceCreationRequest,
- InlineServiceCreationResponse,
- PropertyDescriptor,
UpdateControllerServiceRequest
} from '../../../../state/shared';
import { Router } from '@angular/router';
-import { ExtensionTypesService } from '../../../../service/extension-types.service';
-import { selectSaving } from './management-controller-services.selectors';
+import { selectSaving, selectStatus } from './management-controller-services.selectors';
import { EnableControllerService } from '../../../../ui/common/controller-service/enable-controller-service/enable-controller-service.component';
import { DisableControllerService } from '../../../../ui/common/controller-service/disable-controller-service/disable-controller-service.component';
import { PropertyTableHelperService } from '../../../../service/property-table-helper.service';
+import { HttpErrorResponse } from '@angular/common/http';
+import { ErrorHelper } from '../../../../service/error-helper.service';
@Injectable()
export class ManagementControllerServicesEffects {
@@ -50,7 +49,7 @@ export class ManagementControllerServicesEffects {
private store: Store
,
private client: Client,
private managementControllerServiceService: ManagementControllerServiceService,
- private extensionTypesService: ExtensionTypesService,
+ private errorHelper: ErrorHelper,
private dialog: MatDialog,
private router: Router,
private propertyTableHelperService: PropertyTableHelperService
@@ -59,7 +58,8 @@ export class ManagementControllerServicesEffects {
loadManagementControllerServices$ = createEffect(() =>
this.actions$.pipe(
ofType(ManagementControllerServicesActions.loadManagementControllerServices),
- switchMap(() =>
+ concatLatestFrom(() => this.store.select(selectStatus)),
+ switchMap(([action, status]) =>
from(this.managementControllerServiceService.getControllerServices()).pipe(
map((response) =>
ManagementControllerServicesActions.loadManagementControllerServicesSuccess({
@@ -69,13 +69,17 @@ export class ManagementControllerServicesEffects {
}
})
),
- catchError((error) =>
- of(
- ManagementControllerServicesActions.managementControllerServicesApiError({
- error: error.error
- })
- )
- )
+ catchError((errorResponse: HttpErrorResponse) => {
+ if (status === 'success') {
+ if (this.errorHelper.showErrorInContext(errorResponse.status)) {
+ return of(ErrorActions.snackBarError({ error: errorResponse.error }));
+ } else {
+ return of(this.errorHelper.fullScreenError(errorResponse));
+ }
+ } else {
+ return of(this.errorHelper.fullScreenError(errorResponse));
+ }
+ })
)
)
)
@@ -130,13 +134,10 @@ export class ManagementControllerServicesEffects {
}
})
),
- catchError((error) =>
- of(
- ManagementControllerServicesActions.managementControllerServicesApiError({
- error: error.error
- })
- )
- )
+ catchError((errorResponse: HttpErrorResponse) => {
+ this.dialog.closeAll();
+ return of(ErrorActions.snackBarError({ error: errorResponse.error }));
+ })
)
)
)
@@ -265,6 +266,8 @@ export class ManagementControllerServicesEffects {
});
editDialogReference.afterClosed().subscribe((response) => {
+ this.store.dispatch(ErrorActions.clearBannerErrors());
+
if (response != 'ROUTED') {
this.store.dispatch(
ManagementControllerServicesActions.selectControllerService({
@@ -295,18 +298,31 @@ export class ManagementControllerServicesEffects {
}
})
),
- catchError((error) =>
- of(
- ManagementControllerServicesActions.managementControllerServicesApiError({
- error: error.error
- })
- )
- )
+ catchError((errorResponse: HttpErrorResponse) => {
+ if (this.errorHelper.showErrorInContext(errorResponse.status)) {
+ return of(
+ ManagementControllerServicesActions.managementControllerServicesBannerApiError({
+ error: errorResponse.error
+ })
+ );
+ } else {
+ this.dialog.getDialogById(request.id)?.close('ROUTED');
+ return of(this.errorHelper.fullScreenError(errorResponse));
+ }
+ })
)
)
)
);
+ managementControllerServicesBannerApiError$ = createEffect(() =>
+ this.actions$.pipe(
+ ofType(ManagementControllerServicesActions.managementControllerServicesBannerApiError),
+ map((action) => action.error),
+ switchMap((error) => of(ErrorActions.addBannerError({ error })))
+ )
+ );
+
configureControllerServiceSuccess$ = createEffect(
() =>
this.actions$.pipe(
@@ -425,13 +441,9 @@ export class ManagementControllerServicesEffects {
}
})
),
- catchError((error) =>
- of(
- ManagementControllerServicesActions.managementControllerServicesApiError({
- error: error.error
- })
- )
- )
+ catchError((errorResponse: HttpErrorResponse) => {
+ return of(ErrorActions.snackBarError({ error: errorResponse.error }));
+ })
)
)
)
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/management-controller-services/management-controller-services.reducer.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/management-controller-services/management-controller-services.reducer.ts
index c23194a49b..f276f37632 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/management-controller-services/management-controller-services.reducer.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/management-controller-services/management-controller-services.reducer.ts
@@ -27,7 +27,7 @@ import {
inlineCreateControllerServiceSuccess,
loadManagementControllerServices,
loadManagementControllerServicesSuccess,
- managementControllerServicesApiError,
+ managementControllerServicesBannerApiError,
resetManagementControllerServicesState
} from './management-controller-services.actions';
import { produce } from 'immer';
@@ -36,7 +36,6 @@ export const initialState: ManagementControllerServicesState = {
controllerServices: [],
saving: false,
loadedTimestamp: '',
- error: null,
status: 'pending'
};
@@ -53,14 +52,11 @@ export const managementControllerServicesReducer = createReducer(
...state,
controllerServices: response.controllerServices,
loadedTimestamp: response.loadedTimestamp,
- error: null,
status: 'success' as const
})),
- on(managementControllerServicesApiError, (state, { error }) => ({
+ on(managementControllerServicesBannerApiError, (state, { error }) => ({
...state,
- saving: false,
- error,
- status: 'error' as const
+ saving: false
})),
on(createControllerService, configureControllerService, deleteControllerService, (state, { request }) => ({
...state,
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/management-controller-services/management-controller-services.selectors.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/management-controller-services/management-controller-services.selectors.ts
index 38a6849574..dccee8af0b 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/management-controller-services/management-controller-services.selectors.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/state/management-controller-services/management-controller-services.selectors.ts
@@ -31,6 +31,11 @@ export const selectSaving = createSelector(
(state: ManagementControllerServicesState) => state.saving
);
+export const selectStatus = createSelector(
+ selectManagementControllerServicesState,
+ (state: ManagementControllerServicesState) => state.status
+);
+
export const selectControllerServiceIdFromRoute = createSelector(selectCurrentRoute, (route) => {
if (route) {
// always select the controller service from the route
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/management-controller-services/management-controller-services.module.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/management-controller-services/management-controller-services.module.ts
index 15fc85f780..815f85abb2 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/management-controller-services/management-controller-services.module.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/settings/ui/management-controller-services/management-controller-services.module.ts
@@ -20,10 +20,11 @@ import { CommonModule } from '@angular/common';
import { ManagementControllerServices } from './management-controller-services.component';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { ControllerServiceTable } from '../../../../ui/common/controller-service/controller-service-table/controller-service-table.component';
+import { ErrorBanner } from '../../../../ui/common/error-banner/error-banner.component';
@NgModule({
declarations: [ManagementControllerServices],
exports: [ManagementControllerServices],
- imports: [CommonModule, NgxSkeletonLoaderModule, ControllerServiceTable]
+ imports: [CommonModule, NgxSkeletonLoaderModule, ControllerServiceTable, ErrorBanner]
})
export class ManagementControllerServicesModule {}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/error-helper.service.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/error-helper.service.ts
new file mode 100644
index 0000000000..3b1ef875b1
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/error-helper.service.ts
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Injectable } from '@angular/core';
+import { HttpErrorResponse } from '@angular/common/http';
+import * as ErrorDetailActions from '../state/error/error.actions';
+import { Action } from '@ngrx/store';
+import { NiFiCommon } from './nifi-common.service';
+
+@Injectable({ providedIn: 'root' })
+export class ErrorHelper {
+ constructor(private nifiCommon: NiFiCommon) {}
+
+ fullScreenError(errorResponse: HttpErrorResponse): Action {
+ let title: string;
+ let message: string;
+
+ switch (errorResponse.status) {
+ case 401:
+ title = 'Unauthorized';
+ break;
+ case 403:
+ title = 'Insufficient Permissions';
+ break;
+ case 409:
+ title = 'Invalid State';
+ break;
+ case 413:
+ title = 'Payload Too Large';
+ break;
+ case 503:
+ default:
+ title = 'An unexpected error has occurred';
+ break;
+ }
+
+ if (this.nifiCommon.isBlank(errorResponse.error)) {
+ message =
+ 'An error occurred communicating with NiFi. Please check the logs and fix any configuration issues before restarting.';
+ } else {
+ message = errorResponse.error;
+ }
+
+ return ErrorDetailActions.fullScreenError({
+ errorDetail: {
+ title,
+ message
+ }
+ });
+ }
+
+ showErrorInContext(status: number): boolean {
+ return [400, 403, 404, 409, 413, 503].includes(status);
+ }
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/extension-types.service.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/extension-types.service.ts
index 2eced9f549..5fbc01928a 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/extension-types.service.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/extension-types.service.ts
@@ -16,7 +16,7 @@
*/
import { Injectable } from '@angular/core';
-import { Observable, throwError } from 'rxjs';
+import { Observable } from 'rxjs';
import { HttpClient } from '@angular/common/http';
import { Bundle } from '../state/shared';
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/guard/authentication.guard.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/guard/authentication.guard.ts
index 7cf5227f6c..bdd3a16388 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/guard/authentication.guard.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/guard/authentication.guard.ts
@@ -80,7 +80,9 @@ export const authenticationGuard: CanMatchFn = (route, state) => {
.select(selectCurrentUserState)
.pipe(take(1))
.subscribe((userState) => {
- if (userState.status == 'pending') {
+ if (userState.status == 'success') {
+ resolve(true);
+ } else {
userService
.getUser()
.pipe(take(1))
@@ -118,7 +120,7 @@ export const authenticationGuard: CanMatchFn = (route, state) => {
}
},
error: (error) => {
- // there is no anonymous access and we don't know this user - open the login page which handles login/registration/etc
+ // there is no anonymous access and we don't know this user - open the login page which handles login
if (error.status === 401) {
authStorage.removeToken();
window.location.href = './login';
@@ -126,8 +128,6 @@ export const authenticationGuard: CanMatchFn = (route, state) => {
resolve(false);
}
});
- } else {
- resolve(true);
}
});
});
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/interceptors/auth.interceptor.spec.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/interceptors/auth.interceptor.spec.ts
index 2f45628ff9..1ce007609f 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/interceptors/auth.interceptor.spec.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/interceptors/auth.interceptor.spec.ts
@@ -18,12 +18,20 @@
import { TestBed } from '@angular/core/testing';
import { AuthInterceptor } from './auth.interceptor';
+import { provideMockStore } from '@ngrx/store/testing';
+import { initialState } from '../../state/error/error.reducer';
describe('AuthInterceptor', () => {
let service: AuthInterceptor;
beforeEach(() => {
- TestBed.configureTestingModule({});
+ TestBed.configureTestingModule({
+ providers: [
+ provideMockStore({
+ initialState
+ })
+ ]
+ });
service = TestBed.inject(AuthInterceptor);
});
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/interceptors/auth.interceptor.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/interceptors/auth.interceptor.ts
index bc07793436..7c7620ca33 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/interceptors/auth.interceptor.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/interceptors/auth.interceptor.ts
@@ -19,25 +19,54 @@ import { Injectable } from '@angular/core';
import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Observable, tap } from 'rxjs';
import { AuthStorage } from '../auth-storage.service';
+import { Store } from '@ngrx/store';
+import { NiFiState } from '../../state';
+import { fullScreenError } from '../../state/error/error.actions';
+import { NiFiCommon } from '../nifi-common.service';
@Injectable({
providedIn: 'root'
})
export class AuthInterceptor implements HttpInterceptor {
- constructor(private authStorage: AuthStorage) {}
+ routedToFullScreenError: boolean = false;
+
+ constructor(
+ private authStorage: AuthStorage,
+ private store: Store,
+ private nifiCommon: NiFiCommon
+ ) {}
intercept(request: HttpRequest, next: HttpHandler): Observable> {
return next.handle(request).pipe(
tap({
- error: (error) => {
- if (error instanceof HttpErrorResponse) {
- if (error.status === 401) {
- this.authStorage.removeToken();
+ error: (errorResponse) => {
+ if (errorResponse instanceof HttpErrorResponse) {
+ if (errorResponse.status === 401) {
+ if (this.authStorage.hasToken()) {
+ this.authStorage.removeToken();
- // navigate to the root of the app which will handle redirection to the
- // login form if appropriate... TODO - replace with logout complete page?
- window.location.href = './login';
- } // TODO handle others (403)
+ let message: string = errorResponse.error;
+ if (this.nifiCommon.isBlank(message)) {
+ message = 'Your session has expired. Please navigate home to log in again.';
+ } else {
+ message += '. Please navigate home to log in again.';
+ }
+
+ this.routedToFullScreenError = true;
+
+ this.store.dispatch(
+ fullScreenError({
+ errorDetail: {
+ title: 'Unauthorized',
+ message
+ }
+ })
+ );
+ } else if (!this.routedToFullScreenError) {
+ // the user has never logged in, redirect them to do so
+ window.location.href = './login';
+ }
+ }
}
}
})
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/property-table-helper.service.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/property-table-helper.service.ts
index c359980e6f..d46068ca9f 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/property-table-helper.service.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/service/property-table-helper.service.ts
@@ -17,7 +17,7 @@
import { Injectable } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
-import { catchError, map, NEVER, Observable, switchMap, take } from 'rxjs';
+import { catchError, EMPTY, map, Observable, switchMap, take, takeUntil, tap } from 'rxjs';
import {
ControllerServiceCreator,
ControllerServiceEntity,
@@ -32,9 +32,12 @@ import {
} from '../state/shared';
import { NewPropertyDialog } from '../ui/common/new-property-dialog/new-property-dialog.component';
import { CreateControllerService } from '../ui/common/controller-service/create-controller-service/create-controller-service.component';
-import { ManagementControllerServiceService } from '../pages/settings/service/management-controller-service.service';
import { ExtensionTypesService } from './extension-types.service';
import { Client } from './client.service';
+import { NiFiState } from '../state';
+import { Store } from '@ngrx/store';
+import { snackBarError } from '../state/error/error.actions';
+import { HttpErrorResponse } from '@angular/common/http';
@Injectable({
providedIn: 'root'
@@ -42,6 +45,7 @@ import { Client } from './client.service';
export class PropertyTableHelperService {
constructor(
private dialog: MatDialog,
+ private store: Store,
private extensionTypesService: ExtensionTypesService,
private client: Client
) {}
@@ -63,12 +67,19 @@ export class PropertyTableHelperService {
});
return newPropertyDialogReference.componentInstance.newProperty.pipe(
- take(1),
+ takeUntil(newPropertyDialogReference.afterClosed()),
switchMap((dialogResponse: NewPropertyDialogResponse) => {
return propertyDescriptorService
.getPropertyDescriptor(id, dialogResponse.name, dialogResponse.sensitive)
.pipe(
take(1),
+ catchError((errorResponse: HttpErrorResponse) => {
+ this.store.dispatch(snackBarError({ error: errorResponse.error }));
+
+ // handle the error here to keep the observable alive so the
+ // user can attempt to create the property again
+ return EMPTY;
+ }),
map((response) => {
newPropertyDialogReference.close();
@@ -112,6 +123,11 @@ export class PropertyTableHelperService {
)
.pipe(
take(1),
+ tap({
+ error: (errorResponse: HttpErrorResponse) => {
+ this.store.dispatch(snackBarError({ error: errorResponse.error }));
+ }
+ }),
switchMap((implementingTypesResponse) => {
// show the create controller service dialog with the types that implemented the interface
const createServiceDialogReference = this.dialog.open(CreateControllerService, {
@@ -122,7 +138,7 @@ export class PropertyTableHelperService {
});
return createServiceDialogReference.componentInstance.createControllerService.pipe(
- take(1),
+ takeUntil(createServiceDialogReference.afterClosed()),
switchMap((controllerServiceType) => {
// typically this sequence would be implemented with ngrx actions, however we are
// currently in an edit session, and we need to return both the value (new service id)
@@ -142,6 +158,17 @@ export class PropertyTableHelperService {
return controllerServiceCreator.createControllerService(payload).pipe(
take(1),
+ catchError((errorResponse: HttpErrorResponse) => {
+ this.store.dispatch(
+ snackBarError({
+ error: `Unable to create new Service: ${errorResponse.error}`
+ })
+ );
+
+ // handle the error here to keep the observable alive so the
+ // user can attempt to create the service again
+ return EMPTY;
+ }),
switchMap((createResponse) => {
// if provided, call the callback function
if (afterServiceCreated) {
@@ -153,6 +180,20 @@ export class PropertyTableHelperService {
.getPropertyDescriptor(id, descriptor.name, false)
.pipe(
take(1),
+ tap({
+ error: (errorResponse: HttpErrorResponse) => {
+ // we've errored getting the descriptor but since the service
+ // was already created, we should close the create service dialog
+ // so multiple service instances are not inadvertently created
+ createServiceDialogReference.close();
+
+ this.store.dispatch(
+ snackBarError({
+ error: `Service created but unable to reload Property Descriptor: ${errorResponse.error}`
+ })
+ );
+ }
+ }),
map((descriptorResponse) => {
createServiceDialogReference.close();
@@ -162,10 +203,6 @@ export class PropertyTableHelperService {
};
})
);
- }),
- catchError((error) => {
- // TODO - show error
- return NEVER;
})
);
})
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/error/error.actions.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/error/error.actions.ts
new file mode 100644
index 0000000000..6278eb92fc
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/error/error.actions.ts
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { createAction, props } from '@ngrx/store';
+import { ErrorDetail } from './index';
+
+export const fullScreenError = createAction('[Error] Full Screen Error', props<{ errorDetail: ErrorDetail }>());
+
+export const snackBarError = createAction('[Error] Snackbar Error', props<{ error: string }>());
+
+export const addBannerError = createAction('[Error] Add Banner Error', props<{ error: string }>());
+
+export const clearBannerErrors = createAction('[Error] Clear Banner Errors');
+
+export const resetErrorState = createAction('[Error] Reset Error State');
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/error/error.effects.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/error/error.effects.ts
new file mode 100644
index 0000000000..4a4d48e3a1
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/error/error.effects.ts
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Injectable } from '@angular/core';
+import { Actions, createEffect, ofType } from '@ngrx/effects';
+import * as ErrorActions from './error.actions';
+import { map, tap } from 'rxjs';
+import { Router } from '@angular/router';
+import { MatSnackBar } from '@angular/material/snack-bar';
+
+@Injectable()
+export class ErrorEffects {
+ constructor(
+ private actions$: Actions,
+ private router: Router,
+ private snackBar: MatSnackBar
+ ) {}
+
+ fullScreenError$ = createEffect(
+ () =>
+ this.actions$.pipe(
+ ofType(ErrorActions.fullScreenError),
+ tap(() => {
+ this.router.navigate(['/error'], { replaceUrl: true });
+ })
+ ),
+ { dispatch: false }
+ );
+
+ snackBarError$ = createEffect(
+ () =>
+ this.actions$.pipe(
+ ofType(ErrorActions.snackBarError),
+ map((action) => action.error),
+ tap((error) => {
+ this.snackBar.open(error, 'Dismiss', { duration: 30000 });
+ })
+ ),
+ { dispatch: false }
+ );
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/error/error.reducer.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/error/error.reducer.ts
new file mode 100644
index 0000000000..939be0688d
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/error/error.reducer.ts
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { createReducer, on } from '@ngrx/store';
+import { ErrorState } from './index';
+import { resetErrorState, fullScreenError, addBannerError, clearBannerErrors } from './error.actions';
+import { produce } from 'immer';
+
+export const initialState: ErrorState = {
+ bannerErrors: null,
+ fullScreenError: null
+};
+
+export const errorReducer = createReducer(
+ initialState,
+ on(fullScreenError, (state, { errorDetail }) => ({
+ ...state,
+ fullScreenError: errorDetail
+ })),
+ on(addBannerError, (state, { error }) => {
+ return produce(state, (draftState) => {
+ if (draftState.bannerErrors === null) {
+ draftState.bannerErrors = [];
+ }
+
+ draftState.bannerErrors.push(error);
+ });
+ }),
+ on(clearBannerErrors, (state) => ({
+ ...state,
+ bannerErrors: null
+ })),
+ on(resetErrorState, (state) => ({
+ ...initialState
+ }))
+);
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/error/error.selectors.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/error/error.selectors.ts
new file mode 100644
index 0000000000..0887a5d3de
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/error/error.selectors.ts
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { createFeatureSelector, createSelector } from '@ngrx/store';
+import { errorFeatureKey, ErrorState } from './index';
+
+export const selectErrorState = createFeatureSelector(errorFeatureKey);
+
+export const selectFullScreenError = createSelector(selectErrorState, (state: ErrorState) => state.fullScreenError);
+
+export const selectBannerErrors = createSelector(selectErrorState, (state: ErrorState) => state.bannerErrors);
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/error/index.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/error/index.ts
new file mode 100644
index 0000000000..b4febc851b
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/error/index.ts
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export const errorFeatureKey = 'error';
+
+export interface ErrorDetail {
+ title: string;
+ message: string;
+}
+
+export interface ErrorState {
+ bannerErrors: string[] | null;
+ fullScreenError: ErrorDetail | null;
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/index.ts b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/index.ts
index 4ae6619448..ed546d4a04 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/index.ts
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/state/index.ts
@@ -33,9 +33,12 @@ import { flowConfigurationFeatureKey, FlowConfigurationState } from './flow-conf
import { flowConfigurationReducer } from './flow-configuration/flow-configuration.reducer';
import { componentStateFeatureKey, ComponentStateState } from './component-state';
import { componentStateReducer } from './component-state/component-state.reducer';
+import { errorFeatureKey, ErrorState } from './error';
+import { errorReducer } from './error/error.reducer';
export interface NiFiState {
router: RouterReducerState;
+ [errorFeatureKey]: ErrorState;
[currentUserFeatureKey]: CurrentUserState;
[extensionTypesFeatureKey]: ExtensionTypesState;
[aboutFeatureKey]: AboutState;
@@ -48,6 +51,7 @@ export interface NiFiState {
export const rootReducers: ActionReducerMap = {
router: routerReducer,
+ [errorFeatureKey]: errorReducer,
[currentUserFeatureKey]: currentUserReducer,
[extensionTypesFeatureKey]: extensionTypesReducer,
[aboutFeatureKey]: aboutReducer,
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/controller-service/edit-controller-service/edit-controller-service.component.html b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/controller-service/edit-controller-service/edit-controller-service.component.html
index b507ec9201..b30e3f85fa 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/controller-service/edit-controller-service/edit-controller-service.component.html
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/controller-service/edit-controller-service/edit-controller-service.component.html
@@ -17,6 +17,7 @@
Edit Controller Service