You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@texera.apache.org by gi...@apache.org on 2026/06/21 19:51:28 UTC
(texera) branch gh-readonly-queue/main/pr-5843-74fecdfa121fc864c4f5f674157791d75c28725c created (now 9032260210)
This is an automated email from the ASF dual-hosted git repository.
github-merge-queue[bot] pushed a change to branch gh-readonly-queue/main/pr-5843-74fecdfa121fc864c4f5f674157791d75c28725c
in repository https://gitbox.apache.org/repos/asf/texera.git
at 9032260210 test(workflow-operator): add unit test coverage for visualization descriptors (GaugeChart, RangeSlider, SankeyDiagram) (#5843)
This branch includes the following new commits:
new 9032260210 test(workflow-operator): add unit test coverage for visualization descriptors (GaugeChart, RangeSlider, SankeyDiagram) (#5843)
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
(texera) 01/01: test(workflow-operator): add unit test coverage for visualization descriptors (GaugeChart, RangeSlider, SankeyDiagram) (#5843)
Posted by gi...@apache.org.
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-5843-74fecdfa121fc864c4f5f674157791d75c28725c
in repository https://gitbox.apache.org/repos/asf/texera.git
commit 90322602101d498e7af273ebfe14dcb5f7501566
Author: Xinyuan Lin <xi...@uci.edu>
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"
+ }
+}