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 85a7dd2dfd added created and modified hop versions to pipeline and 
workflow info. fixes #8039 (#8121)
85a7dd2dfd is described below

commit 85a7dd2dfd300f6f9502e43e0460b726d0ac4ff7
Author: Bart Maertens <[email protected]>
AuthorDate: Thu Aug 27 14:15:19 2026 +0200

    added created and modified hop versions to pipeline and workflow info. 
fixes #8039 (#8121)
---
 core/src/main/java/org/apache/hop/core/Const.java  |  27 ++-
 .../org/apache/hop/pipeline/AbstractMetaInfo.java  |  16 ++
 .../java/org/apache/hop/pipeline/PipelineMeta.java |  36 ++++
 .../java/org/apache/hop/workflow/WorkflowMeta.java |  36 ++++
 .../hop/pipeline/HopVersionSerializationTest.java  | 195 +++++++++++++++++++++
 .../apache/hop/imports/kettle/KettleImport.java    |  20 +++
 .../hopgui/file/pipeline/HopGuiPipelineGraph.java  |   4 +
 .../hopgui/file/pipeline/HopPipelineFileType.java  |   5 +
 .../hopgui/file/workflow/HopGuiWorkflowGraph.java  |   4 +
 .../hopgui/file/workflow/HopWorkflowFileType.java  |   5 +
 .../hop/ui/pipeline/dialog/PipelineDialog.java     |  75 ++++++--
 .../hop/ui/workflow/dialog/WorkflowDialog.java     |  74 ++++++--
 .../dialog/messages/messages_en_US.properties      |   2 +
 .../dialog/messages/messages_en_US.properties      |   2 +
 14 files changed, 469 insertions(+), 32 deletions(-)

diff --git a/core/src/main/java/org/apache/hop/core/Const.java 
b/core/src/main/java/org/apache/hop/core/Const.java
index 882e1b1ea3..497ee0a1bf 100644
--- a/core/src/main/java/org/apache/hop/core/Const.java
+++ b/core/src/main/java/org/apache/hop/core/Const.java
@@ -2165,22 +2165,35 @@ public class Const {
   public static String getBaseDocUrl() {
     String url = BaseMessages.getString(PKG, "Const.BaseDocUrl");
 
-    // Get the implementation version:
-    // Temporary build: 2.4.0-SNAPSHOT (2023-02-13 08.50.52)
-    // Release version: 2.4.0
-    String version = Const.class.getPackage().getImplementationVersion();
+    String version = getHopVersion();
 
     // Check if implementation version is a SNAPHOT build or if version is not 
known.
     if (version == null || version.contains("SNAPSHOT")) {
       version = "next";
-    } else {
-      // Only keep until first space to remove the build date
-      version = version.split(" ")[0];
     }
 
     return url + version + "/";
   }
 
+  /**
+   * Provides the version of Hop this code was built as, without the build 
date.
+   *
+   * @return the version, for example "2.20.0" or "2.20.0-SNAPSHOT", or null 
when the version can't
+   *     be determined. That is the case whenever Hop doesn't run from its 
packaged jars, for
+   *     example in an IDE or during unit tests.
+   */
+  public static String getHopVersion() {
+    // Get the implementation version:
+    // Temporary build: 2.4.0-SNAPSHOT (2023-02-13 08.50.52)
+    // Release version: 2.4.0
+    String version = Const.class.getPackage().getImplementationVersion();
+    if (version == null) {
+      return null;
+    }
+    // Only keep until first space to remove the build date
+    return version.split(" ")[0];
+  }
+
   /**
    * Provides the documentation url with the configured base + the given URI.
    *
diff --git a/engine/src/main/java/org/apache/hop/pipeline/AbstractMetaInfo.java 
b/engine/src/main/java/org/apache/hop/pipeline/AbstractMetaInfo.java
index f51085e697..95c1b8ddaa 100644
--- a/engine/src/main/java/org/apache/hop/pipeline/AbstractMetaInfo.java
+++ b/engine/src/main/java/org/apache/hop/pipeline/AbstractMetaInfo.java
@@ -49,11 +49,27 @@ public abstract class AbstractMetaInfo {
   @HopMetadataProperty(key = "modified_date")
   protected Date modifiedDate;
 
+  /**
+   * The version of Hop that created this. Initialized empty rather than to 
the running version: the
+   * XML deserializer only assigns a field when its element is present, so a 
real version defaulted
+   * here would survive both loading a file written before this element 
existed and every undo,
+   * which clears and re-deserializes. That file would then claim to have been 
created by whichever
+   * version happened to open it. An empty value makes no such claim and reads 
as "unknown".
+   */
+  @HopMetadataProperty(key = "created_hop_version")
+  protected String createdHopVersion;
+
+  /** The version of Hop that last saved this. Initialized empty for the same 
reason, see above. */
+  @HopMetadataProperty(key = "modified_hop_version")
+  protected String modifiedHopVersion;
+
   protected AbstractMetaInfo() {
     this.nameSynchronizedWithFilename = true;
     this.createdDate = new Date();
     this.modifiedDate = new Date();
     this.createdUser = "-";
     this.modifiedUser = "-";
+    this.createdHopVersion = "";
+    this.modifiedHopVersion = "";
   }
 }
