NIFI-13650: Fixing condition when including References Parameter... o… (#9174)

* NIFI-13650: Fixing condition when including References Parameter option.
- Updating property editors to better convey different parameter states.
- Updating hint tooltip to indicate that parameters are supported but no parameter context is bound.
- Fixing minor layout issue in Processor schedule tab.
- Fix combo editor unit tests.

* NIFI-13650: Fixing track warning errors when editing Parameter Contexts.

* NIFI-13650: Fixing z-index autocomplete issue.

This closes #9174
This commit is contained in:
Matt Gilman 2024-08-16 16:20:01 -04:00 committed by GitHub
parent e4ec0cb20d
commit 38f4110521
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 81 additions and 66 deletions

View File

@ -15,10 +15,6 @@
* limitations under the License. * limitations under the License.
*/ */
ul.CodeMirror-hints {
overflow-y: scroll;
}
div.el-section { div.el-section {
margin: 5px 0; margin: 5px 0;
} }

View File

@ -143,7 +143,7 @@
</mat-form-field> </mat-form-field>
</div> </div>
<div class="flex gap-x-4"> <div class="flex gap-x-4">
<div class="w-44"> <div class="w-1/2">
<mat-form-field> <mat-form-field>
<mat-label> <mat-label>
Concurrent Tasks Concurrent Tasks
@ -163,7 +163,7 @@
[readonly]="readonly" /> [readonly]="readonly" />
</mat-form-field> </mat-form-field>
</div> </div>
<div class="w-44"> <div class="w-1/2">
<mat-form-field> <mat-form-field>
<mat-label> <mat-label>
Run Schedule Run Schedule

View File

@ -26,7 +26,7 @@
<div class="w-full flex flex-col"> <div class="w-full flex flex-col">
<div>Steps To Update Parameters</div> <div>Steps To Update Parameters</div>
<div class="flex flex-col gap-y-1.5"> <div class="flex flex-col gap-y-1.5">
@for (updateStep of requestEntity.request.updateSteps; track updateStep) { @for (updateStep of requestEntity.request.updateSteps; track updateStep.description) {
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<div class="accent-color font-medium"> <div class="accent-color font-medium">
{{ updateStep.description }} {{ updateStep.description }}

View File

@ -259,6 +259,7 @@ export interface ElFunctionTipInput {
export interface PropertyHintTipInput { export interface PropertyHintTipInput {
supportsEl: boolean; supportsEl: boolean;
supportsParameters: boolean; supportsParameters: boolean;
hasParameterContext: boolean;
} }
export interface RestrictionsTipInput { export interface RestrictionsTipInput {
@ -372,6 +373,11 @@ export interface ParameterContextReference {
name: string; name: string;
} }
export interface ParameterConfig {
supportsParameters: boolean;
parameters: Parameter[] | null;
}
export interface AffectedComponentEntity { export interface AffectedComponentEntity {
permissions: Permissions; permissions: Permissions;
id: string; id: string;

View File

@ -19,7 +19,7 @@
@if (parameterReferenceMap == null || parameterReferenceMap.size == 0) { @if (parameterReferenceMap == null || parameterReferenceMap.size == 0) {
<div class="accent-color font-medium">No referencing components</div> <div class="accent-color font-medium">No referencing components</div>
} @else { } @else {
@for (pg of processGroups; track pg) { @for (pg of processGroups; track pg.id) {
<ng-container *ngTemplateOutlet="pgListing; context: { $implicit: pg }"></ng-container> <ng-container *ngTemplateOutlet="pgListing; context: { $implicit: pg }"></ng-container>
} }
<ng-template #pgListing let-pg> <ng-template #pgListing let-pg>
@ -55,7 +55,7 @@
<li> <li>
<h4 class="accent-color">Processors ({{ references.length }})</h4> <h4 class="accent-color">Processors ({{ references.length }})</h4>
<div class="references"> <div class="references">
@for (reference of references; track reference) { @for (reference of references; track reference.component.id) {
<div class="flex items-center gap-x-2"> <div class="flex items-center gap-x-2">
@if (isNonServiceInvalid(reference.component)) { @if (isNonServiceInvalid(reference.component)) {
<div <div
@ -90,7 +90,7 @@
<li> <li>
<h4 class="accent-color">Controller Services ({{ references.length }})</h4> <h4 class="accent-color">Controller Services ({{ references.length }})</h4>
<div class="references"> <div class="references">
@for (service of references; track service) { @for (service of references; track service.component.id) {
<div class="flex flex-col"> <div class="flex flex-col">
<div class="flex items-center gap-x-2"> <div class="flex items-center gap-x-2">
@if (isServiceInvalid(service.component)) { @if (isServiceInvalid(service.component)) {
@ -124,7 +124,7 @@
<li> <li>
<h4 class="accent-color">Unauthorized ({{ references.length }})</h4> <h4 class="accent-color">Unauthorized ({{ references.length }})</h4>
<div class="references"> <div class="references">
@for (reference of references; track reference) { @for (reference of references; track reference.id) {
<div class="flex"> <div class="flex">
<div class="unset surface-color">{{ reference.id }}</div> <div class="unset surface-color">{{ reference.id }}</div>
</div> </div>

View File

@ -180,47 +180,50 @@ describe('ComboEditor', () => {
} }
}); });
it('verify combo with parameter reference', () => { it('verify combo with parameter reference', async () => {
if (item) { if (item) {
item.value = '#{one}'; item.value = '#{one}';
component.item = item; component.item = item;
component.parameters = parameters; component.parameters = parameters;
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(() => { await fixture.whenStable();
const formValue = component.comboEditorForm.get('value')?.value; const formValue = component.comboEditorForm.get('value')?.value;
expect(component.itemLookup.get(formValue)?.value).toEqual(item?.value); expect(component.itemLookup.get(Number(formValue))?.value).toBeNull();
expect(component.comboEditorForm.get('parameterReference')).toBeDefined(); expect(component.comboEditorForm.get('parameterReference')).toBeDefined();
const parameterReferenceValue = component.comboEditorForm.get('parameterReference')?.value; const parameterReferenceValue = component.comboEditorForm.get('parameterReference')?.value;
expect(component.itemLookup.get(parameterReferenceValue)?.value).toEqual(item?.value); expect(component.itemLookup.get(Number(parameterReferenceValue))?.value).toEqual(item.value);
jest.spyOn(component.ok, 'next'); jest.spyOn(component.ok, 'next');
component.okClicked(); component.okClicked();
expect(component.ok.next).toHaveBeenCalledWith(item?.value); expect(component.ok.next).toHaveBeenCalledWith(item.value);
});
} }
}); });
it('verify combo with missing parameter reference', () => { it('verify combo with missing parameter reference', async () => {
if (item) { if (item) {
item.value = '#{three}'; item.value = '#{three}';
component.item = item; component.item = item;
component.parameters = parameters; component.parameters = parameters;
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(() => { await fixture.whenStable();
const formValue = component.comboEditorForm.get('value')?.value; const formValue = component.comboEditorForm.get('value')?.value;
expect(component.itemLookup.get(formValue)?.value).toEqual('#{' + parameters[0].value + '}'); expect(component.itemLookup.get(Number(formValue))?.value).toBeNull();
expect(component.comboEditorForm.get('parameterReference')).toBeDefined(); expect(component.comboEditorForm.get('parameterReference')).toBeDefined();
// since the value does not match any parameters it should match the first
const firstParameterValue = '#{' + parameters[0].name + '}';
const parameterReferenceValue = component.comboEditorForm.get('parameterReference')?.value; const parameterReferenceValue = component.comboEditorForm.get('parameterReference')?.value;
expect(component.itemLookup.get(parameterReferenceValue)?.value).toEqual(item?.value); expect(component.itemLookup.get(Number(parameterReferenceValue))?.value).toEqual(firstParameterValue);
jest.spyOn(component.ok, 'next'); jest.spyOn(component.ok, 'next');
component.okClicked(); component.okClicked();
expect(component.ok.next).toHaveBeenCalledWith('#{' + parameters[0].value + '}'); expect(component.ok.next).toHaveBeenCalledWith(firstParameterValue);
});
} }
}); });
}); });

View File

@ -24,7 +24,7 @@ import { MatInputModule } from '@angular/material/input';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatCheckboxModule } from '@angular/material/checkbox';
import { NgForOf, NgIf, NgTemplateOutlet } from '@angular/common'; import { NgForOf, NgIf, NgTemplateOutlet } from '@angular/common';
import { AllowableValue, Parameter, PropertyDescriptor } from '../../../../../state/shared'; import { AllowableValue, Parameter, ParameterConfig, PropertyDescriptor } from '../../../../../state/shared';
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 { MatTooltipModule } from '@angular/material/tooltip'; import { MatTooltipModule } from '@angular/material/tooltip';
@ -75,10 +75,9 @@ export class ComboEditor {
this.initialAllowableValues(); this.initialAllowableValues();
} }
@Input() set parameters(parameters: Parameter[]) { @Input() set parameterConfig(parameterConfig: ParameterConfig) {
this._parameters = parameters; this.parameters = parameterConfig.parameters;
this.supportsParameters = parameterConfig.supportsParameters;
this.supportsParameters = parameters != null;
this.initialAllowableValues(); this.initialAllowableValues();
} }
@Input() width!: number; @Input() width!: number;
@ -105,7 +104,7 @@ export class ComboEditor {
itemSet = false; itemSet = false;
configuredValue: string | null = null; configuredValue: string | null = null;
_parameters!: Parameter[]; parameters: Parameter[] | null = null;
constructor( constructor(
private formBuilder: FormBuilder, private formBuilder: FormBuilder,
@ -181,14 +180,13 @@ export class ComboEditor {
this.allowableValueChanged(this.referencesParametersId); this.allowableValueChanged(this.referencesParametersId);
} }
const parameters: Parameter[] = this._parameters; if (this.parameters !== null && this.parameters.length > 0) {
if (parameters.length > 0) {
// capture the value of i which will be the id of the first // capture the value of i which will be the id of the first
// parameter // parameter
this.configuredParameterId = i; this.configuredParameterId = i;
// create allowable values for each parameter // create allowable values for each parameter
parameters.forEach((parameter) => { this.parameters.forEach((parameter) => {
const parameterItem: AllowableValueItem = { const parameterItem: AllowableValueItem = {
id: i++, id: i++,
displayName: parameter.name, displayName: parameter.name,

View File

@ -26,7 +26,7 @@ import { MatCheckboxModule } from '@angular/material/checkbox';
import { NgTemplateOutlet } from '@angular/common'; import { NgTemplateOutlet } from '@angular/common';
import { NifiTooltipDirective, Resizable } from '@nifi/shared'; import { NifiTooltipDirective, Resizable } from '@nifi/shared';
import { PropertyHintTip } from '../../../tooltips/property-hint-tip/property-hint-tip.component'; import { PropertyHintTip } from '../../../tooltips/property-hint-tip/property-hint-tip.component';
import { Parameter, PropertyHintTipInput } from '../../../../../state/shared'; import { Parameter, ParameterConfig, 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 { NfEl } from './modes/nfel'; import { NfEl } from './modes/nfel';
@ -74,8 +74,9 @@ export class NfEditor implements OnDestroy {
this.loadParameters(); this.loadParameters();
} }
@Input() set parameters(parameters: Parameter[] | null) { @Input() set parameterConfig(parameterConfig: ParameterConfig) {
this._parameters = parameters; this.parameters = parameterConfig.parameters;
this.supportsParameters = parameterConfig.supportsParameters;
this.getParametersSet = true; this.getParametersSet = true;
this.loadParameters(); this.loadParameters();
@ -98,7 +99,7 @@ export class NfEditor implements OnDestroy {
blank = false; blank = false;
mode!: string; mode!: string;
_parameters!: Parameter[] | null; parameters: Parameter[] | null = null;
editor!: Editor; editor!: Editor;
@ -137,10 +138,8 @@ 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._parameters) { if (this.parameters) {
this.supportsParameters = true; const parameters: Parameter[] = this.parameters;
const parameters: Parameter[] = this._parameters;
if (this.supportsEl) { if (this.supportsEl) {
this.nfel.enableParameters(); this.nfel.enableParameters();
this.nfel.setParameters(parameters); this.nfel.setParameters(parameters);
@ -151,8 +150,6 @@ export class NfEditor implements OnDestroy {
this.nfpr.configureAutocomplete(); this.nfpr.configureAutocomplete();
} }
} else { } else {
this.supportsParameters = false;
this.nfel.disableParameters(); this.nfel.disableParameters();
this.nfpr.disableParameters(); this.nfpr.disableParameters();
@ -187,7 +184,8 @@ export class NfEditor implements OnDestroy {
getPropertyHintTipData(): PropertyHintTipInput { getPropertyHintTipData(): PropertyHintTipInput {
return { return {
supportsEl: this.supportsEl, supportsEl: this.supportsEl,
supportsParameters: this.supportsParameters supportsParameters: this.supportsParameters,
hasParameterContext: this.parameters !== null
}; };
} }

View File

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

View File

@ -38,6 +38,7 @@ import {
InlineServiceCreationRequest, InlineServiceCreationRequest,
InlineServiceCreationResponse, InlineServiceCreationResponse,
Parameter, Parameter,
ParameterConfig,
ParameterContextEntity, ParameterContextEntity,
Property, Property,
PropertyDependency, PropertyDependency,
@ -136,7 +137,7 @@ export class PropertyTable implements AfterViewInit, ControlValueAccessor {
editorOpen = false; editorOpen = false;
editorTrigger: any = null; editorTrigger: any = null;
editorItem!: PropertyItem; editorItem!: PropertyItem;
editorParameters: Parameter[] | null = []; editorParameterConfig!: ParameterConfig;
editorWidth = 0; editorWidth = 0;
editorOffsetX = 0; editorOffsetX = 0;
editorOffsetY = 0; editorOffsetY = 0;
@ -318,11 +319,18 @@ export class PropertyTable implements AfterViewInit, ControlValueAccessor {
this.initFilter(); this.initFilter();
} }
private getParameterConfig(propertyItem: PropertyItem): ParameterConfig {
return {
supportsParameters: this.supportsParameters,
parameters: this.getParametersForItem(propertyItem)
};
}
private getParametersForItem(propertyItem: PropertyItem): Parameter[] | null { private getParametersForItem(propertyItem: PropertyItem): Parameter[] | null {
if (!this.supportsParameters) { if (!this.supportsParameters || !this.parameterContext) {
return null; return null;
} }
if (this.parameterContext?.permissions.canRead) { if (this.parameterContext.permissions.canRead) {
return this.parameterContext.component.parameters return this.parameterContext.component.parameters
.map((parameterEntity) => parameterEntity.parameter) .map((parameterEntity) => parameterEntity.parameter)
.filter((parameter: Parameter) => parameter.sensitive == propertyItem.descriptor.sensitive); .filter((parameter: Parameter) => parameter.sensitive == propertyItem.descriptor.sensitive);
@ -450,7 +458,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.editorParameterConfig = this.getParameterConfig(this.editorItem);
this.editorTrigger = editorTrigger; this.editorTrigger = editorTrigger;
this.editorOpen = true; this.editorOpen = true;

View File

@ -43,12 +43,18 @@
<div class="fa fa-check"></div> <div class="fa fa-check"></div>
<div class="flex flex-col"> <div class="flex flex-col">
<div class="font-bold">Parameters (PARAM) supported</div> <div class="font-bold">Parameters (PARAM) supported</div>
@if (data?.hasParameterContext) {
<div> <div>
After beginning with the start delimiter After beginning with the start delimiter
<span class="hint-pattern font-normal">#&#123;</span> use the keystroke <span class="hint-pattern font-normal">#&#123;</span> use the keystroke
<span class="hint-keystroke font-light">control+space</span> to see a list of available <span class="hint-keystroke font-light">control+space</span> to see a list of available
parameters. parameters.
</div> </div>
} @else {
<div>
Parameters are supported but no Parameter Context is currently bound to this Process Group.
</div>
}
</div> </div>
</div> </div>
</ng-template> </ng-template>

View File

@ -255,7 +255,7 @@
background: if($is-dark, rgba(255, 255, 255, 0.5), rgba(0, 0, 0, 0.5)); background: if($is-dark, rgba(255, 255, 255, 0.5), rgba(0, 0, 0, 0.5));
} }
.cm-s-nifi .CodeMirror-hints { .CodeMirror-hints {
z-index: 1000 !important; z-index: 1000 !important;
overflow-y: scroll !important; overflow-y: scroll !important;
} }