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.
*/
ul.CodeMirror-hints {
overflow-y: scroll;
}
div.el-section {
margin: 5px 0;
}

View File

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

View File

@ -26,7 +26,7 @@
<div class="w-full flex flex-col">
<div>Steps To Update Parameters</div>
<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="accent-color font-medium">
{{ updateStep.description }}

View File

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

View File

@ -19,7 +19,7 @@
@if (parameterReferenceMap == null || parameterReferenceMap.size == 0) {
<div class="accent-color font-medium">No referencing components</div>
} @else {
@for (pg of processGroups; track pg) {
@for (pg of processGroups; track pg.id) {
<ng-container *ngTemplateOutlet="pgListing; context: { $implicit: pg }"></ng-container>
}
<ng-template #pgListing let-pg>
@ -55,7 +55,7 @@
<li>
<h4 class="accent-color">Processors ({{ references.length }})</h4>
<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">
@if (isNonServiceInvalid(reference.component)) {
<div
@ -90,7 +90,7 @@
<li>
<h4 class="accent-color">Controller Services ({{ references.length }})</h4>
<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 items-center gap-x-2">
@if (isServiceInvalid(service.component)) {
@ -124,7 +124,7 @@
<li>
<h4 class="accent-color">Unauthorized ({{ references.length }})</h4>
<div class="references">
@for (reference of references; track reference) {
@for (reference of references; track reference.id) {
<div class="flex">
<div class="unset surface-color">{{ reference.id }}</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) {
item.value = '#{one}';
component.item = item;
component.parameters = parameters;
fixture.detectChanges();
fixture.whenStable().then(() => {
const formValue = component.comboEditorForm.get('value')?.value;
expect(component.itemLookup.get(formValue)?.value).toEqual(item?.value);
expect(component.comboEditorForm.get('parameterReference')).toBeDefined();
await fixture.whenStable();
const parameterReferenceValue = component.comboEditorForm.get('parameterReference')?.value;
expect(component.itemLookup.get(parameterReferenceValue)?.value).toEqual(item?.value);
const formValue = component.comboEditorForm.get('value')?.value;
expect(component.itemLookup.get(Number(formValue))?.value).toBeNull();
expect(component.comboEditorForm.get('parameterReference')).toBeDefined();
jest.spyOn(component.ok, 'next');
component.okClicked();
expect(component.ok.next).toHaveBeenCalledWith(item?.value);
});
const parameterReferenceValue = component.comboEditorForm.get('parameterReference')?.value;
expect(component.itemLookup.get(Number(parameterReferenceValue))?.value).toEqual(item.value);
jest.spyOn(component.ok, 'next');
component.okClicked();
expect(component.ok.next).toHaveBeenCalledWith(item.value);
}
});
it('verify combo with missing parameter reference', () => {
it('verify combo with missing parameter reference', async () => {
if (item) {
item.value = '#{three}';
component.item = item;
component.parameters = parameters;
fixture.detectChanges();
fixture.whenStable().then(() => {
const formValue = component.comboEditorForm.get('value')?.value;
expect(component.itemLookup.get(formValue)?.value).toEqual('#{' + parameters[0].value + '}');
expect(component.comboEditorForm.get('parameterReference')).toBeDefined();
await fixture.whenStable();
const parameterReferenceValue = component.comboEditorForm.get('parameterReference')?.value;
expect(component.itemLookup.get(parameterReferenceValue)?.value).toEqual(item?.value);
const formValue = component.comboEditorForm.get('value')?.value;
expect(component.itemLookup.get(Number(formValue))?.value).toBeNull();
expect(component.comboEditorForm.get('parameterReference')).toBeDefined();
jest.spyOn(component.ok, 'next');
component.okClicked();
expect(component.ok.next).toHaveBeenCalledWith('#{' + parameters[0].value + '}');
});
// 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;
expect(component.itemLookup.get(Number(parameterReferenceValue))?.value).toEqual(firstParameterValue);
jest.spyOn(component.ok, 'next');
component.okClicked();
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 { MatCheckboxModule } from '@angular/material/checkbox';
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 { MatSelectModule } from '@angular/material/select';
import { MatTooltipModule } from '@angular/material/tooltip';
@ -75,10 +75,9 @@ export class ComboEditor {
this.initialAllowableValues();
}
@Input() set parameters(parameters: Parameter[]) {
this._parameters = parameters;
this.supportsParameters = parameters != null;
@Input() set parameterConfig(parameterConfig: ParameterConfig) {
this.parameters = parameterConfig.parameters;
this.supportsParameters = parameterConfig.supportsParameters;
this.initialAllowableValues();
}
@Input() width!: number;
@ -105,7 +104,7 @@ export class ComboEditor {
itemSet = false;
configuredValue: string | null = null;
_parameters!: Parameter[];
parameters: Parameter[] | null = null;
constructor(
private formBuilder: FormBuilder,
@ -181,14 +180,13 @@ export class ComboEditor {
this.allowableValueChanged(this.referencesParametersId);
}
const parameters: Parameter[] = this._parameters;
if (parameters.length > 0) {
if (this.parameters !== null && this.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) => {
this.parameters.forEach((parameter) => {
const parameterItem: AllowableValueItem = {
id: i++,
displayName: parameter.name,

View File

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

View File

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

View File

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

View File

@ -43,12 +43,18 @@
<div class="fa fa-check"></div>
<div class="flex flex-col">
<div class="font-bold">Parameters (PARAM) supported</div>
<div>
After beginning with the start delimiter
<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
parameters.
</div>
@if (data?.hasParameterContext) {
<div>
After beginning with the start delimiter
<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
parameters.
</div>
} @else {
<div>
Parameters are supported but no Parameter Context is currently bound to this Process Group.
</div>
}
</div>
</div>
</ng-template>

View File

@ -255,7 +255,7 @@
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;
overflow-y: scroll !important;
}