diff --git a/engine/src/main/java/org/apache/hop/pipeline/PipelineMeta.java 
b/engine/src/main/java/org/apache/hop/pipeline/PipelineMeta.java
index d50f408754..fbf8da6984 100644
--- a/engine/src/main/java/org/apache/hop/pipeline/PipelineMeta.java
+++ b/engine/src/main/java/org/apache/hop/pipeline/PipelineMeta.java
@@ -3574,6 +3574,42 @@ public class PipelineMeta extends AbstractMeta
     return info.getModifiedUser();
   }
 
+  /**
+   * Gets the version of Hop that created the pipeline.
+   *
+   * @return the Hop version that created the pipeline, or null when it isn't 
known.
+   */
+  public String getCreatedHopVersion() {
+    return info.getCreatedHopVersion();
+  }
+
+  /**
+   * Sets the version of Hop that created the pipeline.
+   *
+   * @param createdHopVersion The Hop version to set.
+   */
+  public void setCreatedHopVersion(String createdHopVersion) {
+    info.setCreatedHopVersion(createdHopVersion);
+  }
+
+  /**
+   * Gets the version of Hop that last saved the pipeline.
+   *
+   * @return the Hop version that last saved the pipeline, or null when it 
isn't known.
+   */
+  public String getModifiedHopVersion() {
+    return info.getModifiedHopVersion();
+  }
+
+  /**
+   * Sets the version of Hop that last saved the pipeline.
+   *
+   * @param modifiedHopVersion The Hop version to set.
+   */
+  public void setModifiedHopVersion(String modifiedHopVersion) {
+    info.setModifiedHopVersion(modifiedHopVersion);
+  }
+
   @Override
   protected INamedParameters getNamedParameters() {
     return info.namedParams;
diff --git a/engine/src/main/java/org/apache/hop/workflow/WorkflowMeta.java 
b/engine/src/main/java/org/apache/hop/workflow/WorkflowMeta.java
index 854adda4d6..afb4826b24 100644
--- a/engine/src/main/java/org/apache/hop/workflow/WorkflowMeta.java
+++ b/engine/src/main/java/org/apache/hop/workflow/WorkflowMeta.java
@@ -1960,4 +1960,40 @@ public class WorkflowMeta extends AbstractMeta
   public void setModifiedUser(String modifiedUser) {
     info.setModifiedUser(modifiedUser);
   }
+
+  /**
+   * Gets the version of Hop that created the workflow.
+   *
+   * @return the Hop version that created the workflow, or null when it isn't 
known.
+   */
+  public String getCreatedHopVersion() {
+    return info.getCreatedHopVersion();
+  }
+
+  /**
+   * Sets the version of Hop that created the workflow.
+   *
+   * @param createdHopVersion The Hop version to set.
+   */
+  public void setCreatedHopVersion(String createdHopVersion) {
+    info.setCreatedHopVersion(createdHopVersion);
+  }
+
+  /**
+   * Gets the version of Hop that last saved the workflow.
+   *
+   * @return the Hop version that last saved the workflow, or null when it 
isn't known.
+   */
+  public String getModifiedHopVersion() {
+    return info.getModifiedHopVersion();
+  }
+
+  /**
+   * Sets the version of Hop that last saved the workflow.
+   *
+   * @param modifiedHopVersion The Hop version to set.
+   */
+  public void setModifiedHopVersion(String modifiedHopVersion) {
+    info.setModifiedHopVersion(modifiedHopVersion);
+  }
 }
