NIFI-12684: Handling error responses in the service listing on the canvas (#8321)

* NIFI-12684:
- Handling error responses in the service listing on the canvas.
- Handling error responses when converting a Property value to a Parameter.

* NIFI-12684:
- Introducing parameter helper to remove duplicated logic and error handling when fetching parameters and converting properties to parameters.

This closes #8321
This commit is contained in:
Matt Gilman 2024-02-01 11:47:52 -05:00 committed by GitHub
parent 7edf0d4e2a
commit 102daa15f8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 330 additions and 332 deletions

View File

@ -48,8 +48,8 @@ export class ControllerServiceService implements ControllerServiceCreator, Prope
); );
} }
getBreadcrumbs(processGroupId: string): Observable<any> { getFlow(processGroupId: string): Observable<any> {
return this.httpClient.get(`${ControllerServiceService.API}/flow/process-groups/${processGroupId}/breadcrumbs`); return this.httpClient.get(`${ControllerServiceService.API}/flow/process-groups/${processGroupId}`);
} }
getControllerService(id: string): Observable<any> { getControllerService(id: string): Observable<any> {

View File

@ -0,0 +1,162 @@
/*
* 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 { MatDialog } from '@angular/material/dialog';
import { catchError, EMPTY, filter, map, Observable, switchMap, take, takeUntil, tap } from 'rxjs';
import { Store } from '@ngrx/store';
import { HttpErrorResponse } from '@angular/common/http';
import { NiFiState } from '../../../state';
import { ParameterService } from './parameter.service';
import { Client } from '../../../service/client.service';
import { EditParameterRequest, EditParameterResponse, Parameter, ParameterEntity } from '../../../state/shared';
import { EditParameterDialog } from '../../../ui/common/edit-parameter-dialog/edit-parameter-dialog.component';
import { selectParameterSaving, selectParameterState } from '../state/parameter/parameter.selectors';
import { ParameterState } from '../state/parameter';
import * as ErrorActions from '../../../state/error/error.actions';
import * as ParameterActions from '../state/parameter/parameter.actions';
import { FlowService } from './flow.service';
@Injectable({
providedIn: 'root'
})
export class ParameterHelperService {
constructor(
private dialog: MatDialog,
private store: Store<NiFiState>,
private flowService: FlowService,
private parameterService: ParameterService,
private client: Client
) {}
/**
* Returns a function that can be used to pass into a PropertyTable to retrieve available Parameters.
*
* @param parameterContextId the current Parameter Context id
*/
getParameters(parameterContextId: string): (sensitive: boolean) => Observable<Parameter[]> {
return (sensitive: boolean) => {
return this.flowService.getParameterContext(parameterContextId).pipe(
take(1),
catchError((errorResponse: HttpErrorResponse) => {
this.store.dispatch(ErrorActions.snackBarError({ error: errorResponse.error }));
// consider the error handled and allow the user to reattempt the action
return EMPTY;
}),
map((response) => response.component.parameters),
map((parameterEntities) => {
return parameterEntities
.map((parameterEntity: ParameterEntity) => parameterEntity.parameter)
.filter((parameter: Parameter) => parameter.sensitive == sensitive);
})
);
};
}
/**
* Returns a function that can be used to pass into a PropertyTable to convert a Property into a Parameter, inline.
*
* @param parameterContextId the current Parameter Context id
*/
convertToParameter(
parameterContextId: string
): (name: string, sensitive: boolean, value: string | null) => Observable<string> {
return (name: string, sensitive: boolean, value: string | null) => {
return this.parameterService.getParameterContext(parameterContextId, false).pipe(
catchError((errorResponse: HttpErrorResponse) => {
this.store.dispatch(ErrorActions.snackBarError({ error: errorResponse.error }));
// consider the error handled and allow the user to reattempt the action
return EMPTY;
}),
switchMap((parameterContextEntity) => {
const existingParameters: string[] = parameterContextEntity.component.parameters.map(
(parameterEntity: ParameterEntity) => parameterEntity.parameter.name
);
const convertToParameterDialogRequest: EditParameterRequest = {
parameter: {
name,
value,
sensitive,
description: ''
},
existingParameters
};
const convertToParameterDialogReference = this.dialog.open(EditParameterDialog, {
data: convertToParameterDialogRequest,
panelClass: 'medium-dialog'
});
convertToParameterDialogReference.componentInstance.saving$ =
this.store.select(selectParameterSaving);
convertToParameterDialogReference.componentInstance.cancel.pipe(
takeUntil(convertToParameterDialogReference.afterClosed()),
tap(() => ParameterActions.stopPollingParameterContextUpdateRequest())
);
return convertToParameterDialogReference.componentInstance.editParameter.pipe(
takeUntil(convertToParameterDialogReference.afterClosed()),
switchMap((dialogResponse: EditParameterResponse) => {
this.store.dispatch(
ParameterActions.submitParameterContextUpdateRequest({
request: {
id: parameterContextId,
payload: {
revision: this.client.getRevision(parameterContextEntity),
component: {
id: parameterContextEntity.id,
parameters: [{ parameter: dialogResponse.parameter }]
}
}
}
})
);
return this.store.select(selectParameterState).pipe(
takeUntil(convertToParameterDialogReference.afterClosed()),
tap((parameterState: ParameterState) => {
if (parameterState.error) {
// if the convert to parameter sequence stores an error,
// throw it to avoid the completion mapping logic below
throw new Error(parameterState.error);
}
}),
filter((parameterState: ParameterState) => !parameterState.saving),
map(() => {
convertToParameterDialogReference.close();
return `#{${dialogResponse.parameter.name}}`;
}),
catchError((error) => {
convertToParameterDialogReference.close();
// show the error in the snack and complete the edit to reset it's state
this.store.dispatch(ErrorActions.snackBarError({ error: error.message }));
this.store.dispatch(ParameterActions.editParameterContextComplete());
// consider the error handled and allow the user to reattempt the action
return EMPTY;
})
);
})
);
})
);
};
}
}

View File

@ -45,8 +45,8 @@ export const loadControllerServicesSuccess = createAction(
props<{ response: LoadControllerServicesResponse }>() props<{ response: LoadControllerServicesResponse }>()
); );
export const controllerServicesApiError = createAction( export const controllerServicesBannerApiError = createAction(
'[Controller Services] Load Controller Service Error', '[Controller Services] Controller Services Banner Api Error',
props<{ error: string }>() props<{ error: string }>()
); );

