mirror of https://github.com/apache/nifi.git
NIFI-12401: Allow combo editor to reference parameters (#8068)
* NIFI-12401: - Allow combo editor to reference parameters. * NIFI-12401: - Addressing review feedback. - Handling corner cases where there is no parameter context and where there are no parameters in a bound parameter context. * NIFI-12401: - Fixing formatting issues. This closes #8068
This commit is contained in:
parent
9d50c6dd53
commit
ebfb5bc12e
|
@ -19,38 +19,35 @@
|
|||
// https://karma-runner.github.io/1.0/config/configuration-file.html
|
||||
|
||||
module.exports = function (config) {
|
||||
config.set({
|
||||
basePath: '',
|
||||
frameworks: ['jasmine', '@angular-devkit/build-angular'],
|
||||
plugins: [
|
||||
require('karma-jasmine'),
|
||||
require('karma-chrome-launcher'),
|
||||
require('karma-jasmine-html-reporter'),
|
||||
require('karma-coverage'),
|
||||
require('@angular-devkit/build-angular/plugins/karma')
|
||||
],
|
||||
client: {
|
||||
jasmine: {
|
||||
// you can add configuration options for Jasmine here
|
||||
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
|
||||
// for example, you can disable the random execution with `random: false`
|
||||
// or set a specific seed with `seed: 4321`
|
||||
},
|
||||
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
||||
},
|
||||
jasmineHtmlReporter: {
|
||||
suppressAll: true // removes the duplicated traces
|
||||
},
|
||||
coverageReporter: {
|
||||
dir: require('path').join(__dirname, './coverage/nifi'),
|
||||
subdir: '.',
|
||||
reporters: [
|
||||
{ type: 'html' },
|
||||
{ type: 'text-summary' }
|
||||
]
|
||||
},
|
||||
reporters: ['progress', 'kjhtml'],
|
||||
browsers: ['ChromeHeadless'],
|
||||
restartOnFileChange: true
|
||||
});
|
||||
config.set({
|
||||
basePath: '',
|
||||
frameworks: ['jasmine', '@angular-devkit/build-angular'],
|
||||
plugins: [
|
||||
require('karma-jasmine'),
|
||||
require('karma-chrome-launcher'),
|
||||
require('karma-jasmine-html-reporter'),
|
||||
require('karma-coverage'),
|
||||
require('@angular-devkit/build-angular/plugins/karma')
|
||||
],
|
||||
client: {
|
||||
jasmine: {
|
||||
// you can add configuration options for Jasmine here
|
||||
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
|
||||
// for example, you can disable the random execution with `random: false`
|
||||
// or set a specific seed with `seed: 4321`
|
||||
},
|
||||
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
||||
},
|
||||
jasmineHtmlReporter: {
|
||||
suppressAll: true // removes the duplicated traces
|
||||
},
|
||||
coverageReporter: {
|
||||
dir: require('path').join(__dirname, './coverage/nifi'),
|
||||
subdir: '.',
|
||||
reporters: [{ type: 'html' }, { type: 'text-summary' }]
|
||||
},
|
||||
reporters: ['progress', 'kjhtml'],
|
||||
browsers: ['ChromeHeadless'],
|
||||
restartOnFileChange: true
|
||||
});
|
||||
};
|
||||
|
|
|
@ -43,11 +43,11 @@
|
|||
"@angular-devkit/build-angular": "^16.2.0",
|
||||
"@angular/cli": "~16.2.0",
|
||||
"@angular/compiler-cli": "^16.2.0",
|
||||
"@types/codemirror": "^5.60.13",
|
||||
"@types/d3": "^7.4.0",
|
||||
"@types/humanize-duration": "^3.27.1",
|
||||
"@types/jasmine": "~4.3.0",
|
||||
"@types/webfontloader": "^1.6.35",
|
||||
"@types/codemirror": "^5.60.13",
|
||||
"autoprefixer": "^10.4.15",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
|
|
|
@ -190,7 +190,7 @@
|
|||
</a>
|
||||
<ng-template #resultLink>
|
||||
<a [routerLink]="['/process-groups', result.parentGroup.id, path, result.id]">
|
||||
{{ result.name }}
|
||||
{{ result.name ? result.name : result.id }}
|
||||
</a>
|
||||
</ng-template>
|
||||
</li>
|
||||
|
|
|
@ -171,6 +171,8 @@ export interface ParameterContextReference {
|
|||
|
||||
export interface AffectedComponentEntity {
|
||||
permissions: Permissions;
|
||||
id: string;
|
||||
revision: Revision;
|
||||
bulletins: BulletinEntity[];
|
||||
component: AffectedComponent;
|
||||
processGroup: ProcessGroupName;
|
||||
|
@ -289,8 +291,8 @@ export interface PropertyDescriptor {
|
|||
name: string;
|
||||
displayName: string;
|
||||
description: string;
|
||||
defaultValue: string;
|
||||
allowableValues: AllowableValueEntity[];
|
||||
defaultValue?: string;
|
||||
allowableValues?: AllowableValueEntity[];
|
||||
required: boolean;
|
||||
sensitive: boolean;
|
||||
dynamic: boolean;
|
||||
|
|
|
@ -24,7 +24,8 @@
|
|||
formControlName="value"
|
||||
[placeholder]="getComboPlaceholder()"
|
||||
[panelClass]="'combo-panel'"
|
||||
(mousedown)="preventDrag($event)">
|
||||
(mousedown)="preventDrag($event)"
|
||||
(selectionChange)="allowableValueChanged($event.value)">
|
||||
<ng-container *ngFor="let allowableValue of allowableValues">
|
||||
<ng-container *ngIf="allowableValue.description; else noDescription">
|
||||
<mat-option
|
||||
|
@ -49,6 +50,41 @@
|
|||
</ng-container>
|
||||
</mat-select>
|
||||
</div>
|
||||
<div *ngIf="showParameterAllowableValues">
|
||||
<div *ngIf="!parametersLoaded; else showParameters">
|
||||
<ngx-skeleton-loader count="1"></ngx-skeleton-loader>
|
||||
</div>
|
||||
<ng-template #showParameters>
|
||||
<mat-select
|
||||
class="combo"
|
||||
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"
|
||||
(mousedown)="preventDrag($event)"
|
||||
nifiTooltip
|
||||
[tooltipComponentType]="TextTip"
|
||||
[tooltipInputData]="getAllowableValueOptionTipData(parameterAllowableValue)"
|
||||
[delayClose]="false">
|
||||
<span class="option-text" [class.unset]="parameterAllowableValue.value == null">{{
|
||||
parameterAllowableValue.displayName
|
||||
}}</span>
|
||||
</mat-option>
|
||||
</ng-container>
|
||||
<ng-template #noDescription>
|
||||
<mat-option [value]="parameterAllowableValue.id" (mousedown)="preventDrag($event)">
|
||||
<span class="option-text" [class.unset]="parameterAllowableValue.value == null">{{
|
||||
parameterAllowableValue.displayName
|
||||
}}</span>
|
||||
</mat-option>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
</mat-select>
|
||||
</ng-template>
|
||||
</div>
|
||||
<div class="flex justify-end items-center gap-x-2">
|
||||
<button
|
||||
color="accent"
|
||||
|
|
|
@ -18,19 +18,67 @@
|
|||
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';
|
||||
|
||||
describe('ComboEditor', () => {
|
||||
let component: ComboEditor;
|
||||
let fixture: ComponentFixture<ComboEditor>;
|
||||
|
||||
let item: PropertyItem | null = null;
|
||||
let parameters: Parameter[] = [
|
||||
{
|
||||
name: 'one',
|
||||
description: 'Description for one.',
|
||||
sensitive: false,
|
||||
value: 'value',
|
||||
provided: false,
|
||||
referencingComponents: [],
|
||||
parameterContext: {
|
||||
id: '95d4f3d2-018b-1000-b7c7-b830c49a8026',
|
||||
permissions: {
|
||||
canRead: true,
|
||||
canWrite: true
|
||||
},
|
||||
component: {
|
||||
id: '95d4f3d2-018b-1000-b7c7-b830c49a8026',
|
||||
name: 'params 1'
|
||||
}
|
||||
},
|
||||
inherited: false
|
||||
},
|
||||
{
|
||||
name: 'two',
|
||||
description: 'Description for two.',
|
||||
sensitive: false,
|
||||
value: 'value',
|
||||
provided: false,
|
||||
referencingComponents: [],
|
||||
parameterContext: {
|
||||
id: '95d4f3d2-018b-1000-b7c7-b830c49a8026',
|
||||
permissions: {
|
||||
canRead: true,
|
||||
canWrite: true
|
||||
},
|
||||
component: {
|
||||
id: '95d4f3d2-018b-1000-b7c7-b830c49a8026',
|
||||
name: 'params 1'
|
||||
}
|
||||
},
|
||||
inherited: false
|
||||
}
|
||||
];
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [ComboEditor]
|
||||
});
|
||||
fixture = TestBed.createComponent(ComboEditor);
|
||||
component = fixture.componentInstance;
|
||||
component.supportsParameters = false;
|
||||
component.item = {
|
||||
|
||||
// re-establish the item before each test execution
|
||||
item = {
|
||||
property: 'Destination',
|
||||
value: 'flowfile-attribute',
|
||||
descriptor: {
|
||||
|
@ -69,10 +117,114 @@ describe('ComboEditor', () => {
|
|||
dirty: false,
|
||||
type: 'required'
|
||||
};
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
if (item) {
|
||||
component.item = item;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component).toBeTruthy();
|
||||
}
|
||||
});
|
||||
|
||||
it('verify combo value', () => {
|
||||
if (item) {
|
||||
component.item = item;
|
||||
fixture.detectChanges();
|
||||
|
||||
const formValue = component.comboEditorForm.get('value')?.value;
|
||||
expect(component.itemLookup.get(formValue)?.value).toEqual(item.value);
|
||||
expect(component.comboEditorForm.get('parameterReference')).toBeNull();
|
||||
|
||||
spyOn(component.ok, 'next');
|
||||
component.okClicked();
|
||||
expect(component.ok.next).toHaveBeenCalledWith(item.value);
|
||||
}
|
||||
});
|
||||
|
||||
it('verify combo not required with null value and default', () => {
|
||||
if (item) {
|
||||
item.value = null;
|
||||
item.descriptor.required = false;
|
||||
|
||||
component.item = item;
|
||||
fixture.detectChanges();
|
||||
|
||||
const formValue = component.comboEditorForm.get('value')?.value;
|
||||
expect(component.itemLookup.get(formValue)?.value).toEqual(item.descriptor.defaultValue);
|
||||
expect(component.comboEditorForm.get('parameterReference')).toBeNull();
|
||||
|
||||
spyOn(component.ok, 'next');
|
||||
component.okClicked();
|
||||
expect(component.ok.next).toHaveBeenCalledWith(item.descriptor.defaultValue);
|
||||
}
|
||||
});
|
||||
|
||||
it('verify combo not required with null value and no default', () => {
|
||||
if (item) {
|
||||
item.value = null;
|
||||
item.descriptor.required = false;
|
||||
item.descriptor.defaultValue = undefined;
|
||||
|
||||
component.item = item;
|
||||
fixture.detectChanges();
|
||||
|
||||
const formValue = component.comboEditorForm.get('value')?.value;
|
||||
expect(component.itemLookup.get(formValue)?.value).toEqual(item.value);
|
||||
expect(component.comboEditorForm.get('parameterReference')).toBeNull();
|
||||
|
||||
spyOn(component.ok, 'next');
|
||||
component.okClicked();
|
||||
expect(component.ok.next).toHaveBeenCalledWith(item.value);
|
||||
}
|
||||
});
|
||||
|
||||
it('verify combo with parameter reference', () => {
|
||||
if (item) {
|
||||
item.value = '#{one}';
|
||||
|
||||
component.item = item;
|
||||
component.getParameters = (sensitive: boolean) => {
|
||||
return of(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();
|
||||
|
||||
const parameterReferenceValue = component.comboEditorForm.get('parameterReference')?.value;
|
||||
expect(component.itemLookup.get(parameterReferenceValue)?.value).toEqual(item?.value);
|
||||
|
||||
spyOn(component.ok, 'next');
|
||||
component.okClicked();
|
||||
expect(component.ok.next).toHaveBeenCalledWith(item?.value);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('verify combo with missing parameter reference', () => {
|
||||
if (item) {
|
||||
item.value = '#{three}';
|
||||
|
||||
component.item = item;
|
||||
component.getParameters = (sensitive: boolean) => {
|
||||
return of(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();
|
||||
|
||||
const parameterReferenceValue = component.comboEditorForm.get('parameterReference')?.value;
|
||||
expect(component.itemLookup.get(parameterReferenceValue)?.value).toEqual(item?.value);
|
||||
|
||||
spyOn(component.ok, 'next');
|
||||
component.okClicked();
|
||||
expect(component.ok.next).toHaveBeenCalledWith('#{' + parameters[0].value + '}');
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -25,12 +25,14 @@ import { MatButtonModule } from '@angular/material/button';
|
|||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { NgForOf, NgIf, NgTemplateOutlet } from '@angular/common';
|
||||
import { NifiTooltipDirective } from '../../../tooltips/nifi-tooltip.directive';
|
||||
import { PropertyDescriptor, AllowableValue, TextTipInput } from '../../../../../state/shared';
|
||||
import { AllowableValue, Parameter, PropertyDescriptor, TextTipInput } from '../../../../../state/shared';
|
||||
import { MatOptionModule } from '@angular/material/core';
|
||||
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 {
|
||||
id: number;
|
||||
|
@ -55,57 +57,32 @@ export interface AllowableValueItem extends AllowableValue {
|
|||
NgForOf,
|
||||
MatTooltipModule,
|
||||
NgIf,
|
||||
A11yModule
|
||||
A11yModule,
|
||||
NgxSkeletonLoaderModule
|
||||
],
|
||||
styleUrls: ['./combo-editor.component.scss']
|
||||
})
|
||||
export class ComboEditor {
|
||||
@Input() set item(item: PropertyItem) {
|
||||
this.itemLookup.clear();
|
||||
if (item.value != null) {
|
||||
this.configuredValue = item.value;
|
||||
} else if (item.descriptor.defaultValue != null) {
|
||||
this.configuredValue = item.descriptor.defaultValue;
|
||||
}
|
||||
|
||||
this.descriptor = item.descriptor;
|
||||
this.allowableValues = [];
|
||||
this.sensitive = item.descriptor.sensitive;
|
||||
|
||||
let i: number = 0;
|
||||
let selectedItem: AllowableValueItem | null = null;
|
||||
|
||||
if (!this.descriptor.required) {
|
||||
const noValue: AllowableValueItem = {
|
||||
id: i++,
|
||||
displayName: 'No value',
|
||||
value: null
|
||||
};
|
||||
this.itemLookup.set(noValue.id, noValue);
|
||||
this.allowableValues.push(noValue);
|
||||
|
||||
if (noValue.value == item.value) {
|
||||
selectedItem = noValue;
|
||||
}
|
||||
}
|
||||
|
||||
const allowableValueItems: AllowableValueItem[] = this.descriptor.allowableValues.map(
|
||||
(allowableValueEntity) => {
|
||||
const allowableValue: AllowableValueItem = {
|
||||
...allowableValueEntity.allowableValue,
|
||||
id: i++
|
||||
};
|
||||
this.itemLookup.set(allowableValue.id, allowableValue);
|
||||
|
||||
if (allowableValue.value == item.value) {
|
||||
selectedItem = allowableValue;
|
||||
}
|
||||
|
||||
return allowableValue;
|
||||
}
|
||||
);
|
||||
this.allowableValues.push(...allowableValueItems);
|
||||
|
||||
if (selectedItem) {
|
||||
// mat-select does not have good support for options with null value so we've
|
||||
// introduced a mapping to work around the shortcoming
|
||||
this.comboEditorForm.get('value')?.setValue(selectedItem.id);
|
||||
}
|
||||
this.itemSet = true;
|
||||
this.initialAllowableValues();
|
||||
}
|
||||
|
||||
@Input() set getParameters(getParameters: (sensitive: boolean) => Observable<Parameter[]>) {
|
||||
this._getParameters = getParameters;
|
||||
|
||||
this.supportsParameters = getParameters != null;
|
||||
this.initialAllowableValues();
|
||||
}
|
||||
@Input() supportsParameters: boolean = false;
|
||||
|
||||
@Output() ok: EventEmitter<any> = new EventEmitter<any>();
|
||||
@Output() cancel: EventEmitter<void> = new EventEmitter<void>();
|
||||
|
@ -113,21 +90,172 @@ export class ComboEditor {
|
|||
protected readonly TextTip = TextTip;
|
||||
|
||||
itemLookup: Map<number, AllowableValueItem> = new Map<number, AllowableValueItem>();
|
||||
referencesParametersId: number = -1;
|
||||
configuredParameterId: number = -1;
|
||||
|
||||
comboEditorForm: FormGroup;
|
||||
descriptor!: PropertyDescriptor;
|
||||
allowableValues!: AllowableValueItem[];
|
||||
|
||||
showParameterAllowableValues: boolean = false;
|
||||
parameterAllowableValues!: AllowableValueItem[];
|
||||
|
||||
sensitive: boolean = false;
|
||||
supportsParameters: boolean = false;
|
||||
parametersLoaded: boolean = false;
|
||||
|
||||
itemSet: boolean = false;
|
||||
configuredValue: string | null = null;
|
||||
_getParameters!: (sensitive: boolean) => Observable<Parameter[]>;
|
||||
|
||||
constructor(private formBuilder: FormBuilder) {
|
||||
this.comboEditorForm = this.formBuilder.group({
|
||||
value: new FormControl(null, Validators.required)
|
||||
});
|
||||
}
|
||||
|
||||
initialAllowableValues(): void {
|
||||
if (this.itemSet) {
|
||||
this.itemLookup.clear();
|
||||
this.allowableValues = [];
|
||||
this.referencesParametersId = -1;
|
||||
|
||||
let i: number = 0;
|
||||
let selectedItem: AllowableValueItem | null = null;
|
||||
|
||||
if (!this.descriptor.required) {
|
||||
const noValue: AllowableValueItem = {
|
||||
id: i++,
|
||||
displayName: 'No value',
|
||||
value: null
|
||||
};
|
||||
this.itemLookup.set(noValue.id, noValue);
|
||||
this.allowableValues.push(noValue);
|
||||
|
||||
if (noValue.value == this.configuredValue) {
|
||||
selectedItem = noValue;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.descriptor.allowableValues) {
|
||||
const allowableValueItems: AllowableValueItem[] = this.descriptor.allowableValues.map(
|
||||
(allowableValueEntity) => {
|
||||
const allowableValue: AllowableValueItem = {
|
||||
...allowableValueEntity.allowableValue,
|
||||
id: i++
|
||||
};
|
||||
this.itemLookup.set(allowableValue.id, allowableValue);
|
||||
|
||||
if (allowableValue.value == this.configuredValue) {
|
||||
selectedItem = allowableValue;
|
||||
}
|
||||
|
||||
return allowableValue;
|
||||
}
|
||||
);
|
||||
this.allowableValues.push(...allowableValueItems);
|
||||
}
|
||||
|
||||
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 = {
|
||||
id: i++,
|
||||
displayName: 'Reference Parameter...',
|
||||
value: null
|
||||
};
|
||||
this.allowableValues.push(referencesParameterOption);
|
||||
this.itemLookup.set(referencesParameterOption.id, referencesParameterOption);
|
||||
|
||||
// record the item of the item to more easily identify this item
|
||||
this.referencesParametersId = referencesParameterOption.id;
|
||||
|
||||
// if the current value references a parameter auto select the
|
||||
// references parameter item
|
||||
if (this.referencesParameter(this.configuredValue)) {
|
||||
selectedItem = referencesParameterOption;
|
||||
|
||||
// trigger allowable value changed to show the parameters
|
||||
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;
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
this.parametersLoaded = true;
|
||||
});
|
||||
} else {
|
||||
this.parameterAllowableValues = [];
|
||||
}
|
||||
|
||||
if (selectedItem) {
|
||||
// mat-select does not have good support for options with null value so we've
|
||||
// introduced a mapping to work around the shortcoming
|
||||
this.comboEditorForm.get('value')?.setValue(selectedItem.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
referencesParameter(value: string | null): boolean {
|
||||
if (value) {
|
||||
return value.startsWith('#{') && value.endsWith('}');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
preventDrag(event: MouseEvent): void {
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
allowableValueChanged(value: number): void {
|
||||
this.showParameterAllowableValues = value === this.referencesParametersId;
|
||||
|
||||
if (this.showParameterAllowableValues) {
|
||||
if (this.configuredParameterId === -1) {
|
||||
this.comboEditorForm.addControl('parameterReference', new FormControl(null, Validators.required));
|
||||
} else {
|
||||
this.comboEditorForm.addControl(
|
||||
'parameterReference',
|
||||
new FormControl(this.configuredParameterId, Validators.required)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this.comboEditorForm.removeControl('parameterReference');
|
||||
}
|
||||
}
|
||||
|
||||
getComboPlaceholder(): string {
|
||||
const valueControl: AbstractControl | null = this.comboEditorForm.get('value');
|
||||
if (valueControl) {
|
||||
|
@ -150,7 +278,21 @@ export class ComboEditor {
|
|||
if (valueControl) {
|
||||
const selectedItem: AllowableValueItem | undefined = this.itemLookup.get(valueControl.value);
|
||||
if (selectedItem) {
|
||||
this.ok.next(selectedItem.value);
|
||||
// if the value currently references a parameter emit the parameter, get the parameter reference control and emit that value
|
||||
if (selectedItem.id == this.referencesParametersId) {
|
||||
const parameterReferenceControl: AbstractControl | null =
|
||||
this.comboEditorForm.get('parameterReference');
|
||||
if (parameterReferenceControl) {
|
||||
const selectedParameterItem: AllowableValueItem | undefined = this.itemLookup.get(
|
||||
parameterReferenceControl.value
|
||||
);
|
||||
if (selectedParameterItem) {
|
||||
this.ok.next(selectedParameterItem.value);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.ok.next(selectedItem.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|||
|
||||
import { NfEditor } from './nf-editor.component';
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing';
|
||||
import { PropertyItem } from '../../property-table.component';
|
||||
|
||||
describe('NfEditor', () => {
|
||||
let component: NfEditor;
|
||||
|
@ -30,10 +31,84 @@ describe('NfEditor', () => {
|
|||
});
|
||||
fixture = TestBed.createComponent(NfEditor);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
fixture.detectChanges();
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('verify value set', () => {
|
||||
const value: string = 'my-group-id';
|
||||
const item: PropertyItem = {
|
||||
property: 'group.id',
|
||||
value,
|
||||
descriptor: {
|
||||
name: 'group.id',
|
||||
displayName: 'Group ID',
|
||||
description:
|
||||
"A Group ID is used to identify consumers that are within the same consumer group. Corresponds to Kafka's 'group.id' property.",
|
||||
required: true,
|
||||
sensitive: false,
|
||||
dynamic: false,
|
||||
supportsEl: true,
|
||||
expressionLanguageScope: 'Environment variables defined at JVM level and system properties',
|
||||
dependencies: []
|
||||
},
|
||||
id: 3,
|
||||
triggerEdit: false,
|
||||
deleted: false,
|
||||
added: false,
|
||||
dirty: false,
|
||||
type: 'required'
|
||||
};
|
||||
|
||||
component.item = item;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.nfEditorForm.get('value')?.value).toEqual(value);
|
||||
expect(component.nfEditorForm.get('value')?.disabled).toBeFalse();
|
||||
expect(component.nfEditorForm.get('setEmptyString')?.value).toBeFalse();
|
||||
|
||||
spyOn(component.ok, 'next');
|
||||
component.okClicked();
|
||||
expect(component.ok.next).toHaveBeenCalledWith(value);
|
||||
});
|
||||
|
||||
it('verify empty value set', () => {
|
||||
const value: string = '';
|
||||
const item: PropertyItem = {
|
||||
property: 'group.id',
|
||||
value,
|
||||
descriptor: {
|
||||
name: 'group.id',
|
||||
displayName: 'Group ID',
|
||||
description:
|
||||
"A Group ID is used to identify consumers that are within the same consumer group. Corresponds to Kafka's 'group.id' property.",
|
||||
required: true,
|
||||
sensitive: false,
|
||||
dynamic: false,
|
||||
supportsEl: true,
|
||||
expressionLanguageScope: 'Environment variables defined at JVM level and system properties',
|
||||
dependencies: []
|
||||
},
|
||||
id: 3,
|
||||
triggerEdit: false,
|
||||
deleted: false,
|
||||
added: false,
|
||||
dirty: false,
|
||||
type: 'required'
|
||||
};
|
||||
|
||||
component.item = item;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(component.nfEditorForm.get('value')?.value).toEqual(value);
|
||||
expect(component.nfEditorForm.get('value')?.disabled).toBeTruthy();
|
||||
expect(component.nfEditorForm.get('setEmptyString')?.value).toBeTruthy();
|
||||
|
||||
spyOn(component.ok, 'next');
|
||||
component.okClicked();
|
||||
expect(component.ok.next).toHaveBeenCalledWith(value);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -15,18 +15,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
AfterViewInit,
|
||||
Component,
|
||||
ElementRef,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnDestroy,
|
||||
Output,
|
||||
Renderer2,
|
||||
ViewChild,
|
||||
ViewContainerRef
|
||||
} from '@angular/core';
|
||||
import { Component, EventEmitter, Input, OnDestroy, Output, Renderer2, ViewContainerRef } from '@angular/core';
|
||||
import { PropertyItem } from '../../property-table.component';
|
||||
import { CdkDrag, CdkDragHandle } from '@angular/cdk/drag-drop';
|
||||
import { AbstractControl, FormBuilder, FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||
|
|
|
@ -145,6 +145,7 @@
|
|||
<combo-editor
|
||||
*ngIf="hasAllowableValues(editorItem); else nfEditor"
|
||||
[item]="editorItem"
|
||||
[getParameters]="getParameters"
|
||||
(ok)="savePropertyValue(editorItem, $event)"
|
||||
(cancel)="closeEditor()"></combo-editor>
|
||||
<ng-template #nfEditor>
|
||||
|
|
|
@ -347,8 +347,8 @@ export class PropertyTable implements AfterViewInit, ControlValueAccessor {
|
|||
}
|
||||
|
||||
resolvePropertyValue(property: Property): string | null {
|
||||
const allowableValues: AllowableValueEntity[] = property.descriptor.allowableValues;
|
||||
if (this.nifiCommon.isEmpty(allowableValues)) {
|
||||
const allowableValues: AllowableValueEntity[] | undefined = property.descriptor.allowableValues;
|
||||
if (allowableValues == null || this.nifiCommon.isEmpty(allowableValues)) {
|
||||
return property.value;
|
||||
} else {
|
||||
const allowableValue: AllowableValueEntity | undefined = allowableValues.find(
|
||||
|
@ -392,7 +392,7 @@ export class PropertyTable implements AfterViewInit, ControlValueAccessor {
|
|||
// TODO - add Input() for supportsGoTo? currently only false in summary table
|
||||
|
||||
const descriptor: PropertyDescriptor = item.descriptor;
|
||||
if (item.value && descriptor.identifiesControllerService) {
|
||||
if (item.value && descriptor.identifiesControllerService && descriptor.allowableValues) {
|
||||
return descriptor.allowableValues.some(
|
||||
(entity: AllowableValueEntity) => entity.allowableValue.value == item.value
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue