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