NIFI-13119: When evaluating dependent Properties, the UI should identify when the Property value is a parameter reference and resolve the value accordingly (#8724)

* NIFI-13119:
- When evaluating dependent Properties, the UI should identify when the Property value is a parameter reference and resolve the value accordingly.

* NIFI-13119:
- Requiring a value to be present when showing dependent property that doesn't require any specific value.

* NIFI-13119:
- Using error helper to get error string.

* NIFI-13119:
- Handle convert to parameter error scenario.

This closes #8724
This commit is contained in:
Matt Gilman 2024-05-03 08:04:44 -04:00 committed by GitHub
parent b608e5a2f0
commit 160c7ae24b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 303 additions and 230 deletions

View File

@ -17,23 +17,27 @@
import { Injectable } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { catchError, EMPTY, filter, map, Observable, switchMap, take, takeUntil, tap } from 'rxjs';
import { catchError, EMPTY, filter, map, Observable, switchMap, 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 { EditParameterRequest, EditParameterResponse, ParameterContext, 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';
import { MEDIUM_DIALOG } from '../../../index';
import { ClusterConnectionService } from '../../../service/cluster-connection.service';
import { ErrorHelper } from '../../../service/error-helper.service';
export interface ConvertToParameterResponse {
propertyValue: string;
parameterContext?: ParameterContext;
}
@Injectable({
providedIn: 'root'
})
@ -41,40 +45,12 @@ export class ParameterHelperService {
constructor(
private dialog: MatDialog,
private store: Store<NiFiState>,
private flowService: FlowService,
private parameterService: ParameterService,
private clusterConnectionService: ClusterConnectionService,
private client: Client,
private errorHelper: ErrorHelper
) {}
/**
* 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: this.errorHelper.getErrorString(errorResponse) })
);
// 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.
*
@ -82,7 +58,7 @@ export class ParameterHelperService {
*/
convertToParameter(
parameterContextId: string
): (name: string, sensitive: boolean, value: string | null) => Observable<string> {
): (name: string, sensitive: boolean, value: string | null) => Observable<ConvertToParameterResponse> {
return (name: string, sensitive: boolean, value: string | null) => {
return this.parameterService.getParameterContext(parameterContextId, false).pipe(
catchError((errorResponse: HttpErrorResponse) => {
@ -127,6 +103,7 @@ export class ParameterHelperService {
request: {
id: parameterContextId,
payload: {
id: parameterContextEntity.id,
revision: this.client.getRevision(parameterContextEntity),
disconnectedNodeAcknowledged:
this.clusterConnectionService.isDisconnectionAcknowledged(),
@ -139,6 +116,8 @@ export class ParameterHelperService {
})
);
let parameterContext: ParameterContext;
return this.store.select(selectParameterState).pipe(
takeUntil(convertToParameterDialogReference.afterClosed()),
tap((parameterState: ParameterState) => {
@ -146,12 +125,24 @@ export class ParameterHelperService {
// if the convert to parameter sequence stores an error,
// throw it to avoid the completion mapping logic below
throw new Error(parameterState.error);
} else if (parameterState.updateRequestEntity?.request.failureReason) {
// if the convert to parameter sequence completes successfully
// with an error, throw the message
throw new Error(parameterState.updateRequestEntity?.request.failureReason);
}
if (parameterState.saving) {
parameterContext = parameterState.updateRequestEntity?.request.parameterContext;
}
}),
filter((parameterState: ParameterState) => !parameterState.saving),
filter((parameterState) => !parameterState.saving),
map(() => {
convertToParameterDialogReference.close();
return `#{${dialogResponse.parameter.name}}`;
return {
propertyValue: `#{${dialogResponse.parameter.name}}`,
parameterContext
} as ConvertToParameterResponse;
}),
catchError((error) => {
convertToParameterDialogReference.close();

View File

@ -53,6 +53,7 @@ import { ParameterHelperService } from '../../service/parameter-helper.service';
import { LARGE_DIALOG, SMALL_DIALOG, XL_DIALOG } from '../../../../index';
import { ExtensionTypesService } from '../../../../service/extension-types.service';
import { ChangeComponentVersionDialog } from '../../../../ui/common/change-component-version-dialog/change-component-version-dialog';
import { FlowService } from '../../service/flow.service';
@Injectable()
export class ControllerServicesEffects {
@ -61,6 +62,7 @@ export class ControllerServicesEffects {
private store: Store<NiFiState>,
private client: Client,
private controllerServiceService: ControllerServiceService,
private flowService: FlowService,
private errorHelper: ErrorHelper,
private dialog: MatDialog,
private router: Router,
@ -236,6 +238,34 @@ export class ControllerServicesEffects {
this.store.select(selectParameterContext),
this.store.select(selectCurrentProcessGroupId)
]),
switchMap(([request, parameterContextReference, processGroupId]) => {
if (parameterContextReference && parameterContextReference.permissions.canRead) {
return from(this.flowService.getParameterContext(parameterContextReference.id)).pipe(
map((parameterContext) => {
return [request, parameterContext, processGroupId];
}),
tap({
error: (errorResponse: HttpErrorResponse) => {
this.store.dispatch(
ControllerServicesActions.selectControllerService({
request: {
processGroupId,
id: request.id
}
})
);
this.store.dispatch(
ErrorActions.snackBarError({
error: this.errorHelper.getErrorString(errorResponse)
})
);
}
})
);
}
return of([request, null, processGroupId]);
}),
tap(([request, parameterContext, processGroupId]) => {
const serviceId: string = request.id;
@ -282,10 +312,6 @@ export class ControllerServicesEffects {
};
if (parameterContext != null) {
editDialogReference.componentInstance.getParameters = this.parameterHelperService.getParameters(
parameterContext.id
);
editDialogReference.componentInstance.parameterContext = parameterContext;
editDialogReference.componentInstance.goToParameter = () => {
const commands: string[] = ['/parameter-contexts', parameterContext.id];

View File

@ -1200,6 +1200,34 @@ export class FlowEffects {
this.store.select(selectCurrentParameterContext),
this.store.select(selectCurrentProcessGroupId)
]),
switchMap(([request, parameterContextReference, processGroupId]) => {
if (parameterContextReference && parameterContextReference.permissions.canRead) {
return from(this.flowService.getParameterContext(parameterContextReference.id)).pipe(
map((parameterContext) => {
return [request, parameterContext, processGroupId];
}),
tap({
error: (errorResponse: HttpErrorResponse) => {
this.store.dispatch(
FlowActions.selectComponents({
request: {
components: [
{
id: request.entity.id,
componentType: request.type
}
]
}
})
);
this.store.dispatch(this.snackBarOrFullScreenError(errorResponse));
}
})
);
}
return of([request, null, processGroupId]);
}),
tap(([request, parameterContext, processGroupId]) => {
const processorId: string = request.entity.id;
@ -1239,10 +1267,6 @@ export class FlowEffects {
};
if (parameterContext != null) {
editDialogReference.componentInstance.getParameters = this.parameterHelperService.getParameters(
parameterContext.id
);
editDialogReference.componentInstance.parameterContext = parameterContext;
editDialogReference.componentInstance.goToParameter = () => {
const commands: string[] = ['/parameter-contexts', parameterContext.id];

View File

@ -22,6 +22,7 @@ import {
ComponentHistory,
ComponentType,
DocumentedType,
ParameterContextEntity,
ParameterContextReferenceEntity,
Permissions,
RegistryClientEntity,
@ -30,7 +31,6 @@ import {
SparseVersionedFlow,
VersionedFlowSnapshotMetadataEntity
} from '../../../../state/shared';
import { ParameterContextEntity } from '../../../parameter-contexts/state/parameter-context-listing';
import { HttpErrorResponse } from '@angular/common/http';
export const flowFeatureKey = 'flowState';

View File

@ -26,13 +26,12 @@ import { MatTabsModule } from '@angular/material/tabs';
import { MatOptionModule } from '@angular/material/core';
import { MatSelectModule } from '@angular/material/select';
import { Observable } from 'rxjs';
import { SelectOption } from '../../../../../../../state/shared';
import { ParameterContextEntity, SelectOption } from '../../../../../../../state/shared';
import { Client } from '../../../../../../../service/client.service';
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';
import { TextTip } from '../../../../../../../ui/common/tooltips/text-tip/text-tip.component';
import { ParameterContextEntity } from '../../../../../../parameter-contexts/state/parameter-context-listing';
import { ControllerServiceTable } from '../../../../../../../ui/common/controller-service/controller-service-table/controller-service-table.component';
import { EditComponentDialogRequest } from '../../../../../state/flow';
import { ClusterConnectionService } from '../../../../../../../service/cluster-connection.service';

View File

@ -237,7 +237,6 @@
formControlName="properties"
[createNewProperty]="createNewProperty"
[createNewService]="createNewService"
[getParameters]="getParameters"
[goToParameter]="goToParameter"
[parameterContext]="parameterContext"
[convertToParameter]="convertToParameter"

View File

@ -29,8 +29,7 @@ import { Observable } from 'rxjs';
import {
InlineServiceCreationRequest,
InlineServiceCreationResponse,
Parameter,
ParameterContextReferenceEntity,
ParameterContextEntity,
Property,
SelectOption
} from '../../../../../../../state/shared';
@ -49,6 +48,7 @@ import {
import { ErrorBanner } from '../../../../../../../ui/common/error-banner/error-banner.component';
import { ClusterConnectionService } from '../../../../../../../service/cluster-connection.service';
import { CanvasUtils } from '../../../../../service/canvas-utils.service';
import { ConvertToParameterResponse } from '../../../../../service/parameter-helper.service';
@Component({
selector: 'edit-processor',
@ -76,10 +76,13 @@ import { CanvasUtils } from '../../../../../service/canvas-utils.service';
export class EditProcessor {
@Input() createNewProperty!: (existingProperties: string[], allowsSensitive: boolean) => Observable<Property>;
@Input() createNewService!: (request: InlineServiceCreationRequest) => Observable<InlineServiceCreationResponse>;
@Input() getParameters!: (sensitive: boolean) => Observable<Parameter[]>;
@Input() parameterContext: ParameterContextReferenceEntity | undefined;
@Input() parameterContext: ParameterContextEntity | undefined;
@Input() goToParameter!: (parameter: string) => void;
@Input() convertToParameter!: (name: string, sensitive: boolean, value: string | null) => Observable<string>;
@Input() convertToParameter!: (
name: string,
sensitive: boolean,
value: string | null
) => Observable<ConvertToParameterResponse>;
@Input() goToService!: (serviceId: string) => void;
@Input() saving$!: Observable<boolean>;
@Output() editProcessor: EventEmitter<UpdateProcessorRequest> = new EventEmitter<UpdateProcessorRequest>();

View File

@ -20,12 +20,12 @@ import { Observable } from 'rxjs';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Client } from '../../../service/client.service';
import { NiFiCommon } from '../../../service/nifi-common.service';
import { CreateParameterContextRequest, DeleteParameterContextRequest } from '../state/parameter-context-listing';
import {
CreateParameterContextRequest,
DeleteParameterContextRequest,
ParameterContextEntity
} from '../state/parameter-context-listing';
import { ParameterContextUpdateRequest, SubmitParameterContextUpdate } from '../../../state/shared';
ParameterContextEntity,
ParameterContextUpdateRequest,
SubmitParameterContextUpdate
} from '../../../state/shared';
import { ClusterConnectionService } from '../../../service/cluster-connection.service';
@Injectable({ providedIn: 'root' })

View File

@ -15,14 +15,7 @@
* limitations under the License.
*/
import {
ParameterContextReferenceEntity,
ParameterContextUpdateRequestEntity,
ParameterEntity,
ParameterProviderConfigurationEntity,
Permissions,
Revision
} from '../../../../state/shared';
import { ParameterContextEntity, ParameterContextUpdateRequestEntity } from '../../../../state/shared';
export const parameterContextListingFeatureKey = 'parameterContextListing';
@ -59,31 +52,6 @@ export interface SelectParameterContextRequest {
id: string;
}
export interface ParameterContextEntity {
revision: Revision;
permissions: Permissions;
id: string;
uri: string;
component: ParameterContext;
}
export interface ParameterContext {
id: string;
name: string;
description: string;
parameters: ParameterEntity[];
boundProcessGroups: BoundProcessGroup[];
inheritedParameterContexts: ParameterContextReferenceEntity[];
parameterProviderConfiguration?: ParameterProviderConfigurationEntity;
}
// TODO - Replace this with ProcessGroupEntity was available
export interface BoundProcessGroup {
permissions: Permissions;
id: string;
component: any;
}
export interface ParameterContextListingState {
parameterContexts: ParameterContextEntity[];
updateRequestEntity: ParameterContextUpdateRequestEntity | null;

View File

@ -17,8 +17,9 @@
import { createSelector } from '@ngrx/store';
import { ParameterContextsState, selectParameterContextState } from '../index';
import { ParameterContextEntity, parameterContextListingFeatureKey, ParameterContextListingState } from './index';
import { parameterContextListingFeatureKey, ParameterContextListingState } from './index';
import { selectCurrentRoute } from '../../../../state/router/router.selectors';
import { ParameterContextEntity } from '../../../../state/shared';
export const selectParameterContextListingState = createSelector(
selectParameterContextState,

View File

@ -18,13 +18,14 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { EditParameterContext } from './edit-parameter-context.component';
import { EditParameterContextRequest, ParameterContextEntity } from '../../../state/parameter-context-listing';
import { EditParameterContextRequest } from '../../../state/parameter-context-listing';
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
import { of } from 'rxjs';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { provideMockStore } from '@ngrx/store/testing';
import { initialState } from '../../../state/parameter-context-listing/parameter-context-listing.reducer';
import { ClusterConnectionService } from '../../../../../service/cluster-connection.service';
import { ParameterContextEntity } from '../../../../../state/shared';
describe('EditParameterContext', () => {
let component: EditParameterContext;

View File

@ -26,12 +26,13 @@ import { MatTabsModule } from '@angular/material/tabs';
import { MatOptionModule } from '@angular/material/core';
import { MatSelectModule } from '@angular/material/select';
import { Observable } from 'rxjs';
import { EditParameterContextRequest, ParameterContextEntity } from '../../../state/parameter-context-listing';
import { EditParameterContextRequest } 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,
ParameterContextEntity,
ParameterContextUpdateRequestEntity,
ParameterEntity,
ParameterProviderConfiguration

View File

@ -24,11 +24,10 @@ import { AsyncPipe, NgTemplateOutlet } from '@angular/common';
import { CdkConnectedOverlay, CdkOverlayOrigin } from '@angular/cdk/overlay';
import { RouterLink } from '@angular/router';
import { NiFiCommon } from '../../../../../service/nifi-common.service';
import { ParameterContextReferenceEntity } from '../../../../../state/shared';
import { ParameterContextEntity, ParameterContextReferenceEntity } from '../../../../../state/shared';
import { NifiTooltipDirective } from '../../../../../ui/common/tooltips/nifi-tooltip.directive';
import { TextTip } from '../../../../../ui/common/tooltips/text-tip/text-tip.component';
import { ParameterReferences } from '../../../../../ui/common/parameter-references/parameter-references.component';
import { ParameterContextEntity } from '../../../state/parameter-context-listing';
import {
DragDropModule,
CdkDrag,

View File

@ -17,7 +17,7 @@
import { Component, OnInit } from '@angular/core';
import { Store } from '@ngrx/store';
import { ParameterContextEntity, ParameterContextListingState } from '../../state/parameter-context-listing';
import { ParameterContextListingState } from '../../state/parameter-context-listing';
import {
selectContext,
selectParameterContextIdFromRoute,
@ -37,6 +37,7 @@ import { filter, switchMap, take } from 'rxjs';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { selectCurrentUser } from '../../../../state/current-user/current-user.selectors';
import { selectFlowConfiguration } from '../../../../state/flow-configuration/flow-configuration.selectors';
import { ParameterContextEntity } from '../../../../state/shared';
@Component({
selector: 'parameter-context-listing',

View File

@ -19,9 +19,9 @@ import { Component, EventEmitter, Input, Output } from '@angular/core';
import { MatTableDataSource } from '@angular/material/table';
import { Sort } from '@angular/material/sort';
import { NiFiCommon } from '../../../../../service/nifi-common.service';
import { ParameterContextEntity } from '../../../state/parameter-context-listing';
import { FlowConfiguration } from '../../../../../state/flow-configuration';
import { CurrentUser } from '../../../../../state/current-user';
import { ParameterContextEntity } from '../../../../../state/shared';
@Component({
selector: 'parameter-context-table',

View File

@ -23,7 +23,7 @@ import { NgClass, NgTemplateOutlet } from '@angular/common';
import { RouterLink } from '@angular/router';
import { MatDialogModule } from '@angular/material/dialog';
import { NifiTooltipDirective } from '../../../../../ui/common/tooltips/nifi-tooltip.directive';
import { BoundProcessGroup } from '../../../state/parameter-context-listing';
import { BoundProcessGroup } from '../../../../../state/shared';
@Component({
selector: 'process-group-references',

View File

@ -18,6 +18,9 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RouteNotFound } from './route-not-found.component';
import { PageContent } from '../../../ui/common/page-content/page-content.component';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { RouterTestingModule } from '@angular/router/testing';
describe('RouteNotFound', () => {
let component: RouteNotFound;
@ -25,7 +28,8 @@ describe('RouteNotFound', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [RouteNotFound]
declarations: [RouteNotFound],
imports: [PageContent, HttpClientTestingModule, RouterTestingModule]
}).compileComponents();
fixture = TestBed.createComponent(RouteNotFound);

View File

@ -61,7 +61,6 @@
formControlName="properties"
[createNewProperty]="createNewProperty"
[createNewService]="createNewService"
[getParameters]="getParameters"
[goToService]="goToService"
[supportsSensitiveDynamicProperties]="
request.registryClient.component.supportsSensitiveDynamicProperties

View File

@ -26,7 +26,6 @@ import { Observable } from 'rxjs';
import {
InlineServiceCreationRequest,
InlineServiceCreationResponse,
Parameter,
Property,
RegistryClientEntity
} from '../../../../../state/shared';
@ -65,7 +64,6 @@ import { ClusterConnectionService } from '../../../../../service/cluster-connect
export class EditRegistryClient {
@Input() createNewProperty!: (existingProperties: string[], allowsSensitive: boolean) => Observable<Property>;
@Input() createNewService!: (request: InlineServiceCreationRequest) => Observable<InlineServiceCreationResponse>;
@Input() getParameters!: (sensitive: boolean) => Observable<Parameter[]>;
@Input() goToService!: (serviceId: string) => void;
@Input() saving$!: Observable<boolean>;
@Output() editRegistryClient: EventEmitter<EditRegistryClientRequest> =

View File

@ -22,6 +22,7 @@ import { NiFiState } from '../../state';
import { Store } from '@ngrx/store';
import { stopCurrentUserPolling } from '../../state/current-user/current-user.actions';
import { stopProcessGroupPolling } from '../../pages/flow-designer/state/flow/flow.actions';
import { stopClusterSummaryPolling } from '../../state/cluster-summary/cluster-summary.actions';
@Injectable({
providedIn: 'root'
@ -36,6 +37,7 @@ export class PollingInterceptor implements HttpInterceptor {
if (error instanceof HttpErrorResponse && error.status === 0) {
this.store.dispatch(stopCurrentUserPolling());
this.store.dispatch(stopProcessGroupPolling());
this.store.dispatch(stopClusterSummaryPolling());
}
}
})

View File

@ -337,6 +337,31 @@ export interface Parameter {
inherited?: boolean;
}
export interface ParameterContextEntity {
revision: Revision;
permissions: Permissions;
id: string;
uri: string;
component: ParameterContext;
}
export interface ParameterContext {
id: string;
name: string;
description: string;
parameters: ParameterEntity[];
boundProcessGroups: BoundProcessGroup[];
inheritedParameterContexts: ParameterContextReferenceEntity[];
parameterProviderConfiguration?: ParameterProviderConfigurationEntity;
}
// TODO - Replace this with ProcessGroupEntity was available
export interface BoundProcessGroup {
permissions: Permissions;
id: string;
component: any;
}
export interface ParameterContextReferenceEntity {
permissions: Permissions;
id: string;
@ -388,6 +413,7 @@ export interface ParameterContextUpdateRequest {
updateSteps: any[];
uri: string;
parameterContext?: any;
failureReason?: string;
}
export interface ParameterContextUpdateRequestEntity {

View File

@ -120,7 +120,6 @@
formControlName="properties"
[createNewProperty]="createNewProperty"
[createNewService]="createNewService"
[getParameters]="getParameters"
[parameterContext]="parameterContext"
[goToParameter]="goToParameter"
[convertToParameter]="convertToParameter"

View File

@ -25,8 +25,7 @@ import {
EditControllerServiceDialogRequest,
InlineServiceCreationRequest,
InlineServiceCreationResponse,
Parameter,
ParameterContextReferenceEntity,
ParameterContextEntity,
Property,
UpdateControllerServiceRequest
} from '../../../../state/shared';
@ -47,6 +46,7 @@ import { ErrorBanner } from '../../error-banner/error-banner.component';
import { ClusterConnectionService } from '../../../../service/cluster-connection.service';
import { TextTip } from '../../tooltips/text-tip/text-tip.component';
import { NifiTooltipDirective } from '../../tooltips/nifi-tooltip.directive';
import { ConvertToParameterResponse } from '../../../../pages/flow-designer/service/parameter-helper.service';
@Component({
selector: 'edit-controller-service',
@ -74,10 +74,13 @@ import { NifiTooltipDirective } from '../../tooltips/nifi-tooltip.directive';
export class EditControllerService {
@Input() createNewProperty!: (existingProperties: string[], allowsSensitive: boolean) => Observable<Property>;
@Input() createNewService!: (request: InlineServiceCreationRequest) => Observable<InlineServiceCreationResponse>;
@Input() getParameters!: (sensitive: boolean) => Observable<Parameter[]>;
@Input() parameterContext: ParameterContextReferenceEntity | undefined;
@Input() parameterContext: ParameterContextEntity | undefined;
@Input() goToParameter!: (parameter: string) => void;
@Input() convertToParameter!: (name: string, sensitive: boolean, value: string | null) => Observable<string>;
@Input() convertToParameter!: (
name: string,
sensitive: boolean,
value: string | null
) => Observable<ConvertToParameterResponse>;
@Input() goToService!: (serviceId: string) => void;
@Input() goToReferencingComponent!: (component: ControllerServiceReferencingComponent) => void;
@Input() saving$!: Observable<boolean>;

View File

@ -63,51 +63,46 @@
</mat-form-field>
</div>
<div *ngIf="showParameterAllowableValues">
<div *ngIf="!parametersLoaded; else showParameters">
<ngx-skeleton-loader count="1"></ngx-skeleton-loader>
</div>
<ng-template #showParameters>
<mat-form-field>
<mat-label>Parameter</mat-label>
<mat-select
formControlName="parameterReference"
[panelClass]="'combo-panel'"
(mousedown)="preventDrag($event)">
<ng-container *ngFor="let parameterAllowableValue of parameterAllowableValues">
<ng-container *ngIf="parameterAllowableValue.description; else noDescription">
<mat-option
[value]="parameterAllowableValue.id"
[disabled]="readonly"
(mousedown)="preventDrag($event)"
nifiTooltip
[tooltipComponentType]="TextTip"
[tooltipInputData]="parameterAllowableValue.description"
[delayClose]="false">
<span
class="option-text"
[class.unset]="parameterAllowableValue.value == null"
[class.surface-color]="parameterAllowableValue.value == null"
>{{ parameterAllowableValue.displayName }}</span
>
</mat-option>
</ng-container>
<ng-template #noDescription>
<mat-option
[value]="parameterAllowableValue.id"
[disabled]="readonly"
(mousedown)="preventDrag($event)">
<span
class="option-text"
[class.unset]="parameterAllowableValue.value == null"
[class.surface-color]="parameterAllowableValue.value == null"
>{{ parameterAllowableValue.displayName }}</span
>
</mat-option>
</ng-template>
<mat-form-field>
<mat-label>Parameter</mat-label>
<mat-select
formControlName="parameterReference"
[panelClass]="'combo-panel'"
(mousedown)="preventDrag($event)">
<ng-container *ngFor="let parameterAllowableValue of parameterAllowableValues">
<ng-container *ngIf="parameterAllowableValue.description; else noDescription">
<mat-option
[value]="parameterAllowableValue.id"
[disabled]="readonly"
(mousedown)="preventDrag($event)"
nifiTooltip
[tooltipComponentType]="TextTip"
[tooltipInputData]="parameterAllowableValue.description"
[delayClose]="false">
<span
class="option-text"
[class.unset]="parameterAllowableValue.value == null"
[class.surface-color]="parameterAllowableValue.value == null"
>{{ parameterAllowableValue.displayName }}</span
>
</mat-option>
</ng-container>
</mat-select>
</mat-form-field>
</ng-template>
<ng-template #noDescription>
<mat-option
[value]="parameterAllowableValue.id"
[disabled]="readonly"
(mousedown)="preventDrag($event)">
<span
class="option-text"
[class.unset]="parameterAllowableValue.value == null"
[class.surface-color]="parameterAllowableValue.value == null"
>{{ parameterAllowableValue.displayName }}</span
>
</mat-option>
</ng-template>
</ng-container>
</mat-select>
</mat-form-field>
</div>
<div class="flex justify-end items-center gap-x-2">
@if (readonly) {

View File

@ -20,7 +20,6 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ComboEditor } from './combo-editor.component';
import { PropertyItem } from '../../property-table.component';
import { Parameter } from '../../../../../state/shared';
import { of } from 'rxjs';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
describe('ComboEditor', () => {
@ -186,9 +185,7 @@ describe('ComboEditor', () => {
item.value = '#{one}';
component.item = item;
component.getParameters = () => {
return of(parameters);
};
component.parameters = parameters;
fixture.detectChanges();
fixture.whenStable().then(() => {
const formValue = component.comboEditorForm.get('value')?.value;
@ -210,9 +207,7 @@ describe('ComboEditor', () => {
item.value = '#{three}';
component.item = item;
component.getParameters = () => {
return of(parameters);
};
component.parameters = parameters;
fixture.detectChanges();
fixture.whenStable().then(() => {
const formValue = component.comboEditorForm.get('value')?.value;

View File

@ -31,7 +31,6 @@ import { MatSelectModule } from '@angular/material/select';
import { MatTooltipModule } from '@angular/material/tooltip';
import { TextTip } from '../../../tooltips/text-tip/text-tip.component';
import { A11yModule } from '@angular/cdk/a11y';
import { Observable, take } from 'rxjs';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
export interface AllowableValueItem extends AllowableValue {
@ -77,10 +76,10 @@ export class ComboEditor {
this.initialAllowableValues();
}
@Input() set getParameters(getParameters: (sensitive: boolean) => Observable<Parameter[]>) {
this._getParameters = getParameters;
@Input() set parameters(parameters: Parameter[]) {
this._parameters = parameters;
this.supportsParameters = getParameters != null;
this.supportsParameters = parameters != null;
this.initialAllowableValues();
}
@Input() width!: number;
@ -104,11 +103,10 @@ export class ComboEditor {
sensitive = false;
supportsParameters = false;
parametersLoaded = false;
itemSet = false;
configuredValue: string | null = null;
_getParameters!: (sensitive: boolean) => Observable<Parameter[]>;
_parameters!: Parameter[];
constructor(private formBuilder: FormBuilder) {
this.comboEditorForm = this.formBuilder.group({
@ -159,8 +157,6 @@ export class ComboEditor {
}
if (this.supportsParameters) {
this.parametersLoaded = false;
// parameters are supported so add the item to support showing
// and hiding the parameter options select
const referencesParameterOption: AllowableValueItem = {
@ -183,40 +179,35 @@ export class ComboEditor {
this.allowableValueChanged(this.referencesParametersId);
}
this._getParameters(this.sensitive)
.pipe(take(1))
.subscribe((parameters) => {
if (parameters.length > 0) {
// capture the value of i which will be the id of the first
// parameter
this.configuredParameterId = i;
const parameters: Parameter[] = this._parameters;
if (parameters.length > 0) {
// capture the value of i which will be the id of the first
// parameter
this.configuredParameterId = i;
// create allowable values for each parameter
parameters.forEach((parameter) => {
const parameterItem: AllowableValueItem = {
id: i++,
displayName: parameter.name,
value: '#{' + parameter.name + '}',
description: parameter.description
};
this.parameterAllowableValues.push(parameterItem);
this.itemLookup.set(parameterItem.id, parameterItem);
// create allowable values for each parameter
parameters.forEach((parameter) => {
const parameterItem: AllowableValueItem = {
id: i++,
displayName: parameter.name,
value: `#{${parameter.name}}`,
description: parameter.description
};
this.parameterAllowableValues.push(parameterItem);
this.itemLookup.set(parameterItem.id, parameterItem);
// if the configured parameter is still available,
// capture the id, so we can auto select it
if (parameterItem.value === this.configuredValue) {
this.configuredParameterId = parameterItem.id;
}
});
// if combo still set to reference a parameter, set the default value
if (this.comboEditorForm.get('value')?.value == this.referencesParametersId) {
this.comboEditorForm.get('parameterReference')?.setValue(this.configuredParameterId);
}
// if the configured parameter is still available,
// capture the id, so we can auto select it
if (parameterItem.value === this.configuredValue) {
this.configuredParameterId = parameterItem.id;
}
this.parametersLoaded = true;
});
// if combo still set to reference a parameter, set the default value
if (selectedItem?.id == this.referencesParametersId) {
this.comboEditorForm.get('parameterReference')?.setValue(this.configuredParameterId);
}
}
} else {
this.parameterAllowableValues = [];
}

View File

@ -29,7 +29,6 @@ import { PropertyHintTip } from '../../../tooltips/property-hint-tip/property-hi
import { Parameter, PropertyHintTipInput } from '../../../../../state/shared';
import { A11yModule } from '@angular/cdk/a11y';
import { CodemirrorModule } from '@ctrl/ngx-codemirror';
import { Observable, take } from 'rxjs';
import { NfEl } from './modes/nfel';
import { NfPr } from './modes/nfpr';
import { Editor } from 'codemirror';
@ -75,8 +74,8 @@ export class NfEditor implements OnDestroy {
this.loadParameters();
}
@Input() set getParameters(getParameters: (sensitive: boolean) => Observable<Parameter[]>) {
this._getParameters = getParameters;
@Input() set parameters(parameters: Parameter[]) {
this._parameters = parameters;
this.getParametersSet = true;
this.loadParameters();
@ -98,7 +97,7 @@ export class NfEditor implements OnDestroy {
supportsParameters = false;
mode!: string;
_getParameters!: (sensitive: boolean) => Observable<Parameter[]>;
_parameters!: Parameter[];
editor!: Editor;
@ -127,22 +126,19 @@ export class NfEditor implements OnDestroy {
this.nfpr.setViewContainerRef(this.viewContainerRef, this.renderer);
if (this.getParametersSet) {
if (this._getParameters) {
if (this._parameters) {
this.supportsParameters = true;
this._getParameters(this.sensitive)
.pipe(take(1))
.subscribe((parameters) => {
if (this.supportsEl) {
this.nfel.enableParameters();
this.nfel.setParameters(parameters);
this.nfel.configureAutocomplete();
} else {
this.nfpr.enableParameters();
this.nfpr.setParameters(parameters);
this.nfpr.configureAutocomplete();
}
});
const parameters: Parameter[] = this._parameters;
if (this.supportsEl) {
this.nfel.enableParameters();
this.nfel.setParameters(parameters);
this.nfel.configureAutocomplete();
} else {
this.nfpr.enableParameters();
this.nfpr.setParameters(parameters);
this.nfpr.configureAutocomplete();
}
} else {
this.supportsParameters = false;

View File

@ -159,7 +159,7 @@
@if (hasAllowableValues(editorItem)) {
<combo-editor
[item]="editorItem"
[getParameters]="getParameters"
[parameters]="editorParameters"
[width]="editorWidth"
[readonly]="isDisabled"
(ok)="savePropertyValue(editorItem, $event)"
@ -167,7 +167,7 @@
} @else {
<nf-editor
[item]="editorItem"
[getParameters]="getParameters"
[parameters]="editorParameters"
[width]="editorWidth"
[readonly]="isDisabled"
(ok)="savePropertyValue(editorItem, $event)"

View File

@ -38,7 +38,7 @@ import {
InlineServiceCreationRequest,
InlineServiceCreationResponse,
Parameter,
ParameterContextReferenceEntity,
ParameterContextEntity,
Property,
PropertyDependency,
PropertyDescriptor,
@ -59,6 +59,7 @@ import { ComboEditor } from './editors/combo-editor/combo-editor.component';
import { Observable, take } from 'rxjs';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { RouterLink } from '@angular/router';
import { ConvertToParameterResponse } from '../../../pages/flow-designer/service/parameter-helper.service';
export interface PropertyItem extends Property {
id: number;
@ -99,10 +100,13 @@ export interface PropertyItem extends Property {
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() parameterContext: ParameterContextReferenceEntity | undefined;
@Input() parameterContext: ParameterContextEntity | undefined;
@Input() goToParameter!: (parameter: string) => void;
@Input() convertToParameter!: (name: string, sensitive: boolean, value: string | null) => Observable<string>;
@Input() convertToParameter!: (
name: string,
sensitive: boolean,
value: string | null
) => Observable<ConvertToParameterResponse>;
@Input() goToService!: (serviceId: string) => void;
@Input() supportsSensitiveDynamicProperties = false;
@Input() propertyHistory: ComponentHistory | undefined;
@ -129,6 +133,7 @@ export class PropertyTable implements AfterViewInit, ControlValueAccessor {
editorOpen = false;
editorTrigger: any = null;
editorItem!: PropertyItem;
editorParameters: Parameter[] = [];
editorWidth = 0;
editorOffsetX = 0;
editorOffsetY = 0;
@ -215,18 +220,49 @@ export class PropertyTable implements AfterViewInit, ControlValueAccessor {
return false;
}
// if the dependent item is visible, but does not require a specific
// dependent value consider the dependency met
if (this.nifiCommon.isEmpty(dependency.dependentValues)) {
// if the dependent item is sensitive, in this case we are lenient and
// consider the dependency met
if (dependentItem.descriptor.sensitive) {
return true;
}
// TODO resolve parameter value in dependentItem if necessary
// ensure the dependent item has a value
let dependentValue = dependentItem.value;
if (dependentValue != null) {
// check if the dependent value is a parameter reference
if (PropertyTable.PARAM_REF_REGEX.test(dependentValue)) {
// the dependent value contains parameter reference, if the user can view
// the parameter context resolve the parameter value to see if it
// satisfies the dependent values
if (this.parameterContext?.permissions.canRead) {
const referencedParameter = this.parameterContext.component.parameters
.map((parameterEntity) => parameterEntity.parameter)
.find((parameter: Parameter) => dependentValue == `#{${parameter.name}}`);
// if the dependent item has a value, see if it is present in the
// allowed dependent value. if so, consider the dependency met
if (dependentItem.value) {
return dependency.dependentValues.includes(dependentItem.value);
// if we found a matching parameter then we'll use its value when determining if the
// dependency is satisfied. if a matching parameter was not found we'll continue using
// the dependent property value
if (referencedParameter) {
dependentValue = referencedParameter.value;
}
} else {
// the user lacks permissions to the parameter context so we cannot
// verify if the dependency is satisfied, in this case we are lenient
// and consider the dependency met
return true;
}
}
// ensure the dependent item has a value
if (dependentValue != null) {
if (this.nifiCommon.isEmpty(dependency.dependentValues)) {
// if the dependency does not require a specific value, consider the dependency met
return true;
} else {
// see if value is present in the allowed dependent values. if so, consider the dependency met.
return dependency.dependentValues.includes(dependentValue);
}
}
}
// if the dependent item does not have a value, consider the
@ -279,6 +315,16 @@ export class PropertyTable implements AfterViewInit, ControlValueAccessor {
this.initFilter();
}
private getParametersForItem(propertyItem: PropertyItem): Parameter[] {
if (this.parameterContext?.permissions.canRead) {
return this.parameterContext.component.parameters
.map((parameterEntity) => parameterEntity.parameter)
.filter((parameter: Parameter) => parameter.sensitive == propertyItem.descriptor.sensitive);
} else {
return [];
}
}
newPropertyClicked(): void {
// filter out deleted properties in case the user needs to re-add one
const existingProperties: string[] = this.dataSource.data
@ -398,6 +444,7 @@ export class PropertyTable implements AfterViewInit, ControlValueAccessor {
this.editorPositions.pop();
this.editorItem = item;
this.editorParameters = this.getParametersForItem(this.editorItem);
this.editorTrigger = editorTrigger;
this.editorOpen = true;
@ -484,10 +531,15 @@ export class PropertyTable implements AfterViewInit, ControlValueAccessor {
convertToParameterClicked(item: PropertyItem): void {
this.convertToParameter(item.descriptor.displayName, item.descriptor.sensitive, item.value)
.pipe(take(1))
.subscribe((propertyValue) => {
item.value = propertyValue;
.subscribe((response) => {
item.value = response.propertyValue;
item.dirty = true;
// update the parameter context which includes any new parameters
if (this.parameterContext && response.parameterContext) {
this.parameterContext.component = response.parameterContext;
}
this.handleChanged();
});
}