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 { Injectable } from '@angular/core';
import { MatDialog } from '@angular/material/dialog'; 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 { Store } from '@ngrx/store';
import { HttpErrorResponse } from '@angular/common/http'; import { HttpErrorResponse } from '@angular/common/http';
import { NiFiState } from '../../../state'; import { NiFiState } from '../../../state';
import { ParameterService } from './parameter.service'; import { ParameterService } from './parameter.service';
import { Client } from '../../../service/client.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 { EditParameterDialog } from '../../../ui/common/edit-parameter-dialog/edit-parameter-dialog.component';
import { selectParameterSaving, selectParameterState } from '../state/parameter/parameter.selectors'; import { selectParameterSaving, selectParameterState } from '../state/parameter/parameter.selectors';
import { ParameterState } from '../state/parameter'; import { ParameterState } from '../state/parameter';
import * as ErrorActions from '../../../state/error/error.actions'; import * as ErrorActions from '../../../state/error/error.actions';
import * as ParameterActions from '../state/parameter/parameter.actions'; import * as ParameterActions from '../state/parameter/parameter.actions';
import { FlowService } from './flow.service';
import { MEDIUM_DIALOG } from '../../../index'; import { MEDIUM_DIALOG } from '../../../index';
import { ClusterConnectionService } from '../../../service/cluster-connection.service'; import { ClusterConnectionService } from '../../../service/cluster-connection.service';
import { ErrorHelper } from '../../../service/error-helper.service'; import { ErrorHelper } from '../../../service/error-helper.service';
export interface ConvertToParameterResponse {
propertyValue: string;
parameterContext?: ParameterContext;
}
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
@ -41,40 +45,12 @@ export class ParameterHelperService {
constructor( constructor(
private dialog: MatDialog, private dialog: MatDialog,
private store: Store<NiFiState>, private store: Store<NiFiState>,
private flowService: FlowService,
private parameterService: ParameterService, private parameterService: ParameterService,
private clusterConnectionService: ClusterConnectionService, private clusterConnectionService: ClusterConnectionService,
private client: Client, private client: Client,
private errorHelper: ErrorHelper 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. * 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( convertToParameter(
parameterContextId: string 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 (name: string, sensitive: boolean, value: string | null) => {
return this.parameterService.getParameterContext(parameterContextId, false).pipe( return this.parameterService.getParameterContext(parameterContextId, false).pipe(
catchError((errorResponse: HttpErrorResponse) => { catchError((errorResponse: HttpErrorResponse) => {
@ -127,6 +103,7 @@ export class ParameterHelperService {
request: { request: {
id: parameterContextId, id: parameterContextId,
payload: { payload: {
id: parameterContextEntity.id,
revision: this.client.getRevision(parameterContextEntity), revision: this.client.getRevision(parameterContextEntity),
disconnectedNodeAcknowledged: disconnectedNodeAcknowledged:
this.clusterConnectionService.isDisconnectionAcknowledged(), this.clusterConnectionService.isDisconnectionAcknowledged(),
@ -139,6 +116,8 @@ export class ParameterHelperService {
}) })
); );
let parameterContext: ParameterContext;
return this.store.select(selectParameterState).pipe( return this.store.select(selectParameterState).pipe(
takeUntil(convertToParameterDialogReference.afterClosed()), takeUntil(convertToParameterDialogReference.afterClosed()),
tap((parameterState: ParameterState) => { tap((parameterState: ParameterState) => {
@ -146,12 +125,24 @@ export class ParameterHelperService {
// if the convert to parameter sequence stores an error, // if the convert to parameter sequence stores an error,
// throw it to avoid the completion mapping logic below // throw it to avoid the completion mapping logic below
throw new Error(parameterState.error); 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(() => { map(() => {
convertToParameterDialogReference.close(); convertToParameterDialogReference.close();
return `#{${dialogResponse.parameter.name}}`;
return {
propertyValue: `#{${dialogResponse.parameter.name}}`,
parameterContext
} as ConvertToParameterResponse;
}), }),
catchError((error) => { catchError((error) => {
convertToParameterDialogReference.close(); 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 { LARGE_DIALOG, SMALL_DIALOG, XL_DIALOG } from '../../../../index';
import { ExtensionTypesService } from '../../../../service/extension-types.service'; import { ExtensionTypesService } from '../../../../service/extension-types.service';
import { ChangeComponentVersionDialog } from '../../../../ui/common/change-component-version-dialog/change-component-version-dialog'; import { ChangeComponentVersionDialog } from '../../../../ui/common/change-component-version-dialog/change-component-version-dialog';
import { FlowService } from '../../service/flow.service';
@Injectable() @Injectable()
export class ControllerServicesEffects { export class ControllerServicesEffects {
@ -61,6 +62,7 @@ export class ControllerServicesEffects {
private store: Store<NiFiState>, private store: Store<NiFiState>,
private client: Client, private client: Client,
private controllerServiceService: ControllerServiceService, private controllerServiceService: ControllerServiceService,
private flowService: FlowService,
private errorHelper: ErrorHelper, private errorHelper: ErrorHelper,
private dialog: MatDialog, private dialog: MatDialog,
private router: Router, private router: Router,
@ -236,6 +238,34 @@ export class ControllerServicesEffects {
this.store.select(selectParameterContext), this.store.select(selectParameterContext),
this.store.select(selectCurrentProcessGroupId) 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]) => { tap(([request, parameterContext, processGroupId]) => {
const serviceId: string = request.id; const serviceId: string = request.id;
@ -282,10 +312,6 @@ export class ControllerServicesEffects {
}; };
if (parameterContext != null) { if (parameterContext != null) {
editDialogReference.componentInstance.getParameters = this.parameterHelperService.getParameters(
parameterContext.id
);
editDialogReference.componentInstance.parameterContext = parameterContext; editDialogReference.componentInstance.parameterContext = parameterContext;
editDialogReference.componentInstance.goToParameter = () => { editDialogReference.componentInstance.goToParameter = () => {
const commands: string[] = ['/parameter-contexts', parameterContext.id]; const commands: string[] = ['/parameter-contexts', parameterContext.id];

View File

@ -1200,6 +1200,34 @@ export class FlowEffects {
this.store.select(selectCurrentParameterContext), this.store.select(selectCurrentParameterContext),
this.store.select(selectCurrentProcessGroupId) 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]) => { tap(([request, parameterContext, processGroupId]) => {
const processorId: string = request.entity.id; const processorId: string = request.entity.id;
@ -1239,10 +1267,6 @@ export class FlowEffects {
}; };
if (parameterContext != null) { if (parameterContext != null) {
editDialogReference.componentInstance.getParameters = this.parameterHelperService.getParameters(
parameterContext.id
);
editDialogReference.componentInstance.parameterContext = parameterContext; editDialogReference.componentInstance.parameterContext = parameterContext;
editDialogReference.componentInstance.goToParameter = () => { editDialogReference.componentInstance.goToParameter = () => {
const commands: string[] = ['/parameter-contexts', parameterContext.id]; const commands: string[] = ['/parameter-contexts', parameterContext.id];

View File

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

View File

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

View File

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

View File

@ -29,8 +29,7 @@ import { Observable } from 'rxjs';
import { import {
InlineServiceCreationRequest, InlineServiceCreationRequest,
InlineServiceCreationResponse, InlineServiceCreationResponse,
Parameter, ParameterContextEntity,
ParameterContextReferenceEntity,
Property, Property,
SelectOption SelectOption
} from '../../../../../../../state/shared'; } from '../../../../../../../state/shared';
@ -49,6 +48,7 @@ import {
import { ErrorBanner } from '../../../../../../../ui/common/error-banner/error-banner.component'; import { ErrorBanner } from '../../../../../../../ui/common/error-banner/error-banner.component';
import { ClusterConnectionService } from '../../../../../../../service/cluster-connection.service'; import { ClusterConnectionService } from '../../../../../../../service/cluster-connection.service';
import { CanvasUtils } from '../../../../../service/canvas-utils.service'; import { CanvasUtils } from '../../../../../service/canvas-utils.service';
import { ConvertToParameterResponse } from '../../../../../service/parameter-helper.service';
@Component({ @Component({
selector: 'edit-processor', selector: 'edit-processor',
@ -76,10 +76,13 @@ import { CanvasUtils } from '../../../../../service/canvas-utils.service';
export class EditProcessor { export class EditProcessor {
@Input() createNewProperty!: (existingProperties: string[], allowsSensitive: boolean) => Observable<Property>; @Input() createNewProperty!: (existingProperties: string[], allowsSensitive: boolean) => Observable<Property>;
@Input() createNewService!: (request: InlineServiceCreationRequest) => Observable<InlineServiceCreationResponse>; @Input() createNewService!: (request: InlineServiceCreationRequest) => Observable<InlineServiceCreationResponse>;
@Input() getParameters!: (sensitive: boolean) => Observable<Parameter[]>; @Input() parameterContext: ParameterContextEntity | undefined;
@Input() parameterContext: ParameterContextReferenceEntity | undefined;
@Input() goToParameter!: (parameter: string) => void; @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() goToService!: (serviceId: string) => void;
@Input() saving$!: Observable<boolean>; @Input() saving$!: Observable<boolean>;
@Output() editProcessor: EventEmitter<UpdateProcessorRequest> = new EventEmitter<UpdateProcessorRequest>(); @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 { HttpClient, HttpParams } from '@angular/common/http';
import { Client } from '../../../service/client.service'; import { Client } from '../../../service/client.service';
import { NiFiCommon } from '../../../service/nifi-common.service'; import { NiFiCommon } from '../../../service/nifi-common.service';
import { CreateParameterContextRequest, DeleteParameterContextRequest } from '../state/parameter-context-listing';
import { import {
CreateParameterContextRequest, ParameterContextEntity,
DeleteParameterContextRequest, ParameterContextUpdateRequest,
ParameterContextEntity SubmitParameterContextUpdate
} from '../state/parameter-context-listing'; } from '../../../state/shared';
import { ParameterContextUpdateRequest, SubmitParameterContextUpdate } from '../../../state/shared';
import { ClusterConnectionService } from '../../../service/cluster-connection.service'; import { ClusterConnectionService } from '../../../service/cluster-connection.service';
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })

View File

@ -15,14 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { import { ParameterContextEntity, ParameterContextUpdateRequestEntity } from '../../../../state/shared';
ParameterContextReferenceEntity,
ParameterContextUpdateRequestEntity,
ParameterEntity,
ParameterProviderConfigurationEntity,
Permissions,
Revision
} from '../../../../state/shared';
export const parameterContextListingFeatureKey = 'parameterContextListing'; export const parameterContextListingFeatureKey = 'parameterContextListing';
@ -59,31 +52,6 @@ export interface SelectParameterContextRequest {
id: string; 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 { export interface ParameterContextListingState {
parameterContexts: ParameterContextEntity[]; parameterContexts: ParameterContextEntity[];
updateRequestEntity: ParameterContextUpdateRequestEntity | null; updateRequestEntity: ParameterContextUpdateRequestEntity | null;

View File

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

View File

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

View File

@ -26,12 +26,13 @@ import { MatTabsModule } from '@angular/material/tabs';
import { MatOptionModule } from '@angular/material/core'; import { MatOptionModule } from '@angular/material/core';
import { MatSelectModule } from '@angular/material/select'; import { MatSelectModule } from '@angular/material/select';
import { Observable } from 'rxjs'; 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 { NifiSpinnerDirective } from '../../../../../ui/common/spinner/nifi-spinner.directive';
import { Client } from '../../../../../service/client.service'; import { Client } from '../../../../../service/client.service';
import { ParameterTable } from '../parameter-table/parameter-table.component'; import { ParameterTable } from '../parameter-table/parameter-table.component';
import { import {
Parameter, Parameter,
ParameterContextEntity,
ParameterContextUpdateRequestEntity, ParameterContextUpdateRequestEntity,
ParameterEntity, ParameterEntity,
ParameterProviderConfiguration ParameterProviderConfiguration

View File

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

View File

@ -17,7 +17,7 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { ParameterContextEntity, ParameterContextListingState } from '../../state/parameter-context-listing'; import { ParameterContextListingState } from '../../state/parameter-context-listing';
import { import {
selectContext, selectContext,
selectParameterContextIdFromRoute, selectParameterContextIdFromRoute,
@ -37,6 +37,7 @@ import { filter, switchMap, take } from 'rxjs';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { selectCurrentUser } from '../../../../state/current-user/current-user.selectors'; import { selectCurrentUser } from '../../../../state/current-user/current-user.selectors';
import { selectFlowConfiguration } from '../../../../state/flow-configuration/flow-configuration.selectors'; import { selectFlowConfiguration } from '../../../../state/flow-configuration/flow-configuration.selectors';
import { ParameterContextEntity } from '../../../../state/shared';
@Component({ @Component({
selector: 'parameter-context-listing', 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 { MatTableDataSource } from '@angular/material/table';
import { Sort } from '@angular/material/sort'; import { Sort } from '@angular/material/sort';
import { NiFiCommon } from '../../../../../service/nifi-common.service'; import { NiFiCommon } from '../../../../../service/nifi-common.service';
import { ParameterContextEntity } from '../../../state/parameter-context-listing';
import { FlowConfiguration } from '../../../../../state/flow-configuration'; import { FlowConfiguration } from '../../../../../state/flow-configuration';
import { CurrentUser } from '../../../../../state/current-user'; import { CurrentUser } from '../../../../../state/current-user';
import { ParameterContextEntity } from '../../../../../state/shared';
@Component({ @Component({
selector: 'parameter-context-table', selector: 'parameter-context-table',

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -337,6 +337,31 @@ export interface Parameter {
inherited?: boolean; 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 { export interface ParameterContextReferenceEntity {
permissions: Permissions; permissions: Permissions;
id: string; id: string;
@ -388,6 +413,7 @@ export interface ParameterContextUpdateRequest {
updateSteps: any[]; updateSteps: any[];
uri: string; uri: string;
parameterContext?: any; parameterContext?: any;
failureReason?: string;
} }
export interface ParameterContextUpdateRequestEntity { export interface ParameterContextUpdateRequestEntity {

View File

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

View File

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

View File

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

View File

@ -20,7 +20,6 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ComboEditor } from './combo-editor.component'; import { ComboEditor } from './combo-editor.component';
import { PropertyItem } from '../../property-table.component'; import { PropertyItem } from '../../property-table.component';
import { Parameter } from '../../../../../state/shared'; import { Parameter } from '../../../../../state/shared';
import { of } from 'rxjs';
import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { NoopAnimationsModule } from '@angular/platform-browser/animations';
describe('ComboEditor', () => { describe('ComboEditor', () => {
@ -186,9 +185,7 @@ describe('ComboEditor', () => {
item.value = '#{one}'; item.value = '#{one}';
component.item = item; component.item = item;
component.getParameters = () => { component.parameters = parameters;
return of(parameters);
};
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
const formValue = component.comboEditorForm.get('value')?.value; const formValue = component.comboEditorForm.get('value')?.value;
@ -210,9 +207,7 @@ describe('ComboEditor', () => {
item.value = '#{three}'; item.value = '#{three}';
component.item = item; component.item = item;
component.getParameters = () => { component.parameters = parameters;
return of(parameters);
};
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
const formValue = component.comboEditorForm.get('value')?.value; 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 { MatTooltipModule } from '@angular/material/tooltip';
import { TextTip } from '../../../tooltips/text-tip/text-tip.component'; import { TextTip } from '../../../tooltips/text-tip/text-tip.component';
import { A11yModule } from '@angular/cdk/a11y'; import { A11yModule } from '@angular/cdk/a11y';
import { Observable, take } from 'rxjs';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
export interface AllowableValueItem extends AllowableValue { export interface AllowableValueItem extends AllowableValue {
@ -77,10 +76,10 @@ export class ComboEditor {
this.initialAllowableValues(); this.initialAllowableValues();
} }
@Input() set getParameters(getParameters: (sensitive: boolean) => Observable<Parameter[]>) { @Input() set parameters(parameters: Parameter[]) {
this._getParameters = getParameters; this._parameters = parameters;
this.supportsParameters = getParameters != null; this.supportsParameters = parameters != null;
this.initialAllowableValues(); this.initialAllowableValues();
} }
@Input() width!: number; @Input() width!: number;
@ -104,11 +103,10 @@ export class ComboEditor {
sensitive = false; sensitive = false;
supportsParameters = false; supportsParameters = false;
parametersLoaded = false;
itemSet = false; itemSet = false;
configuredValue: string | null = null; configuredValue: string | null = null;
_getParameters!: (sensitive: boolean) => Observable<Parameter[]>; _parameters!: Parameter[];
constructor(private formBuilder: FormBuilder) { constructor(private formBuilder: FormBuilder) {
this.comboEditorForm = this.formBuilder.group({ this.comboEditorForm = this.formBuilder.group({
@ -159,8 +157,6 @@ export class ComboEditor {
} }
if (this.supportsParameters) { if (this.supportsParameters) {
this.parametersLoaded = false;
// parameters are supported so add the item to support showing // parameters are supported so add the item to support showing
// and hiding the parameter options select // and hiding the parameter options select
const referencesParameterOption: AllowableValueItem = { const referencesParameterOption: AllowableValueItem = {
@ -183,40 +179,35 @@ export class ComboEditor {
this.allowableValueChanged(this.referencesParametersId); this.allowableValueChanged(this.referencesParametersId);
} }
this._getParameters(this.sensitive) const parameters: Parameter[] = this._parameters;
.pipe(take(1)) if (parameters.length > 0) {
.subscribe((parameters) => { // capture the value of i which will be the id of the first
if (parameters.length > 0) { // parameter
// capture the value of i which will be the id of the first this.configuredParameterId = i;
// parameter
this.configuredParameterId = i;
// create allowable values for each parameter // create allowable values for each parameter
parameters.forEach((parameter) => { parameters.forEach((parameter) => {
const parameterItem: AllowableValueItem = { const parameterItem: AllowableValueItem = {
id: i++, id: i++,
displayName: parameter.name, displayName: parameter.name,
value: '#{' + parameter.name + '}', value: `#{${parameter.name}}`,
description: parameter.description description: parameter.description
}; };
this.parameterAllowableValues.push(parameterItem); this.parameterAllowableValues.push(parameterItem);
this.itemLookup.set(parameterItem.id, parameterItem); this.itemLookup.set(parameterItem.id, parameterItem);
// if the configured parameter is still available, // if the configured parameter is still available,
// capture the id, so we can auto select it // capture the id, so we can auto select it
if (parameterItem.value === this.configuredValue) { if (parameterItem.value === this.configuredValue) {
this.configuredParameterId = parameterItem.id; 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);
}
} }
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 { } else {
this.parameterAllowableValues = []; this.parameterAllowableValues = [];
} }

View File

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

View File

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

View File

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