This is an automated email from the ASF dual-hosted git repository.

github-merge-queue[bot] pushed a commit to branch 
gh-readonly-queue/release/v1.2/pr-7518-80de40f1b3b4900cd2a1cb49642dc9174dfdb0c3
in repository https://gitbox.apache.org/repos/asf/texera.git

commit 9ef3744deb816c99d4f92e1dc9ed4c915c542040
Author: Kary Zheng <[email protected]>
AuthorDate: Thu Aug 13 05:20:57 2026 +0000

    fix(visualization, v1.2): take the union of Network Graph's two node 
columns (#7518)
    
    ### What changes were proposed in this PR?
    
    Backport of #7327 to `release/v1.2`, superseding #7401. That PR's branch
    lives on `apache/texera` rather than a fork, so the conflict resolution
    could not be pushed to it.
    
    The operator change is #7327's, unchanged. 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. This takes the union in
    first-appearance order.
    
    The cherry-pick's one conflict was `NetworkGraphOpDescSpec.scala`, which
    does not exist on `release/v1.2` — it was created on main by #5640,
    which was never backported. Resolving that modify/delete by taking the
    whole file, which is what #7401 carries, brings across three cases
    asserting the assertion text names the empty field. This branch still
    has a bare `assert(source.nonEmpty)`, so those three fail here: running
    #7401's branch as it stands gives three passed and three failed. This
    backport instead carries only the case #7327 added, so what lands is the
    one-line operator fix and the test that pins it.
    
    ### Any related issues, documentation, discussions?
    
    Backport of #7327. Originally linked #7325. Supersedes #7401, which can
    be closed.
    
    ### How was this PR tested?
    
    `NetworkGraphOpDescSpec` on this branch: one case, passing. Reverting
    the operator line leaves it failing. `WorkflowOperator/scalafmtCheckAll`
    and `WorkflowOperator/scalafixAll --check` are both clean.
    
    The operator change is byte-identical to #7327, where it was verified by
    dumping and executing the generated module over rows carrying known
    edges.
    
    ### 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]>
    Co-authored-by: Xinyuan Lin <[email protected]>
---
 .../networkGraph/NetworkGraphOpDesc.scala          |  4 +-
 .../networkGraph/NetworkGraphOpDescSpec.scala      | 48 ++++++++++++++++++++++
 2 files changed, 51 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 4a5ea6725e..0693967165 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
@@ -94,7 +94,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
new file mode 100644
index 0000000000..d968e3276f
--- /dev/null
+++ 
b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/visualization/networkGraph/NetworkGraphOpDescSpec.scala
@@ -0,0 +1,48 @@
+/*
+ * 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.networkGraph
+
+import org.scalatest.BeforeAndAfter
+import org.scalatest.flatspec.AnyFlatSpec
+import org.scalatest.matchers.should.Matchers
+
+class NetworkGraphOpDescSpec extends AnyFlatSpec with BeforeAndAfter with 
Matchers {
+
+  var opDesc: NetworkGraphOpDesc = _
+
+  before {
+    opDesc = new NetworkGraphOpDesc()
+  }
+
+  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