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]
