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 71faf440de feat(visualization): retarget attributeTypeRules at the
properties they name (#7249)
71faf440de is described below
commit 71faf440de7936d07451d214dbd5777e6906b949
Author: Kary Zheng <[email protected]>
AuthorDate: Wed Aug 12 21:46:55 2026 +0000
feat(visualization): retarget attributeTypeRules at the properties they
name (#7249)
### What changes were proposed in this PR?
Four `attributeTypeRules` name keys that match no property, so the
property editor's `findAttributeType` returns `undefined` and
`checkConstraint` returns without checking anything.
`LineConfig` used the Scala field names rather than the `@JsonProperty`
names, and `ScatterMatrixChartOpDesc` named `value` where the property
is `Selected Attributes`; both are retargeted at the property they
meant. `Scatter3dChartOpDesc` and `FunnelPlotOpDesc` constrained a
`title` that neither operator declares, and did so with the bare string
`"string"` rather than an object, so even under a correct key
`constraint.enum` / `const` / `allOf` would all be undefined and the
check would still no-op; those two rules are removed rather than
invented anew.
### Why are the changes needed?
A line chart's x and y axes accept string columns today despite
declaring `["integer", "long", "double"]`, and the same holds for the
scatter matrix's dimensions. `BandConfig` extends `LineConfig` and
inherited the same dead rule. Nothing reports a key that names no
property, so the rules read as enforced while enforcing nothing.
### Any related issues, documentation, discussions?
Closes #7210
### How was this PR tested?
`WorkflowOperator/compile`, `WorkflowOperator/scalafmtCheckAll`, and the
four operators' existing descriptor specs (24 tests, all passing).
The new `AttributeTypeRuleTargetSpec` guards the class of mistake
repo-wide rather than just the four sites fixed here: it walks every
registered operator's generated schema and fails if an
`attributeTypeRules` key names no declared property, or if a rule is not
stated as an object.
### Does this PR introduce any user-facing change?
Yes. Selecting a non-numeric column for a line chart's axes or the
scatter matrix's Selected Attributes now shows the type warning the rule
always intended. Nothing changes for a numeric column, and removing the
two `title` rules changes nothing at all, since they never applied.
### Was this PR authored or co-authored using generative AI tooling?
Generated-by: Claude Code (Claude Opus 5)
---------
Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
Co-authored-by: Xuan Gu <[email protected]>
---
.../ScatterMatrixChartOpDesc.scala | 5 +-
.../funnelPlot/FunnelPlotOpDesc.scala | 9 +-
.../visualization/lineChart/LineConfig.scala | 8 +-
.../scatter3DChart/Scatter3dChartOpDesc.scala | 9 +-
.../metadata/AttributeTypeRuleTargetSpec.scala | 111 +++++++++++++++++++++
5 files changed, 122 insertions(+), 20 deletions(-)
diff --git
a/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/visualization/ScatterMatrixChart/ScatterMatrixChartOpDesc.scala
b/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/visualization/ScatterMatrixChart/ScatterMatrixChartOpDesc.scala
index e6397d1877..f30b5cee90 100644
---
a/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/visualization/ScatterMatrixChart/ScatterMatrixChartOpDesc.scala
+++
b/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/visualization/ScatterMatrixChart/ScatterMatrixChartOpDesc.scala
@@ -34,10 +34,13 @@ import
org.apache.texera.amber.operator.metadata.{OperatorGroupConstants, Operat
import org.apache.texera.amber.pybuilder.PythonTemplateBuilder
import javax.validation.constraints.{NotEmpty, NotNull}
+// Type constraint: every axis of the matrix is plotted numerically. The key
is the
+// PROPERTY name -- the `@JsonProperty` value, which is what the property
editor looks
+// up -- and this operator's is "Selected Attributes"; keyed "value" it
matched nothing.
@JsonSchemaInject(json = """
{
"attributeTypeRules": {
- "value": {
+ "Selected Attributes": {
"enum": ["integer", "long", "double"]
}
}
diff --git
a/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/visualization/funnelPlot/FunnelPlotOpDesc.scala
b/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/visualization/funnelPlot/FunnelPlotOpDesc.scala
index cd9f38619d..eb83ed5000 100644
---
a/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/visualization/funnelPlot/FunnelPlotOpDesc.scala
+++
b/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/visualization/funnelPlot/FunnelPlotOpDesc.scala
@@ -20,7 +20,7 @@
package org.apache.texera.amber.operator.visualization.funnelPlot
import com.fasterxml.jackson.annotation.{JsonProperty, JsonPropertyDescription}
-import com.kjetland.jackson.jsonSchema.annotations.{JsonSchemaInject,
JsonSchemaTitle}
+import com.kjetland.jackson.jsonSchema.annotations.JsonSchemaTitle
import org.apache.texera.amber.core.tuple.{AttributeType, Schema}
import
org.apache.texera.amber.pybuilder.PythonTemplateBuilder.PythonTemplateBuilderStringContext
import org.apache.texera.amber.pybuilder.PyStringTypes.EncodableString
@@ -31,13 +31,6 @@ import
org.apache.texera.amber.operator.metadata.{OperatorGroupConstants, Operat
import org.apache.texera.amber.pybuilder.PythonTemplateBuilder
import javax.validation.constraints.NotNull
-@JsonSchemaInject(json = """
-{
- "attributeTypeRules": {
- "title": "string"
- }
-}
-""")
class FunnelPlotOpDesc extends PythonOperatorDescriptor {
@JsonProperty(required = true)
diff --git
a/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/visualization/lineChart/LineConfig.scala
b/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/visualization/lineChart/LineConfig.scala
index 09f682e12e..82b9210c98 100644
---
a/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/visualization/lineChart/LineConfig.scala
+++
b/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/visualization/lineChart/LineConfig.scala
@@ -26,14 +26,16 @@ import
org.apache.texera.amber.operator.metadata.annotations.AutofillAttributeNa
import javax.validation.constraints.NotNull
-//type constraint: value can only be numeric
+// Type constraint: both axes can only be numeric. The keys are the PROPERTY
names --
+// the `@JsonProperty` values, which is what the property editor looks up --
not the
+// Scala field names; keyed by the latter the rule matched nothing.
@JsonSchemaInject(json = """
{
"attributeTypeRules": {
- "yValue": {
+ "y": {
"enum": ["integer", "long", "double"]
},
- "xValue": {
+ "x": {
"enum": ["integer", "long", "double"]
}
}
diff --git
a/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/visualization/scatter3DChart/Scatter3dChartOpDesc.scala
b/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/visualization/scatter3DChart/Scatter3dChartOpDesc.scala
index e9976f56e3..c86d9e3cc6 100644
---
a/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/visualization/scatter3DChart/Scatter3dChartOpDesc.scala
+++
b/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/visualization/scatter3DChart/Scatter3dChartOpDesc.scala
@@ -20,7 +20,7 @@
package org.apache.texera.amber.operator.visualization.scatter3DChart
import com.fasterxml.jackson.annotation.{JsonProperty, JsonPropertyDescription}
-import com.kjetland.jackson.jsonSchema.annotations.{JsonSchemaInject,
JsonSchemaTitle}
+import com.kjetland.jackson.jsonSchema.annotations.JsonSchemaTitle
import org.apache.texera.amber.core.tuple.{AttributeType, Schema}
import
org.apache.texera.amber.pybuilder.PythonTemplateBuilder.PythonTemplateBuilderStringContext
import org.apache.texera.amber.pybuilder.PyStringTypes.EncodableString
@@ -31,13 +31,6 @@ import
org.apache.texera.amber.operator.metadata.{OperatorGroupConstants, Operat
import org.apache.texera.amber.pybuilder.PythonTemplateBuilder
import javax.validation.constraints.NotNull
-@JsonSchemaInject(json = """
-{
- "attributeTypeRules": {
- "title": "string"
- }
-}
-""")
class Scatter3dChartOpDesc extends PythonOperatorDescriptor {
@JsonProperty(value = "x", required = true)
@JsonSchemaTitle("X Column")
diff --git
a/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/metadata/AttributeTypeRuleTargetSpec.scala
b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/metadata/AttributeTypeRuleTargetSpec.scala
new file mode 100644
index 0000000000..0bd4b2b8f1
--- /dev/null
+++
b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/metadata/AttributeTypeRuleTargetSpec.scala
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.texera.amber.operator.metadata
+
+import com.fasterxml.jackson.databind.JsonNode
+import org.scalatest.flatspec.AnyFlatSpec
+import org.scalatest.matchers.should.Matchers
+
+import scala.jdk.CollectionConverters._
+
+/**
+ * Guard for `attributeTypeRules`, over every registered operator.
+ *
+ * The property editor looks a rule's key up among the enclosing object's
properties,
+ * by the `@JsonProperty` name. A key that names no property, or a rule that
is not an
+ * object, makes the editor's constraint check return without checking
anything -- the
+ * rule reads as enforced while enforcing nothing, and no existing test
notices,
+ * because the operator still compiles and still runs.
+ *
+ * Sweeping every operator rather than the ones a fix happened to touch is
deliberate:
+ * the mistake is invisible at the declaration site, so it is as easy to make
in the
+ * next operator as it was in these.
+ */
+class AttributeTypeRuleTargetSpec extends AnyFlatSpec with Matchers {
+
+ private val RulesKeyword = "attributeTypeRules"
+
+ /** One declared rule, and the properties it could legally have named. */
+ private case class Rule(
+ schemaPath: String,
+ key: String,
+ value: JsonNode,
+ declaredProperties: Set[String]
+ ) {
+ override def toString: String =
+ s"$schemaPath.$RulesKeyword.$key " +
+ s"(declared properties at $schemaPath:
${declaredProperties.toSeq.sorted.mkString(", ")})"
+ }
+
+ /**
+ * Rules are injected per class, so they appear both at the top level and,
for a
+ * nested config class, under its own definition -- each alongside the
properties it
+ * constrains. Collect them wherever they sit.
+ */
+ private def rulesIn(node: JsonNode, schemaPath: String): Seq[Rule] = {
+ if (!node.isObject) return Seq.empty
+
+ val here = if (node.has(RulesKeyword)) {
+ val rules = node.path(RulesKeyword)
+ val properties = node.path("properties").fieldNames().asScala.toSet
+ rules.fieldNames().asScala.toSeq.map { key =>
+ Rule(schemaPath, key, rules.path(key), properties)
+ }
+ } else Seq.empty
+
+ here ++ node
+ .fields()
+ .asScala
+ .toSeq
+ .flatMap(child => rulesIn(child.getValue,
s"$schemaPath.${child.getKey}"))
+ }
+
+ private val rulesByOperator: Seq[(String, Seq[Rule])] =
+ OperatorMetadataGenerator.operatorTypeMap.keys.toSeq
+ .map(opClass =>
+ opClass.getSimpleName -> rulesIn(
+ OperatorMetadataGenerator.generateOperatorJsonSchema(opClass),
+ "$"
+ )
+ )
+ .filter { case (_, rules) => rules.nonEmpty }
+ .sortBy { case (operator, _) => operator }
+
+ // Without this the per-operator cases below would all vanish, and the suite
would
+ // pass by testing nothing.
+ "Some operator" should s"declare $RulesKeyword, so the cases below are not
vacuous" in {
+ rulesByOperator should not be empty
+ }
+
+ rulesByOperator.foreach {
+ case (operator, rules) =>
+ behavior of s"The $RulesKeyword on $operator"
+
+ it should "name only properties the generated schema declares" in {
+ val unmatched = rules.filterNot(rule =>
rule.declaredProperties.contains(rule.key))
+ withClue("rules naming no property: ")(unmatched shouldBe empty)
+ }
+
+ it should "state each rule as an object, so the constraint is readable"
in {
+ val notObjects = rules.filterNot(_.value.isObject)
+ withClue("rules that are not objects: ")(notObjects shouldBe empty)
+ }
+ }
+}