This is an automated email from the ASF dual-hosted git repository. github-merge-queue[bot] pushed a commit to branch gh-readonly-queue/main/pr-6810-110a54c151dc692a0720235f3951c41e60ec363a in repository https://gitbox.apache.org/repos/asf/texera.git
commit 1c411dedaad2bb8bd8fd4f5301843ae1f2f1c781 Author: Kary Zheng <[email protected]> AuthorDate: Wed Jul 22 21:34:19 2026 -0700 fix(QuiverPlot, RangeSlider): retarget numeric attributeTypeRules to real fields (#6810) ### What changes were proposed in this PR? Both `QuiverPlotOpDesc` and `RangeSliderOpDesc` carry a class-level `@JsonSchemaInject` `attributeTypeRules` block that constrains a field named **`value`** to numeric types — but neither operator declares a `value` field, so the rule matches nothing and **no type constraint is applied**. The frontend column pickers therefore allow columns of any type for fields the operator can only handle as numeric, which then fails at chart time instead of being prevented up front. **QuiverPlot** — fields are `x`, `y`, `u`, `v` (all required, no `value`). All four are used as numeric vector coordinates in `ff.create_quiver(x, y, u, v)`, and the generated code even runtime-checks them with `isinstance(value, (int, float))`. So all four must be numeric. **RangeSlider** — fields are `Y-axis` and `X-axis` (no `value`). The y-axis column is aggregated (`groupby(X-axis)[Y-axis].mean()/.sum()`), so it must be numeric; the x-axis is only a grouping key and may be any type. **Fix:** retarget each rule to the real `@JsonProperty` field name(s): ```diff # QuiverPlot - "value": { - "enum": ["integer", "long", "double"] - } + "x": { "enum": ["integer", "long", "double"] }, + "y": { "enum": ["integer", "long", "double"] }, + "u": { "enum": ["integer", "long", "double"] }, + "v": { "enum": ["integer", "long", "double"] } # RangeSlider - "value": { + "Y-axis": { "enum": ["integer", "long", "double"] } ``` ### Any related issues, documentation, discussions? Closes #6795 ### How was this PR tested? Added a regression test to each spec that reads the class-level `@JsonSchemaInject` `json()` via reflection and asserts the `attributeTypeRules` keys are the real fields (`x`/`y`/`u`/`v`; `Y-axis`) — not `value` — and that each is constrained to the numeric enum. Both fail on `main` (the key set is `{"value"}`) and pass with this change. ``` sbt "WorkflowOperator/testOnly org.apache.texera.amber.operator.visualization.quiverPlot.QuiverPlotOpDescSpec org.apache.texera.amber.operator.visualization.rangeSlider.RangeSliderOpDescSpec" ... Tests: succeeded 12, failed 0, canceled 0, ignored 0, pending 0 All tests passed. ``` ### Was this PR authored or co-authored using generative AI tooling? Generated-by: Claude Code (Claude Opus 4.8) 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: Kary Zheng <[email protected]> Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]> Co-authored-by: Copilot Autofix powered by AI <[email protected]> --- .../visualization/quiverPlot/QuiverPlotOpDesc.scala | 11 ++++++++++- .../rangeSlider/RangeSliderOpDesc.scala | 5 +++-- .../quiverPlot/QuiverPlotOpDescSpec.scala | 21 +++++++++++++++++++++ .../rangeSlider/RangeSliderOpDescSpec.scala | 20 ++++++++++++++++++++ 4 files changed, 54 insertions(+), 3 deletions(-) diff --git a/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/visualization/quiverPlot/QuiverPlotOpDesc.scala b/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/visualization/quiverPlot/QuiverPlotOpDesc.scala index 4903c00dd8..991ba362a1 100644 --- a/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/visualization/quiverPlot/QuiverPlotOpDesc.scala +++ b/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/visualization/quiverPlot/QuiverPlotOpDesc.scala @@ -35,7 +35,16 @@ import javax.validation.constraints.NotNull @JsonSchemaInject(json = """ { "attributeTypeRules": { - "value": { + "x": { + "enum": ["integer", "long", "double"] + }, + "y": { + "enum": ["integer", "long", "double"] + }, + "u": { + "enum": ["integer", "long", "double"] + }, + "v": { "enum": ["integer", "long", "double"] } } diff --git a/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/visualization/rangeSlider/RangeSliderOpDesc.scala b/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/visualization/rangeSlider/RangeSliderOpDesc.scala index 051c0ba2cc..6b1425708d 100644 --- a/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/visualization/rangeSlider/RangeSliderOpDesc.scala +++ b/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/visualization/rangeSlider/RangeSliderOpDesc.scala @@ -32,11 +32,12 @@ import org.apache.texera.amber.pybuilder.PythonTemplateBuilder import javax.validation.constraints.NotNull -// type constraint: value can only be numeric +// type constraint: Y-axis is aggregated (mean/sum), so it must be numeric; +// X-axis is only a grouping key and may be any type. @JsonSchemaInject(json = """ { "attributeTypeRules": { - "value": { + "Y-axis": { "enum": ["integer", "long", "double"] } } diff --git a/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/visualization/quiverPlot/QuiverPlotOpDescSpec.scala b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/visualization/quiverPlot/QuiverPlotOpDescSpec.scala index 4331139f78..ee11e3a54e 100644 --- a/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/visualization/quiverPlot/QuiverPlotOpDescSpec.scala +++ b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/visualization/quiverPlot/QuiverPlotOpDescSpec.scala @@ -23,11 +23,13 @@ import org.apache.texera.amber.core.tuple.{AttributeType, Schema} import org.apache.texera.amber.operator.LogicalOp import org.apache.texera.amber.operator.metadata.OperatorGroupConstants import org.apache.texera.amber.util.JSONUtils.objectMapper +import com.kjetland.jackson.jsonSchema.annotations.JsonSchemaInject import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers import java.nio.charset.StandardCharsets import java.util.Base64 +import scala.jdk.CollectionConverters._ class QuiverPlotOpDescSpec extends AnyFlatSpec with Matchers { @@ -96,4 +98,23 @@ class QuiverPlotOpDescSpec extends AnyFlatSpec with Matchers { q.u shouldBe "vu" q.v shouldBe "vv" } + + "QuiverPlotOpDesc @JsonSchemaInject" should + "constrain the real coordinate fields (x/y/u/v) to numeric" in { + // The rule keys must be actual @JsonProperty names; a key of "value" (no such + // field) matches nothing, so no numeric constraint reaches the column pickers. + val ann = classOf[QuiverPlotOpDesc].getAnnotation(classOf[JsonSchemaInject]) + ann should not be null + val rules = objectMapper.readTree(ann.json).path("attributeTypeRules") + rules.fieldNames().asScala.toSet shouldBe Set("x", "y", "u", "v") + rules.fieldNames().asScala.foreach { f => + rules + .path(f) + .path("enum") + .elements() + .asScala + .map(_.asText()) + .toSet shouldBe Set("integer", "long", "double") + } + } } diff --git a/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/visualization/rangeSlider/RangeSliderOpDescSpec.scala b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/visualization/rangeSlider/RangeSliderOpDescSpec.scala index d601572c32..706c39a289 100644 --- a/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/visualization/rangeSlider/RangeSliderOpDescSpec.scala +++ b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/visualization/rangeSlider/RangeSliderOpDescSpec.scala @@ -23,9 +23,12 @@ import org.apache.texera.amber.core.tuple.{AttributeType, Schema} import org.apache.texera.amber.operator.LogicalOp import org.apache.texera.amber.operator.metadata.OperatorGroupConstants import org.apache.texera.amber.util.JSONUtils.objectMapper +import com.kjetland.jackson.jsonSchema.annotations.JsonSchemaInject import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers +import scala.jdk.CollectionConverters._ + class RangeSliderOpDescSpec extends AnyFlatSpec with Matchers { "RangeSliderOpDesc.operatorInfo" should @@ -74,4 +77,21 @@ class RangeSliderOpDescSpec extends AnyFlatSpec with Matchers { r.yAxis shouldBe "sales" r.duplicateType shouldBe RangeSliderHandleDuplicateFunction.MEAN } + + "RangeSliderOpDesc @JsonSchemaInject" should + "constrain the aggregated Y-axis to numeric and leave X-axis unconstrained" in { + // The rule key must be an actual @JsonProperty name; a key of "value" (no such + // field) matches nothing, so no numeric constraint reaches the column pickers. + val ann = classOf[RangeSliderOpDesc].getAnnotation(classOf[JsonSchemaInject]) + ann should not be null + val rules = objectMapper.readTree(ann.json).path("attributeTypeRules") + rules.fieldNames().asScala.toSet shouldBe Set("Y-axis") + rules + .path("Y-axis") + .path("enum") + .elements() + .asScala + .map(_.asText()) + .toSet shouldBe Set("integer", "long", "double") + } }
