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 6fdc6e1034 fix duplicate result rows, fixes #5860 (#5861)
6fdc6e1034 is described below

commit 6fdc6e1034c5879e3c3dcf5195735e3a473caabc
Author: Hans Van Akelyen <[email protected]>
AuthorDate: Mon Oct 20 23:40:58 2025 +0200

    fix duplicate result rows, fixes #5860 (#5861)
---
 core/src/main/java/org/apache/hop/core/Result.java | 381 +++------------------
 .../org/apache/hop/core/gui/WorkflowTracker.java   | 137 +++++---
 .../apache/hop/execution/ExecutionDataBuilder.java |   2 +-
 .../hop/execution/ExecutionStateBuilder.java       |   2 +-
 .../engines/local/LocalPipelineEngine.java         |   2 +-
 .../main/java/org/apache/hop/run/HopRunBase.java   |   2 +-
 .../java/org/apache/hop/workflow/ActionResult.java | 123 ++-----
 .../java/org/apache/hop/workflow/Workflow.java     |  23 +-
 .../org/apache/hop/workflow/WorkflowPainter.java   |   4 +-
 .../engines/local/LocalWorkflowEngine.java         |   2 +-
 .../apache/hop/www/GetWorkflowStatusServlet.java   |   2 +-
 .../main/java/org/apache/hop/www/HopServer.java    |   2 +-
 .../concurrency/WorkflowTrackerUniquenessTest.java | 262 ++++++++++++++
 .../apache/hop/core/gui/WorkflowTrackerTest.java   |  19 +-
 .../copyfiles/WorkflowActionCopyFilesTest.java     |   4 +-
 .../WorkflowActionEvalTableContentTest.java        |   6 +-
 .../filesexist/WorkflowActionFilesExistTest.java   |   8 +-
 .../WorkflowActionFolderIsEmptyTest.java           |   4 +-
 .../WorkflowActionMoveFilesLocalTest.java          |  10 +-
 .../apache/hop/workflow/actions/repeat/Repeat.java |   2 +-
 .../setvariables/ActionSetVariablesTest.java       |  22 +-
 .../actions/snowflake/WarehouseManager.java        |   2 +-
 .../actions/waitforsql/ActionWaitForSql.java       |   2 +-
 .../workflow/actions/workflow/ActionWorkflow.java  |   2 +-
 .../action/ModifyActionLogLevelExtensionPoint.java |   2 +-
 .../hop/mail/workflow/actions/mail/ActionMail.java |   2 +-
 .../workflow/transform/WorkflowLogging.java        |   2 +-
 .../logging/xp/WorkflowLoggingExtensionPoint.java  |   2 +-
 .../pipelineexecutor/PipelineExecutor.java         |   2 +-
 .../pipeline/transforms/systemdata/SystemData.java |   2 +-
 .../workflowexecutor/WorkflowExecutor.java         |   2 +-
 .../hopgui/file/pipeline/HopGuiPipelineGraph.java  |   2 +-
 .../hopgui/file/workflow/HopGuiWorkflowGraph.java  |   6 +-
 .../delegates/HopGuiWorkflowGridDelegate.java      |  10 +-
 ui/src/main/resources/ui/laf.properties            |   4 +-
 35 files changed, 506 insertions(+), 555 deletions(-)

diff --git a/core/src/main/java/org/apache/hop/core/Result.java 
b/core/src/main/java/org/apache/hop/core/Result.java
index 74f0178608..2e8137046d 100644
--- a/core/src/main/java/org/apache/hop/core/Result.java
+++ b/core/src/main/java/org/apache/hop/core/Result.java
@@ -24,6 +24,8 @@ import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
+import lombok.Getter;
+import lombok.Setter;
 import org.apache.hop.core.exception.HopException;
 import org.apache.hop.core.exception.HopFileException;
 import org.apache.hop.core.row.IRowMeta;
@@ -54,6 +56,8 @@ import org.w3c.dom.Node;
  *       <p>After execution of a workflow or pipeline, the Result can be 
evaluated.
  * </ul>
  */
