AnzhiZhang commented on issue #3192:
URL: https://github.com/apache/texera/issues/3192#issuecomment-3212983673

   There are two parts of this issue.
   
   ## Button Not Updated After Validation
   
   We can see from the screenshot also, by reproducing this issue, the operator 
appears red, but the button still shows valid.
   
   This section handles workflow validation and posts errors to 
`workflowValidationErrorStream`
   
   
https://github.com/apache/texera/blob/c7fcd9f4278f850e3c1456866ba6f25f85a8f489/core/gui/src/app/workspace/service/validation/validation-workflow.service.ts#L137-L145
   
   `workflowValidationErrorStream` is subscribed here
   
   
https://github.com/apache/texera/blob/c7fcd9f4278f850e3c1456866ba6f25f85a8f489/core/gui/src/app/workspace/component/menu/menu.component.ts#L178-L186
   
   The message will be posted only when the workflow validation result is valid.
   
   ## `required` in JSON Schema Does Not Apply to Empty String in Frontend 
Validation
   
   We are using Ajv in the frontend to validate JSON schema.
   
   This is the JSON schema of 
[BarChartOpDesc](https://github.com/apache/texera/blob/main/core/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/visualization/barChart/BarChartOpDesc.scala).
   
   <details>
   <summary>JSON Schema</summary>
   
   ```json
   {
     "$schema" : "http://json-schema.org/draft-07/schema#";,
     "type" : "object",
     "additionalProperties" : false,
     "attributeTypeRules" : {
       "value" : {
         "enum" : [ "integer", "long", "double" ]
       }
     },
     "properties" : {
       "dummyPropertyList" : {
         "propertyOrder" : 1,
         "nullable" : true,
         "type" : "array",
         "items" : {
           "$ref" : "#/definitions/DummyProperties"
         },
         "description" : "Add dummy property if needed",
         "title" : "Dummy Property List"
       },
       "fields" : {
         "propertyOrder" : 4,
         "type" : "string",
         "description" : "Visualize categorical data in a Bar Chart",
         "title" : "Fields",
         "autofill" : "attributeName",
         "autofillAttributeOnPort" : 0
       },
       "categoryColumn" : {
         "propertyOrder" : 5,
         "nullable" : true,
         "type" : "string",
         "default" : "No Selection",
         "description" : "Optional - Select a column to Color Code the 
Categories",
         "title" : "Category Column",
         "autofill" : "attributeName",
         "autofillAttributeOnPort" : 0
       },
       "horizontalOrientation" : {
         "propertyOrder" : 6,
         "type" : "boolean",
         "default" : false,
         "description" : "Orientation Style",
         "title" : "Horizontal Orientation"
       },
       "pattern" : {
         "propertyOrder" : 7,
         "nullable" : true,
         "type" : "string",
         "description" : "Add texture to the chart based on an attribute",
         "title" : "Pattern",
         "autofill" : "attributeName",
         "autofillAttributeOnPort" : 0
       },
       "value" : {
         "propertyOrder" : 10,
         "type" : "string",
         "description" : "The value associated with each category",
         "title" : "Value Column",
         "autofill" : "attributeName",
         "autofillAttributeOnPort" : 0
       }
     },
     "required" : [ "fields", "horizontalOrientation", "value" ],
     "options" : {
       "multiple_editor_select_via_property" : {
         "property" : "operatorType",
         "value" : "BarChart"
       }
     },
     "definitions" : {
       "DummyProperties" : {
         "type" : "object",
         "additionalProperties" : false,
         "properties" : {
           "dummyProperty" : {
             "propertyOrder" : 1,
             "nullable" : true,
             "type" : "string",
             "title" : "Dummy Property"
           },
           "dummyValue" : {
             "propertyOrder" : 2,
             "nullable" : true,
             "type" : "string",
             "title" : "Dummy Value"
           }
         }
       },
       "PortDescription" : {
         "type" : "object",
         "additionalProperties" : false,
         "properties" : {
           "portID" : {
             "propertyOrder" : 1,
             "nullable" : true,
             "type" : "string",
             "title" : "Port ID"
           },
           "displayName" : {
             "propertyOrder" : 2,
             "nullable" : true,
             "type" : "string",
             "title" : "Display Name"
           },
           "allowMultiInputs" : {
             "propertyOrder" : 3,
             "type" : "boolean",
             "title" : "Allow Multi Inputs"
           },
           "isDynamicPort" : {
             "propertyOrder" : 4,
             "type" : "boolean",
             "title" : "Is Dynamic Port"
           },
           "partitionRequirement" : {
             "propertyOrder" : 5,
             "nullable" : true,
             "oneOf" : [ {
               "$ref" : "#/definitions/HashPartition"
             }, {
               "$ref" : "#/definitions/RangePartition"
             }, {
               "$ref" : "#/definitions/SinglePartition"
             }, {
               "$ref" : "#/definitions/BroadcastPartition"
             }, {
               "$ref" : "#/definitions/UnknownPartition"
             } ],
             "title" : "Partition Requirement"
           },
           "dependencies" : {
             "propertyOrder" : 6,
             "nullable" : true,
             "type" : "array",
             "items" : {
               "$ref" : "#/definitions/Object"
             },
             "title" : "Dependencies"
           }
         },
         "required" : [ "allowMultiInputs", "isDynamicPort" ]
       },
       "HashPartition" : {
         "type" : "object",
         "additionalProperties" : false,
         "properties" : {
           "type" : {
             "type" : "string",
             "enum" : [ "hash" ],
             "default" : "hash",
             "options" : {
               "hidden" : true
             }
           },
           "hashAttributeNames" : {
             "propertyOrder" : 1,
             "nullable" : true,
             "type" : "array",
             "items" : {
               "type" : "string"
             },
             "title" : "Hash Attribute Names"
           }
         },
         "title" : "hash",
         "required" : [ "type" ],
         "options" : {
           "multiple_editor_select_via_property" : {
             "property" : "type",
             "value" : "hash"
           }
         }
       },
       "RangePartition" : {
         "type" : "object",
         "additionalProperties" : false,
         "properties" : {
           "type" : {
             "type" : "string",
             "enum" : [ "range" ],
             "default" : "range",
             "options" : {
               "hidden" : true
             }
           },
           "rangeAttributeNames" : {
             "propertyOrder" : 1,
             "nullable" : true,
             "type" : "array",
             "items" : {
               "type" : "string"
             },
             "title" : "Range Attribute Names"
           },
           "rangeMin" : {
             "propertyOrder" : 2,
             "type" : "integer",
             "title" : "Range Min"
           },
           "rangeMax" : {
             "propertyOrder" : 3,
             "type" : "integer",
             "title" : "Range Max"
           }
         },
         "title" : "range",
         "required" : [ "type", "rangeMin", "rangeMax" ],
         "options" : {
           "multiple_editor_select_via_property" : {
             "property" : "type",
             "value" : "range"
           }
         }
       },
       "SinglePartition" : {
         "type" : "object",
         "additionalProperties" : false,
         "properties" : {
           "type" : {
             "type" : "string",
             "enum" : [ "single" ],
             "default" : "single",
             "options" : {
               "hidden" : true
             }
           }
         },
         "title" : "single",
         "required" : [ "type" ],
         "options" : {
           "multiple_editor_select_via_property" : {
             "property" : "type",
             "value" : "single"
           }
         }
       },
       "BroadcastPartition" : {
         "type" : "object",
         "additionalProperties" : false,
         "properties" : {
           "type" : {
             "type" : "string",
             "enum" : [ "broadcast" ],
             "default" : "broadcast",
             "options" : {
               "hidden" : true
             }
           }
         },
         "title" : "broadcast",
         "required" : [ "type" ],
         "options" : {
           "multiple_editor_select_via_property" : {
             "property" : "type",
             "value" : "broadcast"
           }
         }
       },
       "UnknownPartition" : {
         "type" : "object",
         "additionalProperties" : false,
         "properties" : {
           "type" : {
             "type" : "string",
             "enum" : [ "none" ],
             "default" : "none",
             "options" : {
               "hidden" : true
             }
           }
         },
         "title" : "none",
         "required" : [ "type" ],
         "options" : {
           "multiple_editor_select_via_property" : {
             "property" : "type",
             "value" : "none"
           }
         }
       },
       "Object" : {
         "type" : "object",
         "additionalProperties" : false,
         "properties" : { }
       }
     }
   }
   ```
   
   </details>
   
   Required fields are included in the `required` list. However, if we test 
this schema in the frontend (a simple example).
   
   ```ts
   import Ajv from "ajv";
   
   const ajv = new Ajv({ allErrors: true, strict: false });
   const schema = {
     type: "object",
     properties: {
       test: {
         type: "string",
       },
     },
     required: ["test"],
   };
   
   const json = {
     test: "",
   };
   
   // true
   console.log(ajv.validate(schema, json));
   
   const json2 = {};
   
   // false
   console.log(ajv.validate(schema, json2));
   ```
   
   It only works for a real null property, not an empty string.
   
   The debug information confirms this finding.
   
   <img width="2360" height="1030" alt="Image" 
src="https://github.com/user-attachments/assets/9d7e1876-9dfe-40b2-862c-0dadc16fcab7";
 />
   
   <img width="2384" height="812" alt="Image" 
src="https://github.com/user-attachments/assets/8e0f026c-37a7-4070-8516-347589559f86";
 />
   
   We will need to add `@NotNull` annotation from 
`javax.validation.constraints.NotNull` to introduce `"minLength" : 1,` 
requirement to the JSON Schema.
   
   ```java
   @JsonProperty(value = "value", required = true)
   @JsonSchemaTitle("Value Column")
   @JsonPropertyDescription("The value associated with each category")
   @AutofillAttributeName
   @NotNull(message = "Value column cannot be empty")
   var value: String = ""
   ```
   
   <details>
   <summary>New JSON Schema</summary>
   
   ```json
   {
     "$schema" : "http://json-schema.org/draft-07/schema#";,
     "type" : "object",
     "additionalProperties" : false,
     "attributeTypeRules" : {
       "value" : {
         "enum" : [ "integer", "long", "double" ]
       }
     },
     "properties" : {
       "dummyPropertyList" : {
         "propertyOrder" : 1,
         "nullable" : true,
         "type" : "array",
         "items" : {
           "$ref" : "#/definitions/DummyProperties"
         },
         "description" : "Add dummy property if needed",
         "title" : "Dummy Property List"
       },
       "fields" : {
         "propertyOrder" : 4,
         "type" : "string",
         "minLength" : 1,
         "description" : "Visualize categorical data in a Bar Chart",
         "title" : "Fields",
         "autofill" : "attributeName",
         "autofillAttributeOnPort" : 0
       },
       "categoryColumn" : {
         "propertyOrder" : 5,
         "type" : "string",
         "default" : "No Selection",
         "minLength" : 1,
         "description" : "Optional - Select a column to Color Code the 
Categories",
         "title" : "Category Column",
         "autofill" : "attributeName",
         "autofillAttributeOnPort" : 0
       },
       "horizontalOrientation" : {
         "propertyOrder" : 6,
         "type" : "boolean",
         "default" : false,
         "description" : "Orientation Style",
         "title" : "Horizontal Orientation"
       },
       "pattern" : {
         "propertyOrder" : 7,
         "type" : "string",
         "minLength" : 1,
         "description" : "Add texture to the chart based on an attribute",
         "title" : "Pattern",
         "autofill" : "attributeName",
         "autofillAttributeOnPort" : 0
       },
       "value" : {
         "propertyOrder" : 10,
         "type" : "string",
         "minLength" : 1,
         "description" : "The value associated with each category",
         "title" : "Value Column",
         "autofill" : "attributeName",
         "autofillAttributeOnPort" : 0
       }
     },
     "required" : [ "fields", "categoryColumn", "horizontalOrientation", 
"pattern", "value" ],
     "options" : {
       "multiple_editor_select_via_property" : {
         "property" : "operatorType",
         "value" : "BarChart"
       }
     },
     "definitions" : {
       "DummyProperties" : {
         "type" : "object",
         "additionalProperties" : false,
         "properties" : {
           "dummyProperty" : {
             "propertyOrder" : 1,
             "nullable" : true,
             "type" : "string",
             "title" : "Dummy Property"
           },
           "dummyValue" : {
             "propertyOrder" : 2,
             "nullable" : true,
             "type" : "string",
             "title" : "Dummy Value"
           }
         }
       },
       "PortDescription" : {
         "type" : "object",
         "additionalProperties" : false,
         "properties" : {
           "portID" : {
             "propertyOrder" : 1,
             "nullable" : true,
             "type" : "string",
             "title" : "Port ID"
           },
           "displayName" : {
             "propertyOrder" : 2,
             "nullable" : true,
             "type" : "string",
             "title" : "Display Name"
           },
           "allowMultiInputs" : {
             "propertyOrder" : 3,
             "type" : "boolean",
             "title" : "Allow Multi Inputs"
           },
           "isDynamicPort" : {
             "propertyOrder" : 4,
             "type" : "boolean",
             "title" : "Is Dynamic Port"
           },
           "partitionRequirement" : {
             "propertyOrder" : 5,
             "nullable" : true,
             "oneOf" : [ {
               "$ref" : "#/definitions/HashPartition"
             }, {
               "$ref" : "#/definitions/RangePartition"
             }, {
               "$ref" : "#/definitions/SinglePartition"
             }, {
               "$ref" : "#/definitions/BroadcastPartition"
             }, {
               "$ref" : "#/definitions/UnknownPartition"
             } ],
             "title" : "Partition Requirement"
           },
           "dependencies" : {
             "propertyOrder" : 6,
             "nullable" : true,
             "type" : "array",
             "items" : {
               "$ref" : "#/definitions/Object"
             },
             "title" : "Dependencies"
           }
         },
         "required" : [ "allowMultiInputs", "isDynamicPort" ]
       },
       "HashPartition" : {
         "type" : "object",
         "additionalProperties" : false,
         "properties" : {
           "type" : {
             "type" : "string",
             "enum" : [ "hash" ],
             "default" : "hash",
             "options" : {
               "hidden" : true
             }
           },
           "hashAttributeNames" : {
             "propertyOrder" : 1,
             "nullable" : true,
             "type" : "array",
             "items" : {
               "type" : "string"
             },
             "title" : "Hash Attribute Names"
           }
         },
         "title" : "hash",
         "required" : [ "type" ],
         "options" : {
           "multiple_editor_select_via_property" : {
             "property" : "type",
             "value" : "hash"
           }
         }
       },
       "RangePartition" : {
         "type" : "object",
         "additionalProperties" : false,
         "properties" : {
           "type" : {
             "type" : "string",
             "enum" : [ "range" ],
             "default" : "range",
             "options" : {
               "hidden" : true
             }
           },
           "rangeAttributeNames" : {
             "propertyOrder" : 1,
             "nullable" : true,
             "type" : "array",
             "items" : {
               "type" : "string"
             },
             "title" : "Range Attribute Names"
           },
           "rangeMin" : {
             "propertyOrder" : 2,
             "type" : "integer",
             "title" : "Range Min"
           },
           "rangeMax" : {
             "propertyOrder" : 3,
             "type" : "integer",
             "title" : "Range Max"
           }
         },
         "title" : "range",
         "required" : [ "type", "rangeMin", "rangeMax" ],
         "options" : {
           "multiple_editor_select_via_property" : {
             "property" : "type",
             "value" : "range"
           }
         }
       },
       "SinglePartition" : {
         "type" : "object",
         "additionalProperties" : false,
         "properties" : {
           "type" : {
             "type" : "string",
             "enum" : [ "single" ],
             "default" : "single",
             "options" : {
               "hidden" : true
             }
           }
         },
         "title" : "single",
         "required" : [ "type" ],
         "options" : {
           "multiple_editor_select_via_property" : {
             "property" : "type",
             "value" : "single"
           }
         }
       },
       "BroadcastPartition" : {
         "type" : "object",
         "additionalProperties" : false,
         "properties" : {
           "type" : {
             "type" : "string",
             "enum" : [ "broadcast" ],
             "default" : "broadcast",
             "options" : {
               "hidden" : true
             }
           }
         },
         "title" : "broadcast",
         "required" : [ "type" ],
         "options" : {
           "multiple_editor_select_via_property" : {
             "property" : "type",
             "value" : "broadcast"
           }
         }
       },
       "UnknownPartition" : {
         "type" : "object",
         "additionalProperties" : false,
         "properties" : {
           "type" : {
             "type" : "string",
             "enum" : [ "none" ],
             "default" : "none",
             "options" : {
               "hidden" : true
             }
           }
         },
         "title" : "none",
         "required" : [ "type" ],
         "options" : {
           "multiple_editor_select_via_property" : {
             "property" : "type",
             "value" : "none"
           }
         }
       },
       "Object" : {
         "type" : "object",
         "additionalProperties" : false,
         "properties" : { }
       }
     }
   }
   ```
   
   </details>
   
   ```ts
   const schema2 = {
     type: "object",
     properties: {
       test: {
         type: "string",
         minLength : 1,
       },
     },
     required: ["test"],
   };
   
   // false
   console.log(ajv.validate(schema2, json));
   ```
   
   See also:
   
   - #3656 
   - https://github.com/ajv-validator/ajv/issues/885


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to