mirror of https://github.com/apache/nifi.git
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:
parent
7edf0d4e2a
commit
102daa15f8
|
@ -48,8 +48,8 @@ export class ControllerServiceService implements ControllerServiceCreator, Prope
|
|||
);
|
||||
}
|
||||
|
||||
getBreadcrumbs(processGroupId: string): Observable<any> {
|
||||
return this.httpClient.get(`${ControllerServiceService.API}/flow/process-groups/${processGroupId}/breadcrumbs`);
|
||||
getFlow(processGroupId: string): Observable<any> {
|
||||
return this.httpClient.get(`${ControllerServiceService.API}/flow/process-groups/${processGroupId}`);
|
||||
}
|
||||
|
||||
getControllerService(id: string): Observable<any> {
|
||||
|
|
|
@ -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;
|
||||
})
|
||||
);
|
||||
})
|
||||
);
|
||||
})
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
|
@ -45,8 +45,8 @@ export const loadControllerServicesSuccess = createAction(
|
|||
props<{ response: LoadControllerServicesResponse }>()
|
||||
);
|
||||
|
||||
export const controllerServicesApiError = createAction(
|
||||
'[Controller Services] Load Controller Service Error',
|
||||
export const controllerServicesBannerApiError = createAction(
|
||||
'[Controller Services] Controller Services Banner Api Error',
|
||||
props<{ error: string }>()
|
||||
);
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { Actions, concatLatestFrom, createEffect, ofType } from '@ngrx/effects';
|
||||
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 { Store } from '@ngrx/store';
|
||||
import { NiFiState } from '../../../../state';
|
||||
|
@ -30,25 +30,24 @@ import { EditControllerService } from '../../../../ui/common/controller-service/
|
|||
import {
|
||||
ComponentType,
|
||||
ControllerServiceReferencingComponent,
|
||||
EditParameterRequest,
|
||||
EditParameterResponse,
|
||||
Parameter,
|
||||
ParameterEntity,
|
||||
UpdateControllerServiceRequest
|
||||
} from '../../../../state/shared';
|
||||
import { Router } from '@angular/router';
|
||||
import { ExtensionTypesService } from '../../../../service/extension-types.service';
|
||||
import { selectCurrentProcessGroupId, selectSaving } from './controller-services.selectors';
|
||||
import {
|
||||
selectCurrentProcessGroupId,
|
||||
selectParameterContext,
|
||||
selectSaving,
|
||||
selectStatus
|
||||
} from './controller-services.selectors';
|
||||
import { ControllerServiceService } from '../../service/controller-service.service';
|
||||
import { selectCurrentParameterContext } from '../flow/flow.selectors';
|
||||
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 { DisableControllerService } from '../../../../ui/common/controller-service/disable-controller-service/disable-controller-service.component';
|
||||
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()
|
||||
export class ControllerServicesEffects {
|
||||
|
@ -58,39 +57,45 @@ export class ControllerServicesEffects {
|
|||
private client: Client,
|
||||
private controllerServiceService: ControllerServiceService,
|
||||
private flowService: FlowService,
|
||||
private parameterService: ParameterService,
|
||||
private extensionTypesService: ExtensionTypesService,
|
||||
private errorHelper: ErrorHelper,
|
||||
private dialog: MatDialog,
|
||||
private router: Router,
|
||||
private propertyTableHelperService: PropertyTableHelperService
|
||||
private propertyTableHelperService: PropertyTableHelperService,
|
||||
private parameterHelperService: ParameterHelperService
|
||||
) {}
|
||||
|
||||
loadControllerServices$ = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
ofType(ControllerServicesActions.loadControllerServices),
|
||||
map((action) => action.request),
|
||||
switchMap((request) =>
|
||||
concatLatestFrom(() => this.store.select(selectStatus)),
|
||||
switchMap(([request, status]) =>
|
||||
combineLatest([
|
||||
this.controllerServiceService.getControllerServices(request.processGroupId),
|
||||
this.controllerServiceService.getBreadcrumbs(request.processGroupId)
|
||||
this.controllerServiceService.getFlow(request.processGroupId)
|
||||
]).pipe(
|
||||
map(([controllerServicesResponse, breadcrumbsResponse]) =>
|
||||
map(([controllerServicesResponse, flowResponse]) =>
|
||||
ControllerServicesActions.loadControllerServicesSuccess({
|
||||
response: {
|
||||
processGroupId: breadcrumbsResponse.id,
|
||||
processGroupId: flowResponse.processGroupFlow.id,
|
||||
controllerServices: controllerServicesResponse.controllerServices,
|
||||
loadedTimestamp: controllerServicesResponse.currentTime,
|
||||
breadcrumb: breadcrumbsResponse
|
||||
breadcrumb: flowResponse.processGroupFlow.breadcrumb,
|
||||
parameterContext: flowResponse.processGroupFlow.parameterContext ?? null
|
||||
}
|
||||
})
|
||||
),
|
||||
catchError((error) =>
|
||||
of(
|
||||
ControllerServicesActions.controllerServicesApiError({
|
||||
error: error.error
|
||||
})
|
||||
)
|
||||
)
|
||||
catchError((errorResponse: HttpErrorResponse) => {
|
||||
if (status === 'success') {
|
||||
if (this.errorHelper.showErrorInContext(errorResponse.status)) {
|
||||
return of(ErrorActions.snackBarError({ error: errorResponse.error }));
|
||||
} else {
|
||||
return of(this.errorHelper.fullScreenError(errorResponse));
|
||||
}
|
||||
} else {
|
||||
return of(this.errorHelper.fullScreenError(errorResponse));
|
||||
}
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
|
@ -149,13 +154,10 @@ export class ControllerServicesEffects {
|
|||
}
|
||||
})
|
||||
),
|
||||
catchError((error) =>
|
||||
of(
|
||||
ControllerServicesActions.controllerServicesApiError({
|
||||
error: error.error
|
||||
})
|
||||
)
|
||||
)
|
||||
catchError((errorResponse: HttpErrorResponse) => {
|
||||
this.dialog.closeAll();
|
||||
return of(ErrorActions.snackBarError({ error: errorResponse.error }));
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
|
@ -191,7 +193,7 @@ export class ControllerServicesEffects {
|
|||
ofType(ControllerServicesActions.openConfigureControllerServiceDialog),
|
||||
map((action) => action.request),
|
||||
concatLatestFrom(() => [
|
||||
this.store.select(selectCurrentParameterContext),
|
||||
this.store.select(selectParameterContext),
|
||||
this.store.select(selectCurrentProcessGroupId)
|
||||
]),
|
||||
tap(([request, parameterContext, processGroupId]) => {
|
||||
|
@ -242,17 +244,9 @@ export class ControllerServicesEffects {
|
|||
};
|
||||
|
||||
if (parameterContext != null) {
|
||||
editDialogReference.componentInstance.getParameters = (sensitive: boolean) => {
|
||||
return this.flowService.getParameterContext(parameterContext.id).pipe(
|
||||
take(1),
|
||||
map((response) => response.component.parameters),
|
||||
map((parameterEntities) => {
|
||||
return parameterEntities
|
||||
.map((parameterEntity: ParameterEntity) => parameterEntity.parameter)
|
||||
.filter((parameter: Parameter) => parameter.sensitive == sensitive);
|
||||
})
|
||||
);
|
||||
};
|
||||
editDialogReference.componentInstance.getParameters = this.parameterHelperService.getParameters(
|
||||
parameterContext.id
|
||||
);
|
||||
|
||||
editDialogReference.componentInstance.parameterContext = parameterContext;
|
||||
editDialogReference.componentInstance.goToParameter = () => {
|
||||
|
@ -260,74 +254,8 @@ export class ControllerServicesEffects {
|
|||
goTo(commands, 'Parameter');
|
||||
};
|
||||
|
||||
editDialogReference.componentInstance.convertToParameter = (
|
||||
name: string,
|
||||
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.convertToParameter =
|
||||
this.parameterHelperService.convertToParameter(parameterContext.id);
|
||||
}
|
||||
|
||||
editDialogReference.componentInstance.goToService = (serviceId: string) => {
|
||||
|
@ -341,8 +269,8 @@ export class ControllerServicesEffects {
|
|||
];
|
||||
goTo(commands, 'Controller Service');
|
||||
},
|
||||
error: () => {
|
||||
// TODO - handle error
|
||||
error: (errorResponse: HttpErrorResponse) => {
|
||||
this.store.dispatch(ErrorActions.snackBarError({ error: errorResponse.error }));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -410,18 +338,31 @@ export class ControllerServicesEffects {
|
|||
}
|
||||
})
|
||||
),
|
||||
catchError((error) =>
|
||||
of(
|
||||
ControllerServicesActions.controllerServicesApiError({
|
||||
error: error.error
|
||||
})
|
||||
)
|
||||
)
|
||||
catchError((errorResponse: HttpErrorResponse) => {
|
||||
if (this.errorHelper.showErrorInContext(errorResponse.status)) {
|
||||
return of(
|
||||
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(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
|
@ -558,12 +499,8 @@ export class ControllerServicesEffects {
|
|||
}
|
||||
})
|
||||
),
|
||||
catchError((error) =>
|
||||
of(
|
||||
ControllerServicesActions.controllerServicesApiError({
|
||||
error: error.error
|
||||
})
|
||||
)
|
||||
catchError((errorResponse: HttpErrorResponse) =>
|
||||
of(ErrorActions.snackBarError({ error: errorResponse.error }))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
|
|
@ -19,7 +19,7 @@ import { createReducer, on } from '@ngrx/store';
|
|||
import {
|
||||
configureControllerService,
|
||||
configureControllerServiceSuccess,
|
||||
controllerServicesApiError,
|
||||
controllerServicesBannerApiError,
|
||||
createControllerService,
|
||||
createControllerServiceSuccess,
|
||||
deleteControllerService,
|
||||
|
@ -47,9 +47,9 @@ export const initialState: ControllerServicesState = {
|
|||
name: ''
|
||||
}
|
||||
},
|
||||
parameterContext: null,
|
||||
saving: false,
|
||||
loadedTimestamp: '',
|
||||
error: null,
|
||||
status: 'pending'
|
||||
};
|
||||
|
||||
|
@ -67,15 +67,13 @@ export const controllerServicesReducer = createReducer(
|
|||
processGroupId: response.processGroupId,
|
||||
controllerServices: response.controllerServices,
|
||||
breadcrumb: response.breadcrumb,
|
||||
parameterContext: response.parameterContext,
|
||||
loadedTimestamp: response.loadedTimestamp,
|
||||
error: null,
|
||||
status: 'success' as const
|
||||
})),
|
||||
on(controllerServicesApiError, (state, { error }) => ({
|
||||
on(controllerServicesBannerApiError, (state) => ({
|
||||
...state,
|
||||
saving: false,
|
||||
error,
|
||||
status: 'error' as const
|
||||
saving: false
|
||||
})),
|
||||
on(createControllerService, configureControllerService, deleteControllerService, (state) => ({
|
||||
...state,
|
||||
|
|
|
@ -31,11 +31,21 @@ export const selectSaving = createSelector(
|
|||
(state: ControllerServicesState) => state.saving
|
||||
);
|
||||
|
||||
export const selectStatus = createSelector(
|
||||
selectControllerServicesState,
|
||||
(state: ControllerServicesState) => state.status
|
||||
);
|
||||
|
||||
export const selectCurrentProcessGroupId = createSelector(
|
||||
selectControllerServicesState,
|
||||
(state: ControllerServicesState) => state.processGroupId
|
||||
);
|
||||
|
||||
export const selectParameterContext = createSelector(
|
||||
selectControllerServicesState,
|
||||
(state: ControllerServicesState) => state.parameterContext
|
||||
);
|
||||
|
||||
export const selectProcessGroupIdFromRoute = createSelector(selectCurrentRoute, (route) => {
|
||||
if (route) {
|
||||
// always select the process group from the route
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ControllerServiceEntity } from '../../../../state/shared';
|
||||
import { ControllerServiceEntity, ParameterContextReferenceEntity } from '../../../../state/shared';
|
||||
import { BreadcrumbEntity } from '../shared';
|
||||
|
||||
export const controllerServicesFeatureKey = 'controllerServiceListing';
|
||||
|
@ -28,6 +28,7 @@ export interface LoadControllerServicesResponse {
|
|||
processGroupId: string;
|
||||
breadcrumb: BreadcrumbEntity;
|
||||
controllerServices: ControllerServiceEntity[];
|
||||
parameterContext: ParameterContextReferenceEntity | null;
|
||||
loadedTimestamp: string;
|
||||
}
|
||||
|
||||
|
@ -65,8 +66,8 @@ export interface ControllerServicesState {
|
|||
processGroupId: string;
|
||||
breadcrumb: BreadcrumbEntity;
|
||||
controllerServices: ControllerServiceEntity[];
|
||||
parameterContext: ParameterContextReferenceEntity | null;
|
||||
saving: boolean;
|
||||
loadedTimestamp: string;
|
||||
error: string | null;
|
||||
status: 'pending' | 'loading' | 'error' | 'success';
|
||||
status: 'pending' | 'loading' | 'success';
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@ import { Injectable } from '@angular/core';
|
|||
import { FlowService } from '../../service/flow.service';
|
||||
import { Actions, concatLatestFrom, createEffect, ofType } from '@ngrx/effects';
|
||||
import * as FlowActions from './flow.actions';
|
||||
import * as ParameterActions from '../parameter/parameter.actions';
|
||||
import * as StatusHistoryActions from '../../../../state/status-history/status-history.actions';
|
||||
import {
|
||||
asyncScheduler,
|
||||
|
@ -30,7 +29,6 @@ import {
|
|||
interval,
|
||||
map,
|
||||
mergeMap,
|
||||
NEVER,
|
||||
Observable,
|
||||
of,
|
||||
switchMap,
|
||||
|
@ -65,13 +63,7 @@ import { ConnectionManager } from '../../service/manager/connection-manager.serv
|
|||
import { MatDialog } from '@angular/material/dialog';
|
||||
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 {
|
||||
ComponentType,
|
||||
EditParameterRequest,
|
||||
EditParameterResponse,
|
||||
Parameter,
|
||||
ParameterEntity
|
||||
} from '../../../../state/shared';
|
||||
import { ComponentType } from '../../../../state/shared';
|
||||
import { Router } from '@angular/router';
|
||||
import { Client } from '../../../../service/client.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 { 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 { ExtensionTypesService } from '../../../../service/extension-types.service';
|
||||
import { ControllerServiceService } from '../../service/controller-service.service';
|
||||
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 { ParameterHelperService } from '../../service/parameter-helper.service';
|
||||
|
||||
@Injectable()
|
||||
export class FlowEffects {
|
||||
|
@ -101,9 +90,7 @@ export class FlowEffects {
|
|||
private actions$: Actions,
|
||||
private store: Store<NiFiState>,
|
||||
private flowService: FlowService,
|
||||
private extensionTypesService: ExtensionTypesService,
|
||||
private controllerServiceService: ControllerServiceService,
|
||||
private parameterService: ParameterService,
|
||||
private client: Client,
|
||||
private canvasUtils: CanvasUtils,
|
||||
private canvasView: CanvasView,
|
||||
|
@ -111,7 +98,8 @@ export class FlowEffects {
|
|||
private connectionManager: ConnectionManager,
|
||||
private router: Router,
|
||||
private dialog: MatDialog,
|
||||
private propertyTableHelperService: PropertyTableHelperService
|
||||
private propertyTableHelperService: PropertyTableHelperService,
|
||||
private parameterHelperService: ParameterHelperService
|
||||
) {}
|
||||
|
||||
reloadFlow$ = createEffect(() =>
|
||||
|
@ -874,17 +862,9 @@ export class FlowEffects {
|
|||
};
|
||||
|
||||
if (parameterContext != null) {
|
||||
editDialogReference.componentInstance.getParameters = (sensitive: boolean) => {
|
||||
return this.flowService.getParameterContext(parameterContext.id).pipe(
|
||||
take(1),
|
||||
map((response) => response.component.parameters),
|
||||
map((parameterEntities) => {
|
||||
return parameterEntities
|
||||
.map((parameterEntity: ParameterEntity) => parameterEntity.parameter)
|
||||
.filter((parameter: Parameter) => parameter.sensitive == sensitive);
|
||||
})
|
||||
);
|
||||
};
|
||||
editDialogReference.componentInstance.getParameters = this.parameterHelperService.getParameters(
|
||||
parameterContext.id
|
||||
);
|
||||
|
||||
editDialogReference.componentInstance.parameterContext = parameterContext;
|
||||
editDialogReference.componentInstance.goToParameter = () => {
|
||||
|
@ -892,74 +872,8 @@ export class FlowEffects {
|
|||
goTo(commands, 'Parameter');
|
||||
};
|
||||
|
||||
editDialogReference.componentInstance.convertToParameter = (
|
||||
name: string,
|
||||
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.convertToParameter =
|
||||
this.parameterHelperService.convertToParameter(parameterContext.id);
|
||||
}
|
||||
|
||||
editDialogReference.componentInstance.goToService = (serviceId: string) => {
|
||||
|
|
|
@ -22,4 +22,5 @@ export const parameterFeatureKey = 'parameter';
|
|||
export interface ParameterState {
|
||||
updateRequestEntity: ParameterContextUpdateRequestEntity | null;
|
||||
saving: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
|
|
|
@ -20,10 +20,11 @@ import { Actions, concatLatestFrom, createEffect, ofType } from '@ngrx/effects';
|
|||
import * as ParameterActions from './parameter.actions';
|
||||
import { Store } from '@ngrx/store';
|
||||
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 { selectUpdateRequest } from './parameter.selectors';
|
||||
import { ParameterService } from '../../service/parameter.service';
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
|
||||
@Injectable()
|
||||
export class ParameterEffects {
|
||||
|
@ -46,12 +47,8 @@ export class ParameterEffects {
|
|||
}
|
||||
})
|
||||
),
|
||||
catchError((error) =>
|
||||
of(
|
||||
ParameterActions.parameterApiError({
|
||||
error: error.error
|
||||
})
|
||||
)
|
||||
catchError((errorResponse: HttpErrorResponse) =>
|
||||
of(ParameterActions.parameterApiError({ error: errorResponse.error }))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
@ -140,12 +137,22 @@ export class ParameterEffects {
|
|||
this.actions$.pipe(
|
||||
ofType(ParameterActions.deleteParameterContextUpdateRequest),
|
||||
concatLatestFrom(() => this.store.select(selectUpdateRequest)),
|
||||
tap(([, updateRequest]) => {
|
||||
switchMap(([, 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()))
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ import { createReducer, on } from '@ngrx/store';
|
|||
import { ParameterState } from './index';
|
||||
import {
|
||||
editParameterContextComplete,
|
||||
parameterApiError,
|
||||
pollParameterContextUpdateRequestSuccess,
|
||||
submitParameterContextUpdateRequest,
|
||||
submitParameterContextUpdateRequestSuccess
|
||||
|
@ -26,7 +27,8 @@ import {
|
|||
|
||||
export const initialState: ParameterState = {
|
||||
updateRequestEntity: null,
|
||||
saving: false
|
||||
saving: false,
|
||||
error: null
|
||||
};
|
||||
|
||||
export const parameterReducer = createReducer(
|
||||
|
@ -35,13 +37,15 @@ export const parameterReducer = createReducer(
|
|||
...state,
|
||||
saving: true
|
||||
})),
|
||||
on(parameterApiError, (state, { error }) => ({
|
||||
...state,
|
||||
error
|
||||
})),
|
||||
on(submitParameterContextUpdateRequestSuccess, pollParameterContextUpdateRequestSuccess, (state, { response }) => ({
|
||||
...state,
|
||||
updateRequestEntity: response.requestEntity
|
||||
})),
|
||||
on(editParameterContextComplete, (state) => ({
|
||||
...state,
|
||||
saving: false,
|
||||
updateRequestEntity: null
|
||||
on(editParameterContextComplete, () => ({
|
||||
...initialState
|
||||
}))
|
||||
);
|
||||
|
|
|
@ -35,14 +35,14 @@
|
|||
<i class="fa fa-plus"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="flex-1" *ngIf="flowConfiguration$ | async; let flowConfiguration">
|
||||
<controller-service-table
|
||||
[selectedServiceId]="selectedServiceId$ | async"
|
||||
[controllerServices]="serviceState.controllerServices"
|
||||
[formatScope]="formatScope(serviceState.breadcrumb)"
|
||||
[definedByCurrentGroup]="definedByCurrentGroup(serviceState.breadcrumb)"
|
||||
[currentUser]="(currentUser$ | async)!"
|
||||
[flowConfiguration]="(flowConfiguration$ | async)!"
|
||||
[flowConfiguration]="flowConfiguration"
|
||||
[canModifyParent]="canModifyParent(serviceState.breadcrumb)"
|
||||
(selectControllerService)="selectControllerService($event)"
|
||||
(configureControllerService)="configureControllerService($event)"
|
||||
|
|
|
@ -39,7 +39,7 @@ import {
|
|||
selectControllerService
|
||||
} from '../../state/controller-services/controller-services.actions';
|
||||
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 { selectCurrentUser } from '../../../../state/current-user/current-user.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);
|
||||
selectedServiceId$ = this.store.select(selectControllerServiceIdFromRoute);
|
||||
currentUser$ = this.store.select(selectCurrentUser);
|
||||
flowConfiguration$ = this.store.select(selectFlowConfiguration);
|
||||
flowConfiguration$ = this.store.select(selectFlowConfiguration).pipe(isDefinedAndNotNull());
|
||||
|
||||
private currentProcessGroupId!: string;
|
||||
|
||||
|
|
|
@ -441,9 +441,9 @@ export class ManagementControllerServicesEffects {
|
|||
}
|
||||
})
|
||||
),
|
||||
catchError((errorResponse: HttpErrorResponse) => {
|
||||
return of(ErrorActions.snackBarError({ error: errorResponse.error }));
|
||||
})
|
||||
catchError((errorResponse: HttpErrorResponse) =>
|
||||
of(ErrorActions.snackBarError({ error: errorResponse.error }))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
|
|
@ -26,62 +26,62 @@ import {
|
|||
} from './index';
|
||||
|
||||
export const resetEnableControllerServiceState = createAction(
|
||||
'[Enable Controller Service] Reset Enable Controller Service State'
|
||||
'[Controller Service State] Reset Enable Controller Service State'
|
||||
);
|
||||
|
||||
export const setControllerService = createAction(
|
||||
'[Enable Controller Service] Set Controller Service',
|
||||
'[Controller Service State] Set Controller Service',
|
||||
props<{
|
||||
request: SetControllerServiceRequest;
|
||||
}>()
|
||||
);
|
||||
|
||||
export const submitEnableRequest = createAction(
|
||||
'[Enable Controller Service] Submit Enable Request',
|
||||
'[Controller Service State] Submit Enable Request',
|
||||
props<{
|
||||
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(
|
||||
'[Enable Controller Service] Set Enable Controller Service Success',
|
||||
'[Controller Service State] Set Enable Controller Service Success',
|
||||
props<{
|
||||
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(
|
||||
'[Enable Controller Service] Update Referencing Services Success',
|
||||
'[Controller Service State] Update Referencing Services Success',
|
||||
props<{
|
||||
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(
|
||||
'[Enable Controller Service] Update Referencing Components Success',
|
||||
'[Controller Service State] Update Referencing Components Success',
|
||||
props<{
|
||||
response: ReferencingComponentsStepResponse;
|
||||
}>()
|
||||
);
|
||||
|
||||
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(
|
||||
'[Enable Controller Service] Poll Controller Service Success',
|
||||
'[Controller Service State] Poll Controller Service Success',
|
||||
props<{
|
||||
response: ControllerServiceStepResponse;
|
||||
previousStep: SetEnableStep;
|
||||
|
@ -89,25 +89,14 @@ export const pollControllerServiceSuccess = createAction(
|
|||
);
|
||||
|
||||
export const setEnableStepFailure = createAction(
|
||||
'[Enable Controller Service] Set Enable Step Failure',
|
||||
'[Controller Service State] Set Enable Step Failure',
|
||||
props<{
|
||||
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(
|
||||
'[Enable Controller Service] Show Ok Dialog',
|
||||
'[Controller Service State] Show Ok Dialog',
|
||||
props<{
|
||||
title: string;
|
||||
message: string;
|
||||
|
|
|
@ -87,42 +87,31 @@ export class ControllerServiceStateEffects {
|
|||
this.actions$.pipe(
|
||||
ofType(ControllerServiceActions.setEnableControllerService),
|
||||
concatLatestFrom(() => [
|
||||
this.store.select(selectControllerService),
|
||||
this.store.select(selectControllerService).pipe(isDefinedAndNotNull()),
|
||||
this.store.select(selectControllerServiceSetEnableRequest)
|
||||
]),
|
||||
switchMap(([, controllerService, setEnableRequest]) => {
|
||||
if (controllerService) {
|
||||
return from(
|
||||
this.controllerServiceStateService.setEnable(controllerService, setEnableRequest.enable)
|
||||
).pipe(
|
||||
map((response) =>
|
||||
ControllerServiceActions.setEnableControllerServiceSuccess({
|
||||
switchMap(([, controllerService, setEnableRequest]) =>
|
||||
from(this.controllerServiceStateService.setEnable(controllerService, setEnableRequest.enable)).pipe(
|
||||
map((response) =>
|
||||
ControllerServiceActions.setEnableControllerServiceSuccess({
|
||||
response: {
|
||||
controllerService: response,
|
||||
currentStep: setEnableRequest.currentStep
|
||||
}
|
||||
})
|
||||
),
|
||||
catchError((error) =>
|
||||
of(
|
||||
ControllerServiceActions.setEnableStepFailure({
|
||||
response: {
|
||||
controllerService: response,
|
||||
currentStep: setEnableRequest.currentStep
|
||||
step: 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'
|
||||
})
|
||||
);
|
||||
}
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
|
|
|
@ -18,8 +18,6 @@
|
|||
import { createReducer, on } from '@ngrx/store';
|
||||
import { ControllerServiceState, SetEnableStep } from './index';
|
||||
import {
|
||||
clearControllerServiceApiError,
|
||||
controllerServiceApiError,
|
||||
setEnableStepFailure,
|
||||
pollControllerServiceSuccess,
|
||||
resetEnableControllerServiceState,
|
||||
|
@ -39,7 +37,6 @@ export const initialState: ControllerServiceState = {
|
|||
scope: 'SERVICE_ONLY'
|
||||
},
|
||||
controllerService: null,
|
||||
error: null,
|
||||
status: 'pending'
|
||||
};
|
||||
|
||||
|
@ -91,15 +88,5 @@ export const controllerServiceStateReducer = createReducer(
|
|||
error: response
|
||||
},
|
||||
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
|
||||
}))
|
||||
);
|
||||
|
|
|
@ -76,6 +76,5 @@ export interface SetEnableRequest {
|
|||
export interface ControllerServiceState {
|
||||
setEnableRequest: SetEnableRequest;
|
||||
controllerService: ControllerServiceEntity | null;
|
||||
error: string | null;
|
||||
status: 'pending' | 'loading' | 'error' | 'success';
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue