NIFI-12937: Adding error handling to the Parameter Context Listing page (#8555)

* NIFI-12937:
- Adding error handling to the Parameter Context Listing page.

* NIFI-12937:
- Addressing review feedback.

This closes #8555
This commit is contained in:
Matt Gilman 2024-03-26 15:07:14 -04:00 committed by GitHub
parent 039fd66911
commit 339a06a8c6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 173 additions and 97 deletions

View File

@ -17,7 +17,7 @@ const target = {
console.log('Received Response from the Target:', proxyRes.statusCode, req.url);
});
},
bypass: function(req, res, proxyOptions) {
bypass: function (req) {
if (req.url.startsWith('/nifi/')) {
return req.url;
}

View File

@ -89,6 +89,5 @@ export interface ParameterContextListingState {
updateRequestEntity: ParameterContextUpdateRequestEntity | null;
saving: boolean;
loadedTimestamp: string;
error: string | null;
status: 'pending' | 'loading' | 'error' | 'success';
status: 'pending' | 'loading' | 'success';
}

View File

@ -35,8 +35,13 @@ export const loadParameterContextsSuccess = createAction(
props<{ response: LoadParameterContextsResponse }>()
);
export const parameterContextListingApiError = createAction(
'[Parameter Context Listing] Load Parameter Context Listing Error',
export const parameterContextListingSnackbarApiError = createAction(
'[Parameter Context Listing] Load Parameter Context Listing Snackbar Api Error',
props<{ error: string }>()
);
export const parameterContextListingBannerApiError = createAction(
'[Parameter Context Listing] Load Parameter Context Listing Banner Api Error',
props<{ error: string }>()
);
@ -100,6 +105,11 @@ export const deleteParameterContextUpdateRequest = createAction(
'[Parameter Context Listing] Delete Parameter Context Update Request'
);
export const deleteParameterContextUpdateRequestSuccess = createAction(
'[Parameter Context Listing] Delete Parameter Context Update Request Success',
props<{ response: PollParameterContextUpdateSuccess }>()
);
export const editParameterContextComplete = createAction('[Parameter Context Listing] Edit Parameter Context Complete');
export const promptParameterContextDeletion = createAction(

View File

@ -18,13 +18,14 @@
import { Injectable } from '@angular/core';
import { Actions, concatLatestFrom, createEffect, ofType } from '@ngrx/effects';
import * as ParameterContextListingActions from './parameter-context-listing.actions';
import * as ErrorActions from '../../../../state/error/error.actions';
import {
asyncScheduler,
catchError,
filter,
from,
interval,
map,
NEVER,
Observable,
of,
switchMap,
@ -39,15 +40,23 @@ import { Router } from '@angular/router';
import { ParameterContextService } from '../../service/parameter-contexts.service';
import { YesNoDialog } from '../../../../ui/common/yes-no-dialog/yes-no-dialog.component';
import { EditParameterContext } from '../../ui/parameter-context-listing/edit-parameter-context/edit-parameter-context.component';
import { selectParameterContexts, selectSaving, selectUpdateRequest } from './parameter-context-listing.selectors';
import {
selectParameterContexts,
selectParameterContextStatus,
selectSaving,
selectUpdateRequest
} from './parameter-context-listing.selectors';
import {
EditParameterRequest,
EditParameterResponse,
isDefinedAndNotNull,
Parameter,
ParameterContextUpdateRequest
} from '../../../../state/shared';
import { EditParameterDialog } from '../../../../ui/common/edit-parameter-dialog/edit-parameter-dialog.component';
import { OkDialog } from '../../../../ui/common/ok-dialog/ok-dialog.component';
import { ErrorHelper } from '../../../../service/error-helper.service';
import { HttpErrorResponse } from '@angular/common/http';
@Injectable()
export class ParameterContextListingEffects {
@ -56,13 +65,15 @@ export class ParameterContextListingEffects {
private store: Store<NiFiState>,
private parameterContextService: ParameterContextService,
private dialog: MatDialog,
private router: Router
private router: Router,
private errorHelper: ErrorHelper
) {}
loadParameterContexts$ = createEffect(() =>
this.actions$.pipe(
ofType(ParameterContextListingActions.loadParameterContexts),
switchMap(() =>
concatLatestFrom(() => this.store.select(selectParameterContextStatus)),
switchMap(([, status]) =>
from(this.parameterContextService.getParameterContexts()).pipe(
map((response) =>
ParameterContextListingActions.loadParameterContextsSuccess({
@ -72,12 +83,8 @@ export class ParameterContextListingEffects {
}
})
),
catchError((error) =>
of(
ParameterContextListingActions.parameterContextListingApiError({
error: error.error
})
)
catchError((errorResponse: HttpErrorResponse) =>
of(this.errorHelper.handleLoadingError(status, errorResponse))
)
)
)
@ -121,13 +128,15 @@ export class ParameterContextListingEffects {
);
};
dialogReference.componentInstance.addParameterContext.pipe(take(1)).subscribe((payload: any) => {
this.store.dispatch(
ParameterContextListingActions.createParameterContext({
request: { payload }
})
);
});
dialogReference.componentInstance.addParameterContext
.pipe(takeUntil(dialogReference.afterClosed()))
.subscribe((payload: any) => {
this.store.dispatch(
ParameterContextListingActions.createParameterContext({
request: { payload }
})
);
});
})
),
{ dispatch: false }
@ -146,13 +155,18 @@ export class ParameterContextListingEffects {
}
})
),
catchError((error) =>
of(
ParameterContextListingActions.parameterContextListingApiError({
error: error.error
})
)
)
catchError((errorResponse: HttpErrorResponse) => {
if (this.errorHelper.showErrorInContext(errorResponse.status)) {
return of(
ParameterContextListingActions.parameterContextListingBannerApiError({
error: errorResponse.error
})
);
} else {
this.dialog.closeAll();
return of(this.errorHelper.fullScreenError(errorResponse));
}
})
)
)
)
@ -169,6 +183,22 @@ export class ParameterContextListingEffects {
{ dispatch: false }
);
parameterContextListingBannerApiError$ = createEffect(() =>
this.actions$.pipe(
ofType(ParameterContextListingActions.parameterContextListingBannerApiError),
map((action) => action.error),
switchMap((error) => of(ErrorActions.addBannerError({ error })))
)
);
parameterContextListingSnackbarApiError$ = createEffect(() =>
this.actions$.pipe(
ofType(ParameterContextListingActions.parameterContextListingSnackbarApiError),
map((action) => action.error),
switchMap((error) => of(ErrorActions.snackBarError({ error })))
)
);
navigateToEditService$ = createEffect(
() =>
this.actions$.pipe(
@ -194,13 +224,14 @@ export class ParameterContextListingEffects {
}
})
),
catchError((error) =>
of(
ParameterContextListingActions.parameterContextListingApiError({
error: error.error
catchError((errorResponse: HttpErrorResponse) => {
this.router.navigate(['/parameter-contexts']);
return of(
ParameterContextListingActions.parameterContextListingSnackbarApiError({
error: errorResponse.error
})
)
)
);
})
)
)
)
@ -281,7 +312,7 @@ export class ParameterContextListingEffects {
};
editDialogReference.componentInstance.editParameterContext
.pipe(take(1))
.pipe(takeUntil(editDialogReference.afterClosed()))
.subscribe((payload: any) => {
this.store.dispatch(
ParameterContextListingActions.submitParameterContextUpdateRequest({
@ -294,6 +325,8 @@ export class ParameterContextListingEffects {
});
editDialogReference.afterClosed().subscribe((response) => {
this.store.dispatch(ErrorActions.clearBannerErrors());
if (response != 'ROUTED') {
this.store.dispatch(
ParameterContextListingActions.selectParameterContext({
@ -323,13 +356,18 @@ export class ParameterContextListingEffects {
}
})
),
catchError((error) =>
of(
ParameterContextListingActions.parameterContextListingApiError({
error: error.error
})
)
)
catchError((errorResponse: HttpErrorResponse) => {
if (this.errorHelper.showErrorInContext(errorResponse.status)) {
return of(
ParameterContextListingActions.parameterContextListingBannerApiError({
error: errorResponse.error
})
);
} else {
this.dialog.closeAll();
return of(this.errorHelper.fullScreenError(errorResponse));
}
})
)
)
)
@ -369,29 +407,31 @@ export class ParameterContextListingEffects {
pollParameterContextUpdateRequest$ = createEffect(() =>
this.actions$.pipe(
ofType(ParameterContextListingActions.pollParameterContextUpdateRequest),
concatLatestFrom(() => this.store.select(selectUpdateRequest)),
switchMap(([, updateRequest]) => {
if (updateRequest) {
return from(this.parameterContextService.pollParameterContextUpdate(updateRequest.request)).pipe(
map((response) =>
ParameterContextListingActions.pollParameterContextUpdateRequestSuccess({
response: {
requestEntity: response
}
})
),
catchError((error) =>
of(
ParameterContextListingActions.parameterContextListingApiError({
error: error.error
concatLatestFrom(() => this.store.select(selectUpdateRequest).pipe(isDefinedAndNotNull())),
switchMap(([, updateRequest]) =>
from(this.parameterContextService.pollParameterContextUpdate(updateRequest.request)).pipe(
map((response) =>
ParameterContextListingActions.pollParameterContextUpdateRequestSuccess({
response: {
requestEntity: response
}
})
),
catchError((errorResponse: HttpErrorResponse) => {
this.store.dispatch(ParameterContextListingActions.stopPollingParameterContextUpdateRequest());
if (this.errorHelper.showErrorInContext(errorResponse.status)) {
return of(
ParameterContextListingActions.parameterContextListingBannerApiError({
error: errorResponse.error
})
)
)
);
} else {
return NEVER;
}
})
);
} else {
return of(this.errorHelper.fullScreenError(errorResponse));
}
})
)
)
)
);
@ -399,14 +439,8 @@ export class ParameterContextListingEffects {
this.actions$.pipe(
ofType(ParameterContextListingActions.pollParameterContextUpdateRequestSuccess),
map((action) => action.response),
switchMap((response) => {
const updateRequest: ParameterContextUpdateRequest = response.requestEntity.request;
if (updateRequest.complete) {
return of(ParameterContextListingActions.stopPollingParameterContextUpdateRequest());
} else {
return NEVER;
}
})
filter((response) => response.requestEntity.request.complete),
switchMap(() => of(ParameterContextListingActions.stopPollingParameterContextUpdateRequest()))
)
);
@ -421,11 +455,19 @@ export class ParameterContextListingEffects {
() =>
this.actions$.pipe(
ofType(ParameterContextListingActions.deleteParameterContextUpdateRequest),
concatLatestFrom(() => this.store.select(selectUpdateRequest)),
concatLatestFrom(() => this.store.select(selectUpdateRequest).pipe(isDefinedAndNotNull())),
tap(([, updateRequest]) => {
if (updateRequest) {
this.parameterContextService.deleteParameterContextUpdate(updateRequest.request).subscribe();
}
this.parameterContextService
.deleteParameterContextUpdate(updateRequest.request)
.subscribe((response) => {
this.store.dispatch(
ParameterContextListingActions.deleteParameterContextUpdateRequestSuccess({
response: {
requestEntity: response
}
})
);
});
})
),
{ dispatch: false }
@ -472,7 +514,7 @@ export class ParameterContextListingEffects {
),
catchError((error) =>
of(
ParameterContextListingActions.parameterContextListingApiError({
ParameterContextListingActions.parameterContextListingSnackbarApiError({
error: error.error
})
)

View File

@ -25,10 +25,12 @@ import {
editParameterContextComplete,
loadParameterContexts,
loadParameterContextsSuccess,
parameterContextListingApiError,
parameterContextListingSnackbarApiError,
parameterContextListingBannerApiError,
pollParameterContextUpdateRequestSuccess,
submitParameterContextUpdateRequest,
submitParameterContextUpdateRequestSuccess
submitParameterContextUpdateRequestSuccess,
deleteParameterContextUpdateRequestSuccess
} from './parameter-context-listing.actions';
import { ParameterContextUpdateRequestEntity, Revision } from '../../../../state/shared';
@ -37,7 +39,6 @@ export const initialState: ParameterContextListingState = {
updateRequestEntity: null,
saving: false,
loadedTimestamp: '',
error: null,
status: 'pending'
};
@ -54,11 +55,9 @@ export const parameterContextListingReducer = createReducer(
error: null,
status: 'success' as const
})),
on(parameterContextListingApiError, (state, { error }) => ({
on(parameterContextListingSnackbarApiError, parameterContextListingBannerApiError, (state) => ({
...state,
saving: false,
error,
status: 'error' as const
saving: false
})),
on(createParameterContext, (state) => ({
...state,
@ -74,10 +73,15 @@ export const parameterContextListingReducer = createReducer(
...state,
saving: true
})),
on(submitParameterContextUpdateRequestSuccess, pollParameterContextUpdateRequestSuccess, (state, { response }) => ({
...state,
updateRequestEntity: response.requestEntity
})),
on(
submitParameterContextUpdateRequestSuccess,
pollParameterContextUpdateRequestSuccess,
deleteParameterContextUpdateRequestSuccess,
(state, { response }) => ({
...state,
updateRequestEntity: response.requestEntity
})
),
on(editParameterContextComplete, (state) => {
return produce(state, (draftState) => {
const updateRequestEntity: ParameterContextUpdateRequestEntity | null = draftState.updateRequestEntity;

View File

@ -54,6 +54,11 @@ export const selectParameterContexts = createSelector(
(state: ParameterContextListingState) => state.parameterContexts
);
export const selectParameterContextStatus = createSelector(
selectParameterContextListingState,
(state: ParameterContextListingState) => state.status
);
export const selectContext = (id: string) =>
createSelector(selectParameterContexts, (parameterContexts: ParameterContextEntity[]) =>
parameterContexts.find((entity) => id == entity.id)

View File

@ -17,6 +17,7 @@
<h2 mat-dialog-title>{{ this.isNew ? 'Add' : 'Edit' }} Parameter Context</h2>
<form class="parameter-context-edit-form" [formGroup]="editParameterContextForm">
<error-banner></error-banner>
@if ((updateRequest | async)!; as requestEntity) {
<mat-dialog-content>
<div class="results-content flex gap-x-8">
@ -26,12 +27,21 @@
@for (updateStep of requestEntity.request.updateSteps; track updateStep) {
<div class="flex justify-between items-center">
<div class="value">{{ updateStep.description }}</div>
@if (updateStep.complete) {
<div class="fa fa-check complete"></div>
@if (updateStep.failureReason) {
<div class="fa fa-times warn-default"></div>
} @else {
<div class="fa fa-spin fa-circle-o-notch"></div>
@if (updateStep.complete) {
<div class="fa fa-check complete"></div>
} @else {
<div class="fa fa-spin fa-circle-o-notch"></div>
}
}
</div>
@if (updateStep.failureReason) {
<div class="text-xs ml-2">
{{ updateStep.failureReason }}
</div>
}
}
</div>
</div>

View File

@ -40,6 +40,7 @@ import { ProcessGroupReferences } from '../process-group-references/process-grou
import { ParameterContextInheritance } from '../parameter-context-inheritance/parameter-context-inheritance.component';
import { ParameterReferences } from '../../../../../ui/common/parameter-references/parameter-references.component';
import { RouterLink } from '@angular/router';
import { ErrorBanner } from '../../../../../ui/common/error-banner/error-banner.component';
@Component({
selector: 'edit-parameter-context',
@ -61,7 +62,8 @@ import { RouterLink } from '@angular/router';
ProcessGroupReferences,
ParameterContextInheritance,
ParameterReferences,
RouterLink
RouterLink,
ErrorBanner
],
styleUrls: ['./edit-parameter-context.component.scss']
})

View File

@ -98,7 +98,7 @@ export class ParameterContextTable {
}
canManageAccessPolicies(): boolean {
return this.flowConfiguration.supportsManagedAuthorizer && this.currentUser.tenantsPermissions.canRead;
return this.flowConfiguration?.supportsManagedAuthorizer && this.currentUser.tenantsPermissions.canRead;
}
canGoToParameterProvider(entity: ParameterContextEntity): boolean {

View File

@ -143,6 +143,6 @@
<div class="fa fa-check complete"></div>
</ng-template>
<ng-template #stepError>
<div class="fa fa-times text-red-400"></div>
<div class="fa fa-times warn-default"></div>
</ng-template>
<ng-template #stepNotStarted><div class="w-3.5"></div></ng-template>

View File

@ -164,6 +164,6 @@
<div class="fa fa-check complete"></div>
</ng-template>
<ng-template #stepError>
<div class="fa fa-times text-red-400"></div>
<div class="fa fa-times warn-default"></div>
</ng-template>
<ng-template #stepNotStarted><div class="w-3.5"></div></ng-template>

View File

@ -287,6 +287,7 @@
fill: $accent-palette-lighter !important;
}
.warn-default,
.stale,
.locally-modified-and-stale {
color: $warn-palette-default !important;

View File

@ -85,7 +85,10 @@
$color-palette: map.get($theme, $palette);
$default: mat.get-color-from-palette($color-palette, default);
$high-contrast: mat.get-color-from-palette($color-palette, if(luminosity($default) > luminosity($surface), lighter, darker));
$high-contrast: mat.get-color-from-palette(
$color-palette,
if(luminosity($default) > luminosity($surface), lighter, darker)
);
$on-surface: ensure-contrast($default, $surface, $high-contrast);
@return $on-surface;