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`,
         };

Reply via email to