diff --git 
a/engine/src/test/java/org/apache/hop/pipeline/HopVersionSerializationTest.java 
b/engine/src/test/java/org/apache/hop/pipeline/HopVersionSerializationTest.java
new file mode 100644
index 0000000000..6739830250
--- /dev/null
+++ 
b/engine/src/test/java/org/apache/hop/pipeline/HopVersionSerializationTest.java
@@ -0,0 +1,195 @@
+/*
+ * 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.pipeline;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.ByteArrayInputStream;
+import java.nio.charset.StandardCharsets;
+import org.apache.hop.core.variables.IVariables;
+import org.apache.hop.core.variables.Variables;
+import org.apache.hop.junit.rules.RestoreHopEngineEnvironmentExtension;
+import org.apache.hop.metadata.api.IHopMetadataProvider;
+import org.apache.hop.metadata.serializer.memory.MemoryMetadataProvider;
+import org.apache.hop.workflow.WorkflowMeta;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+/**
+ * The Hop version that created and last saved a pipeline or workflow is 
recorded in the file so a
+ * project can be scanned for files built with an older release.
+ *
+ * <p>Files written before these elements existed must keep them empty rather 
than silently claim to
+ * have been created by the running version: the XML deserializer only assigns 
a field when its
+ * element is present, so anything defaulted in the constructor would survive 
a load and get written
+ * back out on the next save.
+ */
+@ExtendWith(RestoreHopEngineEnvironmentExtension.class)
+class HopVersionSerializationTest {
+
+  private static final String CREATED_VERSION = "2.19.0";
+  private static final String MODIFIED_VERSION = "2.20.0-SNAPSHOT";
+
+  /** A pipeline as written before the created/modified Hop version elements 
existed. */
+  private static final String PIPELINE_WITHOUT_VERSIONS =
+      """
+      <pipeline>
+        <info>
+          <name>no-hop-versions</name>
+          <created_user>-</created_user>
+          <created_date>2023/09/16 22:31:19.820</created_date>
+          <modified_user>-</modified_user>
+          <modified_date>2023/09/16 22:31:19.820</modified_date>
+        </info>
+      </pipeline>
+      """;
+
+  /** A workflow as written before the created/modified Hop version elements 
existed. */
+  private static final String WORKFLOW_WITHOUT_VERSIONS =
+      """
+      <workflow>
+        <name>no-hop-versions</name>
+        <created_user>-</created_user>
+        <created_date>2023/09/16 22:31:19.820</created_date>
+        <modified_user>-</modified_user>
+        <modified_date>2023/09/16 22:31:19.820</modified_date>
+      </workflow>
+      """;
+
+  /** A pipeline where the created version was backfilled by hand, without a 
modified version. */
+  private static final String PIPELINE_WITH_BACKFILLED_VERSION =
+      """
+      <pipeline>
+        <info>
+          <name>backfilled</name>
+          <created_hop_version>1.2.0</created_hop_version>
+        </info>
+      </pipeline>
+      """;
+
+  /** A workflow where the created version was backfilled by hand, without a 
modified version. */
+  private static final String WORKFLOW_WITH_BACKFILLED_VERSION =
+      """
+      <workflow>
+        <name>backfilled</name>
+        <created_hop_version>1.2.0</created_hop_version>
+      </workflow>
+      """;
+
+  private final IVariables variables = new Variables();
+  private final IHopMetadataProvider metadataProvider = new 
MemoryMetadataProvider();
+
+  private PipelineMeta loadPipeline(String xml) throws Exception {
+    return new PipelineMeta(
+        new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8)),
+        metadataProvider,
+        variables);
+  }
+
+  private WorkflowMeta loadWorkflow(String xml) throws Exception {
+    return new WorkflowMeta(
+        new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8)),
+        metadataProvider,
+        variables);
+  }
+
+  @Test
+  void pipelineKeepsHopVersionsAcrossSaveAndLoad() throws Exception {
+    PipelineMeta pipelineMeta = new PipelineMeta();
+    pipelineMeta.setName("with-hop-versions");
+    pipelineMeta.setCreatedHopVersion(CREATED_VERSION);
+    pipelineMeta.setModifiedHopVersion(MODIFIED_VERSION);
+
+    String xml = pipelineMeta.getXml(variables);
+    assertTrue(xml.contains("<created_hop_version>" + CREATED_VERSION + 
"</created_hop_version>"));
+    assertTrue(
+        xml.contains("<modified_hop_version>" + MODIFIED_VERSION + 
"</modified_hop_version>"));
+
+    PipelineMeta loaded = loadPipeline(xml);
+    assertEquals(CREATED_VERSION, loaded.getCreatedHopVersion());
+    assertEquals(MODIFIED_VERSION, loaded.getModifiedHopVersion());
+  }
+
+  @Test
+  void workflowKeepsHopVersionsAcrossSaveAndLoad() throws Exception {
+    WorkflowMeta workflowMeta = new WorkflowMeta();
+    workflowMeta.setName("with-hop-versions");
+    workflowMeta.setCreatedHopVersion(CREATED_VERSION);
+    workflowMeta.setModifiedHopVersion(MODIFIED_VERSION);
+
+    String xml = workflowMeta.getXml(variables);
+    assertTrue(xml.contains("<created_hop_version>" + CREATED_VERSION + 
"</created_hop_version>"));
+    assertTrue(
+        xml.contains("<modified_hop_version>" + MODIFIED_VERSION + 
"</modified_hop_version>"));
+
+    WorkflowMeta loaded = loadWorkflow(xml);
+    assertEquals(CREATED_VERSION, loaded.getCreatedHopVersion());
+    assertEquals(MODIFIED_VERSION, loaded.getModifiedHopVersion());
+  }
+
+  @Test
+  void pipelineWithoutHopVersionsDoesNotClaimTheRunningVersion() throws 
Exception {
+    PipelineMeta loaded = loadPipeline(PIPELINE_WITHOUT_VERSIONS);
+
+    assertEquals("", loaded.getCreatedHopVersion());
+    assertEquals("", loaded.getModifiedHopVersion());
+
+    // Both elements are written on every save, empty meaning "we don't know".
+    String xml = loaded.getXml(variables);
+    assertTrue(xml.contains("<created_hop_version/>"));
+    assertTrue(xml.contains("<modified_hop_version/>"));
+  }
+
+  @Test
+  void workflowWithoutHopVersionsDoesNotClaimTheRunningVersion() throws 
Exception {
+    WorkflowMeta loaded = loadWorkflow(WORKFLOW_WITHOUT_VERSIONS);
+
+    assertEquals("", loaded.getCreatedHopVersion());
+    assertEquals("", loaded.getModifiedHopVersion());
+
+    String xml = loaded.getXml(variables);
+    assertTrue(xml.contains("<created_hop_version/>"));
+    assertTrue(xml.contains("<modified_hop_version/>"));
+  }
+
+  @Test
+  void pipelineKeepsAManuallyBackfilledCreatedHopVersion() throws Exception {
+    PipelineMeta loaded = loadPipeline(PIPELINE_WITH_BACKFILLED_VERSION);
+
+    // Whatever is in the file is kept verbatim, nothing overwrites or 
validates it.
+    assertEquals("1.2.0", loaded.getCreatedHopVersion());
+    assertEquals("", loaded.getModifiedHopVersion());
+
+    String xml = loaded.getXml(variables);
+    
assertTrue(xml.contains("<created_hop_version>1.2.0</created_hop_version>"));
+    assertTrue(xml.contains("<modified_hop_version/>"));
+  }
+
+  @Test
+  void workflowKeepsAManuallyBackfilledCreatedHopVersion() throws Exception {
+    WorkflowMeta loaded = loadWorkflow(WORKFLOW_WITH_BACKFILLED_VERSION);
+
+    assertEquals("1.2.0", loaded.getCreatedHopVersion());
+    assertEquals("", loaded.getModifiedHopVersion());
+
+    String xml = loaded.getXml(variables);
+    
assertTrue(xml.contains("<created_hop_version>1.2.0</created_hop_version>"));
+    assertTrue(xml.contains("<modified_hop_version/>"));
+  }
+}
diff --git 
a/plugins/misc/import/src/main/java/org/apache/hop/imports/kettle/KettleImport.java
 
