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 439c59e733 NIFI-12742: Error Handling in Summary, Users, and Queue 
Listing (#8366)
439c59e733 is described below

commit 439c59e73362e002e8576580e9ad3f3a696ffaae
Author: Matt Gilman <matt.c.gil...@gmail.com>
AuthorDate: Fri Feb 9 09:37:07 2024 -0500

    NIFI-12742: Error Handling in Summary, Users, and Queue Listing (#8366)
    
    - Error handling in Users.
    - Error handling in Summary.
    - Error handling in Queue Listing.
    - Addressing review feedback.
    - Dispatching delete success to ensure the active request is reset upon 
completion.
    
    This closes #8366
---
 .../app/pages/queue/state/queue-listing/index.ts   |   4 +-
 .../state/queue-listing/queue-listing.actions.ts   |   2 +
 .../state/queue-listing/queue-listing.effects.ts   | 103 +++++++++++++--------
 .../state/queue-listing/queue-listing.reducer.ts   |  48 ++++++++--
 .../state/queue-listing/queue-listing.selectors.ts |  11 ++-
 .../flowfile-table/flowfile-table.component.html   |   1 +
 .../flowfile-table.component.spec.ts               |  17 +++-
 .../flowfile-table/flowfile-table.component.ts     |   3 +-
 .../ui/queue-listing/queue-listing.component.html  |  17 ++--
 .../ui/queue-listing/queue-listing.component.ts    |   8 +-
 .../queue/ui/queue-listing/queue-listing.module.ts |   4 +-
 .../summary/service/cluster-summary.service.ts     |   6 +-
 .../service/process-group-status.service.ts        |   6 +-
 .../pages/summary/state/summary-listing/index.ts   |   3 +-
 .../summary-listing/summary-listing.actions.ts     |   5 -
 .../summary-listing/summary-listing.effects.ts     |  13 ++-
 .../summary-listing/summary-listing.reducer.ts     |  15 +--
 .../app/pages/users/state/user-listing/index.ts    |   3 +-
 .../state/user-listing/user-listing.actions.ts     |   7 +-
 .../state/user-listing/user-listing.effects.ts     |  63 ++++++++++---
 .../state/user-listing/user-listing.reducer.ts     |  10 +-
 .../state/user-listing/user-listing.selectors.ts   |   2 +
 .../edit-tenant/edit-tenant-dialog.component.html  |   1 +
 .../edit-tenant-dialog.component.spec.ts           |  19 +++-
 .../edit-tenant/edit-tenant-dialog.component.ts    |   4 +-
 25 files changed, 244 insertions(+), 131 deletions(-)

diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/state/queue-listing/index.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/state/queue-listing/index.ts
index 0ce0492f81..b5de716db7 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/state/queue-listing/index.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/state/queue-listing/index.ts
@@ -101,9 +101,9 @@ export interface FlowFileDialogRequest {
 }
 
 export interface QueueListingState {
-    requestEntity: ListingRequestEntity | null;
+    activeListingRequest: ListingRequest | null;
+    completedListingRequest: ListingRequest | null;
     connectionLabel: string;
     loadedTimestamp: string;
-    error: string | null;
     status: 'pending' | 'loading' | 'error' | 'success';
 }
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/state/queue-listing/queue-listing.actions.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/state/queue-listing/queue-listing.actions.ts
index 52c4200dba..b4ff565d1d 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/state/queue-listing/queue-listing.actions.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/state/queue-listing/queue-listing.actions.ts
@@ -68,6 +68,8 @@ export const stopPollingQueueListingRequest = 
createAction(`${QUEUE_PREFIX} Stop
 
 export const deleteQueueListingRequest = createAction(`${QUEUE_PREFIX} Delete 
Queue Listing Request`);
 
+export const deleteQueueListingRequestSuccess = createAction(`${QUEUE_PREFIX} 
Delete Queue Listing Request Success`);
+
 export const viewFlowFile = createAction(`${QUEUE_PREFIX} View FlowFile`, 
props<{ request: ViewFlowFileRequest }>());
 
 export const openFlowFileDialog = createAction(
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/state/queue-listing/queue-listing.effects.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/state/queue-listing/queue-listing.effects.ts
index 2d9eba388d..00564e1657 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/state/queue-listing/queue-listing.effects.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/state/queue-listing/queue-listing.effects.ts
@@ -21,7 +21,7 @@ import * as QueueListingActions from 
'./queue-listing.actions';
 import { Store } from '@ngrx/store';
 import { CanvasState } from '../../../flow-designer/state';
 import { asyncScheduler, catchError, filter, from, interval, map, of, 
switchMap, take, takeUntil, tap } from 'rxjs';
-import { selectConnectionIdFromRoute, selectListingRequestEntity } from 
'./queue-listing.selectors';
+import { selectConnectionIdFromRoute, selectActiveListingRequest } from 
'./queue-listing.selectors';
 import { QueueService } from '../../service/queue.service';
 import { ListingRequest } from './index';
 import { CancelDialog } from 
'../../../../ui/common/cancel-dialog/cancel-dialog.component';
@@ -30,6 +30,10 @@ import { selectAbout } from 
'../../../../state/about/about.selectors';
 import { FlowFileDialog } from 
'../../ui/queue-listing/flowfile-dialog/flowfile-dialog.component';
 import { NiFiCommon } from '../../../../service/nifi-common.service';
 import { isDefinedAndNotNull } from '../../../../state/shared';
+import { HttpErrorResponse } from '@angular/common/http';
+import * as ErrorActions from '../../../../state/error/error.actions';
+import { ErrorHelper } from '../../../../service/error-helper.service';
+import { stopPollingQueueListingRequest } from './queue-listing.actions';
 
 @Injectable()
 export class QueueListingEffects {
@@ -37,6 +41,7 @@ export class QueueListingEffects {
         private actions$: Actions,
         private store: Store<CanvasState>,
         private queueService: QueueService,
+        private errorHelper: ErrorHelper,
         private dialog: MatDialog,
         private nifiCommon: NiFiCommon
     ) {}
@@ -103,13 +108,19 @@ export class QueueListingEffects {
                             }
                         })
                     ),
-                    catchError((error) =>
-                        of(
-                            QueueListingActions.queueListingApiError({
-                                error: error.error
-                            })
-                        )
-                    )
+                    catchError((errorResponse: HttpErrorResponse) => {
+                        if 
(this.errorHelper.showErrorInContext(errorResponse.status)) {
+                            return of(
+                                QueueListingActions.queueListingApiError({
+                                    error: errorResponse.error
+                                })
+                            );
+                        } else {
+                            
this.store.dispatch(stopPollingQueueListingRequest());
+
+                            return 
of(this.errorHelper.fullScreenError(errorResponse));
+                        }
+                    })
                 );
             })
         )
@@ -155,9 +166,9 @@ export class QueueListingEffects {
     pollQueueListingRequest$ = createEffect(() =>
         this.actions$.pipe(
             ofType(QueueListingActions.pollQueueListingRequest),
-            concatLatestFrom(() => 
this.store.select(selectListingRequestEntity).pipe(isDefinedAndNotNull())),
-            switchMap(([, requestEntity]) => {
-                return 
from(this.queueService.pollQueueListingRequest(requestEntity.listingRequest)).pipe(
+            concatLatestFrom(() => 
this.store.select(selectActiveListingRequest).pipe(isDefinedAndNotNull())),
+            switchMap(([, listingRequest]) => {
+                return 
from(this.queueService.pollQueueListingRequest(listingRequest)).pipe(
                     map((response) =>
                         QueueListingActions.pollQueueListingRequestSuccess({
                             response: {
@@ -165,13 +176,19 @@ export class QueueListingEffects {
                             }
                         })
                     ),
-                    catchError((error) =>
-                        of(
-                            QueueListingActions.queueListingApiError({
-                                error: error.error
-                            })
-                        )
-                    )
+                    catchError((errorResponse: HttpErrorResponse) => {
+                        if 
(this.errorHelper.showErrorInContext(errorResponse.status)) {
+                            return of(
+                                QueueListingActions.queueListingApiError({
+                                    error: errorResponse.error
+                                })
+                            );
+                        } else {
+                            
this.store.dispatch(stopPollingQueueListingRequest());
+
+                            return 
of(this.errorHelper.fullScreenError(errorResponse));
+                        }
+                    })
                 );
             })
         )
@@ -193,20 +210,23 @@ export class QueueListingEffects {
         )
     );
 
-    deleteQueueListingRequest$ = createEffect(
-        () =>
-            this.actions$.pipe(
-                ofType(QueueListingActions.deleteQueueListingRequest),
-                concatLatestFrom(() => 
this.store.select(selectListingRequestEntity)),
-                tap(([, requestEntity]) => {
-                    this.dialog.closeAll();
+    deleteQueueListingRequest$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(QueueListingActions.deleteQueueListingRequest),
+            concatLatestFrom(() => 
this.store.select(selectActiveListingRequest)),
+            tap(([, listingRequest]) => {
+                this.dialog.closeAll();
 
-                    if (requestEntity) {
-                        
this.queueService.deleteQueueListingRequest(requestEntity.listingRequest).subscribe();
-                    }
-                })
-            ),
-        { dispatch: false }
+                if (listingRequest) {
+                    
this.queueService.deleteQueueListingRequest(listingRequest).subscribe({
+                        error: (errorResponse: HttpErrorResponse) => {
+                            this.store.dispatch(ErrorActions.snackBarError({ 
error: errorResponse.error }));
+                        }
+                    });
+                }
+            }),
+            switchMap(() => 
of(QueueListingActions.deleteQueueListingRequestSuccess()))
+        )
     );
 
     viewFlowFile$ = createEffect(() =>
@@ -222,10 +242,10 @@ export class QueueListingEffects {
                             }
                         })
                     ),
-                    catchError((error) =>
+                    catchError((errorResponse: HttpErrorResponse) =>
                         of(
-                            QueueListingActions.queueListingApiError({
-                                error: error.error
+                            ErrorActions.snackBarError({
+                                error: errorResponse.error
                             })
                         )
                     )
@@ -298,12 +318,13 @@ export class QueueListingEffects {
         { dispatch: false }
     );
 
-    queueListingApiError$ = createEffect(
-        () =>
-            this.actions$.pipe(
-                ofType(QueueListingActions.queueListingApiError),
-                tap(() => this.dialog.closeAll())
-            ),
-        { dispatch: false }
+    queueListingApiError$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(QueueListingActions.queueListingApiError),
+            tap(() => {
+                
this.store.dispatch(QueueListingActions.stopPollingQueueListingRequest());
+            }),
+            switchMap(({ error }) => of(ErrorActions.addBannerError({ error 
})))
+        )
     );
 }
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/state/queue-listing/queue-listing.reducer.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/state/queue-listing/queue-listing.reducer.ts
index 27f7fde081..28749d0657 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/state/queue-listing/queue-listing.reducer.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/state/queue-listing/queue-listing.reducer.ts
@@ -23,14 +23,33 @@ import {
     submitQueueListingRequestSuccess,
     resetQueueListingState,
     queueListingApiError,
-    loadConnectionLabelSuccess
+    loadConnectionLabelSuccess,
+    deleteQueueListingRequestSuccess
 } from './queue-listing.actions';
+import { produce } from 'immer';
 
 export const initialState: QueueListingState = {
-    requestEntity: null,
+    activeListingRequest: null,
+    completedListingRequest: {
+        id: '',
+        uri: '',
+        submissionTime: '',
+        lastUpdated: '',
+        percentCompleted: 100,
+        finished: true,
+        failureReason: '',
+        maxResults: 0,
+        sourceRunning: false,
+        destinationRunning: false,
+        state: '',
+        queueSize: {
+            objectCount: 0,
+            byteCount: 0
+        },
+        flowFileSummaries: []
+    },
     connectionLabel: 'Connection',
     loadedTimestamp: 'N/A',
-    error: null,
     status: 'pending'
 };
 
@@ -44,16 +63,25 @@ export const queueListingReducer = createReducer(
         ...state,
         status: 'loading' as const
     })),
-    on(submitQueueListingRequestSuccess, pollQueueListingRequestSuccess, 
(state, { response }) => ({
+    on(submitQueueListingRequestSuccess, pollQueueListingRequestSuccess, 
(state, { response }) => {
+        return produce(state, (draftState) => {
+            const listingRequest = response.requestEntity.listingRequest;
+
+            if (listingRequest.finished) {
+                draftState.completedListingRequest = listingRequest;
+                draftState.loadedTimestamp = listingRequest.lastUpdated;
+                draftState.status = 'success' as const;
+            } else {
+                draftState.activeListingRequest = listingRequest;
+            }
+        });
+    }),
+    on(deleteQueueListingRequestSuccess, (state) => ({
         ...state,
-        requestEntity: response.requestEntity,
-        loadedTimestamp: response.requestEntity.listingRequest.lastUpdated,
-        error: null,
-        status: 'success' as const
+        activeListingRequest: null
     })),
-    on(queueListingApiError, (state, { error }) => ({
+    on(queueListingApiError, (state) => ({
         ...state,
-        error,
         status: 'error' as const
     })),
     on(resetQueueListingState, () => ({
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/state/queue-listing/queue-listing.selectors.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/state/queue-listing/queue-listing.selectors.ts
index 01f3e8af39..a43fe859e0 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/state/queue-listing/queue-listing.selectors.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/state/queue-listing/queue-listing.selectors.ts
@@ -25,14 +25,17 @@ export const selectQueueListingState = createSelector(
     (state: QueueState) => state[queueListingFeatureKey]
 );
 
-export const selectListingRequestEntity = createSelector(
+export const selectActiveListingRequest = createSelector(
     selectQueueListingState,
-    (state: QueueListingState) => state.requestEntity
+    (state: QueueListingState) => state.activeListingRequest
 );
 
-export const selectStatus = createSelector(selectQueueListingState, (state: 
QueueListingState) => state.status);
+export const selectCompletedListingRequest = createSelector(
+    selectQueueListingState,
+    (state: QueueListingState) => state.completedListingRequest
+);
 
-export const selectError = createSelector(selectQueueListingState, (state: 
QueueListingState) => state.error);
+export const selectStatus = createSelector(selectQueueListingState, (state: 
QueueListingState) => state.status);
 
 export const selectConnectionLabel = createSelector(
     selectQueueListingState,
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/flowfile-table/flowfile-table.component.html
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/flowfile-table/flowfile-table.component.html
index 2b0f76c870..ddef523062 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/flowfile-table/flowfile-table.component.html
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/flowfile-table/flowfile-table.component.html
@@ -17,6 +17,7 @@
 
 <div class="flowfile-table h-full flex flex-col gap-y-2">
     <h3 class="text-xl bold queue-listing-header">{{ connectionLabel }}</h3>
+    <error-banner></error-banner>
     <div class="flex justify-between">
         <div class="value">
             Display {{ displayObjectCount }} of {{ 
formatCount(queueSizeObjectCount) }} ({{
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/flowfile-table/flowfile-table.component.spec.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/flowfile-table/flowfile-table.component.spec.ts
index fd491f6d65..7e66b66c9a 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/flowfile-table/flowfile-table.component.spec.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/flowfile-table/flowfile-table.component.spec.ts
@@ -20,14 +20,29 @@ import { ComponentFixture, TestBed } from 
'@angular/core/testing';
 import { FlowFileTable } from './flowfile-table.component';
 import { MatTableModule } from '@angular/material/table';
 import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { Component } from '@angular/core';
+import { provideMockStore } from '@ngrx/store/testing';
+import { initialState } from '../../../../../state/error/error.reducer';
 
 describe('FlowFileTable', () => {
     let component: FlowFileTable;
     let fixture: ComponentFixture<FlowFileTable>;
 
+    @Component({
+        selector: 'error-banner',
+        standalone: true,
+        template: ''
+    })
+    class MockErrorBanner {}
+
     beforeEach(() => {
         TestBed.configureTestingModule({
-            imports: [FlowFileTable, MatTableModule, BrowserAnimationsModule]
+            imports: [FlowFileTable, MockErrorBanner, MatTableModule, 
BrowserAnimationsModule],
+            providers: [
+                provideMockStore({
+                    initialState
+                })
+            ]
         });
         fixture = TestBed.createComponent(FlowFileTable);
         component = fixture.componentInstance;
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/flowfile-table/flowfile-table.component.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/flowfile-table/flowfile-table.component.ts
index be07533874..2404537651 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/flowfile-table/flowfile-table.component.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/flowfile-table/flowfile-table.component.ts
@@ -25,12 +25,13 @@ import { NgForOf, NgIf } from '@angular/common';
 import { RouterLink } from '@angular/router';
 import { FlowFileSummary, ListingRequest } from '../../../state/queue-listing';
 import { CurrentUser } from '../../../../../state/current-user';
+import { ErrorBanner } from 
'../../../../../ui/common/error-banner/error-banner.component';
 
 @Component({
     selector: 'flowfile-table',
     standalone: true,
     templateUrl: './flowfile-table.component.html',
-    imports: [MatTableModule, NgForOf, NgIf, RouterLink],
+    imports: [MatTableModule, NgForOf, NgIf, RouterLink, ErrorBanner],
     styleUrls: ['./flowfile-table.component.scss']
 })
 export class FlowFileTable {
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/queue-listing.component.html
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/queue-listing.component.html
index c5824e47dc..7ade9f96e0 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/queue-listing.component.html
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/queue-listing.component.html
@@ -17,23 +17,20 @@
 
 <div class="flex flex-col gap-y-2 h-full" *ngIf="status$ | async; let status">
     <div class="flex-1">
-        <div class="value" *ngIf="status === 'error'; else noError">
-            {{ error$ | async }}
-        </div>
-        <ng-template #noError>
-            <ng-container *ngIf="listingRequestEntity$ | async as entity; else 
initialLoading">
+        <ng-container *ngIf="listingRequest$ | async as listingRequest; else 
initialLoading">
+            <ng-container *ngIf="about$ | async as about">
                 <flowfile-table
                     [connectionLabel]="(connectionLabel$ | async)!"
-                    [listingRequest]="entity.listingRequest"
+                    [listingRequest]="listingRequest"
                     [currentUser]="(currentUser$ | async)!"
-                    [contentViewerAvailable]="contentViewerAvailable((about$ | 
async)!)"
+                    [contentViewerAvailable]="contentViewerAvailable(about)"
                     (viewFlowFile)="viewFlowFile($event)"
                     (downloadContent)="downloadContent($event)"
                     (viewContent)="viewContent($event)"></flowfile-table>
             </ng-container>
-            <ng-template #initialLoading>
-                <ngx-skeleton-loader count="3"></ngx-skeleton-loader>
-            </ng-template>
+        </ng-container>
+        <ng-template #initialLoading>
+            <ngx-skeleton-loader count="3"></ngx-skeleton-loader>
         </ng-template>
     </div>
     <div class="flex justify-between">
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/queue-listing.component.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/queue-listing.component.ts
index 2f48c84425..b340c24fcb 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/queue-listing.component.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/queue-listing.component.ts
@@ -21,8 +21,7 @@ import { distinctUntilChanged, filter } from 'rxjs';
 import {
     selectConnectionIdFromRoute,
     selectConnectionLabel,
-    selectError,
-    selectListingRequestEntity,
+    selectCompletedListingRequest,
     selectLoadedTimestamp,
     selectStatus
 } from '../../state/queue-listing/queue-listing.selectors';
@@ -41,6 +40,7 @@ import { NiFiState } from '../../../../state';
 import { selectAbout } from '../../../../state/about/about.selectors';
 import { About } from '../../../../state/about';
 import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
+import { clearBannerErrors } from '../../../../state/error/error.actions';
 
 @Component({
     selector: 'queue-listing',
@@ -49,10 +49,9 @@ import { takeUntilDestroyed } from 
'@angular/core/rxjs-interop';
 })
 export class QueueListing implements OnDestroy {
     status$ = this.store.select(selectStatus);
-    error$ = this.store.select(selectError);
     connectionLabel$ = this.store.select(selectConnectionLabel);
     loadedTimestamp$ = this.store.select(selectLoadedTimestamp);
-    listingRequestEntity$ = this.store.select(selectListingRequestEntity);
+    listingRequest$ = this.store.select(selectCompletedListingRequest);
     currentUser$ = this.store.select(selectCurrentUser);
     about$ = this.store.select(selectAbout);
 
@@ -104,5 +103,6 @@ export class QueueListing implements OnDestroy {
 
     ngOnDestroy(): void {
         this.store.dispatch(resetQueueListingState());
+        this.store.dispatch(clearBannerErrors());
     }
 }
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/queue-listing.module.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/queue-listing.module.ts
index a35f591f7f..5e77a8f6ab 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/queue-listing.module.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/queue/ui/queue-listing/queue-listing.module.ts
@@ -26,6 +26,7 @@ import { StoreModule } from '@ngrx/store';
 import { EffectsModule } from '@ngrx/effects';
 import { queueFeatureKey, reducers } from '../../state';
 import { QueueListingEffects } from 
'../../state/queue-listing/queue-listing.effects';
+import { ErrorBanner } from 
'../../../../ui/common/error-banner/error-banner.component';
 
 @NgModule({
     declarations: [QueueListing],
@@ -37,7 +38,8 @@ import { QueueListingEffects } from 
'../../state/queue-listing/queue-listing.eff
         NifiTooltipDirective,
         FlowFileTable,
         StoreModule.forFeature(queueFeatureKey, reducers),
-        EffectsModule.forFeature(QueueListingEffects)
+        EffectsModule.forFeature(QueueListingEffects),
+        ErrorBanner
     ]
 })
 export class QueueListingModule {}
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/service/cluster-summary.service.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/service/cluster-summary.service.ts
index 476e999fd0..fab47aebf3 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/service/cluster-summary.service.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/service/cluster-summary.service.ts
@@ -17,17 +17,13 @@
 
 import { Injectable } from '@angular/core';
 import { HttpClient } from '@angular/common/http';
-import { Client } from '../../../service/client.service';
 import { Observable } from 'rxjs';
 
 @Injectable({ providedIn: 'root' })
 export class ClusterSummaryService {
     private static readonly API: string = '../nifi-api';
 
-    constructor(
-        private httpClient: HttpClient,
-        private client: Client
-    ) {}
+    constructor(private httpClient: HttpClient) {}
 
     getClusterSummary(): Observable<any> {
         return 
this.httpClient.get(`${ClusterSummaryService.API}/flow/cluster/summary`);
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/service/process-group-status.service.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/service/process-group-status.service.ts
index 6d9818d6b5..e9d6618d93 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/service/process-group-status.service.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/service/process-group-status.service.ts
@@ -17,17 +17,13 @@
 
 import { Injectable } from '@angular/core';
 import { HttpClient } from '@angular/common/http';
-import { Client } from '../../../service/client.service';
 import { Observable } from 'rxjs';
 
 @Injectable({ providedIn: 'root' })
 export class ProcessGroupStatusService {
     private static readonly API: string = '../nifi-api';
 
-    constructor(
-        private httpClient: HttpClient,
-        private client: Client
-    ) {}
+    constructor(private httpClient: HttpClient) {}
 
     getProcessGroupsStatus(recursive?: boolean): Observable<any> {
         if (recursive) {
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/state/summary-listing/index.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/state/summary-listing/index.ts
index f1b1aefafa..cecb59c9de 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/state/summary-listing/index.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/state/summary-listing/index.ts
@@ -199,6 +199,5 @@ export interface SummaryListingState {
     connectionStatusSnapshots: ConnectionStatusSnapshotEntity[];
     remoteProcessGroupStatusSnapshots: 
RemoteProcessGroupStatusSnapshotEntity[];
     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/summary/state/summary-listing/summary-listing.actions.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/state/summary-listing/summary-listing.actions.ts
index c531bc1426..5b79d7b453 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/state/summary-listing/summary-listing.actions.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/state/summary-listing/summary-listing.actions.ts
@@ -37,11 +37,6 @@ export const loadSummaryListingSuccess = createAction(
     props<{ response: SummaryListingResponse }>()
 );
 
-export const summaryListingApiError = createAction(
-    `${SUMMARY_LISTING_PREFIX} Load Summary Listing error`,
-    props<{ error: string }>()
-);
-
 export const selectProcessorStatus = createAction(
     `${SUMMARY_LISTING_PREFIX} Select Processor Status`,
     props<{ request: SelectProcessorStatusRequest }>()
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/state/summary-listing/summary-listing.effects.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/state/summary-listing/summary-listing.effects.ts
index fe41f8aca5..ccf143ebe5 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/state/summary-listing/summary-listing.effects.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/state/summary-listing/summary-listing.effects.ts
@@ -16,7 +16,7 @@
  */
 
 import { Injectable } from '@angular/core';
-import { Actions, createEffect, ofType } from '@ngrx/effects';
+import { Actions, concatLatestFrom, createEffect, ofType } from 
'@ngrx/effects';
 import { Store } from '@ngrx/store';
 import { NiFiState } from '../../../../state';
 import { ClusterSummaryService } from '../../service/cluster-summary.service';
@@ -27,6 +27,9 @@ import * as StatusHistoryActions from 
'../../../../state/status-history/status-h
 import { catchError, combineLatest, filter, map, of, switchMap, tap } from 
'rxjs';
 import { Router } from '@angular/router';
 import { ComponentType } from '../../../../state/shared';
+import { ErrorHelper } from '../../../../service/error-helper.service';
+import { HttpErrorResponse } from '@angular/common/http';
+import { selectSummaryListingStatus } from './summary-listing.selectors';
 
 @Injectable()
 export class SummaryListingEffects {
@@ -35,6 +38,7 @@ export class SummaryListingEffects {
         private store: Store<NiFiState>,
         private clusterSummaryService: ClusterSummaryService,
         private pgStatusService: ProcessGroupStatusService,
+        private errorHelper: ErrorHelper,
         private router: Router
     ) {}
 
@@ -42,7 +46,8 @@ export class SummaryListingEffects {
         this.actions$.pipe(
             ofType(SummaryListingActions.loadSummaryListing),
             map((action) => action.recursive),
-            switchMap((recursive) =>
+            concatLatestFrom(() => 
this.store.select(selectSummaryListingStatus)),
+            switchMap(([recursive, listingStatus]) =>
                 combineLatest([
                     this.clusterSummaryService.getClusterSummary(),
                     this.pgStatusService.getProcessGroupsStatus(recursive)
@@ -55,7 +60,9 @@ export class SummaryListingEffects {
                             }
                         })
                     ),
-                    catchError((error) => 
of(SummaryListingActions.summaryListingApiError({ error: error.error })))
+                    catchError((errorResponse: HttpErrorResponse) =>
+                        of(this.errorHelper.handleLoadingError(listingStatus, 
errorResponse))
+                    )
                 )
             )
         )
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/state/summary-listing/summary-listing.reducer.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/state/summary-listing/summary-listing.reducer.ts
index d8c0a94c66..b3a19d533a 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/state/summary-listing/summary-listing.reducer.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/summary/state/summary-listing/summary-listing.reducer.ts
@@ -25,12 +25,7 @@ import {
     RemoteProcessGroupStatusSnapshotEntity,
     SummaryListingState
 } from './index';
-import {
-    loadSummaryListing,
-    loadSummaryListingSuccess,
-    resetSummaryState,
-    summaryListingApiError
-} from './summary-listing.actions';
+import { loadSummaryListing, loadSummaryListingSuccess, resetSummaryState } 
from './summary-listing.actions';
 
 export const initialState: SummaryListingState = {
     clusterSummary: null,
@@ -42,7 +37,6 @@ export const initialState: SummaryListingState = {
     connectionStatusSnapshots: [],
     remoteProcessGroupStatusSnapshots: [],
     status: 'pending',
-    error: null,
     loadedTimestamp: ''
 };
 
@@ -85,7 +79,6 @@ export const summaryListingReducer = createReducer(
 
         return {
             ...state,
-            error: null,
             status: 'success' as const,
             loadedTimestamp: 
response.status.processGroupStatus.statsLastRefreshed,
             processGroupStatus: response.status,
@@ -99,12 +92,6 @@ export const summaryListingReducer = createReducer(
         };
     }),
 
-    on(summaryListingApiError, (state, { error }) => ({
-        ...state,
-        error,
-        status: 'error' as const
-    })),
-
     on(resetSummaryState, () => ({
         ...initialState
     }))
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/state/user-listing/index.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/state/user-listing/index.ts
index b88ea61c4f..ec9bdf9529 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/state/user-listing/index.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/state/user-listing/index.ts
@@ -116,6 +116,5 @@ export interface UserListingState {
     userGroups: UserGroupEntity[];
     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/users/state/user-listing/user-listing.actions.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/state/user-listing/user-listing.actions.ts
index 0e33230507..32d8cfeada 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/state/user-listing/user-listing.actions.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/state/user-listing/user-listing.actions.ts
@@ -44,7 +44,12 @@ export const loadTenantsSuccess = createAction(
     props<{ response: LoadTenantsSuccess }>()
 );
 
-export const usersApiError = createAction(`${USER_PREFIX} Users Api Error`, 
props<{ error: string }>());
+export const usersApiSnackbarError = createAction(
+    `${USER_PREFIX} Users Api Snackbar Error`,
+    props<{ error: string }>()
+);
+
+export const usersApiBannerError = createAction(`${USER_PREFIX} Users Api 
Banner Error`, props<{ error: string }>());
 
 export const openCreateTenantDialog = createAction(`${USER_PREFIX} Open Create 
Tenant Dialog`);
 
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/state/user-listing/user-listing.effects.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/state/user-listing/user-listing.effects.ts
index c134684d79..04baf2b6c4 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/state/user-listing/user-listing.effects.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/state/user-listing/user-listing.effects.ts
@@ -26,12 +26,15 @@ import { MatDialog } from '@angular/material/dialog';
 import { UsersService } from '../../service/users.service';
 import { YesNoDialog } from 
'../../../../ui/common/yes-no-dialog/yes-no-dialog.component';
 import { EditTenantDialog } from 
'../../../../ui/common/edit-tenant/edit-tenant-dialog.component';
-import { selectSaving, selectUserGroups, selectUsers } from 
'./user-listing.selectors';
+import { selectSaving, selectStatus, selectUserGroups, selectUsers } from 
'./user-listing.selectors';
 import { EditTenantRequest, UserGroupEntity } from '../../../../state/shared';
 import { selectTenant } from './user-listing.actions';
 import { Client } from '../../../../service/client.service';
 import { NiFiCommon } from '../../../../service/nifi-common.service';
 import { UserAccessPolicies } from 
'../../ui/user-listing/user-access-policies/user-access-policies.component';
+import * as ErrorActions from '../../../../state/error/error.actions';
+import { ErrorHelper } from '../../../../service/error-helper.service';
+import { HttpErrorResponse } from '@angular/common/http';
 
 @Injectable()
 export class UserListingEffects {
@@ -44,13 +47,15 @@ export class UserListingEffects {
         private store: Store<NiFiState>,
         private router: Router,
         private usersService: UsersService,
+        private errorHelper: ErrorHelper,
         private dialog: MatDialog
     ) {}
 
     loadTenants$ = createEffect(() =>
         this.actions$.pipe(
             ofType(UserListingActions.loadTenants),
-            switchMap(() =>
+            concatLatestFrom(() => this.store.select(selectStatus)),
+            switchMap(([, status]) =>
                 combineLatest([this.usersService.getUsers(), 
this.usersService.getUserGroups()]).pipe(
                     map(([usersResponse, userGroupsResponse]) =>
                         UserListingActions.loadTenantsSuccess({
@@ -61,12 +66,8 @@ export class UserListingEffects {
                             }
                         })
                     ),
-                    catchError((error) =>
-                        of(
-                            UserListingActions.usersApiError({
-                                error: error.error
-                            })
-                        )
+                    catchError((errorResponse: HttpErrorResponse) =>
+                        of(this.errorHelper.handleLoadingError(status, 
errorResponse))
                     )
                 )
             )
@@ -155,7 +156,10 @@ export class UserListingEffects {
                             }
                         })
                     ),
-                    catchError((error) => 
of(UserListingActions.usersApiError({ error: error.error })))
+                    catchError((errorResponse: HttpErrorResponse) => {
+                        this.dialog.closeAll();
+                        return of(UserListingActions.usersApiSnackbarError({ 
error: errorResponse.error }));
+                    })
                 )
             )
         )
@@ -219,6 +223,22 @@ export class UserListingEffects {
         )
     );
 
+    usersApiBannerError$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(UserListingActions.usersApiBannerError),
+            map((action) => action.error),
+            switchMap((error) => of(ErrorActions.addBannerError({ error })))
+        )
+    );
+
+    usersApiSnackbarError$ = createEffect(() =>
+        this.actions$.pipe(
+            ofType(UserListingActions.usersApiSnackbarError),
+            map((action) => action.error),
+            switchMap((error) => of(ErrorActions.snackBarError({ error })))
+        )
+    );
+
     awaitUpdateUserGroupsForCreateUser$ = createEffect(() =>
         this.actions$.pipe(
             ofType(UserListingActions.createUserSuccess),
@@ -263,7 +283,10 @@ export class UserListingEffects {
                             }
                         })
                     ),
-                    catchError((error) => 
of(UserListingActions.usersApiError({ error: error.error })))
+                    catchError((errorResponse: HttpErrorResponse) => {
+                        this.dialog.closeAll();
+                        return of(UserListingActions.usersApiSnackbarError({ 
error: errorResponse.error }));
+                    })
                 )
             )
         )
@@ -360,6 +383,8 @@ export class UserListingEffects {
                         });
 
                     dialogReference.afterClosed().subscribe(() => {
+                        this.store.dispatch(ErrorActions.clearBannerErrors());
+
                         this.store.dispatch(
                             selectTenant({
                                 id: request.user.id
@@ -385,7 +410,9 @@ export class UserListingEffects {
                             }
                         })
                     ),
-                    catchError((error) => 
of(UserListingActions.usersApiError({ error: error.error })))
+                    catchError((errorResponse: HttpErrorResponse) =>
+                        of(UserListingActions.usersApiBannerError({ error: 
errorResponse.error }))
+                    )
                 )
             )
         )
@@ -560,6 +587,8 @@ export class UserListingEffects {
                         });
 
                     dialogReference.afterClosed().subscribe(() => {
+                        this.store.dispatch(ErrorActions.clearBannerErrors());
+
                         this.store.dispatch(
                             selectTenant({
                                 id: request.userGroup.id
@@ -585,7 +614,9 @@ export class UserListingEffects {
                             }
                         })
                     ),
-                    catchError((error) => 
of(UserListingActions.usersApiError({ error: error.error })))
+                    catchError((errorResponse: HttpErrorResponse) =>
+                        of(UserListingActions.usersApiBannerError({ error: 
errorResponse.error }))
+                    )
                 )
             )
         )
@@ -674,7 +705,9 @@ export class UserListingEffects {
             switchMap((request) =>
                 from(this.usersService.deleteUser(request.user)).pipe(
                     map(() => UserListingActions.loadTenants()),
-                    catchError((error) => 
of(UserListingActions.usersApiError({ error: error.error })))
+                    catchError((errorResponse: HttpErrorResponse) =>
+                        of(UserListingActions.usersApiSnackbarError({ error: 
errorResponse.error }))
+                    )
                 )
             )
         )
@@ -713,7 +746,9 @@ export class UserListingEffects {
             switchMap((request) =>
                 
from(this.usersService.deleteUserGroup(request.userGroup)).pipe(
                     map(() => UserListingActions.loadTenants()),
-                    catchError((error) => 
of(UserListingActions.usersApiError({ error: error.error })))
+                    catchError((errorResponse: HttpErrorResponse) =>
+                        of(UserListingActions.usersApiSnackbarError({ 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/users/state/user-listing/user-listing.reducer.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/state/user-listing/user-listing.reducer.ts
index 7602d059fe..80b37a2b27 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/state/user-listing/user-listing.reducer.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/state/user-listing/user-listing.reducer.ts
@@ -28,7 +28,9 @@ import {
     updateUser,
     updateUserComplete,
     updateUserGroup,
-    updateUserGroupSuccess
+    updateUserGroupSuccess,
+    usersApiBannerError,
+    usersApiSnackbarError
 } from './user-listing.actions';
 
 export const initialState: UserListingState = {
@@ -36,7 +38,6 @@ export const initialState: UserListingState = {
     userGroups: [],
     saving: false,
     loadedTimestamp: '',
-    error: null,
     status: 'pending'
 };
 
@@ -54,7 +55,6 @@ export const userListingReducer = createReducer(
         users: response.users,
         userGroups: response.userGroups,
         loadedTimestamp: response.loadedTimestamp,
-        error: null,
         status: 'success' as const
     })),
     on(createUser, updateUser, createUserGroup, updateUserGroup, (state) => ({
@@ -76,5 +76,9 @@ export const userListingReducer = createReducer(
     on(updateUserGroupSuccess, (state, { response }) => ({
         ...state,
         saving: response.requestId == null ? false : state.saving
+    })),
+    on(usersApiSnackbarError, usersApiBannerError, (state) => ({
+        ...state,
+        saving: false
     }))
 );
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/state/user-listing/user-listing.selectors.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/state/user-listing/user-listing.selectors.ts
index 632381dbbe..b84caa15ca 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/state/user-listing/user-listing.selectors.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/users/state/user-listing/user-listing.selectors.ts
@@ -24,6 +24,8 @@ export const selectUserListingState = 
createSelector(selectUserState, (state: Us
 
 export const selectSaving = createSelector(selectUserListingState, (state: 
UserListingState) => state.saving);
 
+export const selectStatus = createSelector(selectUserListingState, (state: 
UserListingState) => state.status);
+
 export const selectUsers = createSelector(selectUserListingState, (state: 
UserListingState) => state.users);
 
 export const selectUserGroups = createSelector(selectUserListingState, (state: 
UserListingState) => state.userGroups);
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/edit-tenant/edit-tenant-dialog.component.html
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/edit-tenant/edit-tenant-dialog.component.html
index 67b6cc27d7..2bf6307f68 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/edit-tenant/edit-tenant-dialog.component.html
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/edit-tenant/edit-tenant-dialog.component.html
@@ -17,6 +17,7 @@
 
 <h2 mat-dialog-title>{{ isNew ? 'Add' : 'Edit' }} {{ isUser ? 'User' : 'User 
Group' }}</h2>
 <form class="edit-tenant-form" [formGroup]="editTenantForm">
+    <error-banner></error-banner>
     <mat-dialog-content>
         <div class="mb-6">
             <mat-radio-group formControlName="tenantType" 
(change)="tenantTypeChanged()">
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/edit-tenant/edit-tenant-dialog.component.spec.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/edit-tenant/edit-tenant-dialog.component.spec.ts
index c23f18a12b..50d88a8255 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/edit-tenant/edit-tenant-dialog.component.spec.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/edit-tenant/edit-tenant-dialog.component.spec.ts
@@ -21,6 +21,9 @@ import { EditTenantDialog } from 
'./edit-tenant-dialog.component';
 import { EditTenantRequest } from '../../../state/shared';
 import { MAT_DIALOG_DATA } from '@angular/material/dialog';
 import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { Component } from '@angular/core';
+import { provideMockStore } from '@ngrx/store/testing';
+import { initialState } from '../../../state/error/error.reducer';
 
 describe('EditTenantDialog', () => {
     let component: EditTenantDialog;
@@ -782,10 +785,22 @@ describe('EditTenantDialog', () => {
         ]
     };
 
+    @Component({
+        selector: 'error-banner',
+        standalone: true,
+        template: ''
+    })
+    class MockErrorBanner {}
+
     beforeEach(() => {
         TestBed.configureTestingModule({
-            imports: [EditTenantDialog, BrowserAnimationsModule],
-            providers: [{ provide: MAT_DIALOG_DATA, useValue: data }]
+            imports: [EditTenantDialog, MockErrorBanner, 
BrowserAnimationsModule],
+            providers: [
+                { provide: MAT_DIALOG_DATA, useValue: data },
+                provideMockStore({
+                    initialState
+                })
+            ]
         });
         fixture = TestBed.createComponent(EditTenantDialog);
         component = fixture.componentInstance;
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/edit-tenant/edit-tenant-dialog.component.ts
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/edit-tenant/edit-tenant-dialog.component.ts
index 2d34ba51de..0e2b5a94f5 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/edit-tenant/edit-tenant-dialog.component.ts
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/ui/common/edit-tenant/edit-tenant-dialog.component.ts
@@ -40,6 +40,7 @@ import { Observable } from 'rxjs';
 import { MatListModule } from '@angular/material/list';
 import { Client } from '../../../service/client.service';
 import { NiFiCommon } from '../../../service/nifi-common.service';
+import { ErrorBanner } from '../error-banner/error-banner.component';
 
 @Component({
     selector: 'edit-tenant-dialog',
@@ -57,7 +58,8 @@ import { NiFiCommon } from 
'../../../service/nifi-common.service';
         NgIf,
         AsyncPipe,
         MatListModule,
-        NgForOf
+        NgForOf,
+        ErrorBanner
     ],
     templateUrl: './edit-tenant-dialog.component.html',
     styleUrls: ['./edit-tenant-dialog.component.scss']

Reply via email to