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") + } }