b/plugins/misc/import/src/main/java/org/apache/hop/imports/kettle/KettleImport.java
index db0359dfeb..c04b1f72af 100644
--- 
a/plugins/misc/import/src/main/java/org/apache/hop/imports/kettle/KettleImport.java
+++ 
b/plugins/misc/import/src/main/java/org/apache/hop/imports/kettle/KettleImport.java
@@ -207,6 +207,7 @@ public class KettleImport extends HopImportBase implements 
IHopImport {
       Node targetNode = XmlHandler.getSubNode(documentElement, "info");
       if (targetNode != null) {
         targetNode.insertBefore(nameSync, XmlHandler.getSubNode(targetNode, 
"description"));
+        addCreatedHopVersion(doc, targetNode);
       }
     } else if (extension.equalsIgnoreCase("kjb")) {
       kjbCounter++;
@@ -215,6 +216,7 @@ public class KettleImport extends HopImportBase implements 
IHopImport {
       // Add the name-sync node in /workflow/
       //
       documentElement.insertBefore(nameSync, 
XmlHandler.getSubNode(documentElement, "description"));
+      addCreatedHopVersion(doc, documentElement);
     }
 
     processNode(doc, documentElement, EntryType.OTHER, 0);
@@ -693,6 +695,24 @@ public class KettleImport extends HopImportBase implements 
IHopImport {
     }
   }
 
