This is an automated email from the ASF dual-hosted git repository.
hansva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git
The following commit(s) were added to refs/heads/main by this push:
new f6cfca5bba Issue #2347 : Add row distribution and partitioning context
actions to pipeline hops (#8253)
f6cfca5bba is described below
commit f6cfca5bba671f55863bc86336118496b2d828e9
Author: Matt Casters <[email protected]>
AuthorDate: Fri Sep 4 16:49:38 2026 +0200
Issue #2347 : Add row distribution and partitioning context actions to
pipeline hops (#8253)
- Add Copy rows / Distribute rows contextual actions to toggle source
transform row routing
- Add Set partitioning / Remove partitioning contextual actions to
configure or clear target transform partitioning
- Register row distribution and partitioning changes with
undoDelegate.addUndoChange() and update GUI/toolbar state
- Allow clicking hop icons to open the hop context menu
- Add unit tests for hop context actions and filtering
- Update user manual documentation with context action details and new
screenshot
- Update .gitignore to ignore .agents
---
.gitignore | 1 +
.../pipeline-hop-context-dialog-data-routing.png | Bin 0 -> 107037 bytes
.../ROOT/pages/hop-gui/hop-gui-popup-dialog.adoc | 18 +-
.../modules/ROOT/pages/pipeline/partitioning.adoc | 2 +
.../org/apache/hop/pipeline/PipelinePainter.java | 12 +-
.../hopgui/file/pipeline/HopGuiPipelineGraph.java | 229 ++++++++++++++++++++-
.../delegates/HopGuiPipelineTransformDelegate.java | 1 +
.../pipeline/messages/messages_en_US.properties | 8 +
.../pipeline/HopGuiPipelineHopContextTest.java | 223 ++++++++++++++++++++
9 files changed, 468 insertions(+), 26 deletions(-)
diff --git a/.gitignore b/.gitignore
index 3cc07bb270..f81e6e54bf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,7 @@
.classpath
.settings
.factorypath
+.agents
build
target
release.properties
diff --git
a/docs/hop-user-manual/modules/ROOT/assets/images/hop-gui/pipeline-hop-context-dialog-data-routing.png
b/docs/hop-user-manual/modules/ROOT/assets/images/hop-gui/pipeline-hop-context-dialog-data-routing.png
new file mode 100644
index 0000000000..10fe52a65a
Binary files /dev/null and
b/docs/hop-user-manual/modules/ROOT/assets/images/hop-gui/pipeline-hop-context-dialog-data-routing.png
differ
diff --git
a/docs/hop-user-manual/modules/ROOT/pages/hop-gui/hop-gui-popup-dialog.adoc
b/docs/hop-user-manual/modules/ROOT/pages/hop-gui/hop-gui-popup-dialog.adoc
index 7a4fe0b706..9619e5ed05 100644
--- a/docs/hop-user-manual/modules/ROOT/pages/hop-gui/hop-gui-popup-dialog.adoc
+++ b/docs/hop-user-manual/modules/ROOT/pages/hop-gui/hop-gui-popup-dialog.adoc
@@ -195,13 +195,19 @@ The Hop options, available from both the workflow and
pipeline editors, offer a
For pipelines, the dialog offers the following options:
* Basic
-** **disable/enable hop**: enable the current hop (if disabled) or disable the
current hop (if enabled). This option will only show the applicable options
(e.g. 'enable hop' won't be shown for active hops and vice versa)
-** **delete hop**: delete the current hop
+** **disable/enable hop**: enable the current hop (if disabled) or disable the
current hop (if enabled). This option will only show the applicable option
(e.g. 'enable hop' won't be shown for active hops and vice versa).
+** **delete hop**: delete the current hop.
+** **Insert transform**: split the hop and insert a new transform.
* Bulk
-** **Enable downstream hops**: enable the current hop, and all hops that
follow later in the workflow (or pipeline).
-** **Disable downstream hops**: disable the current hop and all hops that
follow later in the current workflow (or pipeline).
-
-image:hop-gui/popup-hop-pipeline.png[Hop Gui Popup Dialog - Pipeline Hop
Options, width="65%"]
+** **Enable downstream hops**: enable the current hop and all hops that follow
downstream in the pipeline.
+** **Disable downstream hops**: disable the current hop and all hops that
follow downstream in the pipeline.
+** **Enable hops between selection**: enable all hops between selected
transforms.
+** **Disable hops between selection**: disable all hops between selected
transforms.
+* Data routing
+** **Distribute rows / Copy rows**: toggle whether the source transform
distributes or copies rows to subsequent transforms. If the source transform is
currently copying rows, **Distribute rows** is shown; if it is distributing
rows, **Copy rows** is shown.
+** **Set partitioning / Remove partitioning**: configure or remove data
partitioning on the target transform. If the target transform does not yet have
partitioning configured, **Set partitioning** opens the partitioning dialog; if
partitioning is already configured, **Remove partitioning** clears it.
+
+image:hop-gui/pipeline-hop-context-dialog-data-routing.png[Hop Gui Popup
Dialog - Pipeline Hop Options, width="65%"]
When working in a workflow, the hops popup dialog has all the options as the
pipeline hop dialog, with one additional category:
diff --git a/docs/hop-user-manual/modules/ROOT/pages/pipeline/partitioning.adoc
b/docs/hop-user-manual/modules/ROOT/pages/pipeline/partitioning.adoc
index 28772b5102..a58b8da37c 100644
--- a/docs/hop-user-manual/modules/ROOT/pages/pipeline/partitioning.adoc
+++ b/docs/hop-user-manual/modules/ROOT/pages/pipeline/partitioning.adoc
@@ -88,6 +88,8 @@ The names used for the partitions can be anything you like.
. Next, apply the partition schema to the Group By transform.
By applying a partition schema to a transform, a matching set of transform
copies is started automatically (for example, if applying a partition schema
with three partitions, three transform copies are launched).
+TIP: You can also set or remove partitioning directly on any hop pointing to
the target transform by clicking the hop and selecting **Set partitioning** (or
**Remove partitioning**) under the **Data routing** category in the
xref:hop-gui/hop-gui-popup-dialog.adoc#_hop_options[hop context dialog].
+
Step Two: select the partitioning method:
* Establish the partitioning method for the transform, which defines the rule
for row distribution across the copies.
diff --git a/engine/src/main/java/org/apache/hop/pipeline/PipelinePainter.java
b/engine/src/main/java/org/apache/hop/pipeline/PipelinePainter.java
index 8d04fd51c1..dd15713c24 100644
--- a/engine/src/main/java/org/apache/hop/pipeline/PipelinePainter.java
+++ b/engine/src/main/java/org/apache/hop/pipeline/PipelinePainter.java
@@ -1241,14 +1241,7 @@ public class PipelinePainter extends
BasePainter<PipelineHopMeta, TransformMeta>
gc.drawImage(svgFile, mx, my, 16, 16, magnification, 0);
areaOwners.add(
new AreaOwner(
- AreaType.ROW_DISTRIBUTION_ICON,
- mx,
- my,
- 16,
- 16,
- offset,
- fs,
- STRING_ROW_DISTRIBUTION));
+ AreaType.ROW_DISTRIBUTION_ICON, mx, my, 16, 16, offset, fs,
pipelineHop));
mx += 16;
}
@@ -1262,8 +1255,7 @@ public class PipelinePainter extends
BasePainter<PipelineHopMeta, TransformMeta>
gc.drawImage(image, mx, my, magnification);
areaOwners.add(
- new AreaOwner(
- AreaType.HOP_COPY_ICON, mx, my, 16, 16, offset, fs,
STRING_HOP_TYPE_COPY));
+ new AreaOwner(AreaType.HOP_COPY_ICON, mx, my, 16, 16, offset, fs,
pipelineHop));
mx += 16;
}
diff --git
a/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/HopGuiPipelineGraph.java
b/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/HopGuiPipelineGraph.java
index 31bdae5841..dcc34101d6 100644
---
a/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/HopGuiPipelineGraph.java
+++
b/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/HopGuiPipelineGraph.java
@@ -134,6 +134,7 @@ import org.apache.hop.pipeline.transform.ITransformMeta;
import org.apache.hop.pipeline.transform.RowDistributionPluginType;
import org.apache.hop.pipeline.transform.TransformErrorMeta;
import org.apache.hop.pipeline.transform.TransformMeta;
+import org.apache.hop.pipeline.transform.TransformPartitioningMeta;
import org.apache.hop.pipeline.transform.stream.IStream;
import org.apache.hop.pipeline.transform.stream.IStream.StreamType;
import org.apache.hop.pipeline.transform.stream.Stream;
@@ -174,6 +175,7 @@ import org.apache.hop.ui.hopgui.context.GuiActionFavorites;
import org.apache.hop.ui.hopgui.context.GuiContextUtil;
import org.apache.hop.ui.hopgui.context.IGuiContextHandler;
import org.apache.hop.ui.hopgui.delegates.HopGuiServerDelegate;
+import org.apache.hop.ui.hopgui.delegates.HopGuiUndoDelegate;
import org.apache.hop.ui.hopgui.dialog.EnterPreviewRowsDialog;
import org.apache.hop.ui.hopgui.dialog.NotePadDialog;
import org.apache.hop.ui.hopgui.dialog.SearchFieldsProgressDialog;
@@ -316,6 +318,14 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
"pipeline-graph-hop-10010-hop-enable";
public static final String ACTION_ID_PIPELINE_GRAPH_HOP_DISABLE =
"pipeline-graph-hop-10015-hop-disable";
+ public static final String ACTION_ID_PIPELINE_GRAPH_HOP_ROWS_DISTRIBUTE =
+ "pipeline-graph-hop-10600-rows-distribute";
+ public static final String ACTION_ID_PIPELINE_GRAPH_HOP_ROWS_COPY =
+ "pipeline-graph-hop-10650-rows-copy";
+ public static final String ACTION_ID_PIPELINE_GRAPH_HOP_SET_PARTITIONING =
+ "pipeline-graph-hop-10700-set-partitioning";
+ public static final String ACTION_ID_PIPELINE_GRAPH_HOP_REMOVE_PARTITIONING =
+ "pipeline-graph-hop-10710-remove-partitioning";
public static final String ACTION_ID_PIPELINE_GRAPH_TRANSFORM_ROWS_COPY =
"pipeline-graph-transform-10650-rows-copy";
public static final String
ACTION_ID_PIPELINE_GRAPH_TRANSFORM_ROWS_DISTRIBUTE =
@@ -932,6 +942,11 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
break;
case HOP_COPY_ICON:
+ case ROW_DISTRIBUTION_ICON:
+ clickedPipelineHop =
+ areaOwner.getOwner() instanceof PipelineHopMeta
+ ? (PipelineHopMeta) areaOwner.getOwner()
+ : findPipelineHop(real.x, real.y);
done = true;
break;
@@ -3539,9 +3554,27 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
category =
"i18n::HopGuiPipelineGraph.ContextualAction.Category.Routing.Text",
categoryOrder = "2")
public void setDistributes(HopGuiPipelineTransformContext context) {
- context.getTransformMeta().setDistributes(true);
- context.getTransformMeta().setRowDistribution(null);
+ TransformMeta transformMeta = context.getTransformMeta();
+ TransformMeta before = (TransformMeta) transformMeta.clone();
+ transformMeta.setDistributes(true);
+ transformMeta.setRowDistribution(null);
+ transformMeta.setChanged();
+ TransformMeta after = (TransformMeta) transformMeta.clone();
+ PipelineMeta meta =
+ context.getPipelineMeta() != null ? context.getPipelineMeta() :
pipelineMeta;
+ if (meta != null) {
+ meta.setChanged();
+ HopGuiUndoDelegate undo = getUndoDelegate();
+ if (undo != null) {
+ undo.addUndoChange(
+ meta,
+ new TransformMeta[] {before},
+ new TransformMeta[] {after},
+ new int[] {meta.indexOfTransform(transformMeta)});
+ }
+ }
redraw();
+ updateGui();
}
@GuiContextAction(
@@ -3554,9 +3587,27 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
category =
"i18n::HopGuiPipelineGraph.ContextualAction.Category.Routing.Text",
categoryOrder = "2")
public void setCopies(HopGuiPipelineTransformContext context) {
- context.getTransformMeta().setDistributes(false);
- context.getTransformMeta().setRowDistribution(null);
+ TransformMeta transformMeta = context.getTransformMeta();
+ TransformMeta before = (TransformMeta) transformMeta.clone();
+ transformMeta.setDistributes(false);
+ transformMeta.setRowDistribution(null);
+ transformMeta.setChanged();
+ TransformMeta after = (TransformMeta) transformMeta.clone();
+ PipelineMeta meta =
+ context.getPipelineMeta() != null ? context.getPipelineMeta() :
pipelineMeta;
+ if (meta != null) {
+ meta.setChanged();
+ HopGuiUndoDelegate undo = getUndoDelegate();
+ if (undo != null) {
+ undo.addUndoChange(
+ meta,
+ new TransformMeta[] {before},
+ new TransformMeta[] {after},
+ new int[] {meta.indexOfTransform(transformMeta)});
+ }
+ }
redraw();
+ updateGui();
}
/**
@@ -3758,14 +3809,33 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
*/
@GuiContextActionFilter(parentId = HopGuiPipelineHopContext.CONTEXT_ID)
public boolean filterHopActions(String contextActionId,
HopGuiPipelineHopContext context) {
- if (contextActionId.equals(ACTION_ID_PIPELINE_GRAPH_HOP_ENABLE)) {
- return !context.getHopMeta().isEnabled();
+ PipelineHopMeta hop = context.getHopMeta();
+ if (hop == null) {
+ return false;
}
- if (contextActionId.equals(ACTION_ID_PIPELINE_GRAPH_HOP_DISABLE)) {
- return context.getHopMeta().isEnabled();
+ switch (contextActionId) {
+ case ACTION_ID_PIPELINE_GRAPH_HOP_ENABLE -> {
+ return !hop.isEnabled();
+ }
+ case ACTION_ID_PIPELINE_GRAPH_HOP_DISABLE -> {
+ return hop.isEnabled();
+ }
+ case ACTION_ID_PIPELINE_GRAPH_HOP_ROWS_DISTRIBUTE -> {
+ return hop.getFromTransform() != null &&
!hop.getFromTransform().isDistributes();
+ }
+ case ACTION_ID_PIPELINE_GRAPH_HOP_ROWS_COPY -> {
+ return hop.getFromTransform() != null &&
hop.getFromTransform().isDistributes();
+ }
+ case ACTION_ID_PIPELINE_GRAPH_HOP_SET_PARTITIONING -> {
+ return hop.getToTransform() != null &&
!hop.getToTransform().isPartitioned();
+ }
+ case ACTION_ID_PIPELINE_GRAPH_HOP_REMOVE_PARTITIONING -> {
+ return hop.getToTransform() != null &&
hop.getToTransform().isPartitioned();
+ }
+ default -> {
+ return true;
+ }
}
-
- return true;
}
/**
@@ -4061,6 +4131,132 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
}
}
+ @GuiContextAction(
+ id = ACTION_ID_PIPELINE_GRAPH_HOP_ROWS_DISTRIBUTE,
+ parentId = HopGuiPipelineHopContext.CONTEXT_ID,
+ type = GuiActionType.Modify,
+ name = "i18n::HopGuiPipelineGraph.HopAction.DistributeRows.Name",
+ tooltip = "i18n::HopGuiPipelineGraph.HopAction.DistributeRows.Tooltip",
+ image = "ui/images/distribute.svg",
+ category =
"i18n::HopGuiPipelineGraph.ContextualAction.Category.Routing.Text",
+ categoryOrder = "2")
+ public void setHopDistributes(HopGuiPipelineHopContext context) {
+ PipelineHopMeta hop = context.getHopMeta();
+ if (hop != null && hop.getFromTransform() != null) {
+ TransformMeta from = hop.getFromTransform();
+ TransformMeta before = (TransformMeta) from.clone();
+ from.setDistributes(true);
+ from.setRowDistribution(null);
+ from.setChanged();
+ TransformMeta after = (TransformMeta) from.clone();
+ PipelineMeta meta =
+ context.getPipelineMeta() != null ? context.getPipelineMeta() :
pipelineMeta;
+ if (meta != null) {
+ meta.setChanged();
+ HopGuiUndoDelegate undo = getUndoDelegate();
+ if (undo != null) {
+ undo.addUndoChange(
+ meta,
+ new TransformMeta[] {before},
+ new TransformMeta[] {after},
+ new int[] {meta.indexOfTransform(from)});
+ }
+ }
+ redraw();
+ updateGui();
+ }
+ }
+
+ @GuiContextAction(
+ id = ACTION_ID_PIPELINE_GRAPH_HOP_ROWS_COPY,
+ parentId = HopGuiPipelineHopContext.CONTEXT_ID,
+ type = GuiActionType.Modify,
+ name = "i18n::HopGuiPipelineGraph.HopAction.CopyRows.Name",
+ tooltip = "i18n::HopGuiPipelineGraph.HopAction.CopyRows.Tooltip",
+ image = "ui/images/copy-rows.svg",
+ category =
"i18n::HopGuiPipelineGraph.ContextualAction.Category.Routing.Text",
+ categoryOrder = "2")
+ public void setHopCopies(HopGuiPipelineHopContext context) {
+ PipelineHopMeta hop = context.getHopMeta();
+ if (hop != null && hop.getFromTransform() != null) {
+ TransformMeta from = hop.getFromTransform();
+ TransformMeta before = (TransformMeta) from.clone();
+ from.setDistributes(false);
+ from.setRowDistribution(null);
+ from.setChanged();
+ TransformMeta after = (TransformMeta) from.clone();
+ PipelineMeta meta =
+ context.getPipelineMeta() != null ? context.getPipelineMeta() :
pipelineMeta;
+ if (meta != null) {
+ meta.setChanged();
+ HopGuiUndoDelegate undo = getUndoDelegate();
+ if (undo != null) {
+ undo.addUndoChange(
+ meta,
+ new TransformMeta[] {before},
+ new TransformMeta[] {after},
+ new int[] {meta.indexOfTransform(from)});
+ }
+ }
+ redraw();
+ updateGui();
+ }
+ }
+
+ @GuiContextAction(
+ id = ACTION_ID_PIPELINE_GRAPH_HOP_SET_PARTITIONING,
+ parentId = HopGuiPipelineHopContext.CONTEXT_ID,
+ type = GuiActionType.Modify,
+ name = "i18n::HopGuiPipelineGraph.HopAction.SetPartitioning.Name",
+ tooltip = "i18n::HopGuiPipelineGraph.HopAction.SetPartitioning.Tooltip",
+ image = "ui/images/partition_schema.svg",
+ category =
"i18n::HopGuiPipelineGraph.ContextualAction.Category.Routing.Text",
+ categoryOrder = "2")
+ public void setHopPartitioning(HopGuiPipelineHopContext context) {
+ PipelineHopMeta hop = context.getHopMeta();
+ if (hop != null && hop.getToTransform() != null) {
+ PipelineMeta meta =
+ context.getPipelineMeta() != null ? context.getPipelineMeta() :
pipelineMeta;
+ pipelineTransformDelegate.editTransformPartitioning(meta,
hop.getToTransform());
+ }
+ }
+
+ @GuiContextAction(
+ id = ACTION_ID_PIPELINE_GRAPH_HOP_REMOVE_PARTITIONING,
+ parentId = HopGuiPipelineHopContext.CONTEXT_ID,
+ type = GuiActionType.Modify,
+ name = "i18n::HopGuiPipelineGraph.HopAction.RemovePartitioning.Name",
+ tooltip =
"i18n::HopGuiPipelineGraph.HopAction.RemovePartitioning.Tooltip",
+ image = "ui/images/partition_schema.svg",
+ category =
"i18n::HopGuiPipelineGraph.ContextualAction.Category.Routing.Text",
+ categoryOrder = "2")
+ public void removeHopPartitioning(HopGuiPipelineHopContext context) {
+ PipelineHopMeta hop = context.getHopMeta();
+ if (hop != null && hop.getToTransform() != null) {
+ TransformMeta to = hop.getToTransform();
+ TransformMeta before = (TransformMeta) to.clone();
+ to.setTransformPartitioningMeta(new TransformPartitioningMeta());
+ to.setTargetTransformPartitioningMeta(null);
+ to.setChanged();
+ TransformMeta after = (TransformMeta) to.clone();
+ PipelineMeta meta =
+ context.getPipelineMeta() != null ? context.getPipelineMeta() :
pipelineMeta;
+ if (meta != null) {
+ meta.setChanged();
+ HopGuiUndoDelegate undo = getUndoDelegate();
+ if (undo != null) {
+ undo.addUndoChange(
+ meta,
+ new TransformMeta[] {before},
+ new TransformMeta[] {after},
+ new int[] {meta.indexOfTransform(to)});
+ }
+ }
+ redraw();
+ updateGui();
+ }
+ }
+
@GuiContextAction(
id = "pipeline-graph-10-edit-note",
parentId = HopGuiPipelineNoteContext.CONTEXT_ID,
@@ -6576,6 +6772,19 @@ public class HopGuiPipelineGraph extends
HopGuiAbstractGraph
this.hopGui = hopGui;
}
+ protected HopGuiUndoDelegate undoDelegate;
+
+ public HopGuiUndoDelegate getUndoDelegate() {
+ if (undoDelegate != null) {
+ return undoDelegate;
+ }
+ return hopGui != null ? hopGui.undoDelegate : null;
+ }
+
+ public void setUndoDelegate(HopGuiUndoDelegate undoDelegate) {
+ this.undoDelegate = undoDelegate;
+ }
+
@Override
public Object getSubject() {
return pipelineMeta;
diff --git
a/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/delegates/HopGuiPipelineTransformDelegate.java
b/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/delegates/HopGuiPipelineTransformDelegate.java
index e1a5ee83ec..3eba6a6a0f 100644
---
a/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/delegates/HopGuiPipelineTransformDelegate.java
+++
b/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/delegates/HopGuiPipelineTransformDelegate.java
@@ -613,6 +613,7 @@ public class HopGuiPipelineTransformDelegate {
transformMeta.setChanged(partitionBefore.hasChanged());
}
pipelineGraph.redraw();
+ pipelineGraph.updateGui();
}
} catch (Exception e) {
new ErrorDialog(
diff --git
a/ui/src/main/resources/org/apache/hop/ui/hopgui/file/pipeline/messages/messages_en_US.properties
b/ui/src/main/resources/org/apache/hop/ui/hopgui/file/pipeline/messages/messages_en_US.properties
index 621354a144..83c741500d 100644
---
a/ui/src/main/resources/org/apache/hop/ui/hopgui/file/pipeline/messages/messages_en_US.properties
+++
b/ui/src/main/resources/org/apache/hop/ui/hopgui/file/pipeline/messages/messages_en_US.properties
@@ -32,6 +32,8 @@ HopGuiPipelineGraph.GuiAction.ZoomFitToScreen.Tooltip=Zoom to
fit screen size
HopGuiPipelineGraph.GuiAction.ZoomIn.Tooltip=Zoom in 10%
HopGuiPipelineGraph.GuiAction.ZoomInOut.Tooltip=Zoom in or out
HopGuiPipelineGraph.GuiAction.ZoomOut.Tooltip=Zoom out 10%
+HopGuiPipelineGraph.HopAction.CopyRows.Name=Copy rows
+HopGuiPipelineGraph.HopAction.CopyRows.Tooltip=Make the from transform copy
rows to next transforms
HopGuiPipelineGraph.HopAction.CreateHop.Name=Create hop
HopGuiPipelineGraph.HopAction.CreateHop.Tooltip=Create a new hop between 2
transforms
HopGuiPipelineGraph.HopAction.DeleteHop.Name=Delete hop
@@ -42,6 +44,8 @@
HopGuiPipelineGraph.HopAction.DisableDownstreamHop.Name=Disable downstream hops
HopGuiPipelineGraph.HopAction.DisableDownstreamHop.Tooltip=Disable all enabled
downstream hops
HopGuiPipelineGraph.HopAction.DisableHop.Name=Disable hop
HopGuiPipelineGraph.HopAction.DisableHop.Tooltip=Disable the hop
+HopGuiPipelineGraph.HopAction.DistributeRows.Name=Distribute rows
+HopGuiPipelineGraph.HopAction.DistributeRows.Tooltip=Make the from transform
distribute rows to next transforms
HopGuiPipelineGraph.HopAction.EnableBetweenSelectedTransforms.Name=Enable hops
between selection
HopGuiPipelineGraph.HopAction.EnableBetweenSelectedTransforms.Tooltip=Enable
all hops between selected transforms
HopGuiPipelineGraph.HopAction.EnableDownstreamHop.Name=Enable downstream hops
@@ -50,6 +54,10 @@ HopGuiPipelineGraph.HopAction.EnableHop.Name=Enable hop
HopGuiPipelineGraph.HopAction.EnableHop.Tooltip=Enable the hop
HopGuiPipelineGraph.HopAction.InsetTransform.Text=Insert transform
HopGuiPipelineGraph.HopAction.InsetTransform.Tooltip=Split the hop and insert
a new transform
+HopGuiPipelineGraph.HopAction.RemovePartitioning.Name=Remove partitioning
+HopGuiPipelineGraph.HopAction.RemovePartitioning.Tooltip=Remove partitioning
from the target transform
+HopGuiPipelineGraph.HopAction.SetPartitioning.Name=Set partitioning
+HopGuiPipelineGraph.HopAction.SetPartitioning.Tooltip=Specify how rows of data
need to be grouped into partitions on the target transform
HopGuiPipelineGraph.NoteAction.BringToFront.Text=Bring to front
HopGuiPipelineGraph.NoteAction.BringToFront.Tooltip=Bring note to front
HopGuiPipelineGraph.NoteAction.CreateNote.Name=Create a note
diff --git
a/ui/src/test/java/org/apache/hop/ui/hopgui/file/pipeline/HopGuiPipelineHopContextTest.java
b/ui/src/test/java/org/apache/hop/ui/hopgui/file/pipeline/HopGuiPipelineHopContextTest.java
new file mode 100644
index 0000000000..912be45357
--- /dev/null
+++
b/ui/src/test/java/org/apache/hop/ui/hopgui/file/pipeline/HopGuiPipelineHopContextTest.java
@@ -0,0 +1,223 @@
+/*
+ * 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.hop.ui.hopgui.file.pipeline;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import org.apache.hop.core.gui.Point;
+import org.apache.hop.partition.PartitionSchema;
+import org.apache.hop.pipeline.PipelineHopMeta;
+import org.apache.hop.pipeline.PipelineMeta;
+import org.apache.hop.pipeline.transform.IRowDistribution;
+import org.apache.hop.pipeline.transform.TransformMeta;
+import org.apache.hop.pipeline.transform.TransformPartitioningMeta;
+import org.apache.hop.ui.hopgui.delegates.HopGuiUndoDelegate;
+import org.apache.hop.ui.hopgui.file.pipeline.context.HopGuiPipelineHopContext;
+import
org.apache.hop.ui.hopgui.file.pipeline.delegates.HopGuiPipelineTransformDelegate;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class HopGuiPipelineHopContextTest {
+
+ private HopGuiPipelineGraph graph;
+ private HopGuiUndoDelegate undoDelegate;
+ private PipelineMeta pipelineMeta;
+ private TransformMeta fromTransform;
+ private TransformMeta toTransform;
+ private PipelineHopMeta hopMeta;
+ private HopGuiPipelineHopContext context;
+
+ @BeforeEach
+ void setUp() {
+ graph = mock(HopGuiPipelineGraph.class,
org.mockito.Mockito.CALLS_REAL_METHODS);
+ undoDelegate = mock(HopGuiUndoDelegate.class);
+ graph.setUndoDelegate(undoDelegate);
+ pipelineMeta = mock(PipelineMeta.class);
+ fromTransform = new TransformMeta("From", null);
+ toTransform = new TransformMeta("To", null);
+ hopMeta = new PipelineHopMeta(fromTransform, toTransform);
+ context = new HopGuiPipelineHopContext(pipelineMeta, hopMeta, graph, new
Point(0, 0));
+ }
+
+ @Test
+ void testFilterHopActions_EnableDisable() {
+ hopMeta.setEnabled(true);
+ assertFalse(
+
graph.filterHopActions(HopGuiPipelineGraph.ACTION_ID_PIPELINE_GRAPH_HOP_ENABLE,
context));
+ assertTrue(
+
graph.filterHopActions(HopGuiPipelineGraph.ACTION_ID_PIPELINE_GRAPH_HOP_DISABLE,
context));
+
+ hopMeta.setEnabled(false);
+ assertTrue(
+
graph.filterHopActions(HopGuiPipelineGraph.ACTION_ID_PIPELINE_GRAPH_HOP_ENABLE,
context));
+ assertFalse(
+
graph.filterHopActions(HopGuiPipelineGraph.ACTION_ID_PIPELINE_GRAPH_HOP_DISABLE,
context));
+ }
+
+ @Test
+ void testFilterHopActions_DistributeAndCopy() {
+ // When from-transform is distributing rows
+ fromTransform.setDistributes(true);
+ assertTrue(
+ graph.filterHopActions(
+ HopGuiPipelineGraph.ACTION_ID_PIPELINE_GRAPH_HOP_ROWS_COPY,
context));
+ assertFalse(
+ graph.filterHopActions(
+ HopGuiPipelineGraph.ACTION_ID_PIPELINE_GRAPH_HOP_ROWS_DISTRIBUTE,
context));
+
+ // When from-transform is copying rows
+ fromTransform.setDistributes(false);
+ assertFalse(
+ graph.filterHopActions(
+ HopGuiPipelineGraph.ACTION_ID_PIPELINE_GRAPH_HOP_ROWS_COPY,
context));
+ assertTrue(
+ graph.filterHopActions(
+ HopGuiPipelineGraph.ACTION_ID_PIPELINE_GRAPH_HOP_ROWS_DISTRIBUTE,
context));
+ }
+
+ @Test
+ void testFilterHopActions_Partitioning() {
+ // When to-transform is not partitioned
+ toTransform.setTransformPartitioningMeta(new TransformPartitioningMeta());
+ assertFalse(toTransform.isPartitioned());
+ assertTrue(
+ graph.filterHopActions(
+ HopGuiPipelineGraph.ACTION_ID_PIPELINE_GRAPH_HOP_SET_PARTITIONING,
context));
+ assertFalse(
+ graph.filterHopActions(
+
HopGuiPipelineGraph.ACTION_ID_PIPELINE_GRAPH_HOP_REMOVE_PARTITIONING, context));
+
+ // When to-transform is partitioned
+ TransformPartitioningMeta partMeta = new TransformPartitioningMeta();
+
partMeta.setMethodType(TransformPartitioningMeta.PARTITIONING_METHOD_MIRROR);
+ partMeta.setPartitionSchema(new PartitionSchema("schema1", new
java.util.ArrayList<>()));
+ toTransform.setTransformPartitioningMeta(partMeta);
+ assertTrue(toTransform.isPartitioned());
+ assertFalse(
+ graph.filterHopActions(
+ HopGuiPipelineGraph.ACTION_ID_PIPELINE_GRAPH_HOP_SET_PARTITIONING,
context));
+ assertTrue(
+ graph.filterHopActions(
+
HopGuiPipelineGraph.ACTION_ID_PIPELINE_GRAPH_HOP_REMOVE_PARTITIONING, context));
+ }
+
+ @Test
+ void testFilterHopActions_NullHopAndTransforms() {
+ HopGuiPipelineHopContext nullHopContext =
+ new HopGuiPipelineHopContext(pipelineMeta, null, graph, new Point(0,
0));
+ assertFalse(
+ graph.filterHopActions(
+ HopGuiPipelineGraph.ACTION_ID_PIPELINE_GRAPH_HOP_ENABLE,
nullHopContext));
+ assertFalse(
+ graph.filterHopActions(
+ HopGuiPipelineGraph.ACTION_ID_PIPELINE_GRAPH_HOP_ROWS_COPY,
nullHopContext));
+ assertFalse(
+ graph.filterHopActions(
+ HopGuiPipelineGraph.ACTION_ID_PIPELINE_GRAPH_HOP_SET_PARTITIONING,
nullHopContext));
+
+ PipelineHopMeta emptyHop = new PipelineHopMeta((TransformMeta) null,
(TransformMeta) null);
+ HopGuiPipelineHopContext emptyHopContext =
+ new HopGuiPipelineHopContext(pipelineMeta, emptyHop, graph, new
Point(0, 0));
+ assertFalse(
+ graph.filterHopActions(
+ HopGuiPipelineGraph.ACTION_ID_PIPELINE_GRAPH_HOP_ROWS_COPY,
emptyHopContext));
+ assertFalse(
+ graph.filterHopActions(
+ HopGuiPipelineGraph.ACTION_ID_PIPELINE_GRAPH_HOP_ROWS_DISTRIBUTE,
emptyHopContext));
+ assertFalse(
+ graph.filterHopActions(
+ HopGuiPipelineGraph.ACTION_ID_PIPELINE_GRAPH_HOP_SET_PARTITIONING,
emptyHopContext));
+ assertFalse(
+ graph.filterHopActions(
+
HopGuiPipelineGraph.ACTION_ID_PIPELINE_GRAPH_HOP_REMOVE_PARTITIONING,
emptyHopContext));
+ }
+
+ @Test
+ void testFilterHopActions_DefaultAction() {
+ assertTrue(graph.filterHopActions("some-other-action", context));
+ }
+
+ @Test
+ void testSetHopDistributes() {
+ fromTransform.setDistributes(false);
+ fromTransform.setRowDistribution(mock(IRowDistribution.class));
+ doNothing().when(graph).redraw();
+ doNothing().when(graph).updateGui();
+
+ graph.setHopDistributes(context);
+
+ assertTrue(fromTransform.isDistributes());
+ assertNull(fromTransform.getRowDistribution());
+ verify(graph).redraw();
+ verify(graph).updateGui();
+ verify(undoDelegate).addUndoChange(any(), any(), any(), any());
+ }
+
+ @Test
+ void testSetHopCopies() {
+ fromTransform.setDistributes(true);
+ fromTransform.setRowDistribution(mock(IRowDistribution.class));
+ doNothing().when(graph).redraw();
+ doNothing().when(graph).updateGui();
+
+ graph.setHopCopies(context);
+
+ assertFalse(fromTransform.isDistributes());
+ assertNull(fromTransform.getRowDistribution());
+ verify(graph).redraw();
+ verify(graph).updateGui();
+ verify(undoDelegate).addUndoChange(any(), any(), any(), any());
+ }
+
+ @Test
+ void testSetHopPartitioning() {
+ HopGuiPipelineTransformDelegate delegate =
mock(HopGuiPipelineTransformDelegate.class);
+ graph.pipelineTransformDelegate = delegate;
+
+ graph.setHopPartitioning(context);
+
+ verify(delegate).editTransformPartitioning(pipelineMeta, toTransform);
+ }
+
+ @Test
+ void testRemoveHopPartitioning() {
+ TransformPartitioningMeta partMeta = new TransformPartitioningMeta();
+
partMeta.setMethodType(TransformPartitioningMeta.PARTITIONING_METHOD_MIRROR);
+ partMeta.setPartitionSchema(new PartitionSchema("schema1", new
java.util.ArrayList<>()));
+ toTransform.setTransformPartitioningMeta(partMeta);
+ toTransform.setChanged(false);
+
+ doNothing().when(graph).redraw();
+ doNothing().when(graph).updateGui();
+
+ graph.removeHopPartitioning(context);
+
+ assertFalse(toTransform.isPartitioned());
+ assertNull(toTransform.getTargetTransformPartitioningMeta());
+ assertTrue(toTransform.hasChanged());
+ verify(graph).redraw();
+ verify(graph).updateGui();
+ verify(undoDelegate).addUndoChange(any(), any(), any(), any());
+ }
+}