+@Getter
+@Setter
 public class Result implements Cloneable {
 
   /** A constant specifying the tag value for the XML node of the result 
object */
@@ -101,7 +105,7 @@ public class Result implements Cloneable {
   /** The exit status. */
   private int exitStatus;
 
-  /** The rows. */
+  /** The rows resulting from the pipeline or workflow execution */
   private List<RowMetaAndData> rows;
 
   /** The result files. */
@@ -195,8 +199,8 @@ public class Result implements Cloneable {
       // Clone result rows and files as well...
       if (rows != null) {
         List<RowMetaAndData> clonedRows = new ArrayList<>();
-        for (int i = 0; i < rows.size(); i++) {
-          clonedRows.add((rows.get(i)).clone());
+        for (RowMetaAndData row : rows) {
+          clonedRows.add(row.clone());
         }
         result.setRows(clonedRows);
       }
@@ -232,205 +236,6 @@ public class Result implements Cloneable {
         + (stopped ? " (Stopped)" : ", result=" + result);
   }
 
-  /**
-   * Returns the number of files retrieved during execution of this pipeline 
or workflow
-   *
-   * @return the number of files retrieved
-   */
-  public long getNrFilesRetrieved() {
-    return nrFilesRetrieved;
-  }
-
-  /**
-   * Sets the number of files retrieved to the specified value
-   *
-   * @param filesRetrieved The number of files retrieved to set.
-   */
-  public void setNrFilesRetrieved(long filesRetrieved) {
-    this.nrFilesRetrieved = filesRetrieved;
-  }
-
-  /**
-   * Returns the entry number
-   *
-   * @return the entry number
-   */
-  public long getEntryNr() {
-    return entryNr;
-  }
-
-  /**
-   * Sets the entry number to the specified value
-   *
-   * @param entryNr The entry number to set.
-   */
-  public void setEntryNr(long entryNr) {
-    this.entryNr = entryNr;
-  }
-
-  /**
-   * Returns the exit status value.
-   *
-   * @return the exit status.
-   */
-  public int getExitStatus() {
-    return exitStatus;
-  }
-
-  /**
-   * Sets the exit status value to the specified value
-   *
-   * @param exitStatus The exit status to set.
-   */
-  public void setExitStatus(int exitStatus) {
-    this.exitStatus = exitStatus;
-  }
-
-  /**
-   * Returns the number of errors that occurred during this pipeline or 
workflow
-   *
-   * @return the number of errors
-   */
-  public long getNrErrors() {
-    return nrErrors;
-  }
-
-  /**
-   * Sets the number of errors that occurred during execution of this pipeline 
or workflow
-   *
-   * @param nrErrors The number of errors to set
-   */
-  public void setNrErrors(long nrErrors) {
-    this.nrErrors = nrErrors;
-  }
-
-  /**
-   * Returns the number of lines input during execution of this pipeline or 
workflow
-   *
-   * @return the number of lines input
-   */
-  public long getNrLinesInput() {
-    return nrLinesInput;
-  }
-
-  /**
-   * Sets the number of lines input during execution of this pipeline or 
workflow
-   *
-   * @param nrLinesInput The number of lines input to set.
-   */
-  public void setNrLinesInput(long nrLinesInput) {
-    this.nrLinesInput = nrLinesInput;
-  }
-
-  /**
-   * Returns the number of lines output during execution of this pipeline or 
workflow
-   *
-   * @return the number of lines output
-   */
-  public long getNrLinesOutput() {
-    return nrLinesOutput;
-  }
-
-  /**
-   * Sets the number of lines output during execution of this pipeline or 
workflow
-   *
-   * @param nrLinesOutput The number of lines output to set
-   */
-  public void setNrLinesOutput(long nrLinesOutput) {
-    this.nrLinesOutput = nrLinesOutput;
-  }
-
-  /**
-   * Returns the number of lines read during execution of this pipeline or 
workflow
-   *
-   * @return the number of lines read
-   */
-  public long getNrLinesRead() {
-    return nrLinesRead;
-  }
-
-  /**
-   * Sets the number of lines read during execution of this pipeline or 
workflow
-   *
-   * @param nrLinesRead The number of lines read to set.
-   */
-  public void setNrLinesRead(long nrLinesRead) {
-    this.nrLinesRead = nrLinesRead;
-  }
-
-  /**
-   * Returns the number of lines updated during execution of this pipeline or 
workflow
-   *
-   * @return the number of lines updated
-   */
-  public long getNrLinesUpdated() {
-    return nrLinesUpdated;
-  }
-
-  /**
-   * Sets the number of lines updated during execution of this pipeline or 
workflow
-   *
-   * @param nrLinesUpdated The number of lines updated to set.
-   */
-  public void setNrLinesUpdated(long nrLinesUpdated) {
-    this.nrLinesUpdated = nrLinesUpdated;
-  }
-
-  /**
-   * Returns the number of lines written during execution of this pipeline or 
workflow
-   *
-   * @return the number of lines written
-   */
-  public long getNrLinesWritten() {
-    return nrLinesWritten;
-  }
-
-  /**
-   * Sets the number of lines written during execution of this pipeline or 
workflow
-   *
-   * @param nrLinesWritten The number of lines written to set.
-   */
-  public void setNrLinesWritten(long nrLinesWritten) {
-    this.nrLinesWritten = nrLinesWritten;
-  }
-
-  /**
-   * Returns the number of lines deleted during execution of this pipeline or 
workflow
-   *
-   * @return the number of lines deleted
-   */
-  public long getNrLinesDeleted() {
-    return nrLinesDeleted;
-  }
-
-  /**
-   * Sets the number of lines deleted during execution of this pipeline or 
workflow
-   *
-   * @param nrLinesDeleted The number of lines deleted to set.
-   */
-  public void setNrLinesDeleted(long nrLinesDeleted) {
-    this.nrLinesDeleted = nrLinesDeleted;
-  }
-
-  /**
-   * Returns the boolean result of this pipeline or workflow
-   *
-   * @return true if the pipeline or workflow was successful, false otherwise
-   */
-  public boolean getResult() {
-    return result;
-  }
-
-  /**
-   * Sets the result of the pipeline or workflow. A value of true should 
indicate a successful
-   * execution, a value of false should indicate an error condition.
-   *
-   * @param result The boolean result to set.
-   */
-  public void setResult(boolean result) {
-    this.result = result;
-  }
-
   /**
    * Returns the resulting rowset from the workflow or pipeline. For example, 
Result rows are used
    * in workflows where entries wish to receive the results of previous 
executions of workflows or
@@ -440,7 +245,7 @@ public class Result implements Cloneable {
    * @return a List of rows associated with the result of execution of a 
workflow or pipeline
    */
   public List<RowMetaAndData> getRows() {
-    return rows;
+    return new ArrayList<>(rows);
   }
 
   /**
@@ -449,25 +254,22 @@ public class Result implements Cloneable {
    * @param rows The List of rows to set.
    */
   public void setRows(List<RowMetaAndData> rows) {
-    this.rows = rows;
-  }
-
-  /**
-   * Returns whether the pipeline or workflow was stopped before completion
-   *
-   * @return true if stopped, false otherwise
-   */
-  public boolean isStopped() {
-    return stopped;
+    if (rows == null) {
+      this.rows = new ArrayList<>();
+    } else {
+      this.rows = new ArrayList<>(rows);
+    }
   }
 
   /**
-   * Sets whether the pipeline or workflow was stopped before completion
+   * Adds a single row to the result. Uses the row's hashCode as key to 
automatically deduplicate.
    *
-   * @param stopped true if the pipeline or workflow was stopped, false 
otherwise
+   * @param row The row to add
    */
-  public void setStopped(boolean stopped) {
-    this.stopped = stopped;
+  public void addRow(RowMetaAndData row) {
+    if (row != null) {
+      this.rows.add(row);
+    }
   }
 
   /** Clears the numbers in this result, setting them all to zero. Also 
deletes the logging text */
@@ -502,7 +304,10 @@ public class Result implements Cloneable {
     resultFiles.putAll(res.getResultFiles());
     logChannelId = res.getLogChannelId();
     logText = res.getLogText();
-    rows.addAll(res.getRows());
+    // Copy rows as well (serial execution case)
+    if (res.rows != null && !res.rows.isEmpty()) {
+      rows.addAll(res.rows);
+    }
   }
 
   /**
@@ -533,9 +338,13 @@ public class Result implements Cloneable {
         if (firstRow) {
           firstRow = false;
           rowMeta = row.getRowMeta();
-          xml.append(rowMeta.getMetaXml());
+          if (rowMeta != null) {
+            xml.append(rowMeta.getMetaXml());
+          }
+        }
+        if (rowMeta != null) {
+          xml.append(rowMeta.getDataXml(row.getData()));
         }
-        xml.append(rowMeta.getDataXml(row.getData()));
       }
       xml.append(XmlHandler.closeTag(XML_ROWS_TAG));
 
@@ -643,21 +452,11 @@ public class Result implements Cloneable {
       RowMeta rowMeta = new RowMeta(XmlHandler.getSubNode(resultRowsNode, 
RowMeta.XML_META_TAG));
       for (Node resultNode : resultNodes) {
         Object[] rowData = rowMeta.getRow(resultNode);
-        rows.add(new RowMetaAndData(rowMeta, rowData));
+        addRow(new RowMetaAndData(rowMeta, rowData));
       }
     }
   }
 
-  /**
-   * Returns the result files as a Map with the filename as key and the 
ResultFile object as value
-   *
-   * @return a Map with String as key and ResultFile as value.
-   * @see ResultFile
-   */
-  public Map<String, ResultFile> getResultFiles() {
-    return resultFiles;
-  }
-
   /**
    * Returns the result files as a List of type ResultFile
    *
@@ -669,53 +468,6 @@ public class Result implements Cloneable {
     return new ArrayList<>(resultFiles.values());
   }
 
-  /**
-   * Sets the result files for this Result to the specified Map of ResultFile 
objects
-   *
-   * @param usedFiles The Map of result files to set. This is a Map with the 
filename as key and
-   *     ResultFile object as value
-   * @see ResultFile
-   */
-  public void setResultFiles(Map<String, ResultFile> usedFiles) {
-    this.resultFiles = usedFiles;
-  }
-
-  /**
-   * Returns the number of lines rejected during execution of this pipeline or 
workflow
-   *
-   * @return the number of lines rejected
-   */
-  public long getNrLinesRejected() {
-    return nrLinesRejected;
-  }
-
-  /**
-   * Sets the number of lines rejected during execution of this pipeline or 
workflow
-   *
-   * @param nrLinesRejected the number of lines rejected to set
-   */
-  public void setNrLinesRejected(long nrLinesRejected) {
-    this.nrLinesRejected = nrLinesRejected;
-  }
-
-  /**
-   * Returns the log channel id of the object that was executed (pipeline, 
workflow, action, etc)
-   *
-   * @return the log channel id
-   */
-  public String getLogChannelId() {
-    return logChannelId;
-  }
-
-  /**
-   * Sets the log channel id of the object that was executed (pipeline, 
workflow, action, etc)
-   *
-   * @param logChannelId the logChannelId to set
-   */
-  public void setLogChannelId(String logChannelId) {
-    this.logChannelId = logChannelId;
-  }
-
   /**
    * Increases the number of lines read by the specified value
    *
@@ -788,76 +540,23 @@ public class Result implements Cloneable {
     nrErrors += incr;
   }
 
+  @Deprecated(since = "2.16")
   /**
-   * Returns all the text from any logging performed by the pipeline or 
workflow
-   *
-   * @return the logging text as a string
-   */
-  public String getLogText() {
-    return logText;
-  }
-
-  /**
-   * Sets the logging text to the specified String
-   *
-   * @param logText the logText to set
-   */
-  public void setLogText(String logText) {
-    this.logText = logText;
-  }
-
-  /**
-   * Returns the elapsed time of the ETL execution in milliseconds
+   * Returns whether the pipeline or workflow was successful.
    *
-   * @return elapsed time of the ETL execution in milliseconds
+   * @deprecated Use {@link #isResult()} instead. This method remains for 
backward compatibility and
+   *     will be removed in a future release.
    */
-  public long getElapsedTimeMillis() {
-    return elapsedTimeMillis;
-  }
-
-  /**
-   * Sets the elapsed time of the ETL execution in milliseconds
-   *
-   * @param elapsedTimeMillis elapsed time of the ETL execution in milliseconds
-   */
-  public void setElapsedTimeMillis(long elapsedTimeMillis) {
-    this.elapsedTimeMillis = elapsedTimeMillis;
-  }
-
-  /**
-   * Returns the unique identifier of an ETL execution, should one ever care 
to declare one such
-   *
-   * @return unique identifier of an ETL execution, should one ever care to 
declare one such
-   */
-  public String getExecutionId() {
-    return executionId;
-  }
-
-  /**
-   * Sets a unique identifier of an ETL execution, should one ever care to 
declare one such
-   *
-   * @param executionId unique identifier of an ETL execution, should one ever 
care to declare one
-   *     such
-   */
-  public void setExecutionId(String executionId) {
-    this.executionId = executionId;
-  }
-
-  /**
-   * Gets containerId
-   *
-   * @return value of containerId
-   */
-  public String getContainerId() {
-    return containerId;
+  public boolean getResult() {
+    return result;
   }
 
   /**
-   * Sets containerId
+   * Returns whether the pipeline or workflow execution was successful.
    *
-   * @param containerId value of containerId
+   * @return true if the execution was successful, false otherwise
    */
-  public void setContainerId(String containerId) {
-    this.containerId = containerId;
+  public boolean isResult() {
+    return result;
   }
 }
diff --git a/engine/src/main/java/org/apache/hop/core/gui/WorkflowTracker.java 
b/engine/src/main/java/org/apache/hop/core/gui/WorkflowTracker.java
index 88b6062ba2..e1ee62de97 100644
--- a/engine/src/main/java/org/apache/hop/core/gui/WorkflowTracker.java
+++ b/engine/src/main/java/org/apache/hop/core/gui/WorkflowTracker.java
@@ -18,10 +18,15 @@
 package org.apache.hop.core.gui;
 
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.ListIterator;
+import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
+import lombok.Getter;
+import lombok.Setter;
 import org.apache.hop.core.Const;
 import org.apache.hop.core.util.EnvUtil;
 import org.apache.hop.workflow.ActionResult;
@@ -29,6 +34,8 @@ import org.apache.hop.workflow.WorkflowMeta;
 import org.apache.hop.workflow.action.ActionMeta;
 
 /** Responsible for tracking the execution of a workflow as a hierarchy. */
+@Getter
+@Setter
 public class WorkflowTracker<T extends WorkflowMeta> {
   /**
    * The trackers for each individual action. Since we invoke 
LinkedList.removeFirst() there is no
@@ -36,6 +43,12 @@ public class WorkflowTracker<T extends WorkflowMeta> {
    */
   private LinkedList<WorkflowTracker> workflowTrackers;
 
+  /**
+   * Set to track unique identifiers of workflow trackers to prevent 
duplicates when actions run in
+   * parallel
+   */
+  private Set<String> trackerIdentifiers;
+
   /** If the workflowTrackers list is empty, then this is the result */
   private ActionResult result;
 
@@ -70,6 +83,7 @@ public class WorkflowTracker<T extends WorkflowMeta> {
     }
 
     this.workflowTrackers = new LinkedList<>();
+    this.trackerIdentifiers = new HashSet<>();
     this.maxChildren = maxChildren;
     this.lock = new ReentrantReadWriteLock();
   }
@@ -100,10 +114,21 @@ public class WorkflowTracker<T extends WorkflowMeta> {
   public void addWorkflowTracker(WorkflowTracker workflowTracker) {
     lock.writeLock().lock();
     try {
-      workflowTrackers.add(workflowTracker);
-      while (workflowTrackers.size() > maxChildren) {
-        // Use remove instead of subList
-        workflowTrackers.removeFirst();
+      String identifier = workflowTracker.getUniqueIdentifier();
+
+      // Only add if this tracker is unique (not already present)
+      if (identifier != null && !trackerIdentifiers.contains(identifier)) {
+        workflowTrackers.add(workflowTracker);
+        trackerIdentifiers.add(identifier);
+
+        // Remove oldest entries if we exceed maxChildren
+        while (workflowTrackers.size() > maxChildren) {
+          WorkflowTracker removed = workflowTrackers.removeFirst();
+          String removedId = removed.getUniqueIdentifier();
+          if (removedId != null) {
+            trackerIdentifiers.remove(removedId);
+          }
+        }
       }
     } finally {
       lock.writeLock().unlock();
@@ -150,7 +175,16 @@ public class WorkflowTracker<T extends WorkflowMeta> {
     lock.writeLock().lock();
     try {
       this.workflowTrackers.clear();
+      this.trackerIdentifiers.clear();
       this.workflowTrackers.addAll(workflowTrackers);
+
+      // Rebuild the identifier set
+      for (WorkflowTracker tracker : workflowTrackers) {
+        String identifier = tracker.getUniqueIdentifier();
+        if (identifier != null) {
+          this.trackerIdentifiers.add(identifier);
+        }
+      }
     } finally {
       lock.writeLock().unlock();
     }
@@ -174,6 +208,7 @@ public class WorkflowTracker<T extends WorkflowMeta> {
     lock.writeLock().lock();
     try {
       workflowTrackers.clear();
+      trackerIdentifiers.clear();
       result = null;
     } finally {
       lock.writeLock().unlock();
@@ -207,20 +242,6 @@ public class WorkflowTracker<T extends WorkflowMeta> {
     return null;
   }
 
-  /**
-   * @return Returns the parentWorkflowTracker.
-   */
-  public WorkflowTracker getParentWorkflowTracker() {
-    return parentWorkflowTracker;
-  }
-
-  /**
-   * @param parentWorkflowTracker The parentWorkflowTracker to set.
-   */
-  public void setParentWorkflowTracker(WorkflowTracker parentWorkflowTracker) {
-    this.parentWorkflowTracker = parentWorkflowTracker;
-  }
-
   public int getTotalNumberOfItems() {
     lock.readLock().lock();
     try {
@@ -237,44 +258,62 @@ public class WorkflowTracker<T extends WorkflowMeta> {
   }
 
   /**
-   * @return the workflowFilename
+   * Gets a unique identifier for this workflow tracker. Uses the log channel 
ID to uniquely
+   * identify each execution event. When actions run in parallel, the SAME 
logChannelId means the
+   * SAME execution event, which should be deduplicated. Different 
logChannelIds means different
+   * events.
+   *
+   * <p>Falls back to a combination of workflow name, action name, comment, 
and timestamp if
+   * logChannelId is not available.
+   *
+   * @return A unique identifier string, or null if insufficient information
    */
-  public String getWorfkflowFilename() {
-    return workflowFilename;
-  }
+  public String getUniqueIdentifier() {
+    if (result != null) {
+      // Fallback: construct identifier from available fields
+      StringBuilder identifier = new StringBuilder();
+      if (workflowName != null) {
+        identifier.append(workflowName).append("::");
+      }
+      if (workflowFilename != null) {
+        identifier.append(workflowFilename);
+      }
 
-  /**
-   * @param workflowFilename the workflowFilename to set
-   */
-  public void setWorkflowFilename(String workflowFilename) {
-    this.workflowFilename = workflowFilename;
+      return identifier.length() > 0 ? identifier.toString() : null;
+    }
+    return null;
   }
 
-  /**
-   * @return the workflowName
-   */
-  public String getWorkflowName() {
-    return workflowName;
-  }
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    WorkflowTracker<?> that = (WorkflowTracker<?>) o;
 
-  /**
-   * @param workflowName the workflowName to set
-   */
-  public void setWorkflowName(String workflowName) {
-    this.workflowName = workflowName;
-  }
+    String thisId = this.getUniqueIdentifier();
+    String thatId = that.getUniqueIdentifier();
 
-  /**
-   * @return the maxChildren
-   */
-  public int getMaxChildren() {
-    return maxChildren;
+    // If both have identifiers, compare them
+    if (thisId != null && thatId != null) {
+      return thisId.equals(thatId);
+    }
+
+    // Fallback to comparing workflow name and filename
+    return Objects.equals(workflowName, that.workflowName)
+        && Objects.equals(workflowFilename, that.workflowFilename)
+        && Objects.equals(result, that.result);
   }
 
-  /**
-   * @param maxChildren the maxChildren to set
-   */
-  public void setMaxChildren(int maxChildren) {
-    this.maxChildren = maxChildren;
+  @Override
+  public int hashCode() {
+    String identifier = getUniqueIdentifier();
+    if (identifier != null) {
+      return identifier.hashCode();
+    }
+    return Objects.hash(workflowName, workflowFilename, result);
   }
 }
diff --git 
a/engine/src/main/java/org/apache/hop/execution/ExecutionDataBuilder.java 
b/engine/src/main/java/org/apache/hop/execution/ExecutionDataBuilder.java
index 189116d5b7..a00787fcdd 100644
--- a/engine/src/main/java/org/apache/hop/execution/ExecutionDataBuilder.java
+++ b/engine/src/main/java/org/apache/hop/execution/ExecutionDataBuilder.java
@@ -200,7 +200,7 @@ public final class ExecutionDataBuilder {
     {
       IRowMeta resultRowMeta = new 
RowMetaBuilder().addString("key").addString(CONST_VALUE).build();
       RowBuffer resultBuffer = new RowBuffer(resultRowMeta);
-      resultBuffer.addRow(RESULT_KEY_RESULT, result.getResult() ? "true" : 
"false");
+      resultBuffer.addRow(RESULT_KEY_RESULT, result.isResult() ? "true" : 
"false");
       resultBuffer.addRow(RESULT_KEY_ERRORS, 
Long.toString(result.getNrErrors()));
       resultBuffer.addRow(RESULT_KEY_STOPPED, result.isStopped() ? "true" : 
"false");
 
diff --git 
a/engine/src/main/java/org/apache/hop/execution/ExecutionStateBuilder.java 
b/engine/src/main/java/org/apache/hop/execution/ExecutionStateBuilder.java
index efd7b8c47a..b11586c0cc 100644
--- a/engine/src/main/java/org/apache/hop/execution/ExecutionStateBuilder.java
+++ b/engine/src/main/java/org/apache/hop/execution/ExecutionStateBuilder.java
@@ -167,7 +167,7 @@ public final class ExecutionStateBuilder {
         .withName(workflow.getWorkflowMeta().getName())
         .withLoggingText(getLoggingText(workflow.getLogChannelId(), 
lastLogLineNr))
         .withLastLogLineNr(lastNrInLogStore)
-        .withFailed(result != null && !result.getResult())
+        .withFailed(result != null && !result.isResult())
         .withStatusDescription(workflow.getStatusDescription())
         .withChildIds(
             
LoggingRegistry.getInstance().getChildrenMap().get(workflow.getLogChannelId()))
diff --git 
a/engine/src/main/java/org/apache/hop/pipeline/engines/local/LocalPipelineEngine.java
 
b/engine/src/main/java/org/apache/hop/pipeline/engines/local/LocalPipelineEngine.java
index d4d5c3c47c..35d9170d65 100644
--- 
a/engine/src/main/java/org/apache/hop/pipeline/engines/local/LocalPipelineEngine.java
+++ 
b/engine/src/main/java/org/apache/hop/pipeline/engines/local/LocalPipelineEngine.java
@@ -162,7 +162,7 @@ public class LocalPipelineEngine extends Pipeline 
implements IPipelineEngine<Pip
               // All fine?  Commit!
               //
               try {
-                if (result.getResult() && !result.isStopped() && 
result.getNrErrors() == 0) {
+                if (result.isResult() && !result.isStopped() && 
result.getNrErrors() == 0) {
                   try {
                     database.commit(true);
                     pipeline
diff --git a/engine/src/main/java/org/apache/hop/run/HopRunBase.java 
b/engine/src/main/java/org/apache/hop/run/HopRunBase.java
index 6e0c8bf57c..263e2e6251 100644
--- a/engine/src/main/java/org/apache/hop/run/HopRunBase.java
+++ b/engine/src/main/java/org/apache/hop/run/HopRunBase.java
@@ -396,7 +396,7 @@ public abstract class HopRunBase implements Runnable, 
IHasHopMetadataProvider {
       log.logMinimal("Starting workflow: " + workflowMeta.getFilename());
 
       workflow.startExecution();
-      setFinishedWithoutError(workflow.getResult().getResult());
+      setFinishedWithoutError(workflow.getResult().isResult());
     } catch (Exception e) {
       throw new ExecutionException(cmd, "Error running workflow locally", e);
     }
diff --git a/engine/src/main/java/org/apache/hop/workflow/ActionResult.java 
b/engine/src/main/java/org/apache/hop/workflow/ActionResult.java
index 4d99e97477..01f918dc35 100644
--- a/engine/src/main/java/org/apache/hop/workflow/ActionResult.java
+++ b/engine/src/main/java/org/apache/hop/workflow/ActionResult.java
@@ -19,6 +19,8 @@ package org.apache.hop.workflow;
 
 import java.util.Comparator;
 import java.util.Date;
+import lombok.Getter;
+import lombok.Setter;
 import org.apache.hop.core.Result;
 
 /**
@@ -31,6 +33,8 @@ import org.apache.hop.core.Result;
  *
  * <p>
  */
+@Getter
+@Setter
 public class ActionResult implements Cloneable, Comparator<ActionResult>, 
Comparable<ActionResult> {
   private Result result;
   private String actionName;
@@ -87,90 +91,6 @@ public class ActionResult implements Cloneable, 
Comparator<ActionResult>, Compar
     }
   }
 
-  /**
-   * @param result The result to set.
-   */
-  public void setResult(Result result) {
-    this.result = result;
-  }
-
-  /**
-   * @return Returns the result.
-   */
-  public Result getResult() {
-    return result;
-  }
-
-  /**
-   * @return Returns the comment.
-   */
-  public String getComment() {
-    return comment;
-  }
-
-  /**
-   * @param comment The comment to set.
-   */
-  public void setComment(String comment) {
-    this.comment = comment;
-  }
-
-  /**
-   * @return Returns the reason.
-   */
-  public String getReason() {
-    return reason;
-  }
-
-  /**
-   * @param reason The reason to set.
-   */
-  public void setReason(String reason) {
-    this.reason = reason;
-  }
-
-  /**
-   * @return Returns the logDate.
-   */
-  public Date getLogDate() {
-    return logDate;
-  }
-
-  /**
-   * @param logDate The logDate to set.
-   */
-  public void setLogDate(Date logDate) {
-    this.logDate = logDate;
-  }
-
-  /**
-   * @return the actionName
-   */
-  public String getActionName() {
-    return actionName;
-  }
-
-  /**
-   * @param actionName the actionName to set
-   */
-  public void setActionName(String actionName) {
-    this.actionName = actionName;
-  }
-
-  /**
-   * @return the actionFilename
-   */
-  public String getActionFilename() {
-    return actionFilename;
-  }
-
-  /**
-   * @param actionFilename the actionFilename to set
-   */
-  public void setActionFilename(String actionFilename) {
-    this.actionFilename = actionFilename;
-  }
-
   @Override
   public int compare(ActionResult one, ActionResult two) {
     if (one == null && two != null) {
@@ -200,21 +120,28 @@ public class ActionResult implements Cloneable, 
Comparator<ActionResult>, Compar
     return compare(this, two);
   }
 
-  public String getLogChannelId() {
-    return logChannelId;
-  }
-
-  /**
-   * @return the checkpoint
-   */
-  public boolean isCheckpoint() {
-    return checkpoint;
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    ActionResult that = (ActionResult) o;
+    return checkpoint == that.checkpoint
+        && java.util.Objects.equals(result, that.result)
+        && java.util.Objects.equals(actionName, that.actionName)
+        && java.util.Objects.equals(comment, that.comment)
+        && java.util.Objects.equals(reason, that.reason)
+        && java.util.Objects.equals(logDate, that.logDate)
+        && java.util.Objects.equals(actionFilename, that.actionFilename)
+        && java.util.Objects.equals(logChannelId, that.logChannelId);
   }
 
-  /**
-   * @param checkpoint the checkpoint to set
-   */
-  public void setCheckpoint(boolean checkpoint) {
-    this.checkpoint = checkpoint;
+  @Override
+  public int hashCode() {
+    return java.util.Objects.hash(
+        result, actionName, comment, reason, logDate, actionFilename, 
logChannelId, checkpoint);
   }
 }
diff --git a/engine/src/main/java/org/apache/hop/workflow/Workflow.java 
b/engine/src/main/java/org/apache/hop/workflow/Workflow.java
index 14c7c1372f..3564d629a0 100644
--- a/engine/src/main/java/org/apache/hop/workflow/Workflow.java
+++ b/engine/src/main/java/org/apache/hop/workflow/Workflow.java
@@ -687,7 +687,7 @@ public abstract class Workflow extends Variables
                 PKG,
                 "Workflow.Log.FinishedAction",
                 previous.getName(),
-                previousResult.getResult() + ""));
+                previousResult.isResult() + ""));
       }
     }
 
@@ -843,7 +843,7 @@ public abstract class Workflow extends Variables
       if (hopMeta.isUnconditional()) {
         nextComment = BaseMessages.getString(PKG, 
"Workflow.Comment.FollowedUnconditional");
       } else {
-        if (newResult.getResult()) {
+        if (newResult.isResult()) {
           nextComment = BaseMessages.getString(PKG, 
"Workflow.Comment.FollowedSuccess");
         } else {
           nextComment = BaseMessages.getString(PKG, 
"Workflow.Comment.FollowedFailure");
@@ -856,7 +856,7 @@ public abstract class Workflow extends Variables
       // green or red, execute the next action...
       //
       if (hopMeta.isUnconditional()
-          || (actionMeta.isEvaluation() && (hopMeta.isEvaluation() == 
newResult.getResult()))) {
+          || (actionMeta.isEvaluation() && (hopMeta.isEvaluation() == 
newResult.isResult()))) {
 
         // If the next action is a join, only execute once
         if (nextAction.isJoin()) {
@@ -882,8 +882,15 @@ public abstract class Workflow extends Variables
           Runnable runnable =
               () -> {
                 try {
+                  // Pass a previous result without rows to the parallel 
branch so that
+                  // branch-local result rows start from a clean slate but 
still inherit
+                  // metrics/files/etc.
+                  Result prevWithoutRows = newResult.lightClone();
+                  prevWithoutRows.setRows(null); // ensure rows are empty
+
                   Result threadResult =
-                      executeFromStart(nr + 1, newResult, nextAction, 
actionMeta, nextComment);
+                      executeFromStart(
+                          nr + 1, prevWithoutRows, nextAction, actionMeta, 
nextComment);
                   threadResults.add(threadResult);
                 } catch (Throwable e) {
                   log.logError(Const.getStackTracker(e));
@@ -967,9 +974,9 @@ public abstract class Workflow extends Variables
       throw threadExceptions.poll();
     }
 
-    // In parallel execution, we aggregate all the results, simply add them to
-    // the previous result...
-    //
+    // In parallel execution, aggregate full results from branches. Since we 
started each
+    // branch with no previous rows, any rows present here were produced by 
the branch and
+    // should be included in the final result.
     for (Result threadResult : threadResults) {
       res.add(threadResult);
     }
@@ -985,7 +992,7 @@ public abstract class Workflow extends Variables
       if (log.isBasic()) {
         log.logBasic(
             BaseMessages.getString(
-                PKG, "Workflow.Log.FinishedAction", actionMeta.getName(), 
res.getResult() + ""));
+                PKG, "Workflow.Log.FinishedAction", actionMeta.getName(), 
res.isResult() + ""));
       }
     }
 
diff --git a/engine/src/main/java/org/apache/hop/workflow/WorkflowPainter.java 
b/engine/src/main/java/org/apache/hop/workflow/WorkflowPainter.java
index 71e9b76c08..5ee7ce74bf 100644
--- a/engine/src/main/java/org/apache/hop/workflow/WorkflowPainter.java
+++ b/engine/src/main/java/org/apache/hop/workflow/WorkflowPainter.java
@@ -252,7 +252,7 @@ public class WorkflowPainter extends 
BasePainter<WorkflowHopMeta, ActionMeta> {
     boolean actionError = false;
     ActionResult actionResult = findActionResult(actionMeta);
     if (actionResult != null && !actionResult.isCheckpoint()) {
-      actionError = !actionResult.getResult().getResult();
+      actionError = !actionResult.getResult().isResult();
     }
 
     if (actionError || actionMeta.isMissing()) {
@@ -372,7 +372,7 @@ public class WorkflowPainter extends 
BasePainter<WorkflowHopMeta, ActionMeta> {
                 actionMeta,
                 actionResult));
       } else {
-        if (result.getResult()) {
+        if (result.isResult()) {
           gc.drawImage(EImage.SUCCESS, iconX, iconY, magnification);
           areaOwners.add(
               new AreaOwner(
diff --git 
a/engine/src/main/java/org/apache/hop/workflow/engines/local/LocalWorkflowEngine.java
 
b/engine/src/main/java/org/apache/hop/workflow/engines/local/LocalWorkflowEngine.java
index 7a13483ba2..0fddf83ef6 100644
--- 
a/engine/src/main/java/org/apache/hop/workflow/engines/local/LocalWorkflowEngine.java
+++ 
b/engine/src/main/java/org/apache/hop/workflow/engines/local/LocalWorkflowEngine.java
@@ -137,7 +137,7 @@ public class LocalWorkflowEngine extends Workflow 
implements IWorkflowEngine<Wor
               // All fine?  Commit!
               //
               try {
-                if (result.getResult() && !result.isStopped() && 
result.getNrErrors() == 0) {
+                if (result.isResult() && !result.isStopped() && 
result.getNrErrors() == 0) {
                   try {
                     database.commit(true);
                     workflow
diff --git 
a/engine/src/main/java/org/apache/hop/www/GetWorkflowStatusServlet.java 
b/engine/src/main/java/org/apache/hop/www/GetWorkflowStatusServlet.java
index 400f031838..d16d5588f7 100644
--- a/engine/src/main/java/org/apache/hop/www/GetWorkflowStatusServlet.java
+++ b/engine/src/main/java/org/apache/hop/www/GetWorkflowStatusServlet.java
@@ -149,7 +149,7 @@ public class GetWorkflowStatusServlet extends 
BaseHttpServlet implements IHopSer
           for (ActionResult actionResult : workflow.getActionResults()) {
             ActionStatus actionState = new ActionStatus();
             actionState.setName(actionResult.getActionName());
-            if (actionResult.getResult().getResult()) {
+            if (actionResult.getResult().isResult()) {
               actionState.setStatus(Status.FINISHED);
             } else {
               actionState.setStatus(Status.STOPPED);
diff --git a/engine/src/main/java/org/apache/hop/www/HopServer.java 
b/engine/src/main/java/org/apache/hop/www/HopServer.java
index e248742be7..7fb4961722 100644
--- a/engine/src/main/java/org/apache/hop/www/HopServer.java
+++ b/engine/src/main/java/org/apache/hop/www/HopServer.java
@@ -468,7 +468,7 @@ public class HopServer implements Runnable, 
IHasHopMetadataProvider, IHopCommand
     System.out.println("      Status:   " + 
workflowStatus.getStatusDescription());
     System.out.println("      Log date: " + 
formatDate(workflowStatus.getLogDate()));
     if (result != null) {
-      System.out.println("      Result:   " + result.getResult());
+      System.out.println("      Result:   " + result.isResult());
       System.out.println("      Errors:   " + result.getNrErrors());
     }
     if (printDetails) {
diff --git 
a/engine/src/test/java/org/apache/hop/concurrency/WorkflowTrackerUniquenessTest.java
 
b/engine/src/test/java/org/apache/hop/concurrency/WorkflowTrackerUniquenessTest.java
new file mode 100644
index 0000000000..c37e9a04cf
--- /dev/null
+++ 
b/engine/src/test/java/org/apache/hop/concurrency/WorkflowTrackerUniquenessTest.java
@@ -0,0 +1,262 @@
+/*
+ * 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.concurrency;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import org.apache.hop.core.gui.WorkflowTracker;
+import org.apache.hop.workflow.ActionResult;
+import org.apache.hop.workflow.WorkflowMeta;
+import org.junit.Test;
+
+/**
+ * Test to verify that WorkflowTracker enforces uniqueness per workflow 
instance when multiple
+ * threads attempt to add duplicate trackers concurrently (as happens when 
actions run in parallel).
+ * Each workflow (identified by name + filename) should only have one tracker.
+ */
+public class WorkflowTrackerUniquenessTest {
+
+  private static WorkflowMeta mockWorkflowMeta(String name) {
+    WorkflowMeta meta = mock(WorkflowMeta.class);
+    when(meta.getName()).thenReturn(name);
+    return meta;
+  }
+
+  /**
+   * Test that when multiple threads try to add WorkflowTrackers for the same 
workflow, only one is
+   * actually added (uniqueness is enforced at workflow level).
+   */
+  @Test
+  public void testUniquenessWithSameWorkflow() throws Exception {
+    WorkflowTracker tracker = new 
WorkflowTracker(mockWorkflowMeta("parent-workflow"));
+
+    final int NUM_THREADS = 10;
+
+    ExecutorService executor = Executors.newFixedThreadPool(NUM_THREADS);
+    CountDownLatch startLatch = new CountDownLatch(1);
+    CountDownLatch doneLatch = new CountDownLatch(NUM_THREADS);
+
+    // All threads will try to add a tracker for the same child workflow
+    for (int i = 0; i < NUM_THREADS; i++) {
+      final int threadNum = i;
+      executor.submit(
+          () -> {
+            try {
+              startLatch.await(); // Wait for all threads to be ready
+
+              ActionResult result =
+                  new ActionResult(
+                      null,
+                      "log-channel-" + threadNum, // Different log channels
+                      "Action started",
+                      null,
+                      "action-" + threadNum, // Different actions
+                      null);
+
+              // All threads create trackers for the SAME workflow (same name)
+              WorkflowTracker childTracker =
+                  new WorkflowTracker(mockWorkflowMeta("child-workflow"), 
result);
+              tracker.addWorkflowTracker(childTracker);
+            } catch (InterruptedException e) {
+              Thread.currentThread().interrupt();
+            } finally {
+              doneLatch.countDown();
+            }
+          });
+    }
+
+    startLatch.countDown(); // Start all threads
+    assertTrue("Threads did not complete in time", doneLatch.await(5, 
TimeUnit.SECONDS));
+    executor.shutdown();
+
+    // Verify only ONE tracker was added for the child workflow (not 10)
+    assertEquals("Expected only 1 unique tracker per workflow", 1, 
tracker.nrWorkflowTrackers());
+  }
+
+  /**
+   * Test that when multiple threads add WorkflowTrackers for DIFFERENT 
workflows, all are added
+   * (uniqueness is per workflow).
+   */
+  @Test
+  public void testUniquenessWithDifferentWorkflows() throws Exception {
+    WorkflowTracker tracker = new 
WorkflowTracker(mockWorkflowMeta("parent-workflow"));
+
+    final int NUM_THREADS = 10;
+
+    ExecutorService executor = Executors.newFixedThreadPool(NUM_THREADS);
+    CountDownLatch startLatch = new CountDownLatch(1);
+    CountDownLatch doneLatch = new CountDownLatch(NUM_THREADS);
+
+    // Each thread adds a tracker for a DIFFERENT child workflow
+    for (int i = 0; i < NUM_THREADS; i++) {
+      final int threadNum = i;
+      executor.submit(
+          () -> {
+            try {
+              startLatch.await();
+
+              ActionResult result =
+                  new ActionResult(
+                      null,
+                      "log-channel-" + threadNum,
+                      "Action started",
+                      null,
+                      "test-action-" + threadNum,
+                      null);
+
+              // Each thread creates a tracker for a DIFFERENT workflow
+              WorkflowTracker childTracker =
+                  new WorkflowTracker(mockWorkflowMeta("child-workflow-" + 
threadNum), result);
+              tracker.addWorkflowTracker(childTracker);
+            } catch (InterruptedException e) {
+              Thread.currentThread().interrupt();
+            } finally {
+              doneLatch.countDown();
+            }
+          });
+    }
+
+    startLatch.countDown();
+    assertTrue("Threads did not complete in time", doneLatch.await(5, 
TimeUnit.SECONDS));
+    executor.shutdown();
+
+    // Verify all 10 unique workflow trackers were added (one per workflow)
+    assertEquals(
+        "Expected all 10 unique workflows to be tracked",
+        NUM_THREADS,
+        tracker.nrWorkflowTrackers());
+
+    // Verify all identifiers are unique
+    Set<String> identifiers = new HashSet<>();
+    for (int i = 0; i < tracker.nrWorkflowTrackers(); i++) {
+      WorkflowTracker child = tracker.getWorkflowTracker(i);
+      String identifier = child.getUniqueIdentifier();
+      assertTrue("Identifier should be unique: " + identifier, 
identifiers.add(identifier));
+    }
+  }
+
+  /**
+   * Test that duplicate prevention works with workflow-level identifiers. 
Multiple actions for the
+   * same workflow should only create one tracker.
+   */
+  @Test
+  public void testUniquenessPerWorkflow() throws Exception {
+    WorkflowTracker tracker = new 
WorkflowTracker(mockWorkflowMeta("parent-workflow"));
+
+    final int NUM_THREADS = 5;
+
+    ExecutorService executor = Executors.newFixedThreadPool(NUM_THREADS);
+    CountDownLatch startLatch = new CountDownLatch(1);
+    CountDownLatch doneLatch = new CountDownLatch(NUM_THREADS);
+
+    // Create multiple trackers for the same workflow but different actions
+    for (int i = 0; i < NUM_THREADS; i++) {
+      final int threadNum = i;
+      executor.submit(
+          () -> {
+            try {
+              startLatch.await();
+
+              ActionResult result = new ActionResult();
+              result.setActionName("action-" + threadNum);
+              result.setComment("Action started");
+
+              // All threads try to add trackers for the SAME child workflow
+              WorkflowTracker childTracker =
+                  new WorkflowTracker(mockWorkflowMeta("same-child-workflow"), 
result);
+              tracker.addWorkflowTracker(childTracker);
+            } catch (InterruptedException e) {
+              Thread.currentThread().interrupt();
+            } finally {
+              doneLatch.countDown();
+            }
+          });
+    }
+
+    startLatch.countDown();
+    assertTrue("Threads did not complete in time", doneLatch.await(10, 
TimeUnit.SECONDS));
+    executor.shutdown();
+
+    // Only one tracker should be added for the workflow (not one per action)
+    assertEquals("Expected only 1 tracker for the workflow", 1, 
tracker.nrWorkflowTrackers());
+  }
+
+  /**
+   * Test the getUniqueIdentifier method directly. It should use workflow name 
and filename only.
+   */
+  @Test
+  public void testGetUniqueIdentifier() {
+    WorkflowMeta meta = mockWorkflowMeta("test-workflow");
+
+    // Test with action result
+    ActionResult result1 = new ActionResult(null, "channel-123", null, null, 
"action-1", null);
+
+    WorkflowTracker tracker1 = new WorkflowTracker(meta, result1);
+    String identifier1 = tracker1.getUniqueIdentifier();
+
+    // Should contain workflow name
+    assertTrue("Identifier should contain workflow name", 
identifier1.contains("test-workflow"));
+
+    // Test that different actions in same workflow have same identifier
+    ActionResult result2 = new ActionResult(null, "channel-456", "Started", 
null, "action-2", null);
+
+    WorkflowTracker tracker2 = new WorkflowTracker(meta, result2);
+    String identifier2 = tracker2.getUniqueIdentifier();
+
+    // Should be the same identifier (workflow-level, not action-level)
+    assertEquals("Same workflow should have same identifier", identifier1, 
identifier2);
+  }
+
+  /**
+   * Test equals and hashCode methods. Trackers for the same workflow should 
be equal, regardless of
+   * action.
+   */
+  @Test
+  public void testEqualsAndHashCode() {
+    WorkflowMeta meta1 = mockWorkflowMeta("workflow-A");
+    WorkflowMeta meta2 = mockWorkflowMeta("workflow-B");
+
+    // Create two trackers for the same workflow but different actions
+    ActionResult result1 = new ActionResult(null, "channel-1", null, null, 
"action-1", null);
+    ActionResult result2 = new ActionResult(null, "channel-2", null, null, 
"action-2", null);
+
+    WorkflowTracker tracker1 = new WorkflowTracker(meta1, result1);
+    WorkflowTracker tracker2 = new WorkflowTracker(meta1, result2);
+
+    // Should be equal because they're for the same workflow
+    assertEquals("Trackers for same workflow should be equal", tracker1, 
tracker2);
+    assertEquals(
+        "Hash codes should match for equal objects", tracker1.hashCode(), 
tracker2.hashCode());
+
+    // Create tracker for different workflow
+    ActionResult result3 = new ActionResult(null, "channel-3", null, null, 
"action-1", null);
+    WorkflowTracker tracker3 = new WorkflowTracker(meta2, result3);
+
+    // Should not be equal (different workflow)
+    assertTrue("Trackers for different workflows should not be equal", 
!tracker1.equals(tracker3));
+  }
+}
diff --git 
a/engine/src/test/java/org/apache/hop/core/gui/WorkflowTrackerTest.java 
b/engine/src/test/java/org/apache/hop/core/gui/WorkflowTrackerTest.java
index 9b0be55537..86808d04e7 100644
--- a/engine/src/test/java/org/apache/hop/core/gui/WorkflowTrackerTest.java
+++ b/engine/src/test/java/org/apache/hop/core/gui/WorkflowTrackerTest.java
@@ -88,8 +88,13 @@ public class WorkflowTrackerTest {
   @Test
   public void findJobTracker_EntryNameFound() {
     WorkflowTracker workflowTracker = createTracker();
+    // Create trackers for different workflows (not just different actions)
     WorkflowTracker[] children =
-        new WorkflowTracker[] {createTracker("0"), createTracker("1"), 
createTracker("2")};
+        new WorkflowTracker[] {
+          createTrackerForWorkflow("workflow-0", "0"),
+          createTrackerForWorkflow("workflow-1", "1"),
+          createTrackerForWorkflow("workflow-2", "2")
+        };
     for (WorkflowTracker child : children) {
       workflowTracker.addWorkflowTracker(child);
     }
@@ -114,6 +119,18 @@ public class WorkflowTrackerTest {
     return workflowTracker;
   }
 
+  private static WorkflowTracker createTrackerForWorkflow(String workflowName, 
String actionName) {
+    WorkflowMeta workflowMeta = mock(WorkflowMeta.class);
+    when(workflowMeta.getName()).thenReturn(workflowName);
+    WorkflowTracker workflowTracker = new WorkflowTracker(workflowMeta);
+    if (actionName != null) {
+      ActionResult result = mock(ActionResult.class);
+      when(result.getActionName()).thenReturn(actionName);
+      workflowTracker.setActionResult(result);
+    }
+    return workflowTracker;
+  }
+
   private static ActionMeta createActionMeta(String actionName) {
     IAction action = mock(IAction.class);
     when(action.getName()).thenReturn(actionName);
diff --git 
a/plugins/actions/copyfiles/src/test/java/org/apache/hop/workflow/actions/copyfiles/WorkflowActionCopyFilesTest.java
 
b/plugins/actions/copyfiles/src/test/java/org/apache/hop/workflow/actions/copyfiles/WorkflowActionCopyFilesTest.java
index b7c9c3ae0d..3df55d729a 100644
--- 
a/plugins/actions/copyfiles/src/test/java/org/apache/hop/workflow/actions/copyfiles/WorkflowActionCopyFilesTest.java
+++ 
b/plugins/actions/copyfiles/src/test/java/org/apache/hop/workflow/actions/copyfiles/WorkflowActionCopyFilesTest.java
@@ -83,7 +83,7 @@ class WorkflowActionCopyFilesTest {
         .processFileFolder(
             anyString(), anyString(), anyString(), any(Workflow.class), 
any(Result.class));
     verify(entry, atLeast(1)).preprocessfilefilder(any(String[].class));
-    Assertions.assertFalse(result.getResult());
+    Assertions.assertFalse(result.isResult());
     Assertions.assertEquals(1, result.getNrErrors());
   }
 
@@ -101,7 +101,7 @@ class WorkflowActionCopyFilesTest {
     verify(entry, times(srcPath.length))
         .processFileFolder(
             anyString(), anyString(), anyString(), any(Workflow.class), 
any(Result.class));
-    Assertions.assertFalse(result.getResult());
+    Assertions.assertFalse(result.isResult());
     Assertions.assertEquals(3, result.getNrErrors());
   }
 
diff --git 
a/plugins/actions/evaluatetablecontent/src/test/java/org/apache/hop/workflow/actions/evaluatetablecontent/WorkflowActionEvalTableContentTest.java
 
b/plugins/actions/evaluatetablecontent/src/test/java/org/apache/hop/workflow/actions/evaluatetablecontent/WorkflowActionEvalTableContentTest.java
index 69eb538d74..b748ad1d1b 100644
--- 
a/plugins/actions/evaluatetablecontent/src/test/java/org/apache/hop/workflow/actions/evaluatetablecontent/WorkflowActionEvalTableContentTest.java
+++ 
b/plugins/actions/evaluatetablecontent/src/test/java/org/apache/hop/workflow/actions/evaluatetablecontent/WorkflowActionEvalTableContentTest.java
@@ -182,7 +182,7 @@ class WorkflowActionEvalTableContentTest {
 
     Result res = action.execute(new Result(), 0);
 
-    assertFalse(res.getResult(), "Eval number of rows should fail");
+    assertFalse(res.isResult(), "Eval number of rows should fail");
     assertEquals(
         0,
         res.getNrErrors(),
@@ -199,7 +199,7 @@ class WorkflowActionEvalTableContentTest {
 
     Result res = action.execute(new Result(), 0);
 
-    assertTrue(res.getResult(), "Eval number of rows should be suceeded");
+    assertTrue(res.isResult(), "Eval number of rows should be suceeded");
     assertEquals(0, res.getNrErrors(), "Apparently there should no error");
   }
 
@@ -214,7 +214,7 @@ class WorkflowActionEvalTableContentTest {
 
     Result res = action.execute(new Result(), 0);
 
-    assertFalse(res.getResult(), "Eval number of rows should fail");
+    assertFalse(res.isResult(), "Eval number of rows should fail");
     assertEquals(1, res.getNrErrors(), "Apparently there should be an error");
   }
 }
diff --git 
a/plugins/actions/filesexist/src/test/java/org/apache/hop/workflow/actions/filesexist/WorkflowActionFilesExistTest.java
 
b/plugins/actions/filesexist/src/test/java/org/apache/hop/workflow/actions/filesexist/WorkflowActionFilesExistTest.java
index 6a3b40e831..21f3cdf252 100644
--- 
a/plugins/actions/filesexist/src/test/java/org/apache/hop/workflow/actions/filesexist/WorkflowActionFilesExistTest.java
+++ 
b/plugins/actions/filesexist/src/test/java/org/apache/hop/workflow/actions/filesexist/WorkflowActionFilesExistTest.java
@@ -82,7 +82,7 @@ class WorkflowActionFilesExistTest {
 
     Result res = action.execute(new Result(), 0);
 
-    assertFalse(res.getResult(), "Entry should fail");
+    assertFalse(res.isResult(), "Entry should fail");
     assertEquals(
         0,
         res.getNrErrors(),
@@ -95,7 +95,7 @@ class WorkflowActionFilesExistTest {
 
     Result res = action.execute(new Result(), 0);
 
-    assertFalse(res.getResult(), "Action should fail");
+    assertFalse(res.isResult(), "Action should fail");
     assertEquals(
         1, res.getNrErrors(), "File with wrong name was specified. One error 
should be reported");
   }
@@ -105,7 +105,7 @@ class WorkflowActionFilesExistTest {
     action.setFileItems(List.of(new FileItem(existingFile1), new 
FileItem(existingFile2)));
 
     Result res = action.execute(new Result(), 0);
-    assertTrue(res.getResult());
+    assertTrue(res.isResult());
   }
 
   @Test
@@ -118,6 +118,6 @@ class WorkflowActionFilesExistTest {
             new FileItem("nonExistingFile2.ext")));
 
     Result res = action.execute(new Result(), 0);
-    assertFalse(res.getResult());
+    assertFalse(res.isResult());
   }
 }
diff --git 
a/plugins/actions/folderisempty/src/test/java/org/apache/hop/workflow/actions/folderisempty/WorkflowActionFolderIsEmptyTest.java
 
b/plugins/actions/folderisempty/src/test/java/org/apache/hop/workflow/actions/folderisempty/WorkflowActionFolderIsEmptyTest.java
index e7a318c3bc..1fb6d2c2e7 100644
--- 
a/plugins/actions/folderisempty/src/test/java/org/apache/hop/workflow/actions/folderisempty/WorkflowActionFolderIsEmptyTest.java
+++ 
b/plugins/actions/folderisempty/src/test/java/org/apache/hop/workflow/actions/folderisempty/WorkflowActionFolderIsEmptyTest.java
@@ -77,7 +77,7 @@ class WorkflowActionFolderIsEmptyTest {
 
     Result result = action.execute(new Result(), 0);
 
-    assertTrue(result.getResult(), "For empty folder result should be true");
+    assertTrue(result.isResult(), "For empty folder result should be true");
     assertEquals(0, result.getNrErrors(), "There should be no errors");
   }
 
@@ -87,7 +87,7 @@ class WorkflowActionFolderIsEmptyTest {
 
     Result result = action.execute(new Result(), 0);
 
-    assertFalse(result.getResult(), "For non-empty folder result should be 
false");
+    assertFalse(result.isResult(), "For non-empty folder result should be 
false");
     assertEquals(0, result.getNrErrors(), "There should be still no errors");
   }
 }
diff --git 
a/plugins/actions/movefiles/src/test/java/org/apache/hop/workflow/actions/movefiles/WorkflowActionMoveFilesLocalTest.java
 
b/plugins/actions/movefiles/src/test/java/org/apache/hop/workflow/actions/movefiles/WorkflowActionMoveFilesLocalTest.java
index b424258823..bbe5b6ae10 100644
--- 
a/plugins/actions/movefiles/src/test/java/org/apache/hop/workflow/actions/movefiles/WorkflowActionMoveFilesLocalTest.java
+++ 
b/plugins/actions/movefiles/src/test/java/org/apache/hop/workflow/actions/movefiles/WorkflowActionMoveFilesLocalTest.java
@@ -67,7 +67,7 @@ class WorkflowActionMoveFilesLocalTest {
     action.setDestinationIsAFile(true);
 
     Result result = action.execute(new Result(), 0);
-    assertTrue(result.getResult(), "Move operation should succeed");
+    assertTrue(result.isResult(), "Move operation should succeed");
     assertFalse(Files.exists(sourceFile), "Source file should not exist");
     assertTrue(Files.exists(destFile), "Destination file should exist");
     assertEquals(
@@ -88,7 +88,7 @@ class WorkflowActionMoveFilesLocalTest {
     action.setDestinationIsAFile(false);
 
     Result result = action.execute(new Result(), 0);
-    assertTrue(result.getResult(), "Move operation should succeed");
+    assertTrue(result.isResult(), "Move operation should succeed");
     assertTrue(
         Files.exists(destinationFolder.toPath().resolve("test1.txt")), 
"test1.txt should be moved");
     assertTrue(
@@ -108,7 +108,7 @@ class WorkflowActionMoveFilesLocalTest {
     action.setDestinationIsAFile(true);
 
     Result result = action.execute(new Result(), 0);
-    assertFalse(result.getResult(), "Move should not succeed when destination 
exists");
+    assertFalse(result.isResult(), "Move should not succeed when destination 
exists");
     assertTrue(Files.exists(sourceFile), "Source file should still exist");
     assertEquals(
         originalContent,
@@ -127,7 +127,7 @@ class WorkflowActionMoveFilesLocalTest {
     action.setIfFileExists("overwrite_file");
 
     Result result = action.execute(new Result(), 0);
-    assertTrue(result.getResult(), "Move with overwrite should succeed");
+    assertTrue(result.isResult(), "Move with overwrite should succeed");
     assertFalse(Files.exists(sourceFile), "Source file should not exist");
     assertTrue(Files.exists(destFile), "Destination file should exist");
     assertEquals(
@@ -149,7 +149,7 @@ class WorkflowActionMoveFilesLocalTest {
     action.setCreateDestinationFolder(true);
 
     Result result = action.execute(new Result(), 0);
-    assertTrue(result.getResult(), "Move should succeed");
+    assertTrue(result.isResult(), "Move should succeed");
     assertTrue(Files.exists(destinationFolder.toPath()), "Destination folder 
should be created");
     assertTrue(Files.exists(destFile), "File should be moved");
   }
diff --git 
a/plugins/actions/repeat/src/main/java/org/apache/hop/workflow/actions/repeat/Repeat.java
 
b/plugins/actions/repeat/src/main/java/org/apache/hop/workflow/actions/repeat/Repeat.java
index 15dcf5af78..c614c2c037 100644
--- 
a/plugins/actions/repeat/src/main/java/org/apache/hop/workflow/actions/repeat/Repeat.java
+++ 
b/plugins/actions/repeat/src/main/java/org/apache/hop/workflow/actions/repeat/Repeat.java
@@ -192,7 +192,7 @@ public class Repeat extends ActionBase implements IAction, 
Cloneable {
       repetitionNr++;
       executionResult = executePipelineOrWorkflow(realFilename, nr, 
executionResult, repetitionNr);
       Result result = executionResult.result;
-      if (!result.getResult() || result.getNrErrors() > 0 || 
result.isStopped()) {
+      if (!result.isResult() || result.getNrErrors() > 0 || 
result.isStopped()) {
         logError("The repeating work encountered and error or was stopped. 
This ends the loop.");
 
         // On an false result, stop the loop
diff --git 
a/plugins/actions/setvariables/src/test/java/org/apache/hop/workflow/actions/setvariables/ActionSetVariablesTest.java
 
b/plugins/actions/setvariables/src/test/java/org/apache/hop/workflow/actions/setvariables/ActionSetVariablesTest.java
index c10a24b00e..f415cc88b7 100644
--- 
a/plugins/actions/setvariables/src/test/java/org/apache/hop/workflow/actions/setvariables/ActionSetVariablesTest.java
+++ 
b/plugins/actions/setvariables/src/test/java/org/apache/hop/workflow/actions/setvariables/ActionSetVariablesTest.java
@@ -114,7 +114,7 @@ class ActionSetVariablesTest {
         
"src/test/resources/org/apache/hop/workflow/actions/setvariables/ASCIIText.properties");
     action.setReplaceVars(true);
     Result result = action.execute(new Result(), 0);
-    assertTrue(result.getResult(), "Result should be true");
+    assertTrue(result.isResult(), "Result should be true");
     assertEquals("日本語", action.getVariable("Japanese"));
     assertEquals("English", action.getVariable("English"));
     assertEquals("中文", action.getVariable("Chinese"));
@@ -127,7 +127,7 @@ class ActionSetVariablesTest {
         
"src/test/resources/org/apache/hop/workflow/actions/setvariables/UTF8Text.properties");
     action.setReplaceVars(true);
     Result result = action.execute(new Result(), 0);
-    assertTrue(result.getResult(), "Result should be true");
+    assertTrue(result.isResult(), "Result should be true");
     assertEquals("日本語", action.getVariable("Japanese"));
     assertEquals("English", action.getVariable("English"));
     assertEquals("中文", action.getVariable("Chinese"));
@@ -141,7 +141,7 @@ class ActionSetVariablesTest {
     action.setFilename(propertiesFilename);
     action.setReplaceVars(true);
     Result result = action.execute(new Result(), 0);
-    assertTrue(result.getResult(), "Result should be true");
+    assertTrue(result.isResult(), "Result should be true");
     RandomAccessFile fos = null;
     try {
       File file = new File(propertiesFilename);
@@ -172,7 +172,7 @@ class ActionSetVariablesTest {
     action.setFilename(
         
"src/test/resources/org/apache/hop/workflow/actions/setvariables/configurationA.properties");
     Result result = action.execute(new Result(), 0);
-    assertTrue(result.getResult(), "Result should be true");
+    assertTrue(result.isResult(), "Result should be true");
     assertEquals("a", parentWorkflow.getVariable("propertyFile"));
     assertEquals("a", parentWorkflow.getVariable("dynamicProperty"));
     assertEquals("parentValue", parentWorkflow.getVariable("parentParam"));
@@ -180,7 +180,7 @@ class ActionSetVariablesTest {
     action.setFilename(
         
"src/test/resources/org/apache/hop/workflow/actions/setvariables/configurationB.properties");
     result = action.execute(new Result(), 0);
-    assertTrue(result.getResult(), "Result should be true");
+    assertTrue(result.isResult(), "Result should be true");
     assertEquals("b", parentWorkflow.getVariable("propertyFile"));
     assertEquals("new", parentWorkflow.getVariable("newProperty"));
     assertEquals("haha", parentWorkflow.getVariable("parentParam"));
@@ -190,7 +190,7 @@ class ActionSetVariablesTest {
     action.setFilename(
         
"src/test/resources/org/apache/hop/workflow/actions/setvariables/configurationA.properties");
     result = action.execute(new Result(), 0);
-    assertTrue(result.getResult(), "Result should be true");
+    assertTrue(result.isResult(), "Result should be true");
     assertEquals("a", parentWorkflow.getVariable("propertyFile"));
     assertEquals("", parentWorkflow.getVariable("newProperty"));
     assertEquals("parentValue", parentWorkflow.getVariable("parentParam"));
@@ -200,7 +200,7 @@ class ActionSetVariablesTest {
     action.setFilename(
         
"src/test/resources/org/apache/hop/workflow/actions/setvariables/configurationB.properties");
     result = action.execute(new Result(), 0);
-    assertTrue(result.getResult(), "Result should be true");
+    assertTrue(result.isResult(), "Result should be true");
     assertEquals("b", parentWorkflow.getVariable("propertyFile"));
     assertEquals("new", parentWorkflow.getVariable("newProperty"));
     assertEquals("haha", parentWorkflow.getVariable("parentParam"));
@@ -213,7 +213,7 @@ class ActionSetVariablesTest {
     IHopMetadataProvider metadataProvider = mock(IHopMetadataProvider.class);
     action.loadXml(getEntryNode("nullVariable", null, "JVM"), 
metadataProvider, new Variables());
     Result result = action.execute(new Result(), 0);
-    assertTrue(result.getResult(), "Result should be true");
+    assertTrue(result.isResult(), "Result should be true");
     assertNull(System.getProperty("nullVariable"));
   }
 
@@ -223,7 +223,7 @@ class ActionSetVariablesTest {
     action.loadXml(
         getEntryNode("nullVariable", null, "CURRENT_WORKFLOW"), 
metadataProvider, new Variables());
     Result result = action.execute(new Result(), 0);
-    assertTrue(result.getResult(), "Result should be true");
+    assertTrue(result.isResult(), "Result should be true");
     assertNull(action.getVariable("nullVariable"));
   }
 
@@ -234,7 +234,7 @@ class ActionSetVariablesTest {
         getEntryNode("variableNotNull", "someValue", "JVM"), metadataProvider, 
new Variables());
     assertNull(System.getProperty("variableNotNull"));
     Result result = action.execute(new Result(), 0);
-    assertTrue(result.getResult(), "Result should be true");
+    assertTrue(result.isResult(), "Result should be true");
     assertEquals("someValue", System.getProperty("variableNotNull"));
   }
 
@@ -247,7 +247,7 @@ class ActionSetVariablesTest {
         new Variables());
     assertNull(System.getProperty("variableNotNull"));
     Result result = action.execute(new Result(), 0);
-    assertTrue(result.getResult(), "Result should be true");
+    assertTrue(result.isResult(), "Result should be true");
     assertEquals("someValue", action.getVariable("variableNotNull"));
   }
 
diff --git 
a/plugins/actions/snowflake/src/main/java/org/apache/hop/workflow/actions/snowflake/WarehouseManager.java
 
b/plugins/actions/snowflake/src/main/java/org/apache/hop/workflow/actions/snowflake/WarehouseManager.java
index f32e598754..ce6694cf97 100644
--- 
a/plugins/actions/snowflake/src/main/java/org/apache/hop/workflow/actions/snowflake/WarehouseManager.java
+++ 
b/plugins/actions/snowflake/src/main/java/org/apache/hop/workflow/actions/snowflake/WarehouseManager.java
@@ -411,7 +411,7 @@ public class WarehouseManager extends ActionBase implements 
Cloneable, IAction {
 
     Result result = previousResult;
     result.setResult(validate());
-    if (!result.getResult()) {
+    if (!result.isResult()) {
       return result;
     }
 
diff --git 
a/plugins/actions/waitforsql/src/main/java/org/apache/hop/workflow/actions/waitforsql/ActionWaitForSql.java
 
b/plugins/actions/waitforsql/src/main/java/org/apache/hop/workflow/actions/waitforsql/ActionWaitForSql.java
index a314e1c96e..0ad3e53ec1 100644
--- 
a/plugins/actions/waitforsql/src/main/java/org/apache/hop/workflow/actions/waitforsql/ActionWaitForSql.java
+++ 
b/plugins/actions/waitforsql/src/main/java/org/apache/hop/workflow/actions/waitforsql/ActionWaitForSql.java
@@ -359,7 +359,7 @@ public class ActionWaitForSql extends ActionBase implements 
Cloneable, IAction {
       logBasic("Exception while waiting for SQL data: " + e.getMessage());
     }
 
-    if (result.getResult()) {
+    if (result.isResult()) {
       // Remove error count set at the beginning of the method
       //
       result.setNrErrors(0);
diff --git 
a/plugins/actions/workflow/src/main/java/org/apache/hop/workflow/actions/workflow/ActionWorkflow.java
 
b/plugins/actions/workflow/src/main/java/org/apache/hop/workflow/actions/workflow/ActionWorkflow.java
index 31a3642af9..7340b4a7d0 100644
--- 
a/plugins/actions/workflow/src/main/java/org/apache/hop/workflow/actions/workflow/ActionWorkflow.java
+++ 
b/plugins/actions/workflow/src/main/java/org/apache/hop/workflow/actions/workflow/ActionWorkflow.java
@@ -537,7 +537,7 @@ public class ActionWorkflow extends ActionBase implements 
Cloneable, IAction {
 
           // if one of them fails (in the loop), increase the number of errors
           //
-          if (oneResult.getResult() == false) {
+          if (oneResult.isResult() == false) {
             result.setNrErrors(result.getNrErrors() + 1);
           }
         }
diff --git 
a/plugins/misc/debug/src/main/java/org/apache/hop/debug/action/ModifyActionLogLevelExtensionPoint.java
 
b/plugins/misc/debug/src/main/java/org/apache/hop/debug/action/ModifyActionLogLevelExtensionPoint.java
index 11388be66a..a65a25ab70 100644
--- 
a/plugins/misc/debug/src/main/java/org/apache/hop/debug/action/ModifyActionLogLevelExtensionPoint.java
+++ 
b/plugins/misc/debug/src/main/java/org/apache/hop/debug/action/ModifyActionLogLevelExtensionPoint.java
@@ -181,7 +181,7 @@ public class ModifyActionLogLevelExtensionPoint
 
                     if (debugLevel.isLoggingResult()) {
                       log.logMinimal("Action results: ");
-                      log.logMinimal("  - result=" + result.getResult());
+                      log.logMinimal("  - result=" + result.isResult());
                       log.logMinimal("  - stopped=" + result.isStopped());
                       log.logMinimal("  - linesRead=" + 
result.getNrLinesRead());
                       log.logMinimal("  - linesWritten=" + 
result.getNrLinesWritten());
diff --git 
a/plugins/misc/mail/src/main/java/org/apache/hop/mail/workflow/actions/mail/ActionMail.java
 
b/plugins/misc/mail/src/main/java/org/apache/hop/mail/workflow/actions/mail/ActionMail.java
index c70c9373a1..7e710a1e8d 100644
--- 
a/plugins/misc/mail/src/main/java/org/apache/hop/mail/workflow/actions/mail/ActionMail.java
+++ 
b/plugins/misc/mail/src/main/java/org/apache/hop/mail/workflow/actions/mail/ActionMail.java
@@ -442,7 +442,7 @@ public class ActionMail extends ActionBase implements 
Cloneable, IAction {
         messageText
             .append(
                 BaseMessages.getString(PKG, "ActionMail.Log.Comment.Result") + 
"               : ")
-            .append(result.getResult())
+            .append(result.isResult())
             .append(endRow);
         messageText.append(endRow);
       }
diff --git 
a/plugins/misc/reflection/src/main/java/org/apache/hop/reflection/workflow/transform/WorkflowLogging.java
 
b/plugins/misc/reflection/src/main/java/org/apache/hop/reflection/workflow/transform/WorkflowLogging.java
index 5dfe9e42a8..f98e29ade9 100644
--- 
a/plugins/misc/reflection/src/main/java/org/apache/hop/reflection/workflow/transform/WorkflowLogging.java
+++ 
b/plugins/misc/reflection/src/main/java/org/apache/hop/reflection/workflow/transform/WorkflowLogging.java
@@ -136,7 +136,7 @@ public class WorkflowLogging extends 
BaseTransform<WorkflowLoggingMeta, Workflow
         transformRow[index++] = result.getEntryNr();
 
         // Result (true/false)
-        transformRow[index++] = result.getResult();
+        transformRow[index++] = result.isResult();
 
         // Log channel ID
         transformRow[index++] = actionResult.getLogChannelId();
diff --git 
a/plugins/tech/neo4j/src/main/java/org/apache/hop/neo4j/logging/xp/WorkflowLoggingExtensionPoint.java
 
b/plugins/tech/neo4j/src/main/java/org/apache/hop/neo4j/logging/xp/WorkflowLoggingExtensionPoint.java
index 8b67d0c7ce..50e44f5483 100644
--- 
a/plugins/tech/neo4j/src/main/java/org/apache/hop/neo4j/logging/xp/WorkflowLoggingExtensionPoint.java
+++ 
b/plugins/tech/neo4j/src/main/java/org/apache/hop/neo4j/logging/xp/WorkflowLoggingExtensionPoint.java
@@ -307,7 +307,7 @@ public class WorkflowLoggingExtensionPoint
                   workflowPars.put("linesWritten", 
workflowResult.getNrLinesWritten());
                   workflowPars.put("linesRejected", 
workflowResult.getNrLinesRejected());
                   workflowPars.put("loggingText", workflowLoggingText);
-                  workflowPars.put("result", workflowResult.getResult());
+                  workflowPars.put("result", workflowResult.isResult());
                   workflowPars.put("nrResultRows", 
workflowResult.getRows().size());
                   workflowPars.put("nrResultFiles", 
workflowResult.getResultFilesList().size());
                   workflowPars.put("containerId", 
workflowResult.getContainerId());
diff --git 
a/plugins/transforms/pipelineexecutor/src/main/java/org/apache/hop/pipeline/transforms/pipelineexecutor/PipelineExecutor.java
 
b/plugins/transforms/pipelineexecutor/src/main/java/org/apache/hop/pipeline/transforms/pipelineexecutor/PipelineExecutor.java
index 51932b6128..adb5832fad 100644
--- 
a/plugins/transforms/pipelineexecutor/src/main/java/org/apache/hop/pipeline/transforms/pipelineexecutor/PipelineExecutor.java
+++ 
b/plugins/transforms/pipelineexecutor/src/main/java/org/apache/hop/pipeline/transforms/pipelineexecutor/PipelineExecutor.java
@@ -427,7 +427,7 @@ public class PipelineExecutor extends 
BaseTransform<PipelineExecutorMeta, Pipeli
         outputRow[idx++] = Long.valueOf(System.currentTimeMillis() - 
getData().groupTimeStart);
       }
       if (!Utils.isEmpty(meta.getExecutionResultField())) {
-        outputRow[idx++] = Boolean.valueOf(result.getResult());
+        outputRow[idx++] = Boolean.valueOf(result.isResult());
       }
       if (!Utils.isEmpty(meta.getExecutionNrErrorsField())) {
         outputRow[idx++] = Long.valueOf(result.getNrErrors());
diff --git 
a/plugins/transforms/systemdata/src/main/java/org/apache/hop/pipeline/transforms/systemdata/SystemData.java
 
b/plugins/transforms/systemdata/src/main/java/org/apache/hop/pipeline/transforms/systemdata/SystemData.java
index 1857b9b4bd..65787aa7d5 100644
--- 
a/plugins/transforms/systemdata/src/main/java/org/apache/hop/pipeline/transforms/systemdata/SystemData.java
+++ 
b/plugins/transforms/systemdata/src/main/java/org/apache/hop/pipeline/transforms/systemdata/SystemData.java
@@ -536,7 +536,7 @@ public class SystemData extends 
BaseTransform<SystemDataMeta, SystemDataData> {
           Result previousResult = getPipeline().getPreviousResult();
           boolean result = false;
           if (previousResult != null) {
-            result = previousResult.getResult();
+            result = previousResult.isResult();
           }
           row[index] = result;
           break;
diff --git 
a/plugins/transforms/workflowexecutor/src/main/java/org/apache/hop/pipeline/transforms/workflowexecutor/WorkflowExecutor.java
 
b/plugins/transforms/workflowexecutor/src/main/java/org/apache/hop/pipeline/transforms/workflowexecutor/WorkflowExecutor.java
index e9993590f1..9d41dcdc46 100644
--- 
a/plugins/transforms/workflowexecutor/src/main/java/org/apache/hop/pipeline/transforms/workflowexecutor/WorkflowExecutor.java
+++ 
b/plugins/transforms/workflowexecutor/src/main/java/org/apache/hop/pipeline/transforms/workflowexecutor/WorkflowExecutor.java
@@ -232,7 +232,7 @@ public class WorkflowExecutor extends 
BaseTransform<WorkflowExecutorMeta, Workfl
         outputRow[idx++] = Long.valueOf(System.currentTimeMillis() - 
data.groupTimeStart);
       }
       if (!Utils.isEmpty(meta.getExecutionResultField())) {
-        outputRow[idx++] = Boolean.valueOf(result.getResult());
+        outputRow[idx++] = Boolean.valueOf(result.isResult());
       }
       if (!Utils.isEmpty(meta.getExecutionNrErrorsField())) {
         outputRow[idx++] = Long.valueOf(result.getNrErrors());
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 460656cc06..3bea8be9a2 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
@@ -2224,7 +2224,7 @@ public class HopGuiPipelineGraph extends 
HopGuiAbstractGraph
       type = GuiActionType.Modify,
       name = "i18n::HopGuiPipelineGraph.TransformAction.EditDescription.Name",
       tooltip = 
"i18n::HopGuiPipelineGraph.TransformAction.EditDescription.Tooltip",
-      image = "ui/images/edit_description.svg",
+      image = "ui/images/edit.svg",
       category = "Basic",
       categoryOrder = "1")
   public void editDescription(HopGuiPipelineTransformContext context) {
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 f9dfc2fb5c..85fc02738e 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
@@ -1846,7 +1846,7 @@ public class HopGuiWorkflowGraph extends 
HopGuiAbstractGraph
       type = GuiActionType.Modify,
       name = 
"i18n::HopGuiWorkflowGraph.ContextualAction.EditActionDescription.Text",
       tooltip = 
"i18n::HopGuiWorkflowGraph.ContextualAction.EditActionDescription.Tooltip",
-      image = "ui/images/edit_description.svg",
+      image = "ui/images/edit.svg",
       category = 
"i18n::HopGuiWorkflowGraph.ContextualAction.Category.Basic.Text",
       categoryOrder = "1")
   public void editActionDescription(HopGuiWorkflowActionContext context) {
@@ -2654,7 +2654,7 @@ public class HopGuiWorkflowGraph extends 
HopGuiAbstractGraph
           actionCopy = (ActionMeta) areaOwner.getParent();
           Result result = actionResult.getResult();
           tip.append("'").append(actionCopy.getName()).append("' ");
-          if (result.getResult()) {
+          if (result.isResult()) {
             tipImage = GuiResource.getInstance().getImageSuccess();
             tip.append("finished successfully.");
           } else {
@@ -2662,7 +2662,7 @@ public class HopGuiWorkflowGraph extends 
HopGuiAbstractGraph
             tip.append("failed.");
           }
           
tip.append(Const.CR).append("------------------------").append(Const.CR).append(Const.CR);
-          tip.append("Result         : 
").append(result.getResult()).append(Const.CR);
+          tip.append("Result         : 
").append(result.isResult()).append(Const.CR);
           tip.append("Errors         : 
").append(result.getNrErrors()).append(Const.CR);
 
           if (result.getNrLinesRead() > 0) {
diff --git 
a/ui/src/main/java/org/apache/hop/ui/hopgui/file/workflow/delegates/HopGuiWorkflowGridDelegate.java
 
b/ui/src/main/java/org/apache/hop/ui/hopgui/file/workflow/delegates/HopGuiWorkflowGridDelegate.java
index 7ae47fcd65..566b3f9dec 100644
--- 
a/ui/src/main/java/org/apache/hop/ui/hopgui/file/workflow/delegates/HopGuiWorkflowGridDelegate.java
+++ 
b/ui/src/main/java/org/apache/hop/ui/hopgui/file/workflow/delegates/HopGuiWorkflowGridDelegate.java
@@ -189,8 +189,8 @@ public class HopGuiWorkflowGridDelegate {
         String workflowName = workflowTracker.getWorkflowName();
 
         if (Utils.isEmpty(workflowName)) {
-          if (!Utils.isEmpty(workflowTracker.getWorfkflowFilename())) {
-            workflowName = workflowTracker.getWorfkflowFilename();
+          if (!Utils.isEmpty(workflowTracker.getWorkflowFilename())) {
+            workflowName = workflowTracker.getWorkflowFilename();
           } else {
             workflowName =
                 BaseMessages.getString(
@@ -198,7 +198,7 @@ public class HopGuiWorkflowGridDelegate {
           }
         }
         treeItem.setText(0, workflowName);
-        treeItem.setText(4, Const.NVL(workflowTracker.getWorfkflowFilename(), 
""));
+        treeItem.setText(4, Const.NVL(workflowTracker.getWorkflowFilename(), 
""));
 
         TreeMemory.getInstance()
             .storeExpanded(STRING_CHEF_LOG_TREE_NAME, new String[] 
{workflowName}, true);
@@ -268,11 +268,11 @@ public class HopGuiWorkflowGridDelegate {
             if (res != null) {
               treeItem.setText(
                   2,
-                  res.getResult()
+                  res.isResult()
                       ? BaseMessages.getString(PKG, "WorkflowLog.Tree.Success")
                       : BaseMessages.getString(PKG, 
"WorkflowLog.Tree.Failure"));
               treeItem.setText(5, Long.toString(res.getEntryNr()));
-              if (res.getResult()) {
+              if (res.isResult()) {
                 treeItem.setImage(2, 
GuiResource.getInstance().getImageSuccess());
                 treeItem.setForeground(2, 
GuiResource.getInstance().getColorSuccessGreen());
               } else {
diff --git a/ui/src/main/resources/ui/laf.properties 
b/ui/src/main/resources/ui/laf.properties
index a348493590..3ac74dd289 100644
--- a/ui/src/main/resources/ui/laf.properties
+++ b/ui/src/main/resources/ui/laf.properties
@@ -38,7 +38,7 @@ CNC_image=ui/images/CNC.svg
 CNC_tree_image=ui/images/CNC_tree.svg
 CollapseAll16_image=ui/images/CollapseAll:16#16.svg
 CollapseAll_image=ui/images/CollapseAll.svg
-Color_image=ui/images/edit_option.svg
+Color_image=ui/images/edit.svg
 ContextMenu_image=ui/images/context_menu.svg
 ContinueLog_image=ui/images/run.svg
 CopyHop_image=ui/images/copy-hop.svg
@@ -57,7 +57,7 @@ documentationUrl=manual/latest
 DropHere_image=ui/images/drop_here.svg
 DUM_image=ui/images/dummy.svg
 Edit_image=ui/images/generic-edit:16#16.svg
-EditOption_image=ui/images/edit_option.svg
+EditOption_image=ui/images/edit.svg
 EditSmall_image=ui/images/generic-edit.svg
 errorArrow_image=ui/images/Error_Arrow.svg
 ErrorHop_image=ui/images/false.svg

Reply via email to