This is an automated email from the ASF dual-hosted git repository.
github-merge-queue[bot] pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/texera.git
The following commit(s) were added to refs/heads/main by this push:
new ba63cedf01 fix(frontend): allow null/undefined to pass enum validation
for optional operator properties (#5676)
ba63cedf01 is described below
commit ba63cedf0185dc1cd92fd4eec5f175a660639c02
Author: Ranjani Veena Belavadi <[email protected]>
AuthorDate: Fri Jul 31 13:15:50 2026 -0700
fix(frontend): allow null/undefined to pass enum validation for optional
operator properties (#5676)
### What changes were proposed in this PR?
Fix optional enum fields in operator properties.
When a dropdown is cleared, the value becomes null/undefined instead of
being removed, which caused validation to incorrectly fail for optional
fields.
The fix has two parts:
1. The validator now allows null/undefined to pass for optional fields
(c.value == null || mapSource.enum?.includes(c.value)) so no false error
is shown in the UI
2. onFormChanges removes the key entirely for optional fields with
null/undefined values before the data is saved, so the backend never
receives a bad value
Before: clearing an optional dropdown left { x: 10, y: null } in the
model, which failed validation and sent bad data to the backend.
After: clearing an optional dropdown results in { x: 10 } with the key
removed entirely — no error shown, clean data sent to backend.
<img width="1915" height="1050" alt="Animation"
src="https://github.com/user-attachments/assets/3e52fbd5-a25d-4f5a-b9c2-4eeec2c16f87"
/>
### Any related issues, documentation, discussions?
Fixes #3524
### How was this PR tested?
Manually tested on UI: recorded GIF showing optional Pattern field in
Bar Chart operator being cleared with no validation error and workflow
executing successfully.
Added 3 unit tests to operator-property-edit-frame.component.spec.ts
covering:
- null values are removed for optional fields
- null values are kept for required fields (so the error still shows)
- normal values are always kept regardless of required status
All existing tests continue to pass.
### Was this PR authored or co-authored using generative AI tooling?
Co-authored using Claude (Anthropic). The fix was reviewed and verified
by the author.
---
.../operator-property-edit-frame.component.spec.ts | 58 ++++++++++++++++++++++
.../operator-property-edit-frame.component.ts | 8 ++-
2 files changed, 64 insertions(+), 2 deletions(-)
diff --git
a/frontend/src/app/workspace/component/property-editor/operator-property-edit-frame/operator-property-edit-frame.component.spec.ts
b/frontend/src/app/workspace/component/property-editor/operator-property-edit-frame/operator-property-edit-frame.component.spec.ts
index 9bb72cea23..aba2811444 100644
---
a/frontend/src/app/workspace/component/property-editor/operator-property-edit-frame/operator-property-edit-frame.component.spec.ts
+++
b/frontend/src/app/workspace/component/property-editor/operator-property-edit-frame/operator-property-edit-frame.component.spec.ts
@@ -2088,6 +2088,64 @@ describe("OperatorPropertyEditFrameComponent", () => {
});
});
+ describe("onFormChanges null handling", () => {
+ it("should strip null values for optional fields", () => {
+ component.currentOperatorSchema = {
+ ...mockScanSourceSchema,
+ jsonSchema: { ...mockScanSourceSchema.jsonSchema, required:
["tableName"] },
+ };
+
+ let emittedEvent: Record<string, unknown> | undefined;
+ component.sourceFormChangeEventStream.subscribe(event => (emittedEvent =
event));
+
+ component.onFormChanges({ tableName: "table1", optionalField: null });
+
+ expect(emittedEvent).toEqual({ tableName: "table1" });
+ });
+
+ it("should keep null values for required fields", () => {
+ component.currentOperatorSchema = {
+ ...mockScanSourceSchema,
+ jsonSchema: { ...mockScanSourceSchema.jsonSchema, required:
["tableName"] },
+ };
+
+ let emittedEvent: Record<string, unknown> | undefined;
+ component.sourceFormChangeEventStream.subscribe(event => (emittedEvent =
event));
+
+ component.onFormChanges({ tableName: null, optionalField: "value" });
+
+ expect(emittedEvent).toEqual({ tableName: null, optionalField: "value"
});
+ });
+
+ it("should keep non-null values regardless of required status", () => {
+ component.currentOperatorSchema = {
+ ...mockScanSourceSchema,
+ jsonSchema: { ...mockScanSourceSchema.jsonSchema, required:
["tableName"] },
+ };
+
+ let emittedEvent: Record<string, unknown> | undefined;
+ component.sourceFormChangeEventStream.subscribe(event => (emittedEvent =
event));
+
+ component.onFormChanges({ tableName: "table1", optionalField: "set" });
+
+ expect(emittedEvent).toEqual({ tableName: "table1", optionalField: "set"
});
+ });
+
+ it("should strip undefined values for optional fields", () => {
+ component.currentOperatorSchema = {
+ ...mockScanSourceSchema,
+ jsonSchema: { ...mockScanSourceSchema.jsonSchema, required:
["tableName"] },
+ };
+
+ let emittedEvent: Record<string, unknown> | undefined;
+ component.sourceFormChangeEventStream.subscribe(event => (emittedEvent =
event));
+
+ component.onFormChanges({ tableName: "table1", optionalField: undefined
});
+
+ expect(emittedEvent).toEqual({ tableName: "table1" });
+ });
+ });
+
describe("modify-operator-logic gating", () => {
it("allowModifyOperatorLogic re-enables editing", () => {
fixture.detectChanges();
diff --git
a/frontend/src/app/workspace/component/property-editor/operator-property-edit-frame/operator-property-edit-frame.component.ts
b/frontend/src/app/workspace/component/property-editor/operator-property-edit-frame/operator-property-edit-frame.component.ts
index 128f674645..f4b356c474 100644
---
a/frontend/src/app/workspace/component/property-editor/operator-property-edit-frame/operator-property-edit-frame.component.ts
+++
b/frontend/src/app/workspace/component/property-editor/operator-property-edit-frame/operator-property-edit-frame.component.ts
@@ -544,7 +544,11 @@ export class OperatorPropertyEditFrameComponent implements
OnInit, OnChanges, On
* @param event
*/
onFormChanges(event: Record<string, unknown>): void {
- this.sourceFormChangeEventStream.next(event);
+ const requiredFields = this.currentOperatorSchema?.jsonSchema?.required ??
[];
+ const cleanedEvent = Object.fromEntries(
+ Object.entries(event).filter(([key, value]) => value != null ||
requiredFields.includes(key))
+ );
+ this.sourceFormChangeEventStream.next(cleanedEvent);
}
/**
@@ -1118,7 +1122,7 @@ export class OperatorPropertyEditFrameComponent
implements OnInit, OnChanges, On
if (isDefined(mapSource.enum)) {
mappedField.validators.inEnum = {
- expression: (c: AbstractControl) => mapSource.enum?.includes(c.value
?? ""),
+ expression: (c: AbstractControl) => c.value == null ||
mapSource.enum?.includes(c.value),
message: (error: any, field: FormlyFieldConfig) =>
`"${field.formControl?.value}" is no longer a valid option`,
};