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

mattcasters 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 2d4399e920 Issue #8605 : Add shortcuts to open a running execution 
(#8610)
2d4399e920 is described below

commit 2d4399e920a39bc8494f0a92c5b3121d0501eed7
Author: Matt Casters <[email protected]>
AuthorDate: Mon Sep 28 11:57:15 2026 +0200

    Issue #8605 : Add shortcuts to open a running execution (#8610)
    
    * Issue #8605 : Add shortcuts to open a running execution
    
    * Issue #8605 : Open execution on Alt-click only while a run is active
    
    The engine stays set after a run finishes, so a null check kept Alt-click
    on Open execution and blocked error handling. The shortcut now uses
    isRunning(). MessageFormat quotes around the x key are escaped.
---
 .../modules/ROOT/pages/hop-gui/canvas-mouse.adoc   |   3 +-
 .../ROOT/pages/hop-gui/hop-gui-popup-dialog.adoc   |   6 +
 .../modules/ROOT/pages/protips/index.adoc          |   4 +
 .../org/apache/hop/ui/hopgui/HopWebEntryPoint.java |   5 +-
 .../apache/hop/ui/hopgui/HopWebEntryPointTest.java |   4 +-
 .../org/apache/hop/ui/hopgui/HopGuiKeyHandler.java |   6 +-
 .../hopgui/file/pipeline/HopGuiPipelineGraph.java  |  37 ++++++
 .../ui/hopgui/file/shared/DrillDownGuiPlugin.java  |  17 +++
 .../hopgui/file/workflow/HopGuiWorkflowGraph.java  |  38 ++++++
 .../file/shared/messages/messages_en_US.properties |   2 +-
 .../file/shared/messages/messages_pt_BR.properties |   2 +-
 .../file/shared/OpenExecutionShortcutTest.java     | 147 +++++++++++++++++++++
 12 files changed, 262 insertions(+), 9 deletions(-)

diff --git a/docs/hop-user-manual/modules/ROOT/pages/hop-gui/canvas-mouse.adoc 
b/docs/hop-user-manual/modules/ROOT/pages/hop-gui/canvas-mouse.adoc
index 6811f44789..ef1dc0840f 100644
--- a/docs/hop-user-manual/modules/ROOT/pages/hop-gui/canvas-mouse.adoc
+++ b/docs/hop-user-manual/modules/ROOT/pages/hop-gui/canvas-mouse.adoc
@@ -151,7 +151,7 @@ These do not depend on the two options.
 |Starts a new hop: click or drag onto the target to create it, onto empty 
canvas or `Esc` to cancel.
 |Adds to or removes from the selection.
 |Opens the referenced pipeline/workflow.
-|Pipeline: opens error handling (transforms that support it). Workflow: as a 
plain click.
+|Opens the running execution when the transform or action supports drill-down 
and a run is active. Otherwise, on a pipeline, opens error handling for 
transforms that support it. On a workflow, as a plain click.
 |Starts a new hop, as `Shift`-click.
 
 |Hop line
@@ -176,6 +176,7 @@ These do not depend on the two options.
 |Drag pans the canvas.
 |===
 
+* **`X`** with the pointer over a transform or action icon opens the running 
execution, the same as `Alt`-click when that icon supports drill-down and a run 
is active. **`Z`** opens the referenced pipeline or workflow, the same as 
`Shift`+`Ctrl`-click. `X` and `Z` are Hop Gui shortcuts: Hop Web leaves a bare 
letter to the browser. `Alt`-click works in Hop Gui and Hop Web.
 * **Left drag** on a transform, action or note moves it, together with 
everything selected. Dropping a transform on a hop offers to split the hop. 
Left drag on empty canvas draws a selection lasso.
 * **Note edges** drag to resize the note.
 * **`Esc`** cancels a hop in progress. A right click on empty canvas cancels 
it too.
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 82f65a676a..1a0ce2e868 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
@@ -129,6 +129,7 @@ When clicking on an action icon, the popup contains a 
number of actions to work
 ** **Detach action**: remove all hops to and from this action. If this action 
connects 2 other actions through hops, a hop between these actions will be 
created.
 ** **Edit action description**: opens the description dialog for this action
 ** **Delete this action**: delete this action from the workflow. No new hops 
will be created if this action was connected to other actions.
+** **Open execution**: while the workflow is running, open the pipeline or 
workflow this action started and attach its execution. Shown for actions that 
can drill down (Pipeline, Workflow, Repeat).
 * Advanced
 ** **Parallel execution**: enable or disable parallel execution for actions 
after the current action.
 * Logging
@@ -137,6 +138,8 @@ When clicking on an action icon, the popup contains a 
number of actions to work
 
 TIP: Two shortcuts to open the workflow or pipeline specified in an action are 
to hover over the icon and hit the `z` key, or `CTRL-SHIFT-click` on the action 
icon.
 
+TIP: While the workflow is running, hover over an action that starts a 
pipeline or workflow and press `X`, or `Alt`-click (Option-click on macOS) the 
icon, to open that execution.
+
 TIP: Click on an action icon to open the popup dialog. Click on the action 
name to directly open the action's properties. This is a shortcut to clicking 
on the icon and selecting `Edit` from the popup dialog.
 
 image:hop-gui/popup-action.png[Hop Gui Popup Dialog - Workflow Action Options, 
width="65%"]
@@ -165,6 +168,7 @@ When clicking on a transform icon, the popup contains a 
number of actions to wor
 ** **Show output fields**: show all output fields resulting from this transform
 ** **Edit description**: opens the description dialog for this transform
 ** **Delete**: delete this transform from the pipeline. No new hops will be 
created if this transform was connected to other transforms.
+** **Open execution**: while the pipeline is running, open the child pipeline 
or workflow this transform started and attach its execution. Shown for 
transforms that can drill down (Pipeline Executor, Workflow Executor, Mapping, 
ETL Metadata Injection, and so on).
 * Data Routing
 ** xref:pipeline/specify-copies.adoc[**Specify copies**]: set the number of 
transform copies to use during execution
 ** **Copy/distribute rows**: make the transform copy/distribute rows during 
execution. The option is contextual: if the transform is copying rows, only the 
distribute option will be shown and vice versa.
@@ -187,6 +191,8 @@ image:hop-gui/popup-transform.png[Hop Gui Popup Dialog - 
Workflow Transform Opti
 
 TIP: The options shown for Unit Testing are just a subset of the available 
options. Check the xref:pipeline/pipeline-unit-testing.adoc[unit testing] docs 
for a detailed explanation of all the unit testing options.
 
+TIP: While the pipeline is running, hover over a transform that can drill down 
and press `X`, or `Alt`-click (Option-click on macOS) the icon, to open that 
execution.
+
 TIP: Click on a transform icon to open the popup dialog. Click on the 
transform name to directly open the transform properties. This is a shortcut to 
clicking on the icon and selecting `Edit` from the popup dialog.
 
 
diff --git a/docs/hop-user-manual/modules/ROOT/pages/protips/index.adoc 
b/docs/hop-user-manual/modules/ROOT/pages/protips/index.adoc
index 9a933b6645..9bd59e0c1c 100644
--- a/docs/hop-user-manual/modules/ROOT/pages/protips/index.adoc
+++ b/docs/hop-user-manual/modules/ROOT/pages/protips/index.adoc
@@ -64,6 +64,10 @@ Action or transform hover + `Z`::
 Hover your mouse pointer over any pipeline action in a workflow, pipeline or 
workflow executor transform etc and hit the `Z` key to open that item in a new 
tab.
 The same behavior can be triggered by hovering over an item and hitting the 
use `CTRL-SHIFT-Click` key combination.
 
+Action or transform hover + `X`::
+While a pipeline or workflow is running, hover over an icon that can drill 
down (Pipeline Executor, Workflow Executor, Mapping, ETL Metadata Injection, a 
Pipeline or Workflow action, and so on) and press `X` to open the child 
execution.
+`Alt`-click (Option-click on macOS) does the same. With no run active, 
`Alt`-click on a pipeline transform still opens error handling when that 
transform supports it.
+
 == Projects and Environments
 
 * Projects can inherit metadata items (e.g. database connections) from parent 
projects.
diff --git a/rap/src/main/java/org/apache/hop/ui/hopgui/HopWebEntryPoint.java 
b/rap/src/main/java/org/apache/hop/ui/hopgui/HopWebEntryPoint.java
index d97b4be258..ff02b0e7bd 100644
--- a/rap/src/main/java/org/apache/hop/ui/hopgui/HopWebEntryPoint.java
+++ b/rap/src/main/java/org/apache/hop/ui/hopgui/HopWebEntryPoint.java
@@ -515,8 +515,9 @@ public class HopWebEntryPoint extends AbstractEntryPoint {
     // Never register a shortcut that is a printable character with no 
modifier held. RAP cancels
     // the browser's own handling of every key it is told about, so 
registering one takes that
     // character away from typing everywhere in Hop Web, whatever has the 
focus: the bare "z" that
-    // opens a referenced object on the pipeline canvas made it impossible to 
type the letter z
-    // anywhere, and searching the context dialog for "fuzzy match" arrived as 
"fuy match".
+    // opens a referenced object, and the bare "x" that opens a running 
execution, made it
+    // impossible to type that letter anywhere. Searching the context dialog 
for "fuzzy match"
+    // arrived as "fuy match".
     //
     // Nothing is lost that a browser could have delivered: the key handler 
already refuses to act
     // on an unmodified printable character while a text widget has the focus, 
so such a shortcut
diff --git 
a/rap/src/test/java/org/apache/hop/ui/hopgui/HopWebEntryPointTest.java 
b/rap/src/test/java/org/apache/hop/ui/hopgui/HopWebEntryPointTest.java
index c288084e77..f4f796b8ac 100644
--- a/rap/src/test/java/org/apache/hop/ui/hopgui/HopWebEntryPointTest.java
+++ b/rap/src/test/java/org/apache/hop/ui/hopgui/HopWebEntryPointTest.java
@@ -128,10 +128,12 @@ class HopWebEntryPointTest {
   void refusesBareLetterShortcuts() {
     // RAP cancels the browser's handling of every key it is told about, so a 
bare "z" - the
     // pipeline canvas shortcut that opens a referenced object - took the 
letter z away from every
-    // text field in Hop Web.
+    // text field in Hop Web. Bare "x" (open execution) is the same kind of 
shortcut.
     KeyboardShortcut shortcut = mock(KeyboardShortcut.class);
     when(shortcut.getKeyCode()).thenReturn((int) 'z');
+    assertNull(new HopWebEntryPoint().convertToRapFormat(shortcut));
 
+    when(shortcut.getKeyCode()).thenReturn((int) 'x');
     assertNull(new HopWebEntryPoint().convertToRapFormat(shortcut));
   }
 
diff --git a/ui/src/main/java/org/apache/hop/ui/hopgui/HopGuiKeyHandler.java 
b/ui/src/main/java/org/apache/hop/ui/hopgui/HopGuiKeyHandler.java
index 7783fbd5ee..40d375cf80 100644
--- a/ui/src/main/java/org/apache/hop/ui/hopgui/HopGuiKeyHandler.java
+++ b/ui/src/main/java/org/apache/hop/ui/hopgui/HopGuiKeyHandler.java
@@ -638,9 +638,9 @@ public class HopGuiKeyHandler extends KeyAdapter {
    * delete/backspace, caret / selection navigation (arrows, home/end, page 
up/down) without
    * CTRL/CMD/ALT, and unmodified printable characters (including space).
    *
-   * <p>Graph shortcuts such as Space (output fields) and {@code z} (open 
referenced object) must
-   * not steal those keys from filter and search fields. App shortcuts with 
CTRL/CMD/ALT (e.g.
-   * Ctrl+S) still run, except the horizontal word-movement keys handled above.
+   * <p>Graph shortcuts such as Space (output fields), {@code z} (open 
referenced object) and {@code
+   * x} (open execution) must not steal those keys from filter and search 
fields. App shortcuts with
+   * CTRL/CMD/ALT (e.g. Ctrl+S) still run, except the horizontal word-movement 
keys handled above.
    */
   private static boolean isNativeTextEditingKey(int keyCode, int stateMask, 
char character) {
     if ((stateMask & (SWT.CONTROL | SWT.COMMAND)) != 0) {
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 bd4b10340f..edf70fa058 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
@@ -1054,6 +1054,17 @@ public class HopGuiPipelineGraph extends 
HopGuiAbstractGraph
                 avoidContextDialog = false;
               }
             }
+          } else if (event.button == 1
+              && alt
+              && DrillDownGuiPlugin.altClickOpensExecution(
+                  this,
+                  currentTransform.getTransform() != null
+                      && currentTransform.getTransform().supportsDrillDown())) 
{
+            // Opening the execution is asynchronous, so claim this release. 
Otherwise mouseUp
+            // also opens the transform context dialog.
+            avoidContextDialog = true;
+            openExecution(currentTransform);
+            return;
           } else if (event.button == 1 && alt && 
currentTransform.supportsErrorHandling()) {
             // ALT-Click: edit error handling
             //
@@ -7537,6 +7548,32 @@ public class HopGuiPipelineGraph extends 
HopGuiAbstractGraph
     }
   }
 
+  /**
+   * Hover the icon and press {@code x}: open the running child execution. 
Same action as the "Open
+   * execution" context menu and as Alt-click while a run is active.
+   */
+  @GuiKeyboardShortcut(key = 'x')
+  @GuiOsxKeyboardShortcut(key = 'x')
+  public void openExecution() {
+    if (lastMove == null) {
+      return;
+    }
+    hideToolTips();
+    openExecution(pipelineMeta.getTransform(lastMove.x, lastMove.y, iconSize));
+  }
+
+  private void openExecution(TransformMeta transformMeta) {
+    if (transformMeta == null
+        || transformMeta.getTransform() == null
+        || !transformMeta.getTransform().supportsDrillDown()) {
+      return;
+    }
+    Point click = lastMove != null ? lastMove : new Point(0, 0);
+    new DrillDownGuiPlugin()
+        .openTransformExecution(
+            new HopGuiPipelineTransformContext(pipelineMeta, transformMeta, 
this, click));
+  }
+
   @Override
   public List<IGuiContextHandler> getContextHandlers() {
     return new ArrayList<>();
diff --git 
a/ui/src/main/java/org/apache/hop/ui/hopgui/file/shared/DrillDownGuiPlugin.java 
b/ui/src/main/java/org/apache/hop/ui/hopgui/file/shared/DrillDownGuiPlugin.java
index b9496f42f3..25336fdc3b 100644
--- 
a/ui/src/main/java/org/apache/hop/ui/hopgui/file/shared/DrillDownGuiPlugin.java
+++ 
b/ui/src/main/java/org/apache/hop/ui/hopgui/file/shared/DrillDownGuiPlugin.java
@@ -269,6 +269,23 @@ public class DrillDownGuiPlugin {
     return workflows != null ? workflows : Map.of();
   }
 
+  /**
+   * Alt-click (Option-click on macOS) opens the running execution when the 
icon supports drill-down
+   * and the graph reports a run in progress. Callers check this before the 
pipeline error-handling
+   * Alt-click. The engine reference stays set after a run finishes, so the 
running check is {@code
+   * isRunning()} on the graph.
+   */
+  public static boolean altClickOpensExecution(
+      HopGuiPipelineGraph pipelineGraph, boolean supportsDrillDown) {
+    return supportsDrillDown && pipelineGraph != null && 
pipelineGraph.isRunning();
+  }
+
+  /** Workflow form of {@link #altClickOpensExecution(HopGuiPipelineGraph, 
boolean)}. */
+  public static boolean altClickOpensExecution(
+      HopGuiWorkflowGraph workflowGraph, boolean supportsDrillDown) {
+    return supportsDrillDown && workflowGraph != null && 
workflowGraph.isRunning();
+  }
+
   // ==================== TRANSFORM CONTEXT ====================
 
   @GuiContextAction(
diff --git 
a/ui/src/main/java/org/apache/hop/ui/hopgui/file/workflow/HopGuiWorkflowGraph.java
 
b/ui/src/main/java/org/apache/hop/ui/hopgui/file/workflow/HopGuiWorkflowGraph.java
index 9afaddbec5..f3c38ac91a 100644
--- 
a/ui/src/main/java/org/apache/hop/ui/hopgui/file/workflow/HopGuiWorkflowGraph.java
+++ 
b/ui/src/main/java/org/apache/hop/ui/hopgui/file/workflow/HopGuiWorkflowGraph.java
@@ -735,6 +735,7 @@ public class HopGuiWorkflowGraph extends HopGuiAbstractGraph
       return;
     }
 
+    boolean alt = (event.stateMask & SWT.ALT) != 0;
     boolean control = (event.stateMask & SWT.MOD1) != 0;
     boolean shift = (event.stateMask & SWT.SHIFT) != 0;
 
@@ -837,6 +838,17 @@ public class HopGuiWorkflowGraph extends 
HopGuiAbstractGraph
                 avoidContextDialog = false;
               }
             }
+          } else if (event.button == 1
+              && alt
+              && DrillDownGuiPlugin.altClickOpensExecution(
+                  this,
+                  currentAction.getAction() != null
+                      && currentAction.getAction().supportsDrillDown())) {
+            // Opening the execution is asynchronous, so claim this release. 
Otherwise mouseUp
+            // also opens the action context dialog.
+            avoidContextDialog = true;
+            openExecution(currentAction);
+            return;
           } else if (canEditGraph() && (event.button == 2 || (event.button == 
1 && shift))) {
             // SHIFT CLICK is start of drag to create a new hop
             //
@@ -5736,6 +5748,32 @@ public class HopGuiWorkflowGraph extends 
HopGuiAbstractGraph
     }
   }
 
