NIFI-12502: Handle additional Property Table capabilities (#8159)

* NIFI-12502:
- Go To Service.
- Save before navigating if form dirty.
- Go To Parameter.
- Convert To Parameter.

* NIFI-12502:
- Ensuring tests are still bootstrapped correctly.

* NIFI-12502:
- Addressing review feedback.

This closes #8159
This commit is contained in:
Matt Gilman 2023-12-18 14:22:07 -05:00 committed by GitHub
parent 80700cc6c6
commit 49bbc38b6b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
52 changed files with 1098 additions and 215 deletions

View File

@ -27,6 +27,7 @@ import { VersionControlTip } from '../ui/common/tooltips/version-control-tip/ver
import { canvasFeatureKey, reducers } from '../state';
import { MatDialogModule } from '@angular/material/dialog';
import { ControllerServicesEffects } from '../state/controller-services/controller-services.effects';
import { ParameterEffects } from '../state/parameter/parameter.effects';
@NgModule({
declarations: [FlowDesigner, VersionControlTip],
@ -35,7 +36,7 @@ import { ControllerServicesEffects } from '../state/controller-services/controll
CommonModule,
FlowDesignerRoutingModule,
StoreModule.forFeature(canvasFeatureKey, reducers),
EffectsModule.forFeature(FlowEffects, TransformEffects, ControllerServicesEffects),
EffectsModule.forFeature(FlowEffects, TransformEffects, ControllerServicesEffects, ParameterEffects),
NgOptimizedImage,
MatDialogModule
]

View File

@ -29,6 +29,8 @@ import { controllerServicesFeatureKey } from '../../state/controller-services';
import * as fromControllerServices from '../../state/controller-services/controller-services.reducer';
import { selectUser } from '../../../../state/user/user.selectors';
import * as fromUser from '../../../../state/user/user.reducer';
import { parameterFeatureKey } from '../../state/parameter';
import * as fromParameter from '../../state/parameter/parameter.reducer';
describe('ConnectableBehavior', () => {
let service: ConnectableBehavior;
@ -37,7 +39,8 @@ describe('ConnectableBehavior', () => {
const initialState: CanvasState = {
[flowFeatureKey]: fromFlow.initialState,
[transformFeatureKey]: fromTransform.initialState,
[controllerServicesFeatureKey]: fromControllerServices.initialState
[controllerServicesFeatureKey]: fromControllerServices.initialState,
[parameterFeatureKey]: fromParameter.initialState
};
TestBed.configureTestingModule({

View File

@ -30,6 +30,8 @@ import { controllerServicesFeatureKey } from '../../state/controller-services';
import * as fromControllerServices from '../../state/controller-services/controller-services.reducer';
import { selectUser } from '../../../../state/user/user.selectors';
import * as fromUser from '../../../../state/user/user.reducer';
import { parameterFeatureKey } from '../../state/parameter';
import * as fromParameter from '../../state/parameter/parameter.reducer';
describe('DraggableBehavior', () => {
let service: DraggableBehavior;
@ -38,7 +40,8 @@ describe('DraggableBehavior', () => {
const initialState: CanvasState = {
[flowFeatureKey]: fromFlow.initialState,
[transformFeatureKey]: fromTransform.initialState,
[controllerServicesFeatureKey]: fromControllerServices.initialState
[controllerServicesFeatureKey]: fromControllerServices.initialState,
[parameterFeatureKey]: fromParameter.initialState
};
TestBed.configureTestingModule({

View File

@ -30,6 +30,8 @@ import { controllerServicesFeatureKey } from '../../state/controller-services';
import * as fromControllerServices from '../../state/controller-services/controller-services.reducer';
import { selectUser } from '../../../../state/user/user.selectors';
import * as fromUser from '../../../../state/user/user.reducer';
import { parameterFeatureKey } from '../../state/parameter';
import * as fromParameter from '../../state/parameter/parameter.reducer';
describe('EditableBehaviorService', () => {
let service: EditableBehavior;
@ -37,7 +39,8 @@ describe('EditableBehaviorService', () => {
const initialState: CanvasState = {
[flowFeatureKey]: fromFlow.initialState,
[transformFeatureKey]: fromTransform.initialState,
[controllerServicesFeatureKey]: fromControllerServices.initialState
[controllerServicesFeatureKey]: fromControllerServices.initialState,
[parameterFeatureKey]: fromParameter.initialState
};
beforeEach(() => {

View File

@ -29,6 +29,8 @@ import { controllerServicesFeatureKey } from '../../state/controller-services';
import * as fromControllerServices from '../../state/controller-services/controller-services.reducer';
import { selectUser } from '../../../../state/user/user.selectors';
import * as fromUser from '../../../../state/user/user.reducer';
import { parameterFeatureKey } from '../../state/parameter';
import * as fromParameter from '../../state/parameter/parameter.reducer';
describe('QuickSelectBehavior', () => {
let service: QuickSelectBehavior;
@ -37,7 +39,8 @@ describe('QuickSelectBehavior', () => {
const initialState: CanvasState = {
[flowFeatureKey]: fromFlow.initialState,
[transformFeatureKey]: fromTransform.initialState,
[controllerServicesFeatureKey]: fromControllerServices.initialState
[controllerServicesFeatureKey]: fromControllerServices.initialState,
[parameterFeatureKey]: fromParameter.initialState
};
TestBed.configureTestingModule({

View File

@ -28,6 +28,8 @@ import { controllerServicesFeatureKey } from '../../state/controller-services';
import * as fromControllerServices from '../../state/controller-services/controller-services.reducer';
import { selectUser } from '../../../../state/user/user.selectors';
import * as fromUser from '../../../../state/user/user.reducer';
import { parameterFeatureKey } from '../../state/parameter';
import * as fromParameter from '../../state/parameter/parameter.reducer';
describe('SelectableBehavior', () => {
let service: SelectableBehavior;
@ -36,7 +38,8 @@ describe('SelectableBehavior', () => {
const initialState: CanvasState = {
[flowFeatureKey]: fromFlow.initialState,
[transformFeatureKey]: fromTransform.initialState,
[controllerServicesFeatureKey]: fromControllerServices.initialState
[controllerServicesFeatureKey]: fromControllerServices.initialState,
[parameterFeatureKey]: fromParameter.initialState
};
TestBed.configureTestingModule({

View File

@ -30,6 +30,8 @@ import { controllerServicesFeatureKey } from '../state/controller-services';
import * as fromControllerServices from '../state/controller-services/controller-services.reducer';
import { selectUser } from '../../../state/user/user.selectors';
import * as fromUser from '../../../state/user/user.reducer';
import { parameterFeatureKey } from '../state/parameter';
import * as fromParameter from '../state/parameter/parameter.reducer';
describe('BirdseyeView', () => {
let service: BirdseyeView;
@ -38,7 +40,8 @@ describe('BirdseyeView', () => {
const initialState: CanvasState = {
[flowFeatureKey]: fromFlow.initialState,
[transformFeatureKey]: fromTransform.initialState,
[controllerServicesFeatureKey]: fromControllerServices.initialState
[controllerServicesFeatureKey]: fromControllerServices.initialState,
[parameterFeatureKey]: fromParameter.initialState
};
TestBed.configureTestingModule({

View File

@ -29,6 +29,8 @@ import { controllerServicesFeatureKey } from '../state/controller-services';
import * as fromControllerServices from '../state/controller-services/controller-services.reducer';
import { selectUser } from '../../../state/user/user.selectors';
import * as fromUser from '../../../state/user/user.reducer';
import { parameterFeatureKey } from '../state/parameter';
import * as fromParameter from '../state/parameter/parameter.reducer';
describe('CanvasUtils', () => {
let service: CanvasUtils;
@ -37,7 +39,8 @@ describe('CanvasUtils', () => {
const initialState: CanvasState = {
[flowFeatureKey]: fromFlow.initialState,
[transformFeatureKey]: fromTransform.initialState,
[controllerServicesFeatureKey]: fromControllerServices.initialState
[controllerServicesFeatureKey]: fromControllerServices.initialState,
[parameterFeatureKey]: fromParameter.initialState
};
TestBed.configureTestingModule({

View File

@ -30,6 +30,8 @@ import { controllerServicesFeatureKey } from '../state/controller-services';
import * as fromControllerServices from '../state/controller-services/controller-services.reducer';
import { selectUser } from '../../../state/user/user.selectors';
import * as fromUser from '../../../state/user/user.reducer';
import { parameterFeatureKey } from '../state/parameter';
import * as fromParameter from '../state/parameter/parameter.reducer';
describe('CanvasView', () => {
let service: CanvasView;
@ -38,7 +40,8 @@ describe('CanvasView', () => {
const initialState: CanvasState = {
[flowFeatureKey]: fromFlow.initialState,
[transformFeatureKey]: fromTransform.initialState,
[controllerServicesFeatureKey]: fromControllerServices.initialState
[controllerServicesFeatureKey]: fromControllerServices.initialState,
[parameterFeatureKey]: fromParameter.initialState
};
TestBed.configureTestingModule({

View File

@ -64,10 +64,6 @@ export class FlowService {
return this.httpClient.get(`${FlowService.API}/flow/process-groups/${processGroupId}`);
}
getControllerService(id: string): Observable<any> {
return this.httpClient.get(`${FlowService.API}/controller-services/${id}`);
}
getProcessGroupStatus(processGroupId: string = 'root', recursive: boolean = false): Observable<any> {
return this.httpClient.get(`${FlowService.API}/flow/process-groups/${processGroupId}/status`, {
params: { recursive: recursive }

View File

@ -30,6 +30,8 @@ import { controllerServicesFeatureKey } from '../../state/controller-services';
import * as fromControllerServices from '../../state/controller-services/controller-services.reducer';
import { selectUser } from '../../../../state/user/user.selectors';
import * as fromUser from '../../../../state/user/user.reducer';
import { parameterFeatureKey } from '../../state/parameter';
import * as fromParameter from '../../state/parameter/parameter.reducer';
describe('ConnectionManager', () => {
let service: ConnectionManager;
@ -38,7 +40,8 @@ describe('ConnectionManager', () => {
const initialState: CanvasState = {
[flowFeatureKey]: fromFlow.initialState,
[transformFeatureKey]: fromTransform.initialState,
[controllerServicesFeatureKey]: fromControllerServices.initialState
[controllerServicesFeatureKey]: fromControllerServices.initialState,
[parameterFeatureKey]: fromParameter.initialState
};
TestBed.configureTestingModule({

View File

@ -30,6 +30,8 @@ import { controllerServicesFeatureKey } from '../../state/controller-services';
import * as fromControllerServices from '../../state/controller-services/controller-services.reducer';
import { selectUser } from '../../../../state/user/user.selectors';
import * as fromUser from '../../../../state/user/user.reducer';
import { parameterFeatureKey } from '../../state/parameter';
import * as fromParameter from '../../state/parameter/parameter.reducer';
describe('FunnelManager', () => {
let service: FunnelManager;
@ -38,7 +40,8 @@ describe('FunnelManager', () => {
const initialState: CanvasState = {
[flowFeatureKey]: fromFlow.initialState,
[transformFeatureKey]: fromTransform.initialState,
[controllerServicesFeatureKey]: fromControllerServices.initialState
[controllerServicesFeatureKey]: fromControllerServices.initialState,
[parameterFeatureKey]: fromParameter.initialState
};
TestBed.configureTestingModule({

View File

@ -30,6 +30,8 @@ import { controllerServicesFeatureKey } from '../../state/controller-services';
import * as fromControllerServices from '../../state/controller-services/controller-services.reducer';
import { selectUser } from '../../../../state/user/user.selectors';
import * as fromUser from '../../../../state/user/user.reducer';
import { parameterFeatureKey } from '../../state/parameter';
import * as fromParameter from '../../state/parameter/parameter.reducer';
describe('LabelManager', () => {
let service: LabelManager;
@ -38,7 +40,8 @@ describe('LabelManager', () => {
const initialState: CanvasState = {
[flowFeatureKey]: fromFlow.initialState,
[transformFeatureKey]: fromTransform.initialState,
[controllerServicesFeatureKey]: fromControllerServices.initialState
[controllerServicesFeatureKey]: fromControllerServices.initialState,
[parameterFeatureKey]: fromParameter.initialState
};
TestBed.configureTestingModule({

View File

@ -30,6 +30,8 @@ import { controllerServicesFeatureKey } from '../../state/controller-services';
import * as fromControllerServices from '../../state/controller-services/controller-services.reducer';
import { selectUser } from '../../../../state/user/user.selectors';
import * as fromUser from '../../../../state/user/user.reducer';
import { parameterFeatureKey } from '../../state/parameter';
import * as fromParameter from '../../state/parameter/parameter.reducer';
describe('PortManager', () => {
let service: PortManager;
@ -38,7 +40,8 @@ describe('PortManager', () => {
const initialState: CanvasState = {
[flowFeatureKey]: fromFlow.initialState,
[transformFeatureKey]: fromTransform.initialState,
[controllerServicesFeatureKey]: fromControllerServices.initialState
[controllerServicesFeatureKey]: fromControllerServices.initialState,
[parameterFeatureKey]: fromParameter.initialState
};
TestBed.configureTestingModule({

View File

@ -30,6 +30,8 @@ import { controllerServicesFeatureKey } from '../../state/controller-services';
import * as fromControllerServices from '../../state/controller-services/controller-services.reducer';
import { selectUser } from '../../../../state/user/user.selectors';
import * as fromUser from '../../../../state/user/user.reducer';
import { parameterFeatureKey } from '../../state/parameter';
import * as fromParameter from '../../state/parameter/parameter.reducer';
describe('ProcessGroupManager', () => {
let service: ProcessGroupManager;
@ -38,7 +40,8 @@ describe('ProcessGroupManager', () => {
const initialState: CanvasState = {
[flowFeatureKey]: fromFlow.initialState,
[transformFeatureKey]: fromTransform.initialState,
[controllerServicesFeatureKey]: fromControllerServices.initialState
[controllerServicesFeatureKey]: fromControllerServices.initialState,
[parameterFeatureKey]: fromParameter.initialState
};
TestBed.configureTestingModule({

View File

@ -30,6 +30,8 @@ import { controllerServicesFeatureKey } from '../../state/controller-services';
import * as fromControllerServices from '../../state/controller-services/controller-services.reducer';
import { selectUser } from '../../../../state/user/user.selectors';
import * as fromUser from '../../../../state/user/user.reducer';
import { parameterFeatureKey } from '../../state/parameter';
import * as fromParameter from '../../state/parameter/parameter.reducer';
describe('ProcessorManager', () => {
let service: ProcessorManager;
@ -38,7 +40,8 @@ describe('ProcessorManager', () => {
const initialState: CanvasState = {
[flowFeatureKey]: fromFlow.initialState,
[transformFeatureKey]: fromTransform.initialState,
[controllerServicesFeatureKey]: fromControllerServices.initialState
[controllerServicesFeatureKey]: fromControllerServices.initialState,
[parameterFeatureKey]: fromParameter.initialState
};
TestBed.configureTestingModule({

View File

@ -30,6 +30,8 @@ import { controllerServicesFeatureKey } from '../../state/controller-services';
import * as fromControllerServices from '../../state/controller-services/controller-services.reducer';
import { selectUser } from '../../../../state/user/user.selectors';
import * as fromUser from '../../../../state/user/user.reducer';
import { parameterFeatureKey } from '../../state/parameter';
import * as fromParameter from '../../state/parameter/parameter.reducer';
describe('RemoteProcessGroupManager', () => {
let service: RemoteProcessGroupManager;
@ -38,7 +40,8 @@ describe('RemoteProcessGroupManager', () => {
const initialState: CanvasState = {
[flowFeatureKey]: fromFlow.initialState,
[transformFeatureKey]: fromTransform.initialState,
[controllerServicesFeatureKey]: fromControllerServices.initialState
[controllerServicesFeatureKey]: fromControllerServices.initialState,
[parameterFeatureKey]: fromParameter.initialState
};
TestBed.configureTestingModule({

View File

@ -0,0 +1,68 @@
/*
* 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 { Observable, throwError } from 'rxjs';
import { HttpClient } from '@angular/common/http';
import { NiFiCommon } from '../../../service/nifi-common.service';
import { ParameterContextUpdateRequest, SubmitParameterContextUpdate } from '../../../state/shared';
@Injectable({ providedIn: 'root' })
export class ParameterService {
private static readonly API: string = '../nifi-api';
constructor(
private httpClient: HttpClient,
private nifiCommon: NiFiCommon
) {}
/**
* The NiFi model contain the url for each component. That URL is an absolute URL. Angular CSRF handling
* does not work on absolute URLs, so we need to strip off the proto for the request header to be added.
*
* https://stackoverflow.com/a/59586462
*
* @param url
* @private
*/
private stripProtocol(url: string): string {
return this.nifiCommon.substringAfterFirst(url, ':');
}
getParameterContext(id: string, includeInheritedParameters: boolean): Observable<any> {
return this.httpClient.get(`${ParameterService.API}/parameter-contexts/${id}`, {
params: {
includeInheritedParameters
}
});
}
submitParameterContextUpdate(configureParameterContext: SubmitParameterContextUpdate): Observable<any> {
return this.httpClient.post(
`${ParameterService.API}/parameter-contexts/${configureParameterContext.id}/update-requests`,
configureParameterContext.payload
);
}
pollParameterContextUpdate(updateRequest: ParameterContextUpdateRequest): Observable<any> {
return this.httpClient.get(this.stripProtocol(updateRequest.uri));
}
deleteParameterContextUpdate(updateRequest: ParameterContextUpdateRequest): Observable<any> {
return this.httpClient.delete(this.stripProtocol(updateRequest.uri));
}
}

View File

@ -21,6 +21,7 @@ import * as ControllerServicesActions from './controller-services.actions';
import {
catchError,
combineLatest,
filter,
from,
map,
NEVER,
@ -28,6 +29,7 @@ import {
of,
switchMap,
take,
takeUntil,
tap,
withLatestFrom
} from 'rxjs';
@ -40,18 +42,29 @@ import { Client } from '../../../../service/client.service';
import { YesNoDialog } from '../../../../ui/common/yes-no-dialog/yes-no-dialog.component';
import { EditControllerService } from '../../../../ui/common/controller-service/edit-controller-service/edit-controller-service.component';
import {
EditParameterRequest,
EditParameterResponse,
InlineServiceCreationRequest,
InlineServiceCreationResponse,
NewPropertyDialogRequest,
NewPropertyDialogResponse,
Parameter,
ParameterEntity,
Property,
PropertyDescriptor
PropertyDescriptor,
UpdateControllerServiceRequest
} from '../../../../state/shared';
import { NewPropertyDialog } from '../../../../ui/common/new-property-dialog/new-property-dialog.component';
import { Router } from '@angular/router';
import { ExtensionTypesService } from '../../../../service/extension-types.service';
import { selectCurrentProcessGroupId, selectSaving } 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';
@Injectable()
export class ControllerServicesEffects {
@ -60,6 +73,8 @@ export class ControllerServicesEffects {
private store: Store<NiFiState>,
private client: Client,
private controllerServiceService: ControllerServiceService,
private flowService: FlowService,
private parameterService: ParameterService,
private extensionTypesService: ExtensionTypesService,
private dialog: MatDialog,
private router: Router
@ -190,14 +205,18 @@ export class ControllerServicesEffects {
this.actions$.pipe(
ofType(ControllerServicesActions.openConfigureControllerServiceDialog),
map((action) => action.request),
withLatestFrom(this.store.select(selectCurrentProcessGroupId)),
tap(([request, processGroupId]) => {
withLatestFrom(
this.store.select(selectCurrentParameterContext),
this.store.select(selectCurrentProcessGroupId)
),
tap(([request, parameterContext, processGroupId]) => {
const serviceId: string = request.id;
const editDialogReference = this.dialog.open(EditControllerService, {
data: {
controllerService: request.controllerService
},
id: serviceId,
panelClass: 'large-dialog'
});
@ -234,18 +253,134 @@ export class ControllerServicesEffects {
);
};
editDialogReference.componentInstance.getServiceLink = (serviceId: string) => {
return this.controllerServiceService.getControllerService(serviceId).pipe(
take(1),
map((serviceEntity) => {
return [
const goTo = (commands: string[], destination: string): void => {
if (editDialogReference.componentInstance.editControllerServiceForm.dirty) {
const saveChangesDialogReference = this.dialog.open(YesNoDialog, {
data: {
title: 'Controller Service Configuration',
message: `Save changes before going to this ${destination}?`
},
panelClass: 'small-dialog'
});
saveChangesDialogReference.componentInstance.yes.pipe(take(1)).subscribe(() => {
editDialogReference.componentInstance.submitForm(commands);
});
saveChangesDialogReference.componentInstance.no.pipe(take(1)).subscribe(() => {
editDialogReference.close('ROUTED');
this.router.navigate(commands);
});
} else {
editDialogReference.close('ROUTED');
this.router.navigate(commands);
}
};
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.parameterContext = parameterContext;
editDialogReference.componentInstance.goToParameter = (parameter: string) => {
const commands: string[] = ['/parameter-contexts', parameterContext.id];
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((error) => {
// TODO handle error
return NEVER;
})
);
};
}
editDialogReference.componentInstance.goToService = (serviceId: string) => {
this.controllerServiceService.getControllerService(serviceId).subscribe({
next: (serviceEntity) => {
const commands: string[] = [
'/process-groups',
serviceEntity.component.parentGroupId,
'controller-services',
serviceEntity.id
];
})
);
goTo(commands, 'Controller Service');
},
error: () => {
// TODO - handle error
}
});
};
editDialogReference.componentInstance.createNewService = (
@ -289,13 +424,13 @@ export class ControllerServicesEffects {
})
.pipe(
take(1),
switchMap((createReponse) => {
switchMap((createResponse) => {
// dispatch an inline create service success action so the new service is in the state
this.store.dispatch(
ControllerServicesActions.inlineCreateControllerServiceSuccess(
{
response: {
controllerService: createReponse
controllerService: createResponse
}
}
)
@ -310,7 +445,7 @@ export class ControllerServicesEffects {
createServiceDialogReference.close();
return {
value: createReponse.id,
value: createResponse.id,
descriptor:
descriptorResponse.propertyDescriptor
};
@ -329,14 +464,15 @@ export class ControllerServicesEffects {
};
editDialogReference.componentInstance.editControllerService
.pipe(take(1))
.subscribe((payload: any) => {
.pipe(takeUntil(editDialogReference.afterClosed()))
.subscribe((updateControllerServiceRequest: UpdateControllerServiceRequest) => {
this.store.dispatch(
ControllerServicesActions.configureControllerService({
request: {
id: request.controllerService.id,
uri: request.controllerService.uri,
payload
payload: updateControllerServiceRequest.payload,
postUpdateNavigation: updateControllerServiceRequest.postUpdateNavigation
}
})
);
@ -369,7 +505,8 @@ export class ControllerServicesEffects {
ControllerServicesActions.configureControllerServiceSuccess({
response: {
id: request.id,
controllerService: response
controllerService: response,
postUpdateNavigation: request.postUpdateNavigation
}
})
),
@ -389,8 +526,14 @@ export class ControllerServicesEffects {
() =>
this.actions$.pipe(
ofType(ControllerServicesActions.configureControllerServiceSuccess),
tap(() => {
this.dialog.closeAll();
map((action) => action.response),
tap((response) => {
if (response.postUpdateNavigation) {
this.router.navigate(response.postUpdateNavigation);
this.dialog.getDialogById(response.id)?.close('ROUTED');
} else {
this.dialog.closeAll();
}
})
),
{ dispatch: false }

View File

@ -46,11 +46,13 @@ export interface ConfigureControllerServiceRequest {
id: string;
uri: string;
payload: any;
postUpdateNavigation?: string[];
}
export interface ConfigureControllerServiceSuccess {
id: string;
controllerService: ControllerServiceEntity;
postUpdateNavigation?: string[];
}
export interface DeleteControllerServiceRequest {

View File

@ -19,6 +19,7 @@ import { Injectable } from '@angular/core';
import { FlowService } from '../../service/flow.service';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import * as FlowActions from './flow.actions';
import * as ParameterActions from '../parameter/parameter.actions';
import {
asyncScheduler,
catchError,
@ -46,7 +47,8 @@ import {
Snippet,
UpdateComponentFailure,
UpdateComponentResponse,
UpdateConnectionSuccess
UpdateConnectionSuccess,
UpdateProcessorRequest
} from './index';
import { Action, Store } from '@ngrx/store';
import {
@ -65,6 +67,8 @@ import { CreatePort } from '../../ui/canvas/items/port/create-port/create-port.c
import { EditPort } from '../../ui/canvas/items/port/edit-port/edit-port.component';
import {
ComponentType,
EditParameterRequest,
EditParameterResponse,
InlineServiceCreationRequest,
InlineServiceCreationResponse,
NewPropertyDialogRequest,
@ -94,6 +98,10 @@ import { CreateControllerService } from '../../../../ui/common/controller-servic
import * as ControllerServicesActions from '../controller-services/controller-services.actions';
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';
@Injectable()
export class FlowEffects {
@ -103,6 +111,7 @@ export class FlowEffects {
private flowService: FlowService,
private extensionTypesService: ExtensionTypesService,
private controllerServiceService: ControllerServiceService,
private parameterService: ParameterService,
private client: Client,
private canvasUtils: CanvasUtils,
private canvasView: CanvasView,
@ -771,6 +780,7 @@ export class FlowEffects {
const editDialogReference = this.dialog.open(EditProcessor, {
data: request,
id: processorId,
panelClass: 'large-dialog'
});
@ -807,6 +817,30 @@ export class FlowEffects {
);
};
const goTo = (commands: string[], destination: string): void => {
if (editDialogReference.componentInstance.editProcessorForm.dirty) {
const saveChangesDialogReference = this.dialog.open(YesNoDialog, {
data: {
title: 'Processor Configuration',
message: `Save changes before going to this ${destination}?`
},
panelClass: 'small-dialog'
});
saveChangesDialogReference.componentInstance.yes.pipe(take(1)).subscribe(() => {
editDialogReference.componentInstance.submitForm(commands);
});
saveChangesDialogReference.componentInstance.no.pipe(take(1)).subscribe(() => {
editDialogReference.close('ROUTED');
this.router.navigate(commands);
});
} else {
editDialogReference.close('ROUTED');
this.router.navigate(commands);
}
};
if (parameterContext != null) {
editDialogReference.componentInstance.getParameters = (sensitive: boolean) => {
return this.flowService.getParameterContext(parameterContext.id).pipe(
@ -819,20 +853,98 @@ export class FlowEffects {
})
);
};
editDialogReference.componentInstance.parameterContext = parameterContext;
editDialogReference.componentInstance.goToParameter = (parameter: string) => {
const commands: string[] = ['/parameter-contexts', parameterContext.id];
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((error) => {
// TODO handle error
return NEVER;
})
);
};
}
editDialogReference.componentInstance.getServiceLink = (serviceId: string) => {
return this.flowService.getControllerService(serviceId).pipe(
take(1),
map((serviceEntity) => {
return [
editDialogReference.componentInstance.goToService = (serviceId: string) => {
this.controllerServiceService.getControllerService(serviceId).subscribe({
next: (serviceEntity) => {
const commands: string[] = [
'/process-groups',
serviceEntity.component.parentGroupId,
'controller-services',
serviceEntity.id
];
})
);
goTo(commands, 'Controller Service');
},
error: () => {
// TODO - handle error
}
});
};
editDialogReference.componentInstance.createNewService = (
@ -917,14 +1029,15 @@ export class FlowEffects {
editDialogReference.componentInstance.editProcessor
.pipe(takeUntil(editDialogReference.afterClosed()))
.subscribe((payload: any) => {
.subscribe((updateProcessorRequest: UpdateProcessorRequest) => {
this.store.dispatch(
FlowActions.updateProcessor({
request: {
id: processorId,
uri: request.uri,
type: request.type,
payload
payload: updateProcessorRequest.payload,
postUpdateNavigation: updateProcessorRequest.postUpdateNavigation
}
})
);
@ -1082,7 +1195,8 @@ export class FlowEffects {
requestId: request.requestId,
id: request.id,
type: request.type,
response: response
postUpdateNavigation: request.postUpdateNavigation,
response
};
return FlowActions.updateComponentSuccess({ response: updateComponentResponse });
}),
@ -1104,8 +1218,14 @@ export class FlowEffects {
() =>
this.actions$.pipe(
ofType(FlowActions.updateComponentSuccess),
tap(() => {
this.dialog.closeAll();
map((action) => action.response),
tap((response) => {
if (response.postUpdateNavigation) {
this.router.navigate(response.postUpdateNavigation);
this.dialog.getDialogById(response.id)?.close('ROUTED');
} else {
this.dialog.closeAll();
}
})
),
{ dispatch: false }
@ -1130,7 +1250,8 @@ export class FlowEffects {
requestId: request.requestId,
id: request.id,
type: request.type,
response: response
postUpdateNavigation: request.postUpdateNavigation,
response
};
return FlowActions.updateProcessorSuccess({ response: updateComponentResponse });
}),
@ -1151,10 +1272,16 @@ export class FlowEffects {
updateProcessorSuccess$ = createEffect(() =>
this.actions$.pipe(
ofType(FlowActions.updateProcessorSuccess),
tap(() => {
this.dialog.closeAll();
}),
map((action) => action.response),
tap((response) => {
if (response.postUpdateNavigation) {
this.router.navigate(response.postUpdateNavigation);
this.dialog.getDialogById(response.id)?.close('ROUTED');
} else {
this.dialog.closeAll();
}
}),
filter((response) => response.postUpdateNavigation == null),
switchMap((response) => of(FlowActions.loadConnectionsForComponent({ id: response.id })))
)
);

View File

@ -237,6 +237,11 @@ export interface EditConnectionDialogRequest extends EditComponentDialogRequest
};
}
export interface UpdateProcessorRequest {
payload: any;
postUpdateNavigation?: string[];
}
export interface UpdateComponentRequest {
requestId?: number;
id: string;
@ -244,6 +249,7 @@ export interface UpdateComponentRequest {
uri: string;
payload: any;
restoreOnFailure?: any;
postUpdateNavigation?: string[];
}
export interface UpdateComponentResponse {
@ -251,6 +257,7 @@ export interface UpdateComponentResponse {
id: string;
type: ComponentType;
response: any;
postUpdateNavigation?: string[];
}
export interface UpdateComponentFailure {

View File

@ -26,6 +26,8 @@ import { transformReducer } from './transform/transform.reducer';
import { flowReducer } from './flow/flow.reducer';
import { controllerServicesFeatureKey, ControllerServicesState } from './controller-services';
import { controllerServicesReducer } from './controller-services/controller-services.reducer';
import { parameterFeatureKey, ParameterState } from './parameter';
import { parameterReducer } from './parameter/parameter.reducer';
export const canvasFeatureKey = 'canvas';
@ -33,13 +35,15 @@ export interface CanvasState {
[flowFeatureKey]: FlowState;
[transformFeatureKey]: CanvasTransform;
[controllerServicesFeatureKey]: ControllerServicesState;
[parameterFeatureKey]: ParameterState;
}
export function reducers(state: CanvasState | undefined, action: Action) {
return combineReducers({
[flowFeatureKey]: flowReducer,
[transformFeatureKey]: transformReducer,
[controllerServicesFeatureKey]: controllerServicesReducer
[controllerServicesFeatureKey]: controllerServicesReducer,
[parameterFeatureKey]: parameterReducer
})(state, action);
}

View File

@ -0,0 +1,25 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ParameterContextUpdateRequestEntity } from '../../../../state/shared';
export const parameterFeatureKey = 'parameter';
export interface ParameterState {
updateRequestEntity: ParameterContextUpdateRequestEntity | null;
saving: boolean;
}

View File

@ -0,0 +1,50 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { createAction, props } from '@ngrx/store';
import { PollParameterContextUpdateSuccess, SubmitParameterContextUpdate } from '../../../../state/shared';
export const parameterApiError = createAction('[Parameter] Parameter Error', props<{ error: string }>());
export const submitParameterContextUpdateRequest = createAction(
'[Parameter] Submit Parameter Context Update Request',
props<{ request: SubmitParameterContextUpdate }>()
);
export const submitParameterContextUpdateRequestSuccess = createAction(
'[Parameter] Submit Parameter Context Update Request Success',
props<{ response: PollParameterContextUpdateSuccess }>()
);
export const startPollingParameterContextUpdateRequest = createAction(
'[Parameter] Start Polling Parameter Context Update Request'
);
export const pollParameterContextUpdateRequest = createAction('[Parameter] Poll Parameter Context Update Request');
export const pollParameterContextUpdateRequestSuccess = createAction(
'[Parameter] Poll Parameter Context Update Request Success',
props<{ response: PollParameterContextUpdateSuccess }>()
);
export const stopPollingParameterContextUpdateRequest = createAction(
'[Parameter] Stop Polling Parameter Context Update Request'
);
export const deleteParameterContextUpdateRequest = createAction('[Parameter] Delete Parameter Context Update Request');
export const editParameterContextComplete = createAction('[Parameter] Edit Parameter Context Complete');

View File

@ -0,0 +1,163 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import * as ParameterActions from './parameter.actions';
import { Store } from '@ngrx/store';
import { CanvasState } from '../index';
import {
asyncScheduler,
catchError,
from,
interval,
map,
NEVER,
of,
switchMap,
takeUntil,
tap,
withLatestFrom
} from 'rxjs';
import { ParameterContextUpdateRequest } from '../../../../state/shared';
import { selectUpdateRequest } from './parameter.selectors';
import { ParameterService } from '../../service/parameter.service';
@Injectable()
export class ParameterEffects {
constructor(
private actions$: Actions,
private store: Store<CanvasState>,
private parameterService: ParameterService
) {}
submitParameterContextUpdateRequest$ = createEffect(() =>
this.actions$.pipe(
ofType(ParameterActions.submitParameterContextUpdateRequest),
map((action) => action.request),
switchMap((request) =>
from(this.parameterService.submitParameterContextUpdate(request)).pipe(
map((response) =>
ParameterActions.submitParameterContextUpdateRequestSuccess({
response: {
requestEntity: response
}
})
),
catchError((error) =>
of(
ParameterActions.parameterApiError({
error: error.error
})
)
)
)
)
)
);
submitParameterContextUpdateRequestSuccess$ = createEffect(() =>
this.actions$.pipe(
ofType(ParameterActions.submitParameterContextUpdateRequestSuccess),
map((action) => action.response),
switchMap((response) => {
const updateRequest: ParameterContextUpdateRequest = response.requestEntity.request;
if (updateRequest.complete) {
return of(ParameterActions.deleteParameterContextUpdateRequest());
} else {
return of(ParameterActions.startPollingParameterContextUpdateRequest());
}
})
)
);
startPollingParameterContextUpdateRequest$ = createEffect(() =>
this.actions$.pipe(
ofType(ParameterActions.startPollingParameterContextUpdateRequest),
switchMap(() =>
interval(2000, asyncScheduler).pipe(
takeUntil(this.actions$.pipe(ofType(ParameterActions.stopPollingParameterContextUpdateRequest)))
)
),
switchMap(() => of(ParameterActions.pollParameterContextUpdateRequest()))
)
);
pollParameterContextUpdateRequest$ = createEffect(() =>
this.actions$.pipe(
ofType(ParameterActions.pollParameterContextUpdateRequest),
withLatestFrom(this.store.select(selectUpdateRequest)),
switchMap(([action, updateRequest]) => {
if (updateRequest) {
return from(this.parameterService.pollParameterContextUpdate(updateRequest.request)).pipe(
map((response) =>
ParameterActions.pollParameterContextUpdateRequestSuccess({
response: {
requestEntity: response
}
})
),
catchError((error) =>
of(
ParameterActions.parameterApiError({
error: error.error
})
)
)
);
} else {
return NEVER;
}
})
)
);
pollParameterContextUpdateRequestSuccess$ = createEffect(() =>
this.actions$.pipe(
ofType(ParameterActions.pollParameterContextUpdateRequestSuccess),
map((action) => action.response),
switchMap((response) => {
const updateRequest: ParameterContextUpdateRequest = response.requestEntity.request;
if (updateRequest.complete) {
return of(ParameterActions.stopPollingParameterContextUpdateRequest());
} else {
return NEVER;
}
})
)
);
stopPollingParameterContextUpdateRequest$ = createEffect(() =>
this.actions$.pipe(
ofType(ParameterActions.stopPollingParameterContextUpdateRequest),
switchMap((response) => of(ParameterActions.deleteParameterContextUpdateRequest()))
)
);
deleteParameterContextUpdateRequest$ = createEffect(() =>
this.actions$.pipe(
ofType(ParameterActions.deleteParameterContextUpdateRequest),
withLatestFrom(this.store.select(selectUpdateRequest)),
tap(([action, updateRequest]) => {
if (updateRequest) {
this.parameterService.deleteParameterContextUpdate(updateRequest.request).subscribe();
}
}),
switchMap(() => of(ParameterActions.editParameterContextComplete()))
)
);
}

View File

@ -0,0 +1,47 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { createReducer, on } from '@ngrx/store';
import { ParameterState } from './index';
import {
editParameterContextComplete,
pollParameterContextUpdateRequestSuccess,
submitParameterContextUpdateRequest,
submitParameterContextUpdateRequestSuccess
} from './parameter.actions';
export const initialState: ParameterState = {
updateRequestEntity: null,
saving: false
};
export const parameterReducer = createReducer(
initialState,
on(submitParameterContextUpdateRequest, (state, { request }) => ({
...state,
saving: true
})),
on(submitParameterContextUpdateRequestSuccess, pollParameterContextUpdateRequestSuccess, (state, { response }) => ({
...state,
updateRequestEntity: response.requestEntity
})),
on(editParameterContextComplete, (state) => ({
...state,
saving: false,
updateRequestEntity: null
}))
);

View File

@ -0,0 +1,32 @@
/*
* 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 { createSelector } from '@ngrx/store';
import { CanvasState, selectCanvasState } from '../index';
import { parameterFeatureKey, ParameterState } from './index';
export const selectParameterState = createSelector(
selectCanvasState,
(state: CanvasState) => state[parameterFeatureKey]
);
export const selectUpdateRequest = createSelector(
selectParameterState,
(state: ParameterState) => state.updateRequestEntity
);
export const selectParameterSaving = createSelector(selectParameterState, (state: ParameterState) => state.saving);

View File

@ -20,6 +20,7 @@ import {
deleteComponents,
getParameterContextsAndOpenGroupComponentsDialog,
navigateToEditComponent,
navigateToEditCurrentProcessGroup,
setOperationCollapsed
} from '../../../../state/flow/flow.actions';
import { Store } from '@ngrx/store';
@ -174,15 +175,19 @@ export class OperationControl {
}
configure(selection: any): void {
const selectionData = selection.datum();
this.store.dispatch(
navigateToEditComponent({
request: {
type: selectionData.type,
id: selectionData.id
}
})
);
if (selection.empty()) {
this.store.dispatch(navigateToEditCurrentProcessGroup());
} else {
const selectionData = selection.datum();
this.store.dispatch(
navigateToEditComponent({
request: {
type: selectionData.type,
id: selectionData.id
}
})
);
}
}
canManageAccess(selection: any): boolean {

View File

@ -153,7 +153,10 @@
[createNewProperty]="createNewProperty"
[createNewService]="createNewService"
[getParameters]="getParameters"
[getServiceLink]="getServiceLink"
[goToParameter]="goToParameter"
[parameterContext]="parameterContext"
[convertToParameter]="convertToParameter"
[goToService]="goToService"
[supportsSensitiveDynamicProperties]="
request.entity.component.supportsSensitiveDynamicProperties
"></property-table>

View File

@ -30,13 +30,14 @@ import {
InlineServiceCreationRequest,
InlineServiceCreationResponse,
Parameter,
ParameterContextReferenceEntity,
Property,
SelectOption,
TextTipInput
} from '../../../../../../../state/shared';
import { Client } from '../../../../../../../service/client.service';
import { NiFiCommon } from '../../../../../../../service/nifi-common.service';
import { EditComponentDialogRequest } from '../../../../../state/flow';
import { EditComponentDialogRequest, UpdateProcessorRequest } from '../../../../../state/flow';
import { PropertyTable } from '../../../../../../../ui/common/property-table/property-table.component';
import { NifiSpinnerDirective } from '../../../../../../../ui/common/spinner/nifi-spinner.directive';
import { NifiTooltipDirective } from '../../../../../../../ui/common/tooltips/nifi-tooltip.directive';
@ -75,9 +76,12 @@ export class EditProcessor {
@Input() createNewProperty!: (existingProperties: string[], allowsSensitive: boolean) => Observable<Property>;
@Input() createNewService!: (request: InlineServiceCreationRequest) => Observable<InlineServiceCreationResponse>;
@Input() getParameters!: (sensitive: boolean) => Observable<Parameter[]>;
@Input() getServiceLink!: (serviceId: string) => Observable<string[]>;
@Input() parameterContext: ParameterContextReferenceEntity | undefined;
@Input() goToParameter!: (parameter: string) => void;
@Input() convertToParameter!: (name: string, sensitive: boolean, value: string | null) => Observable<string>;
@Input() goToService!: (serviceId: string) => void;
@Input() saving$!: Observable<boolean>;
@Output() editProcessor: EventEmitter<any> = new EventEmitter<any>();
@Output() editProcessor: EventEmitter<UpdateProcessorRequest> = new EventEmitter<UpdateProcessorRequest>();
protected readonly TextTip = TextTip;
@ -275,7 +279,7 @@ export class EditProcessor {
);
}
submitForm() {
submitForm(postUpdateNavigation?: string[]) {
const relationshipConfiguration: RelationshipConfiguration =
this.editProcessorForm.get('relationshipConfiguration')?.value;
const autoTerminated: string[] = relationshipConfiguration.relationships
@ -326,6 +330,9 @@ export class EditProcessor {
payload.component.config.retryCount = relationshipConfiguration.retryCount;
}
this.editProcessor.next(payload);
this.editProcessor.next({
postUpdateNavigation,
payload
});
}
}

View File

@ -21,12 +21,11 @@ import { HttpClient } from '@angular/common/http';
import { Client } from '../../../service/client.service';
import { NiFiCommon } from '../../../service/nifi-common.service';
import {
SubmitParameterContextUpdate,
CreateParameterContextRequest,
DeleteParameterContextRequest,
ParameterContextEntity,
ParameterContextUpdateRequest
ParameterContextEntity
} from '../state/parameter-context-listing';
import { ParameterContextUpdateRequest, SubmitParameterContextUpdate } from '../../../state/shared';
@Injectable({ providedIn: 'root' })
export class ParameterContextService {

View File

@ -18,6 +18,7 @@
import {
AffectedComponentEntity,
ParameterContextReferenceEntity,
ParameterContextUpdateRequestEntity,
ParameterEntity,
Permissions,
Revision
@ -46,15 +47,6 @@ export interface EditParameterContextRequest {
parameterContext?: ParameterContextEntity;
}
export interface SubmitParameterContextUpdate {
id: string;
payload: any;
}
export interface PollParameterContextUpdateSuccess {
requestEntity: ParameterContextUpdateRequestEntity;
}
export interface DeleteParameterContextRequest {
parameterContext: ParameterContextEntity;
}
@ -67,23 +59,6 @@ export interface SelectParameterContextRequest {
id: string;
}
export interface ParameterContextUpdateRequest {
complete: boolean;
lastUpdated: string;
percentComponent: number;
referencingComponents: AffectedComponentEntity[];
requestId: string;
state: string;
updateSteps: any[];
uri: string;
parameterContext?: any;
}
export interface ParameterContextUpdateRequestEntity {
parameterContextRevision: Revision;
request: ParameterContextUpdateRequest;
}
export interface ParameterContextEntity {
revision: Revision;
permissions: Permissions;

View File

@ -17,7 +17,6 @@
import { createAction, props } from '@ngrx/store';
import {
SubmitParameterContextUpdate,
CreateParameterContextRequest,
CreateParameterContextSuccess,
DeleteParameterContextRequest,
@ -25,9 +24,9 @@ import {
EditParameterContextRequest,
LoadParameterContextsResponse,
SelectParameterContextRequest,
PollParameterContextUpdateSuccess,
GetEffectiveParameterContext
} from './index';
import { PollParameterContextUpdateSuccess, SubmitParameterContextUpdate } from '../../../../state/shared';
export const loadParameterContexts = createAction('[Parameter Context Listing] Load Parameter Contexts');

View File

@ -41,9 +41,13 @@ import { ParameterContextService } from '../../service/parameter-contexts.servic
import { YesNoDialog } from '../../../../ui/common/yes-no-dialog/yes-no-dialog.component';
import { EditParameterContext } from '../../ui/parameter-context-listing/edit-parameter-context/edit-parameter-context.component';
import { selectParameterContexts, selectSaving, selectUpdateRequest } from './parameter-context-listing.selectors';
import { EditParameterRequest, EditParameterResponse, Parameter } from '../../../../state/shared';
import {
EditParameterRequest,
EditParameterResponse,
Parameter,
ParameterContextUpdateRequest
} from '../../../../state/shared';
import { EditParameterDialog } from '../../../../ui/common/edit-parameter-dialog/edit-parameter-dialog.component';
import { ParameterContextUpdateRequest } from './index';
import { OkDialog } from '../../../../ui/common/ok-dialog/ok-dialog.component';
@Injectable()
@ -104,7 +108,7 @@ export class ParameterContextListingEffects {
panelClass: 'medium-dialog'
});
newParameterDialogReference.componentInstance.saving = false;
newParameterDialogReference.componentInstance.saving$ = of(false);
return newParameterDialogReference.componentInstance.editParameter.pipe(
take(1),
@ -236,7 +240,7 @@ export class ParameterContextListingEffects {
panelClass: 'medium-dialog'
});
newParameterDialogReference.componentInstance.saving = false;
newParameterDialogReference.componentInstance.saving$ = of(false);
return newParameterDialogReference.componentInstance.editParameter.pipe(
take(1),
@ -263,7 +267,7 @@ export class ParameterContextListingEffects {
panelClass: 'medium-dialog'
});
editParameterDialogReference.componentInstance.saving = false;
editParameterDialogReference.componentInstance.saving$ = of(false);
return editParameterDialogReference.componentInstance.editParameter.pipe(
take(1),
@ -336,7 +340,14 @@ export class ParameterContextListingEffects {
this.actions$.pipe(
ofType(ParameterContextListingActions.submitParameterContextUpdateRequestSuccess),
map((action) => action.response),
switchMap((response) => of(ParameterContextListingActions.startPollingParameterContextUpdateRequest()))
switchMap((response) => {
const updateRequest: ParameterContextUpdateRequest = response.requestEntity.request;
if (updateRequest.complete) {
return of(ParameterContextListingActions.deleteParameterContextUpdateRequest());
} else {
return of(ParameterContextListingActions.startPollingParameterContextUpdateRequest());
}
})
)
);

View File

@ -16,7 +16,7 @@
*/
import { createReducer, on } from '@ngrx/store';
import { ParameterContextListingState, ParameterContextUpdateRequestEntity } from './index';
import { ParameterContextListingState } from './index';
import { produce } from 'immer';
import {
createParameterContext,
@ -30,7 +30,7 @@ import {
submitParameterContextUpdateRequest,
submitParameterContextUpdateRequestSuccess
} from './parameter-context-listing.actions';
import { Revision } from '../../../../state/shared';
import { ParameterContextUpdateRequestEntity, Revision } from '../../../../state/shared';
export const initialState: ParameterContextListingState = {
parameterContexts: [],
@ -74,11 +74,7 @@ export const parameterContextListingReducer = createReducer(
...state,
saving: true
})),
on(submitParameterContextUpdateRequestSuccess, (state, { response }) => ({
...state,
updateRequestEntity: response.requestEntity
})),
on(pollParameterContextUpdateRequestSuccess, (state, { response }) => ({
on(submitParameterContextUpdateRequestSuccess, pollParameterContextUpdateRequestSuccess, (state, { response }) => ({
...state,
updateRequestEntity: response.requestEntity
})),

View File

@ -25,16 +25,12 @@ import { AsyncPipe, NgForOf, NgIf } from '@angular/common';
import { MatTabsModule } from '@angular/material/tabs';
import { MatOptionModule } from '@angular/material/core';
import { MatSelectModule } from '@angular/material/select';
import { Observable, take, tap } from 'rxjs';
import {
EditParameterContextRequest,
ParameterContextEntity,
ParameterContextUpdateRequestEntity
} from '../../../state/parameter-context-listing';
import { Observable } from 'rxjs';
import { EditParameterContextRequest, ParameterContextEntity } from '../../../state/parameter-context-listing';
import { NifiSpinnerDirective } from '../../../../../ui/common/spinner/nifi-spinner.directive';
import { Client } from '../../../../../service/client.service';
import { ParameterTable } from '../parameter-table/parameter-table.component';
import { Parameter, ParameterEntity } from '../../../../../state/shared';
import { Parameter, ParameterContextUpdateRequestEntity, ParameterEntity } from '../../../../../state/shared';
import { ProcessGroupReferences } from '../process-group-references/process-group-references.component';
import { ParameterContextInheritance } from '../parameter-context-inheritance/parameter-context-inheritance.component';
import { ParameterReferences } from '../parameter-references/parameter-references.component';

View File

@ -38,11 +38,13 @@ export interface ConfigureControllerServiceRequest {
id: string;
uri: string;
payload: any;
postUpdateNavigation?: string[];
}
export interface ConfigureControllerServiceSuccess {
id: string;
controllerService: ControllerServiceEntity;
postUpdateNavigation?: string[];
}
export interface DeleteControllerServiceRequest {

View File

@ -34,7 +34,8 @@ import {
NewPropertyDialogRequest,
NewPropertyDialogResponse,
Property,
PropertyDescriptor
PropertyDescriptor,
UpdateControllerServiceRequest
} from '../../../../state/shared';
import { NewPropertyDialog } from '../../../../ui/common/new-property-dialog/new-property-dialog.component';
import { Router } from '@angular/router';
@ -182,6 +183,7 @@ export class ManagementControllerServicesEffects {
data: {
controllerService: request.controllerService
},
id: serviceId,
panelClass: 'large-dialog'
});
@ -218,8 +220,33 @@ export class ManagementControllerServicesEffects {
);
};
editDialogReference.componentInstance.getServiceLink = (serviceId: string) => {
return of(['/settings', 'management-controller-services', serviceId]);
const goTo = (commands: string[]): void => {
if (editDialogReference.componentInstance.editControllerServiceForm.dirty) {
const saveChangesDialogReference = this.dialog.open(YesNoDialog, {
data: {
title: 'Controller Service Configuration',
message: `Save changes before going to this Controller Service?`
},
panelClass: 'small-dialog'
});
saveChangesDialogReference.componentInstance.yes.pipe(take(1)).subscribe(() => {
editDialogReference.componentInstance.submitForm(commands);
});
saveChangesDialogReference.componentInstance.no.pipe(take(1)).subscribe(() => {
editDialogReference.close('ROUTED');
this.router.navigate(commands);
});
} else {
editDialogReference.close('ROUTED');
this.router.navigate(commands);
}
};
editDialogReference.componentInstance.goToService = (serviceId: string) => {
const commands: string[] = ['/settings', 'management-controller-services', serviceId];
goTo(commands);
};
editDialogReference.componentInstance.createNewService = (
@ -303,13 +330,14 @@ export class ManagementControllerServicesEffects {
editDialogReference.componentInstance.editControllerService
.pipe(takeUntil(editDialogReference.afterClosed()))
.subscribe((payload: any) => {
.subscribe((updateControllerServiceRequest: UpdateControllerServiceRequest) => {
this.store.dispatch(
ManagementControllerServicesActions.configureControllerService({
request: {
id: request.controllerService.id,
uri: request.controllerService.uri,
payload
payload: updateControllerServiceRequest.payload,
postUpdateNavigation: updateControllerServiceRequest.postUpdateNavigation
}
})
);
@ -341,7 +369,8 @@ export class ManagementControllerServicesEffects {
ManagementControllerServicesActions.configureControllerServiceSuccess({
response: {
id: request.id,
controllerService: response
controllerService: response,
postUpdateNavigation: request.postUpdateNavigation
}
})
),
@ -361,8 +390,14 @@ export class ManagementControllerServicesEffects {
() =>
this.actions$.pipe(
ofType(ManagementControllerServicesActions.configureControllerServiceSuccess),
tap(() => {
this.dialog.closeAll();
map((action) => action.response),
tap((response) => {
if (response.postUpdateNavigation) {
this.router.navigate(response.postUpdateNavigation);
this.dialog.getDialogById(response.id)?.close('ROUTED');
} else {
this.dialog.closeAll();
}
})
),
{ dispatch: false }

View File

@ -49,11 +49,13 @@ export interface EditRegistryClientRequest {
id: string;
uri: string;
payload: any;
postUpdateNavigation?: string[];
}
export interface EditRegistryClientRequestSuccess {
id: string;
registryClient: RegistryClientEntity;
postUpdateNavigation?: string[];
}
export interface DeleteRegistryClientRequest {

View File

@ -42,6 +42,7 @@ import { ExtensionTypesService } from '../../../../service/extension-types.servi
import { CreateControllerService } from '../../../../ui/common/controller-service/create-controller-service/create-controller-service.component';
import { ManagementControllerServiceService } from '../../service/management-controller-service.service';
import { Client } from '../../../../service/client.service';
import { EditRegistryClientRequest } from './index';
@Injectable()
export class RegistryClientsEffects {
@ -174,6 +175,7 @@ export class RegistryClientsEffects {
const editDialogReference = this.dialog.open(EditRegistryClient, {
data: request,
id: registryClientId,
panelClass: 'large-dialog'
});
@ -214,8 +216,30 @@ export class RegistryClientsEffects {
);
};
editDialogReference.componentInstance.getServiceLink = (serviceId: string) => {
return of(['/settings', 'management-controller-services', serviceId]);
editDialogReference.componentInstance.goToService = (serviceId: string) => {
const commands: string[] = ['/settings', 'management-controller-services', serviceId];
if (editDialogReference.componentInstance.editRegistryClientForm.dirty) {
const saveChangesDialogReference = this.dialog.open(YesNoDialog, {
data: {
title: 'Registry Client Configuration',
message: `Save changes before going to this Controller Service?`
},
panelClass: 'small-dialog'
});
saveChangesDialogReference.componentInstance.yes.pipe(take(1)).subscribe(() => {
editDialogReference.componentInstance.submitForm(commands);
});
saveChangesDialogReference.componentInstance.no.pipe(take(1)).subscribe(() => {
editDialogReference.close('ROUTED');
this.router.navigate(commands);
});
} else {
editDialogReference.close('ROUTED');
this.router.navigate(commands);
}
};
editDialogReference.componentInstance.createNewService = (
@ -292,14 +316,10 @@ export class RegistryClientsEffects {
editDialogReference.componentInstance.editRegistryClient
.pipe(takeUntil(editDialogReference.afterClosed()))
.subscribe((payload: any) => {
.subscribe((editRegistryClientRequest: EditRegistryClientRequest) => {
this.store.dispatch(
RegistryClientsActions.configureRegistryClient({
request: {
id: registryClientId,
uri: request.registryClient.uri,
payload
}
request: editRegistryClientRequest
})
);
});
@ -320,7 +340,7 @@ export class RegistryClientsEffects {
{ dispatch: false }
);
configureControllerService$ = createEffect(() =>
configureRegistryClient$ = createEffect(() =>
this.actions$.pipe(
ofType(RegistryClientsActions.configureRegistryClient),
map((action) => action.request),
@ -330,7 +350,8 @@ export class RegistryClientsEffects {
RegistryClientsActions.configureRegistryClientSuccess({
response: {
id: request.id,
registryClient: response
registryClient: response,
postUpdateNavigation: request.postUpdateNavigation
}
})
),
@ -350,8 +371,14 @@ export class RegistryClientsEffects {
() =>
this.actions$.pipe(
ofType(RegistryClientsActions.configureRegistryClientSuccess),
tap(() => {
this.dialog.closeAll();
map((action) => action.response),
tap((response) => {
if (response.postUpdateNavigation) {
this.router.navigate(response.postUpdateNavigation);
this.dialog.getDialogById(response.id)?.close('ROUTED');
} else {
this.dialog.closeAll();
}
})
),
{ dispatch: false }

View File

@ -50,7 +50,7 @@
[createNewProperty]="createNewProperty"
[createNewService]="createNewService"
[getParameters]="getParameters"
[getServiceLink]="getServiceLink"
[goToService]="goToService"
[supportsSensitiveDynamicProperties]="
request.registryClient.component.supportsSensitiveDynamicProperties
"></property-table>
@ -64,7 +64,7 @@
[disabled]="!editRegistryClientForm.dirty || editRegistryClientForm.invalid || saving.value"
type="button"
color="primary"
(click)="createRegistryClientClicked()"
(click)="submitForm()"
mat-raised-button>
<span *nifiSpinner="saving.value">Apply</span>
</button>

View File

@ -31,7 +31,7 @@ import {
Property,
TextTipInput
} from '../../../../../state/shared';
import { EditRegistryClientDialogRequest } from '../../../state/registry-clients';
import { EditRegistryClientDialogRequest, EditRegistryClientRequest } from '../../../state/registry-clients';
import { NifiSpinnerDirective } from '../../../../../ui/common/spinner/nifi-spinner.directive';
import { Client } from '../../../../../service/client.service';
import { MatSelectModule } from '@angular/material/select';
@ -66,9 +66,10 @@ export class EditRegistryClient {
@Input() createNewProperty!: (existingProperties: string[], allowsSensitive: boolean) => Observable<Property>;
@Input() createNewService!: (request: InlineServiceCreationRequest) => Observable<InlineServiceCreationResponse>;
@Input() getParameters!: (sensitive: boolean) => Observable<Parameter[]>;
@Input() getServiceLink!: (serviceId: string) => Observable<string[]>;
@Input() goToService!: (serviceId: string) => void;
@Input() saving$!: Observable<boolean>;
@Output() editRegistryClient: EventEmitter<any> = new EventEmitter<any>();
@Output() editRegistryClient: EventEmitter<EditRegistryClientRequest> =
new EventEmitter<EditRegistryClientRequest>();
protected readonly TextTip = TextTip;
@ -109,7 +110,7 @@ export class EditRegistryClient {
};
}
createRegistryClientClicked() {
submitForm(postUpdateNavigation?: string[]) {
const payload: any = {
revision: this.client.getRevision(this.request.registryClient),
component: {
@ -131,6 +132,11 @@ export class EditRegistryClient {
.map((property) => property.descriptor.name);
}
this.editRegistryClient.next(payload);
this.editRegistryClient.next({
id: this.request.registryClient.id,
uri: this.request.registryClient.uri,
payload,
postUpdateNavigation
});
}
}

View File

@ -58,6 +58,11 @@ export interface EditControllerServiceDialogRequest {
controllerService: ControllerServiceEntity;
}
export interface UpdateControllerServiceRequest {
payload: any;
postUpdateNavigation?: string[];
}
export interface ProvenanceEventSummary {
id: string;
eventId: number;
@ -253,6 +258,32 @@ export interface AffectedComponent {
validationErrors: string[];
}
export interface SubmitParameterContextUpdate {
id: string;
payload: any;
}
export interface PollParameterContextUpdateSuccess {
requestEntity: ParameterContextUpdateRequestEntity;
}
export interface ParameterContextUpdateRequest {
complete: boolean;
lastUpdated: string;
percentComponent: number;
referencingComponents: AffectedComponentEntity[];
requestId: string;
state: string;
updateSteps: any[];
uri: string;
parameterContext?: any;
}
export interface ParameterContextUpdateRequestEntity {
parameterContextRevision: Revision;
request: ParameterContextUpdateRequest;
}
export interface ElFunction {
name: string;
description: string;

View File

@ -84,7 +84,10 @@
[createNewProperty]="createNewProperty"
[createNewService]="createNewService"
[getParameters]="getParameters"
[getServiceLink]="getServiceLink"
[parameterContext]="parameterContext"
[goToParameter]="goToParameter"
[convertToParameter]="convertToParameter"
[goToService]="goToService"
[supportsSensitiveDynamicProperties]="
request.controllerService.component.supportsSensitiveDynamicProperties
"></property-table>

View File

@ -25,7 +25,9 @@ import {
InlineServiceCreationRequest,
InlineServiceCreationResponse,
Parameter,
Property
ParameterContextReferenceEntity,
Property,
UpdateControllerServiceRequest
} from '../../../../state/shared';
import { MatInputModule } from '@angular/material/input';
import { MatCheckboxModule } from '@angular/material/checkbox';
@ -68,9 +70,13 @@ export class EditControllerService {
@Input() createNewProperty!: (existingProperties: string[], allowsSensitive: boolean) => Observable<Property>;
@Input() createNewService!: (request: InlineServiceCreationRequest) => Observable<InlineServiceCreationResponse>;
@Input() getParameters!: (sensitive: boolean) => Observable<Parameter[]>;
@Input() getServiceLink!: (serviceId: string) => Observable<string[]>;
@Input() parameterContext: ParameterContextReferenceEntity | undefined;
@Input() goToParameter!: (parameter: string) => void;
@Input() convertToParameter!: (name: string, sensitive: boolean, value: string | null) => Observable<string>;
@Input() goToService!: (serviceId: string) => void;
@Input() saving$!: Observable<boolean>;
@Output() editControllerService: EventEmitter<any> = new EventEmitter<any>();
@Output() editControllerService: EventEmitter<UpdateControllerServiceRequest> =
new EventEmitter<UpdateControllerServiceRequest>();
editControllerServiceForm: FormGroup;
@ -130,7 +136,7 @@ export class EditControllerService {
return this.nifiCommon.formatBundle(entity.component.bundle);
}
submitForm() {
submitForm(postUpdateNavigation?: string[]) {
const payload: any = {
revision: this.client.getRevision(this.request.controllerService),
component: {
@ -151,6 +157,9 @@ export class EditControllerService {
.map((property) => property.descriptor.name);
}
this.editControllerService.next(payload);
this.editControllerService.next({
payload,
postUpdateNavigation
});
}
}

View File

@ -48,14 +48,14 @@
</mat-form-field>
</div>
</mat-dialog-content>
<mat-dialog-actions align="end">
<mat-dialog-actions align="end" *ngIf="{ value: (saving$ | async)! } as saving">
<button mat-raised-button mat-dialog-close color="accent">Cancel</button>
<button
mat-raised-button
[disabled]="!editParameterForm.dirty || editParameterForm.invalid || saving"
(click)="addProperty()"
[disabled]="editParameterForm.invalid || saving.value"
(click)="okClicked()"
color="primary">
<span *nifiSpinner="saving">Ok</span>
<span *nifiSpinner="saving.value">Ok</span>
</button>
</mat-dialog-actions>
</form>

View File

@ -35,7 +35,8 @@ import { MatInputModule } from '@angular/material/input';
import { MatRadioModule } from '@angular/material/radio';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { NifiSpinnerDirective } from '../spinner/nifi-spinner.directive';
import { NgIf } from '@angular/common';
import { AsyncPipe, NgIf } from '@angular/common';
import { Observable } from 'rxjs';
@Component({
selector: 'edit-parameter-dialog',
@ -50,16 +51,19 @@ import { NgIf } from '@angular/common';
MatRadioModule,
MatCheckboxModule,
NifiSpinnerDirective,
NgIf
NgIf,
AsyncPipe
],
templateUrl: './edit-parameter-dialog.component.html',
styleUrls: ['./edit-parameter-dialog.component.scss']
})
export class EditParameterDialog {
@Input() saving!: boolean;
@Input() saving$!: Observable<boolean>;
@Output() editParameter: EventEmitter<EditParameterResponse> = new EventEmitter<EditParameterResponse>();
@Output() cancel: EventEmitter<void> = new EventEmitter<void>();
name: FormControl;
sensitive: FormControl;
editParameterForm: FormGroup;
isNew: boolean;
@ -67,38 +71,48 @@ export class EditParameterDialog {
@Inject(MAT_DIALOG_DATA) public request: EditParameterRequest,
private formBuilder: FormBuilder
) {
// get the optional parameter. when existingParameters are specified this parameter is used to
// seed the form for the new parameter. when existingParameters are not specified, this is the
// existing parameter that populates the form
const parameter: Parameter | undefined = request.parameter;
if (parameter) {
this.isNew = false;
// in edit scenarios the existing parameters shouldn't be enforced since the parameter does exist
this.name = new FormControl({ value: parameter.name, disabled: true }, Validators.required);
this.editParameterForm = this.formBuilder.group({
name: this.name,
value: new FormControl(parameter.value),
empty: new FormControl(parameter.value == ''),
sensitive: new FormControl({ value: parameter.sensitive, disabled: true }, Validators.required),
description: new FormControl(parameter.description)
});
} else {
const validators: any[] = [Validators.required];
if (request.existingParameters) {
this.isNew = true;
const validators: any[] = [Validators.required];
if (request.existingParameters) {
validators.push(this.existingParameterValidator(request.existingParameters));
}
this.name = new FormControl('', validators);
// since there were existing parameters in the request, add the existing parameters validator because
// parameters names must be unique
validators.push(this.existingParameterValidator(request.existingParameters));
this.editParameterForm = this.formBuilder.group({
name: this.name,
value: new FormControl(''),
empty: new FormControl(false),
sensitive: new FormControl({ value: false, disabled: false }, Validators.required),
description: new FormControl('')
});
this.name = new FormControl(parameter ? parameter.name : '', validators);
// when seeding a new parameter with a sensitivity flag do not allow it to be changed
const disableSensitive: boolean = parameter != null;
this.sensitive = new FormControl(
{ value: parameter ? parameter.sensitive : false, disabled: disableSensitive },
Validators.required
);
} else {
this.isNew = false;
// without existingParameters, we are editing an existing parameter. in this case the name and sensitivity cannot be modified
this.name = new FormControl(
{ value: parameter ? parameter.name : '', disabled: true },
Validators.required
);
this.sensitive = new FormControl(
{ value: parameter ? parameter.sensitive : false, disabled: true },
Validators.required
);
}
this.editParameterForm = this.formBuilder.group({
name: this.name,
value: new FormControl(parameter ? parameter.value : ''),
empty: new FormControl(parameter ? parameter.value == '' : false),
sensitive: this.sensitive,
description: new FormControl(parameter ? parameter.description : '')
});
}
private existingParameterValidator(existingParameters: string[]): ValidatorFn {
@ -136,7 +150,11 @@ export class EditParameterDialog {
}
}
addProperty(): void {
cancelClicked(): void {
this.cancel.next();
}
okClicked(): void {
const value: string = this.editParameterForm.get('value')?.value;
const empty: boolean = this.editParameterForm.get('empty')?.value;

View File

@ -103,19 +103,26 @@
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let item">
<div class="flex items-center gap-x-3">
<!-- TODO - convert to parameter, go to parameter -->
<div
class="pointer fa fa-plus"
*ngIf="item.descriptor.identifiesControllerService"
(click)="createNewControllerService(item)"
title="Create new service"></div>
<!-- TODO - need to prevent navigation if outstanding changes -->
<div
class="pointer fa fa-level-up"
*ngIf="canConvertToParameter(item)"
(click)="convertToParameterClicked(item)"
title="Convert to Parameter"></div>
<div
class="pointer fa fa-long-arrow-right"
*ngIf="item.serviceLink"
[routerLink]="item.serviceLink"
mat-dialog-close="ROUTED"
title="Go to"></div>
*ngIf="canGoToParameter(item)"
(click)="goToParameterClicked(item)"
title="Go to Parameter"></div>
<div
class="pointer fa fa-long-arrow-right"
*ngIf="canGoToService(item)"
(click)="goToServiceClicked(item)"
title="Go to Service"></div>
<div
class="pointer fa fa-trash"
*ngIf="item.type == 'userDefined'"

View File

@ -37,6 +37,7 @@ import {
InlineServiceCreationRequest,
InlineServiceCreationResponse,
Parameter,
ParameterContextReferenceEntity,
Property,
PropertyDependency,
PropertyDescriptor,
@ -100,9 +101,14 @@ export class PropertyTable implements AfterViewInit, ControlValueAccessor {
@Input() createNewProperty!: (existingProperties: string[], allowsSensitive: boolean) => Observable<Property>;
@Input() createNewService!: (request: InlineServiceCreationRequest) => Observable<InlineServiceCreationResponse>;
@Input() getParameters!: (sensitive: boolean) => Observable<Parameter[]>;
@Input() getServiceLink!: (serviceId: string) => Observable<string[]>;
@Input() parameterContext: ParameterContextReferenceEntity | undefined;
@Input() goToParameter!: (parameter: string) => void;
@Input() convertToParameter!: (name: string, sensitive: boolean, value: string | null) => Observable<string>;
@Input() goToService!: (serviceId: string) => void;
@Input() supportsSensitiveDynamicProperties: boolean = false;
private static readonly PARAM_REF_REGEX: RegExp = /#{[a-zA-Z0-9-_. ]+}/;
private destroyRef = inject(DestroyRef);
protected readonly NfEditor = NfEditor;
@ -264,8 +270,6 @@ export class PropertyTable implements AfterViewInit, ControlValueAccessor {
: 'optional'
};
this.populateServiceLink(item);
// store the property item in a map for an efficient lookup later
this.itemLookup.set(property.property, item);
return item;
@ -274,16 +278,6 @@ export class PropertyTable implements AfterViewInit, ControlValueAccessor {
this.setPropertyItems(propertyItems);
}
private populateServiceLink(item: PropertyItem): void {
if (this.canGoTo(item) && item.value) {
this.getServiceLink(item.value)
.pipe(take(1))
.subscribe((serviceLink: string[]) => {
item.serviceLink = serviceLink;
});
}
}
private setPropertyItems(propertyItems: PropertyItem[]): void {
this.dataSource = new MatTableDataSource<PropertyItem>(propertyItems);
this.initFilter();
@ -311,8 +305,6 @@ export class PropertyTable implements AfterViewInit, ControlValueAccessor {
: 'optional'
};
this.populateServiceLink(item);
this.itemLookup.set(property.property, item);
const propertyItems: PropertyItem[] = [...currentPropertyItems, item];
@ -388,7 +380,7 @@ export class PropertyTable implements AfterViewInit, ControlValueAccessor {
this.editorOpen = true;
}
canGoTo(item: PropertyItem): boolean {
canGoToService(item: PropertyItem): boolean {
// TODO - add Input() for supportsGoTo? currently only false in summary table
const descriptor: PropertyDescriptor = item.descriptor;
@ -401,6 +393,11 @@ export class PropertyTable implements AfterViewInit, ControlValueAccessor {
return false;
}
goToServiceClicked(item: PropertyItem): void {
// @ts-ignore
this.goToService(item.value);
}
createNewControllerService(item: PropertyItem): void {
this.createNewService({ descriptor: item.descriptor })
.pipe(take(1))
@ -413,6 +410,48 @@ export class PropertyTable implements AfterViewInit, ControlValueAccessor {
});
}
canGoToParameter(item: PropertyItem): boolean {
// TODO - currently parameter context route does not support navigating
// directly to a specific parameter so the parameter context link
// is not item specific.
if (this.parameterContext && item.value) {
return this.parameterContext.permissions.canRead && PropertyTable.PARAM_REF_REGEX.test(item.value);
}
return false;
}
goToParameterClicked(item: PropertyItem): void {
// @ts-ignore
this.goToParameter(item.value);
}
canConvertToParameter(item: PropertyItem): boolean {
let canUpdateParameterContext: boolean = false;
if (this.parameterContext) {
canUpdateParameterContext =
this.parameterContext.permissions.canRead && this.parameterContext.permissions.canWrite;
}
let propertyReferencesParameter: boolean = false;
if (canUpdateParameterContext && item.value) {
propertyReferencesParameter = PropertyTable.PARAM_REF_REGEX.test(item.value);
}
return canUpdateParameterContext && !propertyReferencesParameter;
}
convertToParameterClicked(item: PropertyItem): void {
this.convertToParameter(item.property, item.descriptor.sensitive, item.value)
.pipe(take(1))
.subscribe((propertyValue) => {
item.value = propertyValue;
item.dirty = true;
this.handleChanged();
});
}
deleteProperty(item: PropertyItem): void {
if (!item.deleted) {
item.value = null;
@ -428,8 +467,6 @@ export class PropertyTable implements AfterViewInit, ControlValueAccessor {
item.value = newValue;
item.dirty = true;
this.populateServiceLink(item);
this.handleChanged();
}

View File

@ -20,6 +20,6 @@
<div class="text-sm" data-qa="yes-no-message">{{ request.message }}</div>
</mat-dialog-content>
<mat-dialog-actions align="end">
<button mat-raised-button mat-dialog-close color="accent">No</button>
<button mat-raised-button mat-dialog-close color="accent" (click)="noClicked()">No</button>
<button mat-raised-button mat-dialog-close cdkFocusInitial color="primary" (click)="yesClicked()">Yes</button>
</mat-dialog-actions>

View File

@ -29,10 +29,15 @@ import { MatButtonModule } from '@angular/material/button';
})
export class YesNoDialog {
@Output() yes: EventEmitter<void> = new EventEmitter<void>();
@Output() no: EventEmitter<void> = new EventEmitter<void>();
constructor(@Inject(MAT_DIALOG_DATA) public request: YesNoDialogRequest) {}
yesClicked(): void {
this.yes.next();
}
noClicked(): void {
this.no.next();
}
}