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 d9c0e19aee feat(dendrogram): say what it cannot cluster instead of
ending the run (#8080)
d9c0e19aee is described below
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.
*/