+  /**
+   * Hover the icon and press {@code x}: open the running child execution. 
Same action as the "Open
+   * execution" context menu and as Alt-click while a run is active.
+   */
+  @GuiKeyboardShortcut(key = 'x')
+  @GuiOsxKeyboardShortcut(key = 'x')
+  public void openExecution() {
+    if (lastMove == null) {
+      return;
+    }
+    hideToolTips();
+    openExecution(workflowMeta.getAction(lastMove.x, lastMove.y, iconSize));
+  }
+
+  private void openExecution(ActionMeta actionMeta) {
+    if (actionMeta == null
+        || actionMeta.getAction() == null
+        || !actionMeta.getAction().supportsDrillDown()) {
+      return;
+    }
+    Point click = lastMove != null ? lastMove : new Point(0, 0);
+    new DrillDownGuiPlugin()
+        .openActionExecution(
+            new HopGuiWorkflowActionContext(workflowMeta, actionMeta, this, 
click));
+  }
+
   @Override
   public Object getSubject() {
     return workflowMeta;
diff --git 
a/ui/src/main/resources/org/apache/hop/ui/hopgui/file/shared/messages/messages_en_US.properties
 
b/ui/src/main/resources/org/apache/hop/ui/hopgui/file/shared/messages/messages_en_US.properties
index ea8cce5e59..9ba0b37db0 100644
--- 
a/ui/src/main/resources/org/apache/hop/ui/hopgui/file/shared/messages/messages_en_US.properties
+++ 
b/ui/src/main/resources/org/apache/hop/ui/hopgui/file/shared/messages/messages_en_US.properties
@@ -16,7 +16,7 @@
 #
 
 DrillDown.OpenExecution.Name=Open execution
-DrillDown.OpenExecution.Tooltip=Open the executed pipeline or workflow and 
attach its running execution state
+DrillDown.OpenExecution.Tooltip=Open the executed pipeline or workflow and 
attach its running execution state.\nYou can hit key ''x'' with the cursor over 
the icon or use ALT+Click
 DrillDown.Error.Title=Error
 DrillDown.Error.OpeningExecution=Error opening execution for drill-down
 DrillDown.NoRunningExecution.Title=No running execution
diff --git 
a/ui/src/main/resources/org/apache/hop/ui/hopgui/file/shared/messages/messages_pt_BR.properties
 
b/ui/src/main/resources/org/apache/hop/ui/hopgui/file/shared/messages/messages_pt_BR.properties
index eb0c6cfe22..467c43086f 100644
--- 
a/ui/src/main/resources/org/apache/hop/ui/hopgui/file/shared/messages/messages_pt_BR.properties
+++ 
b/ui/src/main/resources/org/apache/hop/ui/hopgui/file/shared/messages/messages_pt_BR.properties
@@ -17,7 +17,7 @@
 #
 
 DrillDown.OpenExecution.Name=Abrir execução
-DrillDown.OpenExecution.Tooltip=Abra o conduto ou fluxo de trabalho executado 
e anexe seu estado de execução.
+DrillDown.OpenExecution.Tooltip=Abra o conduto ou fluxo de trabalho executado 
e anexe seu estado de execução.\nPodes teclar ''x'' com o cursor sobre o ícone 
ou usar ALT+Click
 DrillDown.Error.Title=Erro
 DrillDown.Error.OpeningExecution=Erro ao abrir execução.
 DrillDown.NoRunningExecution.Title=Sem execução
diff --git 
a/ui/src/test/java/org/apache/hop/ui/hopgui/file/shared/OpenExecutionShortcutTest.java
 
b/ui/src/test/java/org/apache/hop/ui/hopgui/file/shared/OpenExecutionShortcutTest.java
new file mode 100644
index 0000000000..f14349d11e
--- /dev/null
+++ 
b/ui/src/test/java/org/apache/hop/ui/hopgui/file/shared/OpenExecutionShortcutTest.java
@@ -0,0 +1,147 @@
+/*
+ * 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.shared;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.CALLS_REAL_METHODS;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Method;
+import java.text.MessageFormat;
+import java.util.Properties;
+import org.apache.hop.core.gui.plugin.key.GuiKeyboardShortcut;
+import org.apache.hop.core.gui.plugin.key.GuiOsxKeyboardShortcut;
+import org.apache.hop.pipeline.PipelineMeta;
+import org.apache.hop.pipeline.engine.IPipelineEngine;
+import org.apache.hop.ui.hopgui.file.pipeline.HopGuiPipelineGraph;
+import org.apache.hop.ui.hopgui.file.workflow.HopGuiWorkflowGraph;
+import org.apache.hop.workflow.WorkflowMeta;
+import org.apache.hop.workflow.engine.IWorkflowEngine;
+import org.junit.jupiter.api.Test;
+
+/** Issue #8605: hover an icon and press x, or Alt-click, to open the running 
execution. */
+class OpenExecutionShortcutTest {
+
+  @Test
+  void altClickOpensExecutionOnlyWhileARunIsActive() {
+    HopGuiPipelineGraph pipelineGraph = mock(HopGuiPipelineGraph.class, 
CALLS_REAL_METHODS);
+    @SuppressWarnings("unchecked")
+    IPipelineEngine<PipelineMeta> pipeline = mock(IPipelineEngine.class);
+    // A finished run leaves the engine set. isRunning() is false, so 
Alt-click must fall through
+    // to error handling.
+    pipelineGraph.pipeline = pipeline;
+    when(pipeline.isRunning()).thenReturn(false);
+    assertFalse(DrillDownGuiPlugin.altClickOpensExecution(pipelineGraph, 
true));
+
+    when(pipeline.isRunning()).thenReturn(true);
+    assertTrue(DrillDownGuiPlugin.altClickOpensExecution(pipelineGraph, true));
+    assertFalse(DrillDownGuiPlugin.altClickOpensExecution(pipelineGraph, 
false));
+
+    when(pipeline.isStopped()).thenReturn(true);
+    assertFalse(DrillDownGuiPlugin.altClickOpensExecution(pipelineGraph, 
true));
+
+    pipelineGraph.pipeline = null;
+    assertFalse(DrillDownGuiPlugin.altClickOpensExecution(pipelineGraph, 
true));
+
+    HopGuiWorkflowGraph workflowGraph = mock(HopGuiWorkflowGraph.class, 
CALLS_REAL_METHODS);
+    @SuppressWarnings("unchecked")
+    IWorkflowEngine<WorkflowMeta> workflow = mock(IWorkflowEngine.class);
+    workflowGraph.setWorkflow(workflow);
+    when(workflow.isFinished()).thenReturn(true);
+    when(workflow.isActive()).thenReturn(true);
+    assertFalse(DrillDownGuiPlugin.altClickOpensExecution(workflowGraph, 
true));
+
+    when(workflow.isFinished()).thenReturn(false);
+    when(workflow.isStopped()).thenReturn(false);
+    when(workflow.isActive()).thenReturn(true);
+    assertTrue(DrillDownGuiPlugin.altClickOpensExecution(workflowGraph, true));
+    assertFalse(DrillDownGuiPlugin.altClickOpensExecution(workflowGraph, 
false));
+
+    workflowGraph.setWorkflow(null);
+    assertFalse(DrillDownGuiPlugin.altClickOpensExecution(workflowGraph, 
true));
+  }
+
+  @Test
+  void openExecutionTooltipKeepsTheQuotedX() throws Exception {
+    assertQuotedX("messages/messages_en_US.properties", "hit key 'x'", "hit 
key x");
+    assertQuotedX("messages/messages_pt_BR.properties", "teclar 'x'", "teclar 
x");
+  }
+
+  private static void assertQuotedX(String resource, String quoted, String 
eaten)
+      throws IOException {
+    Properties properties = new Properties();
+    try (InputStream in = 
DrillDownGuiPlugin.class.getResourceAsStream(resource)) {
+      assertNotNull(in, resource);
+      properties.load(in);
+    }
+    String formatted =
+        MessageFormat.format(
+            properties.getProperty("DrillDown.OpenExecution.Tooltip"), new 
Object[0]);
+    assertTrue(formatted.contains(quoted), formatted);
+    assertFalse(formatted.contains(eaten), formatted);
+  }
+
+  @Test
+  void pipelineAndWorkflowGraphsBindBareX() throws Exception {
+    assertBareX(HopGuiPipelineGraph.class);
+    assertBareX(HopGuiWorkflowGraph.class);
+  }
+
+  @Test
+  void cutStaysOnCtrlOrCommandX() throws Exception {
+    
assertModifiedX(HopGuiPipelineGraph.class.getMethod("cutSelectedToClipboard"), 
true);
+    
assertModifiedX(HopGuiWorkflowGraph.class.getMethod("cutSelectedToClipboard"), 
true);
+  }
+
+  private static void assertBareX(Class<?> graphClass) throws Exception {
+    Method method = graphClass.getMethod("openExecution");
+    GuiKeyboardShortcut shortcut = 
method.getAnnotation(GuiKeyboardShortcut.class);
+    GuiOsxKeyboardShortcut osx = 
method.getAnnotation(GuiOsxKeyboardShortcut.class);
+    assertNotNull(shortcut, graphClass.getSimpleName());
+    assertNotNull(osx, graphClass.getSimpleName());
+    assertEquals('x', shortcut.key());
+    assertEquals('x', osx.key());
+    assertFalse(shortcut.control());
+    assertFalse(shortcut.alt());
+    assertFalse(shortcut.shift());
+    assertFalse(shortcut.command());
+    assertFalse(osx.control());
+    assertFalse(osx.alt());
+    assertFalse(osx.shift());
+    assertFalse(osx.command());
+  }
+
+  private static void assertModifiedX(Method method, boolean commandOnOsx) {
+    GuiKeyboardShortcut shortcut = 
method.getAnnotation(GuiKeyboardShortcut.class);
+    GuiOsxKeyboardShortcut osx = 
method.getAnnotation(GuiOsxKeyboardShortcut.class);
+    assertNotNull(shortcut);
+    assertNotNull(osx);
+    assertEquals('x', shortcut.key());
+    assertTrue(shortcut.control());
+    assertFalse(shortcut.alt());
+    assertFalse(shortcut.shift());
+    assertEquals(commandOnOsx, osx.command());
+    assertFalse(osx.control());
+  }
+}

Reply via email to