This is an automated email from the ASF dual-hosted git repository.
rfellows pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git
The following commit(s) were added to refs/heads/main by this push:
new 4c052994bd NIFI-14988: Fixing issue with default values for dynamic
properties. (#10320)
4c052994bd is described below
commit 4c052994bd67d64ee829849db198e42c972017c7
Author: Matt Gilman <[email protected]>
AuthorDate: Thu Sep 18 11:27:55 2025 -0400
NIFI-14988: Fixing issue with default values for dynamic properties.
(#10320)
- In the combo editor, the form was not marked as dirty which caused the
Apply button to not be enabled.
- In the nf editor, the default value was not considered.
This closes #10320
---
.../combo-editor/combo-editor.component.spec.ts | 70 +++++++
.../editors/combo-editor/combo-editor.component.ts | 219 +++++++++++----------
.../editors/nf-editor/nf-editor.component.spec.ts | 58 ++++++
.../editors/nf-editor/nf-editor.component.ts | 17 +-
4 files changed, 261 insertions(+), 103 deletions(-)
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/property-table/editors/combo-editor/combo-editor.component.spec.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/property-table/editors/combo-editor/combo-editor.component.spec.ts
index 6f472d13cc..ecbc01d03c 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/property-table/editors/combo-editor/combo-editor.component.spec.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/property-table/editors/combo-editor/combo-editor.component.spec.ts
@@ -177,6 +177,76 @@ describe('ComboEditor', () => {
}
});
+ it('verify form is dirty when using default value', () => {
+ if (item) {
+ item.value = null;
+ item.descriptor.required = false;
+ item.savedValue = 'some-other-value';
+
+ component.item = item;
+ component.parameterConfig = {
+ supportsParameters: false,
+ parameters: null
+ };
+
+ fixture.detectChanges();
+
+ // Form should be dirty because we're using the default value
instead of the saved value
+ expect(component.comboEditorForm.dirty).toBe(true);
+
expect(component.configuredValue).toEqual(item.descriptor.defaultValue);
+ }
+ });
+
+ it('verify input order independence - item first then parameterConfig', ()
=> {
+ if (item) {
+ item.value = null;
+ item.descriptor.required = false;
+ item.savedValue = 'some-other-value';
+
+ // Set item first
+ component.item = item;
+
+ // Set parameterConfig second
+ component.parameterConfig = {
+ supportsParameters: true,
+ parameters
+ };
+
+ fixture.detectChanges();
+
+ // Should work correctly regardless of input order
+ expect(component.comboEditorForm.dirty).toBe(true);
+
expect(component.configuredValue).toEqual(item.descriptor.defaultValue);
+ expect(component.supportsParameters).toBe(true);
+ expect(component.parameters).toBe(parameters);
+ }
+ });
+
+ it('verify input order independence - parameterConfig first then item', ()
=> {
+ if (item) {
+ item.value = null;
+ item.descriptor.required = false;
+ item.savedValue = 'some-other-value';
+
+ // Set parameterConfig first
+ component.parameterConfig = {
+ supportsParameters: true,
+ parameters
+ };
+
+ // Set item second
+ component.item = item;
+
+ fixture.detectChanges();
+
+ // Should work correctly regardless of input order
+ expect(component.comboEditorForm.dirty).toBe(true);
+
expect(component.configuredValue).toEqual(item.descriptor.defaultValue);
+ expect(component.supportsParameters).toBe(true);
+ expect(component.parameters).toBe(parameters);
+ }
+ });
+
it('verify combo not required with null value and no default', () => {
if (item) {
item.value = null;
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/property-table/editors/combo-editor/combo-editor.component.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/property-table/editors/combo-editor/combo-editor.component.ts
index 37ffb08ef7..ece7c9f794 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/property-table/editors/combo-editor/combo-editor.component.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/property-table/editors/combo-editor/combo-editor.component.ts
@@ -62,6 +62,8 @@ export class ComboEditor {
this.configuredValue = item.value;
} else if (item.descriptor.defaultValue != null) {
this.configuredValue = item.descriptor.defaultValue;
+ // Mark as dirty since we're using default value instead of the
saved value
+ this.shouldMarkDirty = true;
}
this.descriptor = item.descriptor;
@@ -69,13 +71,14 @@ export class ComboEditor {
this.savedValue = item.savedValue;
this.itemSet = true;
- this.initialAllowableValues();
+ this.initializeComponent();
}
@Input() set parameterConfig(parameterConfig: ParameterConfig) {
this.parameters = parameterConfig.parameters;
this.supportsParameters = parameterConfig.supportsParameters;
- this.initialAllowableValues();
+ this.parameterConfigSet = true;
+ this.initializeComponent();
}
@Input() width!: number;
@Input() readonly: boolean = false;
@@ -100,6 +103,8 @@ export class ComboEditor {
supportsParameters = false;
itemSet = false;
+ parameterConfigSet = false;
+ shouldMarkDirty = false;
configuredValue: string | null = null;
savedValue: string | null = null;
parameters: Parameter[] | null = null;
@@ -113,116 +118,128 @@ export class ComboEditor {
});
}
- initialAllowableValues(): void {
- if (this.itemSet) {
- this.itemLookup.clear();
- this.allowableValues = [];
- this.referencesParametersId = -1;
-
- let i = 0;
- let selectedItem: AllowableValueItem | null = null;
-
- if (!this.descriptor.required) {
- const noValue: AllowableValueItem = {
- id: i++,
- disabled: false,
- displayName: 'No value',
- value: null
- };
- this.itemLookup.set(noValue.id, noValue);
- this.allowableValues.push(noValue);
-
- if (noValue.value == this.configuredValue) {
- selectedItem = noValue;
- }
+ private initializeComponent(): void {
+ // Only initialize when both required inputs are set
+ if (this.itemSet && this.parameterConfigSet) {
+ this.initializeAllowableValues();
+
+ // Mark the form as dirty if we used a default value
+ if (this.shouldMarkDirty) {
+ this.comboEditorForm.markAsDirty();
+ this.shouldMarkDirty = false; // Reset flag
}
+ }
+ }
- if (this.descriptor.allowableValues) {
- const allowableValueItems: AllowableValueItem[] =
this.descriptor.allowableValues.map(
- (allowableValueEntity) => {
- const allowableValue: AllowableValueItem = {
- ...allowableValueEntity.allowableValue,
- id: i++,
- disabled:
- !allowableValueEntity.canRead &&
- allowableValueEntity.allowableValue.value !==
this.savedValue
- };
- this.itemLookup.set(allowableValue.id, allowableValue);
-
- if (allowableValue.value == this.configuredValue) {
- selectedItem = allowableValue;
- }
+ private initializeAllowableValues(): void {
+ this.itemLookup.clear();
+ this.allowableValues = [];
+ this.parameterAllowableValues = [];
+ this.referencesParametersId = -1;
+
+ let i = 0;
+ let selectedItem: AllowableValueItem | null = null;
+
+ if (!this.descriptor.required) {
+ const noValue: AllowableValueItem = {
+ id: i++,
+ disabled: false,
+ displayName: 'No value',
+ value: null
+ };
+ this.itemLookup.set(noValue.id, noValue);
+ this.allowableValues.push(noValue);
+
+ if (noValue.value == this.configuredValue) {
+ selectedItem = noValue;
+ }
+ }
- return allowableValue;
+ if (this.descriptor.allowableValues) {
+ const allowableValueItems: AllowableValueItem[] =
this.descriptor.allowableValues.map(
+ (allowableValueEntity) => {
+ const allowableValue: AllowableValueItem = {
+ ...allowableValueEntity.allowableValue,
+ id: i++,
+ disabled:
+ !allowableValueEntity.canRead &&
+ allowableValueEntity.allowableValue.value !==
this.savedValue
+ };
+ this.itemLookup.set(allowableValue.id, allowableValue);
+
+ if (allowableValue.value == this.configuredValue) {
+ selectedItem = allowableValue;
}
- );
- this.allowableValues.push(...allowableValueItems);
- }
- if (this.supportsParameters) {
- // parameters are supported so add the item to support showing
- // and hiding the parameter options select
- const referencesParameterOption: AllowableValueItem = {
- id: i++,
- disabled: false,
- 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);
+ return allowableValue;
}
+ );
+ this.allowableValues.push(...allowableValueItems);
+ }
- 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
- this.parameters.forEach((parameter) => {
- const parameterItem: AllowableValueItem = {
- id: i++,
- disabled: false,
- 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;
- }
- });
- this.parameterAllowableValues.sort((a, b) =>
- this.nifiCommon.compareString(a.displayName,
b.displayName)
- );
- // if combo still set to reference a parameter, set the
default value
- if (selectedItem?.id == this.referencesParametersId) {
-
this.comboEditorForm.get('parameterReference')?.setValue(this.configuredParameterId);
+ if (this.supportsParameters) {
+ // parameters are supported so add the item to support showing
+ // and hiding the parameter options select
+ const referencesParameterOption: AllowableValueItem = {
+ id: i++,
+ disabled: false,
+ 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);
+ }
+
+ 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
+ this.parameters.forEach((parameter) => {
+ const parameterItem: AllowableValueItem = {
+ id: i++,
+ disabled: false,
+ 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;
}
+ });
+ this.parameterAllowableValues.sort((a, b) =>
+ this.nifiCommon.compareString(a.displayName, b.displayName)
+ );
+ // if combo still set to reference a parameter, set the
default value
+ if (selectedItem?.id == this.referencesParametersId) {
+
this.comboEditorForm.get('parameterReference')?.setValue(this.configuredParameterId);
}
- } else {
- this.parameterAllowableValues = [];
}
+ } 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);
- }
+ 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);
}
}
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/property-table/editors/nf-editor/nf-editor.component.spec.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/property-table/editors/nf-editor/nf-editor.component.spec.ts
index e252af4679..7235b838b0 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/property-table/editors/nf-editor/nf-editor.component.spec.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/property-table/editors/nf-editor/nf-editor.component.spec.ts
@@ -181,4 +181,62 @@ describe('NfEditor', () => {
expect(mockNifiLanguagePackage.isValidParameter).toHaveBeenCalledWith('testParam');
expect(mockNifiLanguagePackage.isValidElFunction).toHaveBeenCalledWith('uuid');
});
+
+ it('should use default value and mark form dirty when item value is null',
() => {
+ const mockItem: PropertyItem = {
+ property: 'test.property',
+ descriptor: {
+ name: 'test.property',
+ displayName: 'Test Property',
+ description: 'A test property',
+ required: false,
+ sensitive: false,
+ dynamic: false,
+ supportsEl: false,
+ expressionLanguageScope: 'NONE',
+ dependencies: [],
+ defaultValue: 'default-test-value'
+ },
+ value: null,
+ id: 1,
+ triggerEdit: false,
+ deleted: false,
+ added: false,
+ dirty: false,
+ savedValue: 'some-other-value',
+ type: 'optional'
+ };
+
+ // Mock the form controls to track setValue and markAsDirty calls
+ const mockValueControl = {
+ setValue: jest.fn(),
+ addValidators: jest.fn(),
+ removeValidators: jest.fn(),
+ disable: jest.fn(),
+ enable: jest.fn()
+ };
+ const mockEmptyStringControl = {
+ setValue: jest.fn(),
+ value: false
+ };
+ const mockForm = {
+ get: jest.fn((control: string) => {
+ if (control === 'value') return mockValueControl;
+ if (control === 'setEmptyString') return
mockEmptyStringControl;
+ return null;
+ }),
+ markAsDirty: jest.fn()
+ };
+
+ component.nfEditorForm = mockForm as any;
+ component.item = mockItem;
+ component.parameterConfig = {
+ parameters: null,
+ supportsParameters: false
+ };
+
+ // Verify that the default value was set and form was marked dirty
+
expect(mockValueControl.setValue).toHaveBeenCalledWith('default-test-value');
+ expect(mockForm.markAsDirty).toHaveBeenCalled();
+ });
});
diff --git
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/property-table/editors/nf-editor/nf-editor.component.ts
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/property-table/editors/nf-editor/nf-editor.component.ts
index 2fecbf3846..32bc99245f 100644
---
a/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/property-table/editors/nf-editor/nf-editor.component.ts
+++
b/nifi-frontend/src/main/frontend/apps/nifi/src/app/ui/common/property-table/editors/nf-editor/nf-editor.component.ts
@@ -67,11 +67,19 @@ import { completionKeymap } from '@codemirror/autocomplete';
})
export class NfEditor {
@Input() set item(item: PropertyItem) {
+ let shouldMarkDirty = false;
+ let valueToSet = item.value;
+
if (item.descriptor.sensitive && item.value !== null) {
this.nfEditorForm.get('value')?.setValue('Sensitive value set');
this.showSensitiveHelperText = true;
} else {
- this.nfEditorForm.get('value')?.setValue(item.value);
+ // If the current value is null but there's a default value, use
the default
+ if (item.value == null && item.descriptor.defaultValue != null) {
+ valueToSet = item.descriptor.defaultValue;
+ shouldMarkDirty = true;
+ }
+ this.nfEditorForm.get('value')?.setValue(valueToSet);
}
if (item.descriptor.required) {
@@ -80,7 +88,7 @@ export class NfEditor {
this.nfEditorForm.get('value')?.removeValidators(Validators.required);
}
- const isEmptyString: boolean = item.value === '';
+ const isEmptyString: boolean = (valueToSet || item.value) === '';
this.nfEditorForm.get('setEmptyString')?.setValue(isEmptyString);
this.setEmptyStringChanged();
@@ -88,6 +96,11 @@ export class NfEditor {
this.itemSet = true;
this.initializeCodeMirror();
+
+ // Mark the form as dirty if we used a default value
+ if (shouldMarkDirty) {
+ this.nfEditorForm.markAsDirty();
+ }
}
@Input() set parameterConfig(parameterConfig: ParameterConfig) {