View File

@ -18,7 +18,7 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Actions, concatLatestFrom, createEffect, ofType } from '@ngrx/effects'; import { Actions, concatLatestFrom, createEffect, ofType } from '@ngrx/effects';
import * as ControllerServicesActions from './controller-services.actions'; import * as ControllerServicesActions from './controller-services.actions';
import { catchError, combineLatest, filter, from, map, NEVER, of, switchMap, take, takeUntil, tap } from 'rxjs'; import { catchError, combineLatest, from, map, of, switchMap, take, takeUntil, tap } from 'rxjs';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { NiFiState } from '../../../../state'; import { NiFiState } from '../../../../state';
@ -30,25 +30,24 @@ import { EditControllerService } from '../../../../ui/common/controller-service/
import { import {
ComponentType, ComponentType,
ControllerServiceReferencingComponent, ControllerServiceReferencingComponent,
EditParameterRequest,
EditParameterResponse,
Parameter,
ParameterEntity,
UpdateControllerServiceRequest UpdateControllerServiceRequest
} from '../../../../state/shared'; } from '../../../../state/shared';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { ExtensionTypesService } from '../../../../service/extension-types.service'; import {
import { selectCurrentProcessGroupId, selectSaving } from './controller-services.selectors'; selectCurrentProcessGroupId,
selectParameterContext,
selectSaving,
selectStatus
} from './controller-services.selectors';
import { ControllerServiceService } from '../../service/controller-service.service'; import { ControllerServiceService } from '../../service/controller-service.service';
import { selectCurrentParameterContext } from '../flow/flow.selectors';
import { FlowService } from '../../service/flow.service'; import { FlowService } from '../../service/flow.service';
import { EditParameterDialog } from '../../../../ui/common/edit-parameter-dialog/edit-parameter-dialog.component';
import { selectParameterSaving } from '../parameter/parameter.selectors';
import * as ParameterActions from '../parameter/parameter.actions';
import { ParameterService } from '../../service/parameter.service';
import { EnableControllerService } from '../../../../ui/common/controller-service/enable-controller-service/enable-controller-service.component'; 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 { DisableControllerService } from '../../../../ui/common/controller-service/disable-controller-service/disable-controller-service.component';
import { PropertyTableHelperService } from '../../../../service/property-table-helper.service'; import { PropertyTableHelperService } from '../../../../service/property-table-helper.service';
import * as ErrorActions from '../../../../state/error/error.actions';
import { ErrorHelper } from '../../../../service/error-helper.service';
import { HttpErrorResponse } from '@angular/common/http';
import { ParameterHelperService } from '../../service/parameter-helper.service';
@Injectable() @Injectable()
export class ControllerServicesEffects { export class ControllerServicesEffects {
@ -58,39 +57,45 @@ export class ControllerServicesEffects {
private client: Client, private client: Client,
private controllerServiceService: ControllerServiceService, private controllerServiceService: ControllerServiceService,
private flowService: FlowService, private flowService: FlowService,
private parameterService: ParameterService, private errorHelper: ErrorHelper,
private extensionTypesService: ExtensionTypesService,
private dialog: MatDialog, private dialog: MatDialog,
private router: Router, private router: Router,
private propertyTableHelperService: PropertyTableHelperService private propertyTableHelperService: PropertyTableHelperService,
private parameterHelperService: ParameterHelperService
) {} ) {}
loadControllerServices$ = createEffect(() => loadControllerServices$ = createEffect(() =>
this.actions$.pipe( this.actions$.pipe(
ofType(ControllerServicesActions.loadControllerServices), ofType(ControllerServicesActions.loadControllerServices),
map((action) => action.request), map((action) => action.request),
switchMap((request) => concatLatestFrom(() => this.store.select(selectStatus)),
switchMap(([request, status]) =>
combineLatest([ combineLatest([
this.controllerServiceService.getControllerServices(request.processGroupId), this.controllerServiceService.getControllerServices(request.processGroupId),
this.controllerServiceService.getBreadcrumbs(request.processGroupId) this.controllerServiceService.getFlow(request.processGroupId)
]).pipe( ]).pipe(
map(([controllerServicesResponse, breadcrumbsResponse]) => map(([controllerServicesResponse, flowResponse]) =>
ControllerServicesActions.loadControllerServicesSuccess({ ControllerServicesActions.loadControllerServicesSuccess({
response: { response: {
processGroupId: breadcrumbsResponse.id, processGroupId: flowResponse.processGroupFlow.id,
controllerServices: controllerServicesResponse.controllerServices, controllerServices: controllerServicesResponse.controllerServices,
loadedTimestamp: controllerServicesResponse.currentTime, loadedTimestamp: controllerServicesResponse.currentTime,
breadcrumb: breadcrumbsResponse breadcrumb: flowResponse.processGroupFlow.breadcrumb,
parameterContext: flowResponse.processGroupFlow.parameterContext ?? null
} }
}) })
), ),
catchError((error) => catchError((errorResponse: HttpErrorResponse) => {
of( if (status === 'success') {
ControllerServicesActions.controllerServicesApiError({ if (this.errorHelper.showErrorInContext(errorResponse.status)) {
error: error.error return of(ErrorActions.snackBarError({ error: errorResponse.error }));
}) } else {
) return of(this.errorHelper.fullScreenError(errorResponse));
) }
} else {
return of(this.errorHelper.fullScreenError(errorResponse));
}
})
) )
) )
) )
@ -149,13 +154,10 @@ export class ControllerServicesEffects {
} }
}) })
), ),
catchError((error) => catchError((errorResponse: HttpErrorResponse) => {
of( this.dialog.closeAll();
ControllerServicesActions.controllerServicesApiError({ return of(ErrorActions.snackBarError({ error: errorResponse.error }));
error: error.error })
})
)
)
) )
) )
) )
@ -191,7 +193,7 @@ export class ControllerServicesEffects {
ofType(ControllerServicesActions.openConfigureControllerServiceDialog), ofType(ControllerServicesActions.openConfigureControllerServiceDialog),
map((action) => action.request), map((action) => action.request),
concatLatestFrom(() => [ concatLatestFrom(() => [
this.store.select(selectCurrentParameterContext), this.store.select(selectParameterContext),
this.store.select(selectCurrentProcessGroupId) this.store.select(selectCurrentProcessGroupId)
]), ]),
tap(([request, parameterContext, processGroupId]) => { tap(([request, parameterContext, processGroupId]) => {
@ -242,17 +244,9 @@ export class ControllerServicesEffects {
}; };
if (parameterContext != null) { if (parameterContext != null) {
editDialogReference.componentInstance.getParameters = (sensitive: boolean) => { editDialogReference.componentInstance.getParameters = this.parameterHelperService.getParameters(
return this.flowService.getParameterContext(parameterContext.id).pipe( parameterContext.id
take(1), );
map((response) => response.component.parameters),
map((parameterEntities) => {
return parameterEntities
.map((parameterEntity: ParameterEntity) => parameterEntity.parameter)
.filter((parameter: Parameter) => parameter.sensitive == sensitive);
})
);
};
editDialogReference.componentInstance.parameterContext = parameterContext; editDialogReference.componentInstance.parameterContext = parameterContext;
editDialogReference.componentInstance.goToParameter = () => { editDialogReference.componentInstance.goToParameter = () => {
@ -260,74 +254,8 @@ export class ControllerServicesEffects {
goTo(commands, 'Parameter'); goTo(commands, 'Parameter');
}; };
editDialogReference.componentInstance.convertToParameter = ( editDialogReference.componentInstance.convertToParameter =
name: string, this.parameterHelperService.convertToParameter(parameterContext.id);
sensitive: boolean,
value: string | null
) => {
return this.parameterService.getParameterContext(parameterContext.id, false).pipe(
switchMap((parameterContextEntity) => {
const existingParameters: string[] =
parameterContextEntity.component.parameters.map(
(parameterEntity: ParameterEntity) => parameterEntity.parameter.name
);
const convertToParameterDialogRequest: EditParameterRequest = {
parameter: {
name,
value,
sensitive,
description: ''
},
existingParameters
};
const convertToParameterDialogReference = this.dialog.open(EditParameterDialog, {
data: convertToParameterDialogRequest,
panelClass: 'medium-dialog'
});
convertToParameterDialogReference.componentInstance.saving$ =
this.store.select(selectParameterSaving);
convertToParameterDialogReference.componentInstance.cancel.pipe(
takeUntil(convertToParameterDialogReference.afterClosed()),
tap(() => ParameterActions.stopPollingParameterContextUpdateRequest())
);
return convertToParameterDialogReference.componentInstance.editParameter.pipe(
takeUntil(convertToParameterDialogReference.afterClosed()),
switchMap((dialogResponse: EditParameterResponse) => {
this.store.dispatch(
ParameterActions.submitParameterContextUpdateRequest({
request: {
id: parameterContext.id,
payload: {
revision: this.client.getRevision(parameterContextEntity),
component: {
id: parameterContextEntity.id,
parameters: [{ parameter: dialogResponse.parameter }]
}
}
}
})
);
return this.store.select(selectParameterSaving).pipe(
takeUntil(convertToParameterDialogReference.afterClosed()),
filter((parameterSaving) => parameterSaving === false),
map(() => {
convertToParameterDialogReference.close();
return `#{${dialogResponse.parameter.name}}`;
})
);
})
);
}),
catchError(() => {
// TODO handle error
return NEVER;
})
);
};
} }
editDialogReference.componentInstance.goToService = (serviceId: string) => { editDialogReference.componentInstance.goToService = (serviceId: string) => {
@ -341,8 +269,8 @@ export class ControllerServicesEffects {
]; ];
goTo(commands, 'Controller Service'); goTo(commands, 'Controller Service');
}, },
error: () => { error: (errorResponse: HttpErrorResponse) => {
// TODO - handle error this.store.dispatch(ErrorActions.snackBarError({ error: errorResponse.error }));
} }
}); });
}; };
@ -410,18 +338,31 @@ export class ControllerServicesEffects {
} }
}) })
), ),
catchError((error) => catchError((errorResponse: HttpErrorResponse) => {
of( if (this.errorHelper.showErrorInContext(errorResponse.status)) {
ControllerServicesActions.controllerServicesApiError({ return of(
error: error.error ControllerServicesActions.controllerServicesBannerApiError({
}) error: errorResponse.error
) })
) );
} else {
this.dialog.getDialogById(request.id)?.close('ROUTED');
return of(this.errorHelper.fullScreenError(errorResponse));
}
})
) )
) )
) )
); );
controllerServicesBannerApiError$ = createEffect(() =>
this.actions$.pipe(
ofType(ControllerServicesActions.controllerServicesBannerApiError),
map((action) => action.error),
switchMap((error) => of(ErrorActions.addBannerError({ error })))
)
);
configureControllerServiceSuccess$ = createEffect( configureControllerServiceSuccess$ = createEffect(
() => () =>
this.actions$.pipe( this.actions$.pipe(
@ -558,12 +499,8 @@ export class ControllerServicesEffects {
} }
}) })
), ),
catchError((error) => catchError((errorResponse: HttpErrorResponse) =>
of( of(ErrorActions.snackBarError({ error: errorResponse.error }))
ControllerServicesActions.controllerServicesApiError({
error: error.error
})
)
) )
) )
) )

