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 9032260210 test(workflow-operator): add unit test coverage for 
visualization descriptors (GaugeChart, RangeSlider, SankeyDiagram) (#5843)
9032260210 is described below

commit 90322602101d498e7af273ebfe14dcb5f7501566
Author: Xinyuan Lin <[email protected]>
AuthorDate: Sun Jun 21 12:51:08 2026 -0700

    test(workflow-operator): add unit test coverage for visualization 
descriptors (GaugeChart, RangeSlider, SankeyDiagram) (#5843)
    
    ### What changes were proposed in this PR?
    
    Pin behavior of three previously-untested visualization
    `PythonOperatorDescriptor`s in `common/workflow-operator/`. No
    production-code changes.
    
    | Spec | Source class | Tests |
    | --- | --- | --- |
    | `GaugeChartOpDescSpec` | `GaugeChartOpDesc` | 5 |
    | `RangeSliderOpDescSpec` | `RangeSliderOpDesc` | 5 |
    | `SankeyDiagramOpDescSpec` | `SankeyDiagramOpDesc` | 5 |
    
    **Behavior pinned (each descriptor)**
    
    | Surface | Contract |
    | --- | --- |
    | `operatorInfo` | exact name + visualization group (`Financial` /
    `Basic` / `Basic`); one input / one output |
    | `getOutputSchemas` | single `html-content` STRING column, asserted as
    the **full map keyed by `operatorInfo.outputPorts.head.id`** (input
    ignored — `Map.empty` proves it) |
    | Field defaults | Gauge `value`/`delta`/`threshold == ""` + empty
    `steps`; RangeSlider `xAxis`/`yAxis == ""`; Sankey
    `source`/`target`/`value == ""` |
    | `generatePythonCode` | Gauge `go.Indicator(`; RangeSlider
    `go.Scatter(`; Sankey `go.Sankey(` (structural Python only) |
    | Round-trip | config fields preserved through the polymorphic base |
    
    Codegen assertions check only structural Python (class def / import /
    plotly call) — never the interpolated `EncodableString` values, which
    are base64-encoded at `.encode` time and do not appear literally.
    
    ### Any related issues, documentation, discussions?
    
    Closes #5838.
    
    ### How was this PR tested?
    
    - `sbt "WorkflowOperator/testOnly
    
org.apache.texera.amber.operator.visualization.gaugeChart.GaugeChartOpDescSpec
    
org.apache.texera.amber.operator.visualization.rangeSlider.RangeSliderOpDescSpec
    
org.apache.texera.amber.operator.visualization.sankeyDiagram.SankeyDiagramOpDescSpec"`
    — 15 tests, all green
    - `sbt "WorkflowOperator/Test/scalafmtCheck"` and `sbt
    "WorkflowOperator/Test/scalafix --check"` — clean
    - CI to confirm
    
    ### Was this PR authored or co-authored using generative AI tooling?
    
    Generated-by: Claude Code (Opus 4.8 [1M context])
---
 .../gaugeChart/GaugeChartOpDescSpec.scala          | 85 ++++++++++++++++++++++
 .../rangeSlider/RangeSliderOpDescSpec.scala        | 77 ++++++++++++++++++++
 .../sankeyDiagram/SankeyDiagramOpDescSpec.scala    | 79 ++++++++++++++++++++
 3 files changed, 241 insertions(+)

diff --git 
a/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/visualization/gaugeChart/GaugeChartOpDescSpec.scala
 
b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/visualization/gaugeChart/GaugeChartOpDescSpec.scala
new file mode 100644
index 0000000000..f01947e61f
--- /dev/null
+++ 
b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/visualization/gaugeChart/GaugeChartOpDescSpec.scala
@@ -0,0 +1,85 @@
+/*
+ * 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.visualization.gaugeChart
+
+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 org.scalatest.flatspec.AnyFlatSpec
+import org.scalatest.matchers.should.Matchers
+
+class GaugeChartOpDescSpec extends AnyFlatSpec with Matchers {
+
+  "GaugeChartOpDesc.operatorInfo" should
+    "advertise the name and Financial visualization group" in {
+    val info = (new GaugeChartOpDesc).operatorInfo
+    info.userFriendlyName shouldBe "Gauge Chart"
+    info.operatorGroupName shouldBe 
OperatorGroupConstants.VISUALIZATION_FINANCIAL_GROUP
+    info.inputPorts should have length 1
+    info.outputPorts should have length 1
+  }
+
+  "GaugeChartOpDesc" should "default value/delta/threshold to empty and steps 
to an empty list" in {
+    val d = new GaugeChartOpDesc
+    d.value shouldBe ""
+    d.delta shouldBe ""
+    d.threshold shouldBe ""
+    d.steps shouldBe empty
+  }
+
+  "GaugeChartOpDesc.getOutputSchemas" should
+    "produce a single html-content STRING column keyed by the declared output 
port" in {
+    val op = new GaugeChartOpDesc
+    op.getOutputSchemas(Map.empty) shouldBe Map(
+      op.operatorInfo.outputPorts.head.id -> Schema().add("html-content", 
AttributeType.STRING)
+    )
+  }
+
+  "GaugeChartOpDesc.generatePythonCode" should "emit a Plotly Indicator 
figure" in {
+    val d = new GaugeChartOpDesc
+    d.value = "score"
+    val code = d.generatePythonCode()
+    code should include("class ProcessTableOperator(UDFTableOperator)")
+    code should include("plotly.graph_objects")
+    code should include("go.Indicator(")
+  }
+
+  "GaugeChartOpDesc" should
+    "round-trip value/delta/threshold and steps through the polymorphic base" 
in {
+    val d = new GaugeChartOpDesc
+    d.value = "v"
+    d.delta = "dl"
+    d.threshold = "th"
+    val step = new GaugeChartSteps
+    step.start = "0"
+    step.end = "50"
+    d.steps = List(step)
+    val restored = objectMapper.readValue(objectMapper.writeValueAsString(d), 
classOf[LogicalOp])
+    restored shouldBe a[GaugeChartOpDesc]
+    val g = restored.asInstanceOf[GaugeChartOpDesc]
+    g.value shouldBe "v"
+    g.delta shouldBe "dl"
+    g.threshold shouldBe "th"
+    g.steps should have length 1
+    g.steps.head.start shouldBe "0"
+    g.steps.head.end shouldBe "50"
+  }
+}
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
new file mode 100644
index 0000000000..d601572c32
--- /dev/null
+++ 
b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/visualization/rangeSlider/RangeSliderOpDescSpec.scala
@@ -0,0 +1,77 @@
+/*
+ * 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.visualization.rangeSlider
+
+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 org.scalatest.flatspec.AnyFlatSpec
+import org.scalatest.matchers.should.Matchers
+
+class RangeSliderOpDescSpec extends AnyFlatSpec with Matchers {
+
+  "RangeSliderOpDesc.operatorInfo" should
+    "advertise the name and Basic visualization group" in {
+    val info = (new RangeSliderOpDesc).operatorInfo
+    info.userFriendlyName shouldBe "Range Slider"
+    info.operatorDescription shouldBe "Visualize data in a Range Slider"
+    info.operatorGroupName shouldBe 
OperatorGroupConstants.VISUALIZATION_BASIC_GROUP
+    info.inputPorts should have length 1
+    info.outputPorts should have length 1
+  }
+
+  "RangeSliderOpDesc" should "default xAxis and yAxis to the empty string" in {
+    val d = new RangeSliderOpDesc
+    d.xAxis shouldBe ""
+    d.yAxis shouldBe ""
+  }
+
+  "RangeSliderOpDesc.getOutputSchemas" should
+    "produce a single html-content STRING column keyed by the declared output 
port" in {
+    val op = new RangeSliderOpDesc
+    op.getOutputSchemas(Map.empty) shouldBe Map(
+      op.operatorInfo.outputPorts.head.id -> Schema().add("html-content", 
AttributeType.STRING)
+    )
+  }
+
+  "RangeSliderOpDesc.generatePythonCode" should "emit a Plotly Scatter figure" 
in {
+    val d = new RangeSliderOpDesc
+    d.xAxis = "x"
+    d.yAxis = "y"
+    val code = d.generatePythonCode()
+    code should include("class ProcessTableOperator(UDFTableOperator)")
+    code should include("go.Scatter(")
+  }
+
+  "RangeSliderOpDesc" should
+    "round-trip xAxis, yAxis, and duplicateType through the polymorphic base" 
in {
+    val d = new RangeSliderOpDesc
+    d.xAxis = "month"
+    d.yAxis = "sales"
+    d.duplicateType = RangeSliderHandleDuplicateFunction.MEAN
+    val restored = objectMapper.readValue(objectMapper.writeValueAsString(d), 
classOf[LogicalOp])
+    restored shouldBe a[RangeSliderOpDesc]
+    val r = restored.asInstanceOf[RangeSliderOpDesc]
+    r.xAxis shouldBe "month"
+    r.yAxis shouldBe "sales"
+    r.duplicateType shouldBe RangeSliderHandleDuplicateFunction.MEAN
+  }
+}
diff --git 
a/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/visualization/sankeyDiagram/SankeyDiagramOpDescSpec.scala
 
b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/visualization/sankeyDiagram/SankeyDiagramOpDescSpec.scala
new file mode 100644
index 0000000000..d22ac2b489
--- /dev/null
+++ 
b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/visualization/sankeyDiagram/SankeyDiagramOpDescSpec.scala
@@ -0,0 +1,79 @@
+/*
+ * 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.visualization.sankeyDiagram
+
+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 org.scalatest.flatspec.AnyFlatSpec
+import org.scalatest.matchers.should.Matchers
+
+class SankeyDiagramOpDescSpec extends AnyFlatSpec with Matchers {
+
+  "SankeyDiagramOpDesc.operatorInfo" should
+    "advertise the name and Basic visualization group" in {
+    val info = (new SankeyDiagramOpDesc).operatorInfo
+    info.userFriendlyName shouldBe "Sankey Diagram"
+    info.operatorDescription shouldBe "Visualize data using a Sankey diagram"
+    info.operatorGroupName shouldBe 
OperatorGroupConstants.VISUALIZATION_BASIC_GROUP
+    info.inputPorts should have length 1
+    info.outputPorts should have length 1
+  }
+
+  "SankeyDiagramOpDesc" should
+    "default sourceAttribute / targetAttribute / valueAttribute to the empty 
string" in {
+    val d = new SankeyDiagramOpDesc
+    d.sourceAttribute shouldBe ""
+    d.targetAttribute shouldBe ""
+    d.valueAttribute shouldBe ""
+  }
+
+  "SankeyDiagramOpDesc.getOutputSchemas" should
+    "produce a single html-content STRING column keyed by the declared output 
port" in {
+    val op = new SankeyDiagramOpDesc
+    op.getOutputSchemas(Map.empty) shouldBe Map(
+      op.operatorInfo.outputPorts.head.id -> Schema().add("html-content", 
AttributeType.STRING)
+    )
+  }
+
+  "SankeyDiagramOpDesc.generatePythonCode" should "emit a Plotly Sankey 
figure" in {
+    val d = new SankeyDiagramOpDesc
+    d.sourceAttribute = "src"
+    d.targetAttribute = "dst"
+    d.valueAttribute = "amount"
+    val code = d.generatePythonCode()
+    code should include("class ProcessTableOperator(UDFTableOperator)")
+    code should include("go.Sankey(")
+  }
+
+  "SankeyDiagramOpDesc" should "round-trip its three attributes through the 
polymorphic base" in {
+    val d = new SankeyDiagramOpDesc
+    d.sourceAttribute = "src"
+    d.targetAttribute = "dst"
+    d.valueAttribute = "amount"
+    val restored = objectMapper.readValue(objectMapper.writeValueAsString(d), 
classOf[LogicalOp])
+    restored shouldBe a[SankeyDiagramOpDesc]
+    val s = restored.asInstanceOf[SankeyDiagramOpDesc]
+    s.sourceAttribute shouldBe "src"
+    s.targetAttribute shouldBe "dst"
+    s.valueAttribute shouldBe "amount"
+  }
+}

Reply via email to