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

commit 357296db573a0b51b3d662d17a4113fce9ed50f7
Author: Kary Zheng <[email protected]>
AuthorDate: Fri Aug 7 17:21:14 2026 -0700

    fix(visualization): take the union of Network Graph's two node columns 
(#7327)
    
    ### What changes were proposed in this PR?
    
    Network Graph built its node set with `set(sources + destinations)`. On
    two pandas Series `+` is element-wise, so the set held each source glued
    to its destination rather than the union of the two columns; those glued
    values were added to the graph as nodes, and the genuine nodes only
    arrived afterwards with the edges. This takes the union instead, in
    first-appearance order — a `set` iterates strings in an order that
    varies between processes, which would leave the node sequence unstable
    from run to run.
    
    One line of code, plus a comment recording why neither `+` nor `set` is
    right here.
    
    ### Any related issues, documentation, discussions?
    
    Closes #7325.
    
    ### How was this PR tested?
    
    `NetworkGraphOpDescSpec` gains a case asserting the node set is built as
    a union: reverting the one-line change leaves it the only failing test.
    It also pins the ordered de-duplication, since a `set` would satisfy
    "union" while reordering the nodes between processes.
    
    ```
    sbt "WorkflowOperator/testOnly 
org.apache.texera.amber.operator.visualization.networkGraph.NetworkGraphOpDescSpec"
    ```
    
    Six cases, all passing.
    
    Beyond that, the operator's generated module was dumped and executed
    over ten rows carrying the edges `n3-to-n4`, `n1-to-n2` and `n2-to-n3`.
    Before the change it produced seven nodes — the four real ones plus
    `n3n4`, `n2n3` and `n1n2`, each reporting zero connections, with `n2n3`
    sitting in the same picture as the genuine edge from `n2` to `n3`. After
    it, four nodes with the correct connection counts and no isolated dots.
    Before the change:
    <img width="1530" height="1001" alt="Screenshot 2026-08-07 at 1 50
    52 PM"
    
src="https://github.com/user-attachments/assets/357f3d44-90a1-466a-bc26-da719dc9dfe3";
    />
    
    Selecting an integer column as the source and a string column as the
    destination aborted the run with `TypeError: unsupported operand type(s)
    for +: 'int' and 'str'` before the change and renders normally after it.
    Before the change:
    <img width="1532" height="1001" alt="Screenshot 2026-08-07 at 1 51
    17 PM"
    
src="https://github.com/user-attachments/assets/674ed5f1-6019-48d5-9a82-818ad228acf0";
    />
    
    ### 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]>
---
 .../visualization/networkGraph/NetworkGraphOpDesc.scala   |  4 +++-
 .../networkGraph/NetworkGraphOpDescSpec.scala             | 15 +++++++++++++++
 2 files changed, 18 insertions(+), 1 deletion(-)

diff --git 
a/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/visualization/networkGraph/NetworkGraphOpDesc.scala
 
b/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/visualization/networkGraph/NetworkGraphOpDesc.scala
index ccbf51c737..789a6b5bb3 100644
--- 
a/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/visualization/networkGraph/NetworkGraphOpDesc.scala
+++ 
b/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/visualization/networkGraph/NetworkGraphOpDesc.scala
@@ -98,7 +98,9 @@ class NetworkGraphOpDesc extends PythonOperatorDescriptor {
          |        if not table.empty:
          |            sources = table[$source]
          |            destinations = table[$destination]
-         |            nodes = set(sources + destinations)
+         |            # Union of the two columns, in first-appearance order. 
Adding the
+         |            # Series pairs them off element-wise; a set reorders per 
run.
+         |            nodes = list(dict.fromkeys(pd.concat([sources, 
destinations]).tolist()))
          |            G = nx.Graph()
          |            for node in nodes:
          |                G.add_node(node)
diff --git 
a/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/visualization/networkGraph/NetworkGraphOpDescSpec.scala
 
b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/visualization/networkGraph/NetworkGraphOpDescSpec.scala
index 840b62b643..fc666c61ab 100644
--- 
a/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/visualization/networkGraph/NetworkGraphOpDescSpec.scala
+++ 
b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/visualization/networkGraph/NetworkGraphOpDescSpec.scala
@@ -85,4 +85,19 @@ class NetworkGraphOpDescSpec extends AnyFlatSpec with 
BeforeAndAfter with Matche
     assert(carries(code, "My Graph"))
     code should include("class ProcessTableOperator(UDFTableOperator)")
   }
+
+  it should "build the node set as a union rather than by adding the two 
columns" in {
+    opDesc.source = "from_node"
+    opDesc.destination = "to_node"
+    val code = opDesc.generatePythonCode()
+
+    // `sources + destinations` is element-wise on two Series, so it glued each
+    // source to its destination and those strings entered the graph as nodes.
+    code should not include "set(sources + destinations)"
+    code should include("pd.concat([sources, destinations])")
+
+    // Ordered de-duplication, not a set: a set iterates strings in an order 
that
+    // varies between processes, which would move the nodes from run to run.
+    code should include("dict.fromkeys")
+  }
 }

Reply via email to