View File

@ -19,7 +19,7 @@ import { createReducer, on } from '@ngrx/store';
import { import {
configureControllerService, configureControllerService,
configureControllerServiceSuccess, configureControllerServiceSuccess,
controllerServicesApiError, controllerServicesBannerApiError,
createControllerService, createControllerService,
createControllerServiceSuccess, createControllerServiceSuccess,
deleteControllerService, deleteControllerService,
@ -47,9 +47,9 @@ export const initialState: ControllerServicesState = {
name: '' name: ''
} }
}, },
parameterContext: null,
saving: false, saving: false,
loadedTimestamp: '', loadedTimestamp: '',
error: null,
status: 'pending' status: 'pending'
}; };
@ -67,15 +67,13 @@ export const controllerServicesReducer = createReducer(
processGroupId: response.processGroupId, processGroupId: response.processGroupId,
controllerServices: response.controllerServices, controllerServices: response.controllerServices,
breadcrumb: response.breadcrumb, breadcrumb: response.breadcrumb,
parameterContext: response.parameterContext,
loadedTimestamp: response.loadedTimestamp, loadedTimestamp: response.loadedTimestamp,
error: null,
status: 'success' as const status: 'success' as const
})), })),
on(controllerServicesApiError, (state, { error }) => ({ on(controllerServicesBannerApiError, (state) => ({
...state, ...state,
saving: false, saving: false
error,
status: 'error' as const
})), })),
on(createControllerService, configureControllerService, deleteControllerService, (state) => ({ on(createControllerService, configureControllerService, deleteControllerService, (state) => ({
...state, ...state,

View File

@ -31,11 +31,21 @@ export const selectSaving = createSelector(
(state: ControllerServicesState) => state.saving (state: ControllerServicesState) => state.saving
); );
export const selectStatus = createSelector(
selectControllerServicesState,
(state: ControllerServicesState) => state.status
);
export const selectCurrentProcessGroupId = createSelector( export const selectCurrentProcessGroupId = createSelector(
selectControllerServicesState, selectControllerServicesState,
(state: ControllerServicesState) => state.processGroupId (state: ControllerServicesState) => state.processGroupId
); );
export const selectParameterContext = createSelector(
selectControllerServicesState,
(state: ControllerServicesState) => state.parameterContext
);
export const selectProcessGroupIdFromRoute = createSelector(selectCurrentRoute, (route) => { export const selectProcessGroupIdFromRoute = createSelector(selectCurrentRoute, (route) => {
if (route) { if (route) {
// always select the process group from the route // always select the process group from the route

View File

@ -15,7 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { ControllerServiceEntity } from '../../../../state/shared'; import { ControllerServiceEntity, ParameterContextReferenceEntity } from '../../../../state/shared';
import { BreadcrumbEntity } from '../shared'; import { BreadcrumbEntity } from '../shared';
export const controllerServicesFeatureKey = 'controllerServiceListing'; export const controllerServicesFeatureKey = 'controllerServiceListing';
@ -28,6 +28,7 @@ export interface LoadControllerServicesResponse {
processGroupId: string; processGroupId: string;
breadcrumb: BreadcrumbEntity; breadcrumb: BreadcrumbEntity;
controllerServices: ControllerServiceEntity[]; controllerServices: ControllerServiceEntity[];
parameterContext: ParameterContextReferenceEntity | null;
loadedTimestamp: string; loadedTimestamp: string;
} }
@ -65,8 +66,8 @@ export interface ControllerServicesState {
processGroupId: string; processGroupId: string;
breadcrumb: BreadcrumbEntity; breadcrumb: BreadcrumbEntity;
controllerServices: ControllerServiceEntity[]; controllerServices: ControllerServiceEntity[];
parameterContext: ParameterContextReferenceEntity | null;
saving: boolean; saving: boolean;
loadedTimestamp: string; loadedTimestamp: string;
error: string | null; status: 'pending' | 'loading' | 'success';
status: 'pending' | 'loading' | 'error' | 'success';
} }

View File

@ -19,7 +19,6 @@ import { Injectable } from '@angular/core';
import { FlowService } from '../../service/flow.service'; import { FlowService } from '../../service/flow.service';
import { Actions, concatLatestFrom, createEffect, ofType } from '@ngrx/effects'; import { Actions, concatLatestFrom, createEffect, ofType } from '@ngrx/effects';
import * as FlowActions from './flow.actions'; import * as FlowActions from './flow.actions';
import * as ParameterActions from '../parameter/parameter.actions';
import * as StatusHistoryActions from '../../../../state/status-history/status-history.actions'; import * as StatusHistoryActions from '../../../../state/status-history/status-history.actions';
import { import {
asyncScheduler, asyncScheduler,
@ -30,7 +29,6 @@ import {
interval, interval,
map, map,
mergeMap, mergeMap,
NEVER,
Observable, Observable,
of, of,
switchMap, switchMap,
@ -65,13 +63,7 @@ import { ConnectionManager } from '../../service/manager/connection-manager.serv
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { CreatePort } from '../../ui/canvas/items/port/create-port/create-port.component'; import { CreatePort } from '../../ui/canvas/items/port/create-port/create-port.component';
import { EditPort } from '../../ui/canvas/items/port/edit-port/edit-port.component'; import { EditPort } from '../../ui/canvas/items/port/edit-port/edit-port.component';
import { import { ComponentType } from '../../../../state/shared';
ComponentType,
EditParameterRequest,
EditParameterResponse,
Parameter,
ParameterEntity
} from '../../../../state/shared';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { Client } from '../../../../service/client.service'; import { Client } from '../../../../service/client.service';
import { CanvasUtils } from '../../service/canvas-utils.service'; import { CanvasUtils } from '../../service/canvas-utils.service';
@ -87,13 +79,10 @@ import { EditConnectionComponent } from '../../ui/canvas/items/connection/edit-c
import { OkDialog } from '../../../../ui/common/ok-dialog/ok-dialog.component'; import { OkDialog } from '../../../../ui/common/ok-dialog/ok-dialog.component';
import { GroupComponents } from '../../ui/canvas/items/process-group/group-components/group-components.component'; import { GroupComponents } from '../../ui/canvas/items/process-group/group-components/group-components.component';
import { EditProcessGroup } from '../../ui/canvas/items/process-group/edit-process-group/edit-process-group.component'; import { EditProcessGroup } from '../../ui/canvas/items/process-group/edit-process-group/edit-process-group.component';
import { ExtensionTypesService } from '../../../../service/extension-types.service';
import { ControllerServiceService } from '../../service/controller-service.service'; import { ControllerServiceService } from '../../service/controller-service.service';
import { YesNoDialog } from '../../../../ui/common/yes-no-dialog/yes-no-dialog.component'; import { YesNoDialog } from '../../../../ui/common/yes-no-dialog/yes-no-dialog.component';
import { EditParameterDialog } from '../../../../ui/common/edit-parameter-dialog/edit-parameter-dialog.component';
import { selectParameterSaving } from '../parameter/parameter.selectors';
import { ParameterService } from '../../service/parameter.service';
import { PropertyTableHelperService } from '../../../../service/property-table-helper.service'; import { PropertyTableHelperService } from '../../../../service/property-table-helper.service';
import { ParameterHelperService } from '../../service/parameter-helper.service';
@Injectable() @Injectable()
export class FlowEffects { export class FlowEffects {
@ -101,9 +90,7 @@ export class FlowEffects {
private actions$: Actions, private actions$: Actions,
private store: Store<NiFiState>, private store: Store<NiFiState>,
private flowService: FlowService, private flowService: FlowService,
private extensionTypesService: ExtensionTypesService,
private controllerServiceService: ControllerServiceService, private controllerServiceService: ControllerServiceService,
private parameterService: ParameterService,
private client: Client, private client: Client,
private canvasUtils: CanvasUtils, private canvasUtils: CanvasUtils,
private canvasView: CanvasView, private canvasView: CanvasView,
@ -111,7 +98,8 @@ export class FlowEffects {
private connectionManager: ConnectionManager, private connectionManager: ConnectionManager,
private router: Router, private router: Router,
private dialog: MatDialog, private dialog: MatDialog,
private propertyTableHelperService: PropertyTableHelperService private propertyTableHelperService: PropertyTableHelperService,
private parameterHelperService: ParameterHelperService
) {} ) {}
reloadFlow$ = createEffect(() => reloadFlow$ = createEffect(() =>
@ -874,17 +862,9 @@ export class FlowEffects {
}; };
if (parameterContext != null) { if (parameterContext != null) {
editDialogReference.componentInstance.getParameters = (sensitive: boolean) => { editDialogReference.componentInstance.getParameters = this.parameterHelperService.getParameters(
return this.flowService.getParameterContext(parameterContext.id).pipe( parameterContext.id
take(1), );
map((response) => response.component.parameters),
map((parameterEntities) => {
return parameterEntities
.map((parameterEntity: ParameterEntity) => parameterEntity.parameter)
.filter((parameter: Parameter) => parameter.sensitive == sensitive);
})
);
};
editDialogReference.componentInstance.parameterContext = parameterContext; editDialogReference.componentInstance.parameterContext = parameterContext;
editDialogReference.componentInstance.goToParameter = () => { editDialogReference.componentInstance.goToParameter = () => {
@ -892,74 +872,8 @@ export class FlowEffects {
goTo(commands, 'Parameter'); goTo(commands, 'Parameter');
}; };
editDialogReference.componentInstance.convertToParameter = ( editDialogReference.componentInstance.convertToParameter =
name: string, this.parameterHelperService.convertToParameter(parameterContext.id);
sensitive: boolean,
value: string | null
) => {
return this.parameterService.getParameterContext(parameterContext.id, false).pipe(
switchMap((parameterContextEntity) => {
const existingParameters: string[] =
parameterContextEntity.component.parameters.map(
(parameterEntity: ParameterEntity) => parameterEntity.parameter.name
);
const convertToParameterDialogRequest: EditParameterRequest = {
parameter: {
name,
value,
sensitive,
description: ''
},
existingParameters
};
const convertToParameterDialogReference = this.dialog.open(EditParameterDialog, {
data: convertToParameterDialogRequest,
panelClass: 'medium-dialog'
});
convertToParameterDialogReference.componentInstance.saving$ =
this.store.select(selectParameterSaving);
convertToParameterDialogReference.componentInstance.cancel.pipe(
takeUntil(convertToParameterDialogReference.afterClosed()),
tap(() => ParameterActions.stopPollingParameterContextUpdateRequest())
);
return convertToParameterDialogReference.componentInstance.editParameter.pipe(
takeUntil(convertToParameterDialogReference.afterClosed()),
switchMap((dialogResponse: EditParameterResponse) => {
this.store.dispatch(
ParameterActions.submitParameterContextUpdateRequest({
request: {
id: parameterContext.id,
payload: {
revision: this.client.getRevision(parameterContextEntity),
component: {
id: parameterContextEntity.id,
parameters: [{ parameter: dialogResponse.parameter }]
}
}
}
})
);
return this.store.select(selectParameterSaving).pipe(
takeUntil(convertToParameterDialogReference.afterClosed()),
filter((parameterSaving) => parameterSaving === false),
map(() => {
convertToParameterDialogReference.close();
return `#{${dialogResponse.parameter.name}}`;
})
);
})
);
}),
catchError(() => {
// TODO handle error
return NEVER;
})
);
};
} }
editDialogReference.componentInstance.goToService = (serviceId: string) => { editDialogReference.componentInstance.goToService = (serviceId: string) => {

View File

@ -22,4 +22,5 @@ export const parameterFeatureKey = 'parameter';
export interface ParameterState { export interface ParameterState {
updateRequestEntity: ParameterContextUpdateRequestEntity | null; updateRequestEntity: ParameterContextUpdateRequestEntity | null;
saving: boolean; saving: boolean;
error: string | null;
} }

View File

@ -20,10 +20,11 @@ import { Actions, concatLatestFrom, createEffect, ofType } from '@ngrx/effects';
import * as ParameterActions from './parameter.actions'; import * as ParameterActions from './parameter.actions';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { CanvasState } from '../index'; import { CanvasState } from '../index';
import { asyncScheduler, catchError, from, interval, map, NEVER, of, switchMap, takeUntil, tap } from 'rxjs'; import { asyncScheduler, catchError, from, interval, map, NEVER, of, switchMap, takeUntil } from 'rxjs';
import { ParameterContextUpdateRequest } from '../../../../state/shared'; import { ParameterContextUpdateRequest } from '../../../../state/shared';
import { selectUpdateRequest } from './parameter.selectors'; import { selectUpdateRequest } from './parameter.selectors';
import { ParameterService } from '../../service/parameter.service'; import { ParameterService } from '../../service/parameter.service';
import { HttpErrorResponse } from '@angular/common/http';
@Injectable() @Injectable()
export class ParameterEffects { export class ParameterEffects {
@ -46,12 +47,8 @@ export class ParameterEffects {
} }
}) })
), ),
catchError((error) => catchError((errorResponse: HttpErrorResponse) =>
of( of(ParameterActions.parameterApiError({ error: errorResponse.error }))
ParameterActions.parameterApiError({
error: error.error
})
)
) )
) )
) )
@ -140,12 +137,22 @@ export class ParameterEffects {
this.actions$.pipe( this.actions$.pipe(
ofType(ParameterActions.deleteParameterContextUpdateRequest), ofType(ParameterActions.deleteParameterContextUpdateRequest),
concatLatestFrom(() => this.store.select(selectUpdateRequest)), concatLatestFrom(() => this.store.select(selectUpdateRequest)),
tap(([, updateRequest]) => { switchMap(([, updateRequest]) => {
if (updateRequest) { if (updateRequest) {
this.parameterService.deleteParameterContextUpdate(updateRequest.request).subscribe(); return from(this.parameterService.deleteParameterContextUpdate(updateRequest.request)).pipe(
map(() => ParameterActions.editParameterContextComplete()),
catchError((error) =>
of(
ParameterActions.parameterApiError({
error: error.error
})
)
)
);
} else {
return of(ParameterActions.editParameterContextComplete());
} }
}), })
switchMap(() => of(ParameterActions.editParameterContextComplete()))
) )
); );
} }

View File

@ -19,6 +19,7 @@ import { createReducer, on } from '@ngrx/store';
import { ParameterState } from './index'; import { ParameterState } from './index';
import { import {
editParameterContextComplete, editParameterContextComplete,
parameterApiError,
pollParameterContextUpdateRequestSuccess, pollParameterContextUpdateRequestSuccess,
submitParameterContextUpdateRequest, submitParameterContextUpdateRequest,
submitParameterContextUpdateRequestSuccess submitParameterContextUpdateRequestSuccess
@ -26,7 +27,8 @@ import {
export const initialState: ParameterState = { export const initialState: ParameterState = {
updateRequestEntity: null, updateRequestEntity: null,
saving: false saving: false,
error: null
}; };
export const parameterReducer = createReducer( export const parameterReducer = createReducer(
@ -35,13 +37,15 @@ export const parameterReducer = createReducer(
...state, ...state,
saving: true saving: true
})), })),
on(parameterApiError, (state, { error }) => ({
...state,
error
})),
on(submitParameterContextUpdateRequestSuccess, pollParameterContextUpdateRequestSuccess, (state, { response }) => ({ on(submitParameterContextUpdateRequestSuccess, pollParameterContextUpdateRequestSuccess, (state, { response }) => ({
...state, ...state,
updateRequestEntity: response.requestEntity updateRequestEntity: response.requestEntity
})), })),
on(editParameterContextComplete, (state) => ({ on(editParameterContextComplete, () => ({
...state, ...initialState
saving: false,
updateRequestEntity: null
})) }))
); );

View File

@ -35,14 +35,14 @@
<i class="fa fa-plus"></i> <i class="fa fa-plus"></i>
</button> </button>
</div> </div>
<div class="flex-1"> <div class="flex-1" *ngIf="flowConfiguration$ | async; let flowConfiguration">
<controller-service-table <controller-service-table
[selectedServiceId]="selectedServiceId$ | async" [selectedServiceId]="selectedServiceId$ | async"
[controllerServices]="serviceState.controllerServices" [controllerServices]="serviceState.controllerServices"
[formatScope]="formatScope(serviceState.breadcrumb)" [formatScope]="formatScope(serviceState.breadcrumb)"
[definedByCurrentGroup]="definedByCurrentGroup(serviceState.breadcrumb)" [definedByCurrentGroup]="definedByCurrentGroup(serviceState.breadcrumb)"
[currentUser]="(currentUser$ | async)!" [currentUser]="(currentUser$ | async)!"
[flowConfiguration]="(flowConfiguration$ | async)!" [flowConfiguration]="flowConfiguration"
[canModifyParent]="canModifyParent(serviceState.breadcrumb)" [canModifyParent]="canModifyParent(serviceState.breadcrumb)"
(selectControllerService)="selectControllerService($event)" (selectControllerService)="selectControllerService($event)"
(configureControllerService)="configureControllerService($event)" (configureControllerService)="configureControllerService($event)"

View File

@ -39,7 +39,7 @@ import {
selectControllerService selectControllerService
} from '../../state/controller-services/controller-services.actions'; } from '../../state/controller-services/controller-services.actions';
import { initialState } from '../../state/controller-services/controller-services.reducer'; import { initialState } from '../../state/controller-services/controller-services.reducer';
import { ControllerServiceEntity } from '../../../../state/shared'; import { ControllerServiceEntity, isDefinedAndNotNull } from '../../../../state/shared';
import { BreadcrumbEntity } from '../../state/shared'; import { BreadcrumbEntity } from '../../state/shared';
import { selectCurrentUser } from '../../../../state/current-user/current-user.selectors'; import { selectCurrentUser } from '../../../../state/current-user/current-user.selectors';
import { selectFlowConfiguration } from '../../../../state/flow-configuration/flow-configuration.selectors'; import { selectFlowConfiguration } from '../../../../state/flow-configuration/flow-configuration.selectors';
@ -56,7 +56,7 @@ export class ControllerServices implements OnInit, OnDestroy {
serviceState$ = this.store.select(selectControllerServicesState); serviceState$ = this.store.select(selectControllerServicesState);
selectedServiceId$ = this.store.select(selectControllerServiceIdFromRoute); selectedServiceId$ = this.store.select(selectControllerServiceIdFromRoute);
currentUser$ = this.store.select(selectCurrentUser); currentUser$ = this.store.select(selectCurrentUser);
flowConfiguration$ = this.store.select(selectFlowConfiguration); flowConfiguration$ = this.store.select(selectFlowConfiguration).pipe(isDefinedAndNotNull());
private currentProcessGroupId!: string; private currentProcessGroupId!: string;

View File

@ -441,9 +441,9 @@ export class ManagementControllerServicesEffects {
} }
}) })
), ),
catchError((errorResponse: HttpErrorResponse) => { catchError((errorResponse: HttpErrorResponse) =>
return of(ErrorActions.snackBarError({ error: errorResponse.error })); of(ErrorActions.snackBarError({ error: errorResponse.error }))
}) )
) )
) )
) )