+  /**
+   * Record the version of Hop that imported this pipeline or workflow. The 
Kettle created and
+   * modified date and user elements carry the same names in Hop, so they pass 
through the import
+   * untouched and keep their original Kettle values.
+   *
+   * @param doc the document being imported
+   * @param parent the node holding the metadata: /pipeline/info/ for a 
pipeline, /workflow/ for a
+   *     workflow
+   */
+  private void addCreatedHopVersion(Document doc, Node parent) {
+    // An empty element when the version isn't known, which happens when we're 
not running from
+    // the packaged jars. That matches what the serializer writes for an 
unknown version.
+    //
+    Element createdHopVersion = doc.createElement("created_hop_version");
+    
createdHopVersion.appendChild(doc.createTextNode(Const.NVL(Const.getHopVersion(),
 "")));
+    parent.insertBefore(createdHopVersion, XmlHandler.getSubNode(parent, 
"description"));
+  }
+
   private void setChildElement(Document doc, Node parent, String name, String 
value) {
     Element child = getChildElement(parent, name);
     if (child == null) {
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 1f0e5e5bf2..303bb87d31 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
@@ -5145,6 +5145,10 @@ public class HopGuiPipelineGraph extends 
HopGuiAbstractGraph
 
       boolean fileExist = HopVfs.fileExists(pipelineMeta.getFilename());
 
+      // Record the version of Hop saving this pipeline
+      //
+      pipelineMeta.setModifiedHopVersion(Const.NVL(Const.getHopVersion(), ""));
+
       String xml = pipelineMeta.getXml(variables);
       OutputStream out = HopVfs.getOutputStream(pipelineMeta.getFilename(), 
false);
       try {
diff --git 
a/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/HopPipelineFileType.java
 
b/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/HopPipelineFileType.java
index 30acd970c2..301c947c4e 100644
--- 
a/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/HopPipelineFileType.java
+++ 
b/ui/src/main/java/org/apache/hop/ui/hopgui/file/pipeline/HopPipelineFileType.java
@@ -20,6 +20,7 @@ package org.apache.hop.ui.hopgui.file.pipeline;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Properties;
+import org.apache.hop.core.Const;
 import org.apache.hop.core.exception.HopException;
 import org.apache.hop.core.extension.ExtensionPointHandler;
 import org.apache.hop.core.extension.HopExtensionPoint;
@@ -168,6 +169,10 @@ public class HopPipelineFileType<T extends PipelineMeta> 
extends HopFileTypeBase
       PipelineMeta pipelineMeta = new PipelineMeta();
       pipelineMeta.setName(BaseMessages.getString(PKG, 
"HopPipelineFileType.New.Text"));
 
+      // Record the version of Hop creating this pipeline
+      //
+      pipelineMeta.setCreatedHopVersion(Const.NVL(Const.getHopVersion(), ""));
+
       // Pass the MetadataProvider for reference lookups
       //
       pipelineMeta.setMetadataProvider(hopGui.getMetadataProvider());
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 47441b3e0e..5c3666f22c 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
@@ -4677,6 +4677,10 @@ public class HopGuiWorkflowGraph extends 
HopGuiAbstractGraph
 
       boolean fileExist = HopVfs.fileExists(workflowMeta.getFilename());
 
+      // Record the version of Hop saving this workflow
+      //
+      workflowMeta.setModifiedHopVersion(Const.NVL(Const.getHopVersion(), ""));
+
       String xml = workflowMeta.getXml(variables);
       OutputStream out = HopVfs.getOutputStream(workflowMeta.getFilename(), 
false);
       try {
diff --git 
a/ui/src/main/java/org/apache/hop/ui/hopgui/file/workflow/HopWorkflowFileType.java
 
b/ui/src/main/java/org/apache/hop/ui/hopgui/file/workflow/HopWorkflowFileType.java
index 50b4a6fad8..16e29de2a8 100644
--- 
a/ui/src/main/java/org/apache/hop/ui/hopgui/file/workflow/HopWorkflowFileType.java
+++ 
b/ui/src/main/java/org/apache/hop/ui/hopgui/file/workflow/HopWorkflowFileType.java
@@ -20,6 +20,7 @@ package org.apache.hop.ui.hopgui.file.workflow;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Properties;
+import org.apache.hop.core.Const;
 import org.apache.hop.core.exception.HopException;
 import org.apache.hop.core.extension.ExtensionPointHandler;
 import org.apache.hop.core.extension.HopExtensionPoint;
@@ -173,6 +174,10 @@ public class HopWorkflowFileType<T extends WorkflowMeta> 
extends HopFileTypeBase
       WorkflowMeta workflowMeta = new WorkflowMeta();
       workflowMeta.setName(BaseMessages.getString(PKG, 
"HopWorkflowFileType.New.Text"));
 
+      // Record the version of Hop creating this workflow
+      //
+      workflowMeta.setCreatedHopVersion(Const.NVL(Const.getHopVersion(), ""));
+
       // Pass the MetaStore for reference lookups
       //
       workflowMeta.setMetadataProvider(hopGui.getMetadataProvider());
diff --git 
a/ui/src/main/java/org/apache/hop/ui/pipeline/dialog/PipelineDialog.java 
b/ui/src/main/java/org/apache/hop/ui/pipeline/dialog/PipelineDialog.java
index f8bbae145b..c81001e696 100644
--- a/ui/src/main/java/org/apache/hop/ui/pipeline/dialog/PipelineDialog.java
+++ b/ui/src/main/java/org/apache/hop/ui/pipeline/dialog/PipelineDialog.java
@@ -43,6 +43,7 @@ import 
org.apache.hop.ui.pipeline.transform.BaseTransformDialog;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.custom.CTabFolder;
 import org.eclipse.swt.custom.CTabItem;
+import org.eclipse.swt.custom.ScrolledComposite;
 import org.eclipse.swt.events.ModifyListener;
 import org.eclipse.swt.events.SelectionAdapter;
 import org.eclipse.swt.events.SelectionEvent;
@@ -97,6 +98,10 @@ public class PipelineDialog extends Dialog {
   private Text wModUser;
   private Text wModDate;
 
+  private Text wCreatedHopVersion;
+
+  private Text wModifiedHopVersion;
+
   private TableView wParamFields;
 
   private final IVariables variables;
@@ -233,7 +238,15 @@ public class PipelineDialog extends Dialog {
     wPipelineTab.setFont(GuiResource.getInstance().getFontDefault());
     wPipelineTab.setText(BaseMessages.getString(PKG, 
"PipelineDialog.PipelineTab.Label"));
 
-    Composite wPipelineComp = new Composite(wTabFolder, SWT.NONE);
+    // The field list is taller than the dialog once it's resized down, so the 
tab scrolls
+    // rather than silently cutting off whatever no longer fits.
+    //
+    ScrolledComposite wPipelineSc = new ScrolledComposite(wTabFolder, 
SWT.V_SCROLL | SWT.H_SCROLL);
+    PropsUi.setLook(wPipelineSc);
+    wPipelineSc.setExpandHorizontal(true);
+    wPipelineSc.setExpandVertical(true);
+
+    Composite wPipelineComp = new Composite(wPipelineSc, SWT.NONE);
     PropsUi.setLook(wPipelineComp);
 
     FormLayout workflowLayout = new FormLayout();
@@ -343,7 +356,7 @@ public class PipelineDialog extends Dialog {
     fdExtendedDescription.left = new FormAttachment(middle, 0);
     fdExtendedDescription.top = new FormAttachment(wPipelineDescription, 
margin);
     fdExtendedDescription.right = new FormAttachment(100, 0);
-    fdExtendedDescription.bottom = new FormAttachment(50, -margin);
+    fdExtendedDescription.height = (int) (120 * PropsUi.getNativeZoomFactor());
     wExtendedDescription.setLayoutData(fdExtendedDescription);
 
     // Pipeline Status
@@ -426,6 +439,25 @@ public class PipelineDialog extends Dialog {
     fdCreateDate.right = new FormAttachment(100, 0);
     wCreateDate.setLayoutData(fdCreateDate);
 
+    // Created with Hop version:
+    Label wlCreatedHopVersion = new Label(wPipelineComp, SWT.RIGHT);
+    wlCreatedHopVersion.setText(
+        BaseMessages.getString(PKG, "PipelineDialog.CreatedHopVersion.Label"));
+    PropsUi.setLook(wlCreatedHopVersion);
+    FormData fdlCreatedHopVersion = new FormData();
+    fdlCreatedHopVersion.left = new FormAttachment(0, 0);
+    fdlCreatedHopVersion.right = new FormAttachment(middle, -margin);
+    fdlCreatedHopVersion.top = new FormAttachment(wCreateDate, margin);
+    wlCreatedHopVersion.setLayoutData(fdlCreatedHopVersion);
+    wCreatedHopVersion = new Text(wPipelineComp, SWT.SINGLE | SWT.LEFT | 
SWT.BORDER);
+    PropsUi.setLook(wCreatedHopVersion);
+    wCreatedHopVersion.setEditable(false);
+    FormData fdCreatedHopVersion = new FormData();
+    fdCreatedHopVersion.left = new FormAttachment(middle, 0);
+    fdCreatedHopVersion.top = new FormAttachment(wCreateDate, margin);
+    fdCreatedHopVersion.right = new FormAttachment(100, 0);
+    wCreatedHopVersion.setLayoutData(fdCreatedHopVersion);
+
     // Modified User:
     Label wlModUser = new Label(wPipelineComp, SWT.RIGHT);
     wlModUser.setText(BaseMessages.getString(PKG, 
"PipelineDialog.LastModifiedUser.Label"));
@@ -433,7 +465,7 @@ public class PipelineDialog extends Dialog {
     FormData fdlModUser = new FormData();
     fdlModUser.left = new FormAttachment(0, 0);
     fdlModUser.right = new FormAttachment(middle, -margin);
-    fdlModUser.top = new FormAttachment(wCreateDate, margin);
+    fdlModUser.top = new FormAttachment(wCreatedHopVersion, margin);
     wlModUser.setLayoutData(fdlModUser);
     wModUser = new Text(wPipelineComp, SWT.SINGLE | SWT.LEFT | SWT.BORDER);
     PropsUi.setLook(wModUser);
@@ -441,7 +473,7 @@ public class PipelineDialog extends Dialog {
     wModUser.addModifyListener(lsMod);
     FormData fdModUser = new FormData();
     fdModUser.left = new FormAttachment(middle, 0);
-    fdModUser.top = new FormAttachment(wCreateDate, margin);
+    fdModUser.top = new FormAttachment(wCreatedHopVersion, margin);
     fdModUser.right = new FormAttachment(100, 0);
     wModUser.setLayoutData(fdModUser);
 
@@ -464,15 +496,29 @@ public class PipelineDialog extends Dialog {
     fdModDate.right = new FormAttachment(100, 0);
     wModDate.setLayoutData(fdModDate);
 
-    FormData fdPipelineComp = new FormData();
-    fdPipelineComp.left = new FormAttachment(0, 0);
-    fdPipelineComp.top = new FormAttachment(0, 0);
-    fdPipelineComp.right = new FormAttachment(100, 0);
-    fdPipelineComp.bottom = new FormAttachment(100, 0);
-    wPipelineComp.setLayoutData(fdPipelineComp);
-
-    wPipelineComp.layout();
-    wPipelineTab.setControl(wPipelineComp);
+    // Last modified with Hop version:
+    Label wlModifiedHopVersion = new Label(wPipelineComp, SWT.RIGHT);
+    wlModifiedHopVersion.setText(
+        BaseMessages.getString(PKG, 
"PipelineDialog.ModifiedHopVersion.Label"));
+    PropsUi.setLook(wlModifiedHopVersion);
+    FormData fdlModifiedHopVersion = new FormData();
+    fdlModifiedHopVersion.left = new FormAttachment(0, 0);
+    fdlModifiedHopVersion.right = new FormAttachment(middle, -margin);
+    fdlModifiedHopVersion.top = new FormAttachment(wModDate, margin);
+    wlModifiedHopVersion.setLayoutData(fdlModifiedHopVersion);
+    wModifiedHopVersion = new Text(wPipelineComp, SWT.SINGLE | SWT.LEFT | 
SWT.BORDER);
+    PropsUi.setLook(wModifiedHopVersion);
+    wModifiedHopVersion.setEditable(false);
+    FormData fdModifiedHopVersion = new FormData();
+    fdModifiedHopVersion.left = new FormAttachment(middle, 0);
+    fdModifiedHopVersion.top = new FormAttachment(wModDate, margin);
+    fdModifiedHopVersion.right = new FormAttachment(100, 0);
+    wModifiedHopVersion.setLayoutData(fdModifiedHopVersion);
+
+    wPipelineComp.layout(true, true);
+    wPipelineSc.setContent(wPipelineComp);
+    wPipelineSc.setMinSize(wPipelineComp.computeSize(SWT.DEFAULT, 
SWT.DEFAULT));
+    wPipelineTab.setControl(wPipelineSc);
 
     // ///////////////////////////////////////////////////////////
     // / END OF PIPELINE TAB
@@ -696,6 +742,9 @@ public class PipelineDialog extends Dialog {
       wModDate.setText(pipelineMeta.getModifiedDate().toString());
     }
 
+    wCreatedHopVersion.setText(Const.NVL(pipelineMeta.getCreatedHopVersion(), 
""));
+    
wModifiedHopVersion.setText(Const.NVL(pipelineMeta.getModifiedHopVersion(), 
""));
+
     // The named parameters
     String[] parameters = pipelineMeta.listParameters();
     for (int idx = 0; idx < parameters.length; idx++) {
diff --git 
a/ui/src/main/java/org/apache/hop/ui/workflow/dialog/WorkflowDialog.java 
b/ui/src/main/java/org/apache/hop/ui/workflow/dialog/WorkflowDialog.java
index b187f8c312..d563a5a858 100644
--- a/ui/src/main/java/org/apache/hop/ui/workflow/dialog/WorkflowDialog.java
+++ b/ui/src/main/java/org/apache/hop/ui/workflow/dialog/WorkflowDialog.java
@@ -46,6 +46,7 @@ import org.apache.hop.workflow.action.IAction;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.custom.CTabFolder;
 import org.eclipse.swt.custom.CTabItem;
+import org.eclipse.swt.custom.ScrolledComposite;
 import org.eclipse.swt.events.ModifyListener;
 import org.eclipse.swt.events.ShellAdapter;
 import org.eclipse.swt.events.ShellEvent;
@@ -112,6 +113,10 @@ public class WorkflowDialog extends Dialog {
 
   private Text wModDate;
 
+  private Text wCreatedHopVersion;
+
+  private Text wModifiedHopVersion;
+
   private ArrayList<IWorkflowDialogPlugin> extraTabs;
 
   public WorkflowDialog(Shell parent, int style, IVariables variables, 
WorkflowMeta workflowMeta) {
@@ -201,7 +206,15 @@ public class WorkflowDialog extends Dialog {
     wWorkflowTab.setFont(GuiResource.getInstance().getFontDefault());
     wWorkflowTab.setText(BaseMessages.getString(PKG, 
"WorkflowDialog.WorkflowTab.Label"));
 
-    Composite wWorkflowComp = new Composite(wTabFolder, SWT.NONE);
+    // The field list is taller than the dialog once it's resized down, so the 
tab scrolls
+    // rather than silently cutting off whatever no longer fits.
+    //
+    ScrolledComposite wWorkflowSc = new ScrolledComposite(wTabFolder, 
SWT.V_SCROLL | SWT.H_SCROLL);
+    PropsUi.setLook(wWorkflowSc);
+    wWorkflowSc.setExpandHorizontal(true);
+    wWorkflowSc.setExpandVertical(true);
+
+    Composite wWorkflowComp = new Composite(wWorkflowSc, SWT.NONE);
     PropsUi.setLook(wWorkflowComp);
 
     FormLayout workflowLayout = new FormLayout();
@@ -307,7 +320,7 @@ public class WorkflowDialog extends Dialog {
     fdExtendedDescription.left = new FormAttachment(middle, 0);
     fdExtendedDescription.top = new FormAttachment(wDescription, margin);
     fdExtendedDescription.right = new FormAttachment(100, 0);
-    fdExtendedDescription.bottom = new FormAttachment(50, -margin);
+    fdExtendedDescription.height = (int) (120 * PropsUi.getNativeZoomFactor());
     wExtendedDescription.setLayoutData(fdExtendedDescription);
 
     // Workflow version:
@@ -366,6 +379,25 @@ public class WorkflowDialog extends Dialog {
     fdCreateDate.right = new FormAttachment(100, 0);
     wCreateDate.setLayoutData(fdCreateDate);
 
+    // Created with Hop version:
+    Label wlCreatedHopVersion = new Label(wWorkflowComp, SWT.RIGHT);
+    wlCreatedHopVersion.setText(
+        BaseMessages.getString(PKG, "WorkflowDialog.CreatedHopVersion.Label"));
+    PropsUi.setLook(wlCreatedHopVersion);
+    FormData fdlCreatedHopVersion = new FormData();
+    fdlCreatedHopVersion.left = new FormAttachment(0, 0);
+    fdlCreatedHopVersion.right = new FormAttachment(middle, -margin);
+    fdlCreatedHopVersion.top = new FormAttachment(wCreateDate, margin);
+    wlCreatedHopVersion.setLayoutData(fdlCreatedHopVersion);
+    wCreatedHopVersion = new Text(wWorkflowComp, SWT.SINGLE | SWT.LEFT | 
SWT.BORDER);
+    PropsUi.setLook(wCreatedHopVersion);
+    wCreatedHopVersion.setEditable(false);
+    FormData fdCreatedHopVersion = new FormData();
+    fdCreatedHopVersion.left = new FormAttachment(middle, 0);
+    fdCreatedHopVersion.top = new FormAttachment(wCreateDate, margin);
+    fdCreatedHopVersion.right = new FormAttachment(100, 0);
+    wCreatedHopVersion.setLayoutData(fdCreatedHopVersion);
+
     // Modified User:
     Label wlModUser = new Label(wWorkflowComp, SWT.RIGHT);
     wlModUser.setText(BaseMessages.getString(PKG, 
"WorkflowDialog.LastModifiedUser.Label"));
@@ -373,7 +405,7 @@ public class WorkflowDialog extends Dialog {
     FormData fdlModUser = new FormData();
     fdlModUser.left = new FormAttachment(0, 0);
     fdlModUser.right = new FormAttachment(middle, -margin);
-    fdlModUser.top = new FormAttachment(wCreateDate, margin);
+    fdlModUser.top = new FormAttachment(wCreatedHopVersion, margin);
     wlModUser.setLayoutData(fdlModUser);
     wModUser = new Text(wWorkflowComp, SWT.SINGLE | SWT.LEFT | SWT.BORDER);
     PropsUi.setLook(wModUser);
@@ -381,7 +413,7 @@ public class WorkflowDialog extends Dialog {
     wModUser.addModifyListener(lsMod);
     FormData fdModUser = new FormData();
     fdModUser.left = new FormAttachment(middle, 0);
-    fdModUser.top = new FormAttachment(wCreateDate, margin);
+    fdModUser.top = new FormAttachment(wCreatedHopVersion, margin);
     fdModUser.right = new FormAttachment(100, 0);
     wModUser.setLayoutData(fdModUser);
 
@@ -404,14 +436,29 @@ public class WorkflowDialog extends Dialog {
     fdModDate.right = new FormAttachment(100, 0);
     wModDate.setLayoutData(fdModDate);
 
-    FormData fdWorkflowComp = new FormData();
-    fdWorkflowComp.left = new FormAttachment(0, 0);
-    fdWorkflowComp.top = new FormAttachment(0, 0);
-    fdWorkflowComp.right = new FormAttachment(100, 0);
-    fdWorkflowComp.bottom = new FormAttachment(100, 0);
-
-    wWorkflowComp.setLayoutData(fdWorkflowComp);
-    wWorkflowTab.setControl(wWorkflowComp);
+    // Last modified with Hop version:
+    Label wlModifiedHopVersion = new Label(wWorkflowComp, SWT.RIGHT);
+    wlModifiedHopVersion.setText(
+        BaseMessages.getString(PKG, 
"WorkflowDialog.ModifiedHopVersion.Label"));
+    PropsUi.setLook(wlModifiedHopVersion);
+    FormData fdlModifiedHopVersion = new FormData();
+    fdlModifiedHopVersion.left = new FormAttachment(0, 0);
+    fdlModifiedHopVersion.right = new FormAttachment(middle, -margin);
+    fdlModifiedHopVersion.top = new FormAttachment(wModDate, margin);
+    wlModifiedHopVersion.setLayoutData(fdlModifiedHopVersion);
+    wModifiedHopVersion = new Text(wWorkflowComp, SWT.SINGLE | SWT.LEFT | 
SWT.BORDER);
+    PropsUi.setLook(wModifiedHopVersion);
+    wModifiedHopVersion.setEditable(false);
+    FormData fdModifiedHopVersion = new FormData();
+    fdModifiedHopVersion.left = new FormAttachment(middle, 0);
+    fdModifiedHopVersion.top = new FormAttachment(wModDate, margin);
+    fdModifiedHopVersion.right = new FormAttachment(100, 0);
+    wModifiedHopVersion.setLayoutData(fdModifiedHopVersion);
+
+    wWorkflowComp.layout(true, true);
+    wWorkflowSc.setContent(wWorkflowComp);
+    wWorkflowSc.setMinSize(wWorkflowComp.computeSize(SWT.DEFAULT, 
SWT.DEFAULT));
+    wWorkflowTab.setControl(wWorkflowSc);
 
     // ///////////////////////////////////////////////////////////
     // / END OF WORKFLOW TAB
@@ -536,6 +583,9 @@ public class WorkflowDialog extends Dialog {
       wModDate.setText(workflowMeta.getModifiedDate().toString());
     }
 
+    wCreatedHopVersion.setText(Const.NVL(workflowMeta.getCreatedHopVersion(), 
""));
+    
wModifiedHopVersion.setText(Const.NVL(workflowMeta.getModifiedHopVersion(), 
""));
+
     // The named parameters
     String[] parameters = workflowMeta.listParameters();
     for (int idx = 0; idx < parameters.length; idx++) {
diff --git 
a/ui/src/main/resources/org/apache/hop/ui/pipeline/dialog/messages/messages_en_US.properties
 
b/ui/src/main/resources/org/apache/hop/ui/pipeline/dialog/messages/messages_en_US.properties
index 1e73e8d9ad..e738e4f860 100644
--- 
a/ui/src/main/resources/org/apache/hop/ui/pipeline/dialog/messages/messages_en_US.properties
+++ 
b/ui/src/main/resources/org/apache/hop/ui/pipeline/dialog/messages/messages_en_US.properties
@@ -25,6 +25,8 @@ PipelineDialog.Draft_PipelineStatus.Label=Draft
 PipelineDialog.Extendeddescription.Label=Extended description
 PipelineDialog.InvalidTransformPerfIntervalNumber.DialogMessage=The transform 
performance interval must be a valid number. Values less or equal to zero are 
not allowed. 
 PipelineDialog.InvalidTransformPerfIntervalNumber.DialogTitle=Error in 
transform performance interval
+PipelineDialog.CreatedHopVersion.Label=Created with Hop version
+PipelineDialog.ModifiedHopVersion.Label=Last modified with Hop version
 PipelineDialog.LastModifiedDate.Label=Last modified at
 PipelineDialog.LastModifiedUser.Label=Last modified by
 PipelineDialog.LoadDialogPlugin.Error.Header=Error
diff --git 
a/ui/src/main/resources/org/apache/hop/ui/workflow/dialog/messages/messages_en_US.properties
 
b/ui/src/main/resources/org/apache/hop/ui/workflow/dialog/messages/messages_en_US.properties
index 6202186071..d8f9faa59b 100644
--- 
a/ui/src/main/resources/org/apache/hop/ui/workflow/dialog/messages/messages_en_US.properties
+++ 
b/ui/src/main/resources/org/apache/hop/ui/workflow/dialog/messages/messages_en_US.properties
@@ -27,6 +27,8 @@ WorkflowDialog.Description.Label=Description
 WorkflowDialog.Draft_WorkflowStatus.Label=Draft
 WorkflowDialog.Extendeddescription.Label=Extended description
 WorkflowDialog.Filename.Label=Workflow filename
+WorkflowDialog.CreatedHopVersion.Label=Created with Hop version
+WorkflowDialog.ModifiedHopVersion.Label=Last modified with Hop version
 WorkflowDialog.LastModifiedDate.Label=Last modified at
 WorkflowDialog.LastModifiedUser.Label=Last modified by
 WorkflowDialog.NameFilenameSync.Label=Synchronize name with filename

Reply via email to