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-8080-b59c7406d0a7d549a50c1f86888e85563f2d3446
in repository https://gitbox.apache.org/repos/asf/texera.git

commit d9c0e19aeed027c4a61d746d875a8b46dd1db568
Author: Kary Zheng <[email protected]>
AuthorDate: Sat Aug 29 00:00:53 2026 +0000

    feat(dendrogram): say what it cannot cluster instead of ending the run 
(#8080)
    
    ### What changes were proposed in this PR?
    
    The operator answered an empty table with an error page and then handed
    the two configured columns to `ff.create_dendrogram` as they arrived.
    Two tables that pass that guard ended the run anyway: one with a blank
    in either coordinate, which scipy refuses with "The condensed distance
    matrix must contain only finite values", and one with a single row,
    which leaves it an empty distance matrix.
    
    It now drops the rows missing either coordinate and reports an emptied
    table with the wording Dumbbell Plot already uses, then answers a table
    with fewer than two rows. Only the coordinates are dropped on. A NaN
    anywhere in the point matrix makes scipy refuse the whole thing, while a
    blank label is just a blank tick on the axis and is no reason to lose
    the row.
    
    The row-count check reads after the drop, so a table the drop reduces to
    one row lands there rather than back in scipy.
    
    ### Any related issues, documentation, discussions?
    
    Closes #8079
    
    ### How was this PR tested?
    
    Two cases added to DendrogramOpDescSpec, over the emitted code. I also
    ran the guarded body against each table in scipy: an empty table, a
    single row, three rows with one blank coordinate, a table the drop
    reduces to one row, a wholly blank coordinate column, two ordinary rows,
    and a row with a blank label. Each reaches the intended page, and the
    last two still draw.
    
    ### 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]>
---
 .../dendrogram/DendrogramOpDesc.scala              | 11 ++++++++++
 .../dendrogram/DendrogramOpDescSpec.scala          | 25 ++++++++++++++++++++++
 2 files changed, 36 insertions(+)

diff --git 
a/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/visualization/dendrogram/DendrogramOpDesc.scala
 
b/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/visualization/dendrogram/DendrogramOpDesc.scala
index f80d7acb5b..826cce9061 100644
--- 
a/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/visualization/dendrogram/DendrogramOpDesc.scala
+++ 
b/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/visualization/dendrogram/DendrogramOpDesc.scala
@@ -127,6 +127,17 @@ class DendrogramOpDesc extends PythonOperatorDescriptor {
          |        if table.empty:
          |           yield {'html-content': self.render_error("input table is 
empty.")}
          |           return
+         |        # A row missing either coordinate has no position to cluster 
from, and
+         |        # scipy refuses a NaN anywhere in the distance matrix.
+         |        table = table.dropna(subset=[$xVal, $yVal]) #remove missing 
values
+         |        if table.empty:
+         |           yield {'html-content': self.render_error("input table has 
no rows with all of the configured columns filled in.")}
+         |           return
+         |        # Clustering starts from the distances between rows, so a 
single row
+         |        # leaves scipy an empty distance matrix and it raises rather 
than draws.
+         |        if len(table) < 2:
+         |           yield {'html-content': self.render_error("input table has 
fewer than two rows to cluster.")}
+         |           return
          |        ${createDendrogram()}
          |        # convert fig to html content
          |        html = plotly.io.to_html(fig, include_plotlyjs='cdn', 
auto_play=False)
diff --git 
a/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/visualization/dendrogram/DendrogramOpDescSpec.scala
 
b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/visualization/dendrogram/DendrogramOpDescSpec.scala
index b56ffae273..9851e27596 100644
--- 
a/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/visualization/dendrogram/DendrogramOpDescSpec.scala
+++ 
b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/visualization/dendrogram/DendrogramOpDescSpec.scala
@@ -96,6 +96,31 @@ class DendrogramOpDescSpec extends AnyFlatSpec with 
BeforeAndAfter with Matchers
     code should not include "color_threshold=None"
   }
 
+  it should "drop the rows a blank coordinate leaves unusable, and answer an 
emptied table" in {
+    // The subset names both coordinates, and only those: a NaN anywhere in the
+    // point matrix makes scipy refuse the whole distance matrix, while a blank
+    // label is only a blank tick on the axis.
+    opDesc.xVal = "coord_a"
+    opDesc.yVal = "coord_b"
+    opDesc.labels = "label_col"
+    val code = opDesc.generatePythonCode()
+    code should include("table = table.dropna(subset=[")
+    assert(carries(code, "coord_a"))
+    assert(carries(code, "coord_b"))
+    code should include("input table has no rows with all of the configured 
columns filled in.")
+  }
+
+  it should "answer a table with too few rows to cluster" in {
+    // One row leaves an empty distance matrix, which scipy raises on rather 
than
+    // drawing an empty picture.
+    opDesc.xVal = "coord_a"
+    opDesc.yVal = "coord_b"
+    opDesc.labels = "label_col"
+    val code = opDesc.generatePythonCode()
+    code should include("if len(table) < 2:")
+    code should include("fewer than two rows to cluster")
+  }
+
   /** Reads the shapes a stored workflow can hold. Without `contentAs` a JSON 
string
     * stays unconverted inside the Option and the first use throws.
     */

Reply via email to