View File

@ -26,62 +26,62 @@ import {
} from './index'; } from './index';
export const resetEnableControllerServiceState = createAction( export const resetEnableControllerServiceState = createAction(
'[Enable Controller Service] Reset Enable Controller Service State' '[Controller Service State] Reset Enable Controller Service State'
); );
export const setControllerService = createAction( export const setControllerService = createAction(
'[Enable Controller Service] Set Controller Service', '[Controller Service State] Set Controller Service',
props<{ props<{
request: SetControllerServiceRequest; request: SetControllerServiceRequest;
}>() }>()
); );
export const submitEnableRequest = createAction( export const submitEnableRequest = createAction(
'[Enable Controller Service] Submit Enable Request', '[Controller Service State] Submit Enable Request',
props<{ props<{
request: SetEnableControllerServiceRequest; request: SetEnableControllerServiceRequest;
}>() }>()
); );
export const submitDisableRequest = createAction('[Enable Controller Service] Submit Disable Request'); export const submitDisableRequest = createAction('[Controller Service State] Submit Disable Request');
export const setEnableControllerService = createAction('[Enable Controller Service] Set Enable Controller Service'); export const setEnableControllerService = createAction('[Controller Service State] Set Enable Controller Service');
export const setEnableControllerServiceSuccess = createAction( export const setEnableControllerServiceSuccess = createAction(
'[Enable Controller Service] Set Enable Controller Service Success', '[Controller Service State] Set Enable Controller Service Success',
props<{ props<{
response: ControllerServiceStepResponse; response: ControllerServiceStepResponse;
}>() }>()
); );
export const updateReferencingServices = createAction('[Enable Controller Service] Update Referencing Services'); export const updateReferencingServices = createAction('[Controller Service State] Update Referencing Services');
export const updateReferencingServicesSuccess = createAction( export const updateReferencingServicesSuccess = createAction(
'[Enable Controller Service] Update Referencing Services Success', '[Controller Service State] Update Referencing Services Success',
props<{ props<{
response: ReferencingComponentsStepResponse; response: ReferencingComponentsStepResponse;
}>() }>()
); );
export const updateReferencingComponents = createAction('[Enable Controller Service] Update Referencing Components'); export const updateReferencingComponents = createAction('[Controller Service State] Update Referencing Components');
export const updateReferencingComponentsSuccess = createAction( export const updateReferencingComponentsSuccess = createAction(
'[Enable Controller Service] Update Referencing Components Success', '[Controller Service State] Update Referencing Components Success',
props<{ props<{
response: ReferencingComponentsStepResponse; response: ReferencingComponentsStepResponse;
}>() }>()
); );
export const startPollingControllerService = createAction( export const startPollingControllerService = createAction(
'[Enable Controller Service] Start Polling Controller Service' '[Controller Service State] Start Polling Controller Service'
); );
export const stopPollingControllerService = createAction('[Enable Controller Service] Stop Polling Controller Service'); export const stopPollingControllerService = createAction('[Controller Service State] Stop Polling Controller Service');
export const pollControllerService = createAction('[Enable Controller Service] Poll Controller Service'); export const pollControllerService = createAction('[Controller Service State] Poll Controller Service');
export const pollControllerServiceSuccess = createAction( export const pollControllerServiceSuccess = createAction(
'[Enable Controller Service] Poll Controller Service Success', '[Controller Service State] Poll Controller Service Success',
props<{ props<{
response: ControllerServiceStepResponse; response: ControllerServiceStepResponse;
previousStep: SetEnableStep; previousStep: SetEnableStep;
@ -89,25 +89,14 @@ export const pollControllerServiceSuccess = createAction(
); );
export const setEnableStepFailure = createAction( export const setEnableStepFailure = createAction(
'[Enable Controller Service] Set Enable Step Failure', '[Controller Service State] Set Enable Step Failure',
props<{ props<{
response: SetEnableStepFailure; response: SetEnableStepFailure;
}>() }>()
); );
export const controllerServiceApiError = createAction(
'[Enable Controller Service] Controller Service Api Error',
props<{
error: string;
}>()
);
export const clearControllerServiceApiError = createAction(
'[Enable Controller Service] Clear Controller Service Api Error'
);
export const showOkDialog = createAction( export const showOkDialog = createAction(
'[Enable Controller Service] Show Ok Dialog', '[Controller Service State] Show Ok Dialog',
props<{ props<{
title: string; title: string;
message: string; message: string;

View File

@ -87,42 +87,31 @@ export class ControllerServiceStateEffects {
this.actions$.pipe( this.actions$.pipe(
ofType(ControllerServiceActions.setEnableControllerService), ofType(ControllerServiceActions.setEnableControllerService),
concatLatestFrom(() => [ concatLatestFrom(() => [
this.store.select(selectControllerService), this.store.select(selectControllerService).pipe(isDefinedAndNotNull()),
this.store.select(selectControllerServiceSetEnableRequest) this.store.select(selectControllerServiceSetEnableRequest)
]), ]),
switchMap(([, controllerService, setEnableRequest]) => { switchMap(([, controllerService, setEnableRequest]) =>
if (controllerService) { from(this.controllerServiceStateService.setEnable(controllerService, setEnableRequest.enable)).pipe(
return from( map((response) =>
this.controllerServiceStateService.setEnable(controllerService, setEnableRequest.enable) ControllerServiceActions.setEnableControllerServiceSuccess({
).pipe( response: {
map((response) => controllerService: response,
ControllerServiceActions.setEnableControllerServiceSuccess({ currentStep: setEnableRequest.currentStep
}
})
),
catchError((error) =>
of(
ControllerServiceActions.setEnableStepFailure({
response: { response: {
controllerService: response, step: setEnableRequest.currentStep,
currentStep: setEnableRequest.currentStep error: error.error
} }
}) })
),
catchError((error) =>
of(
ControllerServiceActions.setEnableStepFailure({
response: {
step: setEnableRequest.currentStep,
error: error.error
}
})
)
) )
); )
} else { )
return of( )
ControllerServiceActions.showOkDialog({
title: 'Enable Service',
message: 'Controller Service not initialized'
})
);
}
})
) )
); );

View File

@ -18,8 +18,6 @@
import { createReducer, on } from '@ngrx/store'; import { createReducer, on } from '@ngrx/store';
import { ControllerServiceState, SetEnableStep } from './index'; import { ControllerServiceState, SetEnableStep } from './index';
import { import {
clearControllerServiceApiError,
controllerServiceApiError,
setEnableStepFailure, setEnableStepFailure,
pollControllerServiceSuccess, pollControllerServiceSuccess,
resetEnableControllerServiceState, resetEnableControllerServiceState,
@ -39,7 +37,6 @@ export const initialState: ControllerServiceState = {
scope: 'SERVICE_ONLY' scope: 'SERVICE_ONLY'
}, },
controllerService: null, controllerService: null,
error: null,
status: 'pending' status: 'pending'
}; };
@ -91,15 +88,5 @@ export const controllerServiceStateReducer = createReducer(
error: response error: response
}, },
status: 'error' as const status: 'error' as const
})),
on(controllerServiceApiError, (state, { error }) => ({
...state,
error: error,
status: 'error' as const
})),
on(clearControllerServiceApiError, (state) => ({
...state,
error: null,
status: 'pending' as const
})) }))
); );

View File

@ -76,6 +76,5 @@ export interface SetEnableRequest {
export interface ControllerServiceState { export interface ControllerServiceState {
setEnableRequest: SetEnableRequest; setEnableRequest: SetEnableRequest;
controllerService: ControllerServiceEntity | null; controllerService: ControllerServiceEntity | null;
error: string | null;
status: 'pending' | 'loading' | 'error' | 'success'; status: 'pending' | 'loading' | 'error' | 'success';
} }