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

bamaer 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 cec93c100d Issue #5370 : Pre-fill new data set name, folder, and file 
(#8698)
cec93c100d is described below

commit cec93c100d1ee9adaec61dc0896033c6e10ec19a
Author: Matt Casters <[email protected]>
AuthorDate: Thu Oct 1 11:46:07 2026 +0200

    Issue #5370 : Pre-fill new data set name, folder, and file (#8698)
    
    * Issue #5370 : Pre-fill new data set name, folder, and file
    
    * Issue #5370 : Leave the data set folder empty when HOP_DATASETS_FOLDER is 
not set
    
    ---------
    
    Co-authored-by: Bart Maertens <[email protected]>
---
 .../ROOT/pages/metadata-types/data-set.adoc        |  11 ++
 .../main/java/org/apache/hop/testing/DataSet.java  |   5 +-
 .../org/apache/hop/testing/DataSetDefaults.java    | 171 ++++++++++++++++++++
 .../apache/hop/testing/gui/TestingGuiPlugin.java   |   7 +
 .../xp/HopGuiDataSetCreateBeforeDialog.java        | 100 ++++++++++++
 .../org/apache/hop/ui/testing/DataSetEditor.java   |  37 ++++-
 .../ui/testing/messages/messages_en_US.properties  |   3 +
 .../apache/hop/testing/DataSetDefaultsTest.java    | 176 +++++++++++++++++++++
 .../xp/HopGuiDataSetCreateBeforeDialogTest.java    |  60 +++++++
 9 files changed, 567 insertions(+), 3 deletions(-)

diff --git 
a/docs/hop-user-manual/modules/ROOT/pages/metadata-types/data-set.adoc 
b/docs/hop-user-manual/modules/ROOT/pages/metadata-types/data-set.adoc
index 9c8203b59d..5824c261af 100644
--- a/docs/hop-user-manual/modules/ROOT/pages/metadata-types/data-set.adoc
+++ b/docs/hop-user-manual/modules/ROOT/pages/metadata-types/data-set.adoc
@@ -48,6 +48,17 @@ xref:pipeline/transforms/datasetinput.adoc[Data set input], 
xref:pipeline/transf
 Describes the file layout for this data set.
 |===
 
+== New data set
+
+Creating a data set suggests values you can still change. An existing data set 
is not rewritten.
+
+* *Name* is `ds-<pipeline file>-<transform>`. `<pipeline file>` is the file 
name of the pipeline open in the explorer, without its directory or extension. 
`<transform>` is included when exactly one transform is selected. Creating a 
data set from a transform uses that transform.
+* *Base file name* uses the same text with a `.csv` extension.
+* *Set folder* is `+'${HOP_DATASETS_FOLDER}'+` when that variable is set, 
otherwise it stays empty, so no machine-specific path is saved. An empty folder 
is resolved when the data set is used.
+* *Browse...* opens in that folder so you can pick it or a subfolder.
+
+If a data set with the suggested name already exists, the suggestion ends with 
` 2`, ` 3`, and so on. The base file name follows that name.
+
 == Samples
 
 None
\ No newline at end of file
diff --git 
a/plugins/misc/testing/src/main/java/org/apache/hop/testing/DataSet.java 
b/plugins/misc/testing/src/main/java/org/apache/hop/testing/DataSet.java
index b3742010c1..1feb07da99 100644
--- a/plugins/misc/testing/src/main/java/org/apache/hop/testing/DataSet.java
+++ b/plugins/misc/testing/src/main/java/org/apache/hop/testing/DataSet.java
@@ -49,6 +49,9 @@ public class DataSet extends HopMetadataBase implements 
Cloneable, IHopMetadata
 
   public static final String VARIABLE_HOP_DATASETS_FOLDER = 
"HOP_DATASETS_FOLDER";
 
+  /** Placeholder base file name until a suggestion or the user replaces it. */
+  public static final String DEFAULT_BASE_FILENAME = "data-set-filename.csv";
+
   @HopMetadataProperty private String description;
 
   @HopMetadataProperty(key = "folder_name")
@@ -62,7 +65,7 @@ public class DataSet extends HopMetadataBase implements 
Cloneable, IHopMetadata
 
   public DataSet() {
     fields = new ArrayList<>();
-    baseFilename = "data-set-filename.csv";
+    baseFilename = DEFAULT_BASE_FILENAME;
   }
 
   public DataSet(
diff --git 
a/plugins/misc/testing/src/main/java/org/apache/hop/testing/DataSetDefaults.java
 
b/plugins/misc/testing/src/main/java/org/apache/hop/testing/DataSetDefaults.java
new file mode 100644
index 0000000000..cd55f26f5c
--- /dev/null
+++ 
b/plugins/misc/testing/src/main/java/org/apache/hop/testing/DataSetDefaults.java
@@ -0,0 +1,171 @@
+/*
+ * 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.testing;
+
+import java.util.regex.Pattern;
+import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.hop.core.variables.IVariables;
+import org.apache.hop.metadata.api.IHopMetadataProvider;
+import org.apache.hop.metadata.api.IHopMetadataSerializer;
+
+/**
+ * Suggestions for a new data set.
+ *
+ * <p>Name pattern: {@code ds-<pipeline file>-<transform>}. The pipeline part 
is the file name
+ * without its directory or extension, and the transform part is omitted when 
unknown. The folder is
+ * the datasets-folder variable expression when that variable is set. 
Otherwise it stays empty, so
+ * no machine-specific path is stored and the folder is resolved when the data 
set is used. A value
+ * that is already set is left alone, except the constructor placeholder base 
file name, which is
+ * replaced when a name is known. A second call does not replace suggestions.
+ */
+public final class DataSetDefaults {
+
+  private static final Pattern UNSAFE = 
Pattern.compile("[\\\\/:*?\"<>|\\p{Cntrl}]+");
+  private static final Pattern REPEATED_DASH = Pattern.compile("-{2,}");
+  private static final Pattern EDGE_DASH = 
Pattern.compile("^[\\s-]+|[\\s-]+$");
+
+  private DataSetDefaults() {}
+
+  public static void apply(
+      DataSet dataSet,
+      String pipelineFilename,
+      String transformName,
+      IVariables variables,
+      IHopMetadataProvider metadataProvider) {
+    if (dataSet == null) {
+      return;
+    }
+
+    if (StringUtils.isEmpty(dataSet.getName())) {
+      String generated = buildName(pipelineFilename, transformName);
+      if (generated != null) {
+        dataSet.setName(uniqueName(generated, metadataProvider));
+      }
+    }
+
+    if (StringUtils.isEmpty(dataSet.getFolderName())) {
+      String folder = defaultFolder(variables);
+      if (StringUtils.isNotEmpty(folder)) {
+        dataSet.setFolderName(folder);
+      }
+    }
+
+    if (isPlaceholderFilename(dataSet) && 
StringUtils.isNotEmpty(dataSet.getName())) {
+      dataSet.setBaseFilename(dataSet.getName() + ".csv");
+    }
+  }
+
+  static String buildName(String pipelineFilename, String transformName) {
+    String pipeline = sanitizeToken(pipelineStem(pipelineFilename));
+    String transform = sanitizeToken(transformName);
+    if (pipeline == null && transform == null) {
+      return null;
+    }
+    StringBuilder name = new StringBuilder("ds");
+    if (pipeline != null) {
+      name.append('-').append(pipeline);
+    }
+    if (transform != null) {
+      name.append('-').append(transform);
+    }
+    return name.toString();
+  }
+
+  static String pipelineStem(String pipelineFilename) {
+    String file = filenameOnly(pipelineFilename);
+    if (file == null) {
+      return null;
+    }
+    String stem = FilenameUtils.getBaseName(file);
+    return StringUtils.isBlank(stem) ? null : stem;
+  }
+
+  static String defaultFolder(IVariables variables) {
+    return datasetsFolderExpression(variables);
+  }
+
+  /**
+   * The datasets-folder variable expression when that variable is set on 
{@code variables} or a
+   * parent, otherwise null.
+   */
+  public static String datasetsFolderExpression(IVariables variables) {
+    if (!hasDatasetsFolder(variables)) {
+      return null;
+    }
+    return "${" + DataSet.VARIABLE_HOP_DATASETS_FOLDER + "}";
+  }
+
+  private static boolean isPlaceholderFilename(DataSet dataSet) {
+    return StringUtils.isEmpty(dataSet.getBaseFilename())
+        || DataSet.DEFAULT_BASE_FILENAME.equals(dataSet.getBaseFilename());
+  }
+
+  private static String filenameOnly(String pipelineFilename) {
+    if (StringUtils.isBlank(pipelineFilename)) {
+      return null;
+    }
+    String normalized = pipelineFilename.trim().replace('\\', '/');
+    int slash = normalized.lastIndexOf('/');
+    String file = slash >= 0 ? normalized.substring(slash + 1) : normalized;
+    return StringUtils.isBlank(file) ? null : file;
+  }
+
+  static String sanitizeToken(String value) {
+    if (value == null) {
+      return null;
+    }
+    String cleaned = UNSAFE.matcher(value.trim()).replaceAll("-");
+    cleaned = REPEATED_DASH.matcher(cleaned).replaceAll("-");
+    cleaned = EDGE_DASH.matcher(cleaned).replaceAll("");
+    return cleaned.isEmpty() ? null : cleaned;
+  }
+
+  private static boolean hasDatasetsFolder(IVariables variables) {
+    IVariables current = variables;
+    while (current != null) {
+      String value = current.getVariable(DataSet.VARIABLE_HOP_DATASETS_FOLDER);
+      if (StringUtils.isNotBlank(value)) {
+        return true;
+      }
+      current = current.getParentVariables();
+    }
+    return false;
+  }
+
+  private static String uniqueName(String base, IHopMetadataProvider 
metadataProvider) {
+    if (metadataProvider == null) {
+      return base;
+    }
+    try {
+      IHopMetadataSerializer<DataSet> serializer = 
metadataProvider.getSerializer(DataSet.class);
+      if (!serializer.exists(base)) {
+        return base;
+      }
+      int suffix = 2;
+      String candidate = base + " " + suffix;
+      while (serializer.exists(candidate) && suffix < 10000) {
+        suffix++;
+        candidate = base + " " + suffix;
+      }
+      return candidate;
+    } catch (Exception e) {
+      return base;
+    }
+  }
+}
diff --git 
a/plugins/misc/testing/src/main/java/org/apache/hop/testing/gui/TestingGuiPlugin.java
 
b/plugins/misc/testing/src/main/java/org/apache/hop/testing/gui/TestingGuiPlugin.java
index 2ee7296b6a..e2d8df47e0 100644
--- 
a/plugins/misc/testing/src/main/java/org/apache/hop/testing/gui/TestingGuiPlugin.java
+++ 
b/plugins/misc/testing/src/main/java/org/apache/hop/testing/gui/TestingGuiPlugin.java
@@ -55,6 +55,7 @@ import org.apache.hop.pipeline.PipelineMeta;
 import org.apache.hop.pipeline.transform.TransformMeta;
 import org.apache.hop.testing.DataSet;
 import org.apache.hop.testing.DataSetCsvUtil;
+import org.apache.hop.testing.DataSetDefaults;
 import org.apache.hop.testing.DataSetField;
 import org.apache.hop.testing.PipelineTweak;
 import org.apache.hop.testing.PipelineUnitTest;
@@ -747,6 +748,12 @@ public class TestingGuiPlugin {
 
     try {
       DataSet dataSet = new DataSet();
+      DataSetDefaults.apply(
+          dataSet,
+          pipelineMeta.getFilename(),
+          transformMeta.getName(),
+          variables,
+          metadataProvider);
 
       IRowMeta rowMeta = pipelineMeta.getTransformFields(variables, 
transformMeta);
       for (int i = 0; i < rowMeta.size(); i++) {
diff --git 
a/plugins/misc/testing/src/main/java/org/apache/hop/testing/xp/HopGuiDataSetCreateBeforeDialog.java
 
b/plugins/misc/testing/src/main/java/org/apache/hop/testing/xp/HopGuiDataSetCreateBeforeDialog.java
new file mode 100644
index 0000000000..52b38c72b9
--- /dev/null
+++ 
b/plugins/misc/testing/src/main/java/org/apache/hop/testing/xp/HopGuiDataSetCreateBeforeDialog.java
@@ -0,0 +1,100 @@
+/*
+ * 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.testing.xp;
+
+import java.util.List;
+import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.extension.ExtensionPoint;
+import org.apache.hop.core.extension.IExtensionPoint;
+import org.apache.hop.core.logging.ILogChannel;
+import org.apache.hop.core.variables.IVariables;
+import org.apache.hop.metadata.api.IHopMetadataProvider;
+import org.apache.hop.pipeline.PipelineMeta;
+import org.apache.hop.pipeline.transform.TransformMeta;
+import org.apache.hop.testing.DataSet;
+import org.apache.hop.testing.DataSetDefaults;
+import org.apache.hop.ui.hopgui.HopGui;
+import org.apache.hop.ui.hopgui.file.IHopFileTypeHandler;
+import org.apache.hop.ui.hopgui.file.pipeline.HopGuiPipelineGraph;
+import org.apache.hop.ui.hopgui.perspective.explorer.ExplorerPerspective;
+
+/**
+ * Suggests a name, folder and base file name before the new data set dialog 
opens. The pipeline is
+ * the one open in the explorer, including when the metadata perspective is 
active. A transform name
+ * is included only when exactly one transform is selected; the 
create-from-transform action sets
+ * the clicked transform itself before this runs.
+ */
+@ExtensionPoint(
+    id = "HopGuiDataSetCreateBeforeDialog",
+    extensionPointId = "HopGuiMetadataObjectCreateBeforeDialog",
+    description = "Suggests a name, folder and filename for a new data set")
+public class HopGuiDataSetCreateBeforeDialog implements 
IExtensionPoint<Object> {
+
+  @Override
+  public void callExtensionPoint(ILogChannel log, IVariables variables, Object 
object)
+      throws HopException {
+    if (!(object instanceof DataSet dataSet)) {
+      return;
+    }
+    try {
+      HopGui hopGui = HopGui.peekInstance();
+      PipelineMeta pipelineMeta = activeExplorerPipeline(hopGui);
+      IHopMetadataProvider metadataProvider = hopGui == null ? null : 
hopGui.getMetadataProvider();
+      DataSetDefaults.apply(
+          dataSet,
+          pipelineMeta == null ? null : pipelineMeta.getFilename(),
+          singleSelectedTransformName(pipelineMeta),
+          variables,
+          metadataProvider);
+    } catch (Exception e) {
+      log.logError("Error suggesting defaults for a new data set", e);
+    }
+  }
+
+  /**
+   * @return the transform name when exactly one transform is selected, 
otherwise null so several
+   *     selected transforms are not collapsed into an arbitrary choice
+   */
+  static String singleSelectedTransformName(PipelineMeta pipelineMeta) {
+    if (pipelineMeta == null) {
+      return null;
+    }
+    List<TransformMeta> selected = pipelineMeta.getSelectedTransforms();
+    if (selected == null || selected.size() != 1) {
+      return null;
+    }
+    TransformMeta transform = selected.get(0);
+    return transform == null ? null : transform.getName();
+  }
+
+  private static PipelineMeta activeExplorerPipeline(HopGui hopGui) {
+    if (hopGui == null || hopGui.getPerspectiveManager() == null) {
+      return null;
+    }
+    ExplorerPerspective explorer =
+        
hopGui.getPerspectiveManager().findPerspective(ExplorerPerspective.class);
+    if (explorer == null) {
+      return null;
+    }
+    IHopFileTypeHandler handler = explorer.getActiveFileTypeHandler();
+    if (handler instanceof HopGuiPipelineGraph graph) {
+      return graph.getPipelineMeta();
+    }
+    return null;
+  }
+}
diff --git 
a/plugins/misc/testing/src/main/java/org/apache/hop/ui/testing/DataSetEditor.java
 
b/plugins/misc/testing/src/main/java/org/apache/hop/ui/testing/DataSetEditor.java
index f2350a4b3c..f082cb1122 100644
--- 
a/plugins/misc/testing/src/main/java/org/apache/hop/ui/testing/DataSetEditor.java
+++ 
b/plugins/misc/testing/src/main/java/org/apache/hop/ui/testing/DataSetEditor.java
@@ -30,8 +30,10 @@ import org.apache.hop.core.vfs.HopVfs;
 import org.apache.hop.i18n.BaseMessages;
 import org.apache.hop.testing.DataSet;
 import org.apache.hop.testing.DataSetCsvUtil;
+import org.apache.hop.testing.DataSetDefaults;
 import org.apache.hop.testing.DataSetField;
 import org.apache.hop.ui.core.PropsUi;
+import org.apache.hop.ui.core.dialog.BaseDialog;
 import org.apache.hop.ui.core.dialog.ErrorDialog;
 import org.apache.hop.ui.core.dialog.MessageBox;
 import org.apache.hop.ui.core.dialog.ShowRowsDialog;
@@ -49,6 +51,7 @@ import org.eclipse.swt.layout.FormData;
 import org.eclipse.swt.widgets.Button;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
 import org.eclipse.swt.widgets.TableItem;
 import org.eclipse.swt.widgets.Text;
 
@@ -138,12 +141,23 @@ public class DataSetEditor extends 
MetadataEditor<DataSet> {
     fdlFolderName.left = new FormAttachment(0, 0);
     fdlFolderName.right = new FormAttachment(100, 0);
     wlFolderName.setLayoutData(fdlFolderName);
+
+    Button wBrowseFolder = new Button(parent, SWT.PUSH);
+    PropsUi.setLook(wBrowseFolder);
+    wBrowseFolder.setText(BaseMessages.getString(PKG, 
"DataSetDialog.Browse.Button"));
+    wBrowseFolder.setToolTipText(BaseMessages.getString(PKG, 
"DataSetDialog.Browse.Tooltip"));
+    FormData fdbFolderName = new FormData();
+    fdbFolderName.right = new FormAttachment(100, 0);
+    fdbFolderName.top = new FormAttachment(wlFolderName, margin);
+    wBrowseFolder.setLayoutData(fdbFolderName);
+    wBrowseFolder.addListener(SWT.Selection, e -> 
browseForFolder(parent.getShell()));
+
     wFolderName = new TextVar(manager.getVariables(), parent, SWT.SINGLE | 
SWT.LEFT | SWT.BORDER);
     PropsUi.setLook(wFolderName);
     FormData fdFolderName = new FormData();
     fdFolderName.top = new FormAttachment(wlFolderName, margin);
     fdFolderName.left = new FormAttachment(0, 0);
-    fdFolderName.right = new FormAttachment(100, 0);
+    fdFolderName.right = new FormAttachment(wBrowseFolder, -margin);
     wFolderName.setLayoutData(fdFolderName);
 
     // The table storing the set...
@@ -152,7 +166,7 @@ public class DataSetEditor extends MetadataEditor<DataSet> {
     PropsUi.setLook(wlBaseFilename);
     wlBaseFilename.setText(BaseMessages.getString(PKG, 
"DataSetDialog.BaseFilename.Label"));
     FormData fdlBaseFilename = new FormData();
-    fdlBaseFilename.top = new FormAttachment(wFolderName, margin);
+    fdlBaseFilename.top = new FormAttachment(wBrowseFolder, margin);
     fdlBaseFilename.left = new FormAttachment(0, 0);
     fdlBaseFilename.right = new FormAttachment(100, 0);
     wlBaseFilename.setLayoutData(fdlBaseFilename);
@@ -376,6 +390,25 @@ public class DataSetEditor extends MetadataEditor<DataSet> 
{
     super.save();
   }
 
+  private void browseForFolder(Shell shell) {
+    String path = wFolderName.getText();
+    if (StringUtil.isEmpty(path)) {
+      String configured = 
DataSetDefaults.datasetsFolderExpression(manager.getVariables());
+      if (configured != null) {
+        path = configured;
+      }
+    }
+    String directory =
+        BaseDialog.presentDirectoryDialog(
+            shell,
+            path,
+            BaseMessages.getString(PKG, "DataSetDialog.Browse.Message"),
+            manager.getVariables());
+    if (directory != null) {
+      wFolderName.setText(directory);
+    }
+  }
+
   private void verifySettings() throws HopException {
     try {
       if (StringUtil.isEmpty(wBaseFilename.getText())) {
diff --git 
a/plugins/misc/testing/src/main/resources/org/apache/hop/ui/testing/messages/messages_en_US.properties
 
b/plugins/misc/testing/src/main/resources/org/apache/hop/ui/testing/messages/messages_en_US.properties
index 16b09c895f..54b3cfd8fd 100644
--- 
a/plugins/misc/testing/src/main/resources/org/apache/hop/ui/testing/messages/messages_en_US.properties
+++ 
b/plugins/misc/testing/src/main/resources/org/apache/hop/ui/testing/messages/messages_en_US.properties
@@ -18,6 +18,9 @@
 #
 
 DataSetDialog.BaseFilename.Label=Base file name
+DataSetDialog.Browse.Button=Browse...
+DataSetDialog.Browse.Message=Select the data set folder
+DataSetDialog.Browse.Tooltip=Open the folder above so you can choose it or a 
subfolder. When that folder is empty this starts in '${HOP_DATASETS_FOLDER}' if 
that variable is set.
 DataSetDialog.ColumnInfo.Comment=Comment
 DataSetDialog.ColumnInfo.FieldFormat=Format
 DataSetDialog.ColumnInfo.FieldLength=Length
diff --git 
a/plugins/misc/testing/src/test/java/org/apache/hop/testing/DataSetDefaultsTest.java
 
b/plugins/misc/testing/src/test/java/org/apache/hop/testing/DataSetDefaultsTest.java
new file mode 100644
index 0000000000..f7973ee4ab
--- /dev/null
+++ 
b/plugins/misc/testing/src/test/java/org/apache/hop/testing/DataSetDefaultsTest.java
@@ -0,0 +1,176 @@
+/*
+ * 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.testing;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import org.apache.hop.core.variables.Variables;
+import org.apache.hop.metadata.serializer.memory.MemoryMetadataProvider;
+import org.junit.jupiter.api.Test;
+
+class DataSetDefaultsTest {
+
+  @Test
+  void suggestsNameFolderAndFilenameFromPipelineAndTransform() throws 
Exception {
+    Variables variables = new Variables();
+    variables.setVariable(DataSet.VARIABLE_HOP_DATASETS_FOLDER, 
"${PROJECT_HOME}/datasets");
+
+    DataSet dataSet = new DataSet();
+    DataSetDefaults.apply(
+        dataSet, "${PROJECT_HOME}/pipelines/load-orders.hpl", "Table input", 
variables, null);
+
+    assertEquals("ds-load-orders-Table input", dataSet.getName());
+    assertEquals("${HOP_DATASETS_FOLDER}", dataSet.getFolderName());
+    assertEquals("ds-load-orders-Table input.csv", dataSet.getBaseFilename());
+  }
+
+  @Test
+  void leavesFolderEmptyWhenDatasetsFolderIsNotConfigured() {
+    DataSet dataSet = new DataSet();
+    DataSetDefaults.apply(dataSet, "C:\\proj\\pipe.hpl", "Check", new 
Variables(), null);
+
+    assertEquals("ds-pipe-Check", dataSet.getName());
+    assertNull(dataSet.getFolderName());
+    assertEquals("ds-pipe-Check.csv", dataSet.getBaseFilename());
+  }
+
+  @Test
+  void readsDatasetsFolderFromParentVariableSpace() {
+    Variables parent = new Variables();
+    parent.setVariable(DataSet.VARIABLE_HOP_DATASETS_FOLDER, "/data/sets");
+    Variables child = new Variables();
+    child.setParentVariables(parent);
+
+    DataSet dataSet = new DataSet();
+    DataSetDefaults.apply(dataSet, "/tmp/pipe/load.hpl", "Check", child, null);
+
+    assertEquals("${HOP_DATASETS_FOLDER}", dataSet.getFolderName());
+  }
+
+  @Test
+  void blankDatasetsFolderLeavesFolderEmpty() {
+    Variables variables = new Variables();
+    variables.setVariable(DataSet.VARIABLE_HOP_DATASETS_FOLDER, "  ");
+
+    DataSet dataSet = new DataSet();
+    DataSetDefaults.apply(dataSet, "/tmp/pipe/load.hpl", null, variables, 
null);
+
+    assertEquals("ds-load", dataSet.getName());
+    assertNull(dataSet.getFolderName());
+    assertEquals("ds-load.csv", dataSet.getBaseFilename());
+  }
+
+  @Test
+  void sanitizesPathCharactersInTheTransformName() {
+    DataSet dataSet = new DataSet();
+    DataSetDefaults.apply(dataSet, "dir/a:b.hpl", "in/out", new Variables(), 
null);
+
+    assertEquals("ds-a-b-in-out", dataSet.getName());
+    assertNull(dataSet.getFolderName());
+    assertEquals("ds-a-b-in-out.csv", dataSet.getBaseFilename());
+  }
+
+  @Test
+  void suggestsFromTransformOnlyWhenThePipelineHasNoFilename() {
+    DataSet dataSet = new DataSet();
+    DataSetDefaults.apply(dataSet, null, "Check", new Variables(), null);
+
+    assertEquals("ds-Check", dataSet.getName());
+    assertNull(dataSet.getFolderName());
+    assertEquals("ds-Check.csv", dataSet.getBaseFilename());
+  }
+
+  @Test
+  void leavesNameEmptyWhenPipelineAndTransformAreUnknown() {
+    Variables variables = new Variables();
+    variables.setVariable(DataSet.VARIABLE_HOP_DATASETS_FOLDER, "/data/sets");
+
+    DataSet dataSet = new DataSet();
+    DataSetDefaults.apply(dataSet, "   ", "  ", variables, null);
+
+    assertNull(dataSet.getName());
+    assertEquals("${HOP_DATASETS_FOLDER}", dataSet.getFolderName());
+    assertEquals(DataSet.DEFAULT_BASE_FILENAME, dataSet.getBaseFilename());
+  }
+
+  @Test
+  void doesNotReplaceValuesTheUserAlreadySet() {
+    Variables variables = new Variables();
+    variables.setVariable(DataSet.VARIABLE_HOP_DATASETS_FOLDER, "/data/sets");
+
+    DataSet dataSet = new DataSet();
+    dataSet.setName("kept");
+    dataSet.setFolderName("kept-folder");
+    dataSet.setBaseFilename("kept.csv");
+    DataSetDefaults.apply(dataSet, "/tmp/pipe.hpl", "Step", variables, null);
+
+    assertEquals("kept", dataSet.getName());
+    assertEquals("kept-folder", dataSet.getFolderName());
+    assertEquals("kept.csv", dataSet.getBaseFilename());
+  }
+
+  @Test
+  void replacesPlaceholderFilenameWhenTheNameIsAlreadySet() {
+    DataSet dataSet = new DataSet();
+    dataSet.setName("golden");
+    dataSet.setFolderName("folder");
+
+    DataSetDefaults.apply(dataSet, "/tmp/other.hpl", "Other", new Variables(), 
null);
+
+    assertEquals("golden", dataSet.getName());
+    assertEquals("folder", dataSet.getFolderName());
+    assertEquals("golden.csv", dataSet.getBaseFilename());
+  }
+
+  @Test
+  void secondApplyDoesNotReplaceTheSuggestion() {
+    Variables variables = new Variables();
+    variables.setVariable(DataSet.VARIABLE_HOP_DATASETS_FOLDER, "/data/sets");
+    DataSet dataSet = new DataSet();
+
+    DataSetDefaults.apply(dataSet, "/tmp/load.hpl", "Check", variables, null);
+    DataSetDefaults.apply(dataSet, "/tmp/other.hpl", "Other", variables, null);
+
+    assertEquals("ds-load-Check", dataSet.getName());
+    assertEquals("${HOP_DATASETS_FOLDER}", dataSet.getFolderName());
+    assertEquals("ds-load-Check.csv", dataSet.getBaseFilename());
+  }
+
+  @Test
+  void avoidsAnExistingDataSetName() throws Exception {
+    MemoryMetadataProvider provider = new MemoryMetadataProvider();
+    DataSet existing = new DataSet();
+    existing.setName("ds-load-orders-Check");
+    provider.getSerializer(DataSet.class).save(existing);
+    DataSet second = new DataSet();
+    second.setName("ds-load-orders-Check 2");
+    provider.getSerializer(DataSet.class).save(second);
+
+    DataSet dataSet = new DataSet();
+    DataSetDefaults.apply(dataSet, "/p/load-orders.hpl", "Check", new 
Variables(), provider);
+
+    assertEquals("ds-load-orders-Check 3", dataSet.getName());
+    assertEquals("ds-load-orders-Check 3.csv", dataSet.getBaseFilename());
+  }
+
+  @Test
+  void nullDataSetIsIgnored() {
+    DataSetDefaults.apply(null, "/tmp/pipe.hpl", "Check", new Variables(), 
null);
+  }
+}
diff --git 
a/plugins/misc/testing/src/test/java/org/apache/hop/testing/xp/HopGuiDataSetCreateBeforeDialogTest.java
 
b/plugins/misc/testing/src/test/java/org/apache/hop/testing/xp/HopGuiDataSetCreateBeforeDialogTest.java
new file mode 100644
index 0000000000..35e51930e8
--- /dev/null
+++ 
b/plugins/misc/testing/src/test/java/org/apache/hop/testing/xp/HopGuiDataSetCreateBeforeDialogTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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.testing.xp;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import org.apache.hop.pipeline.PipelineMeta;
+import org.apache.hop.pipeline.transform.TransformMeta;
+import org.junit.jupiter.api.Test;
+
+class HopGuiDataSetCreateBeforeDialogTest {
+
+  @Test
+  void singleSelectedTransformNameUsesTheOnlySelection() {
+    PipelineMeta pipelineMeta = new PipelineMeta();
+    pipelineMeta.addTransform(transform("Ignored", false));
+    pipelineMeta.addTransform(transform("Check", true));
+
+    assertEquals(
+        "Check", 
HopGuiDataSetCreateBeforeDialog.singleSelectedTransformName(pipelineMeta));
+  }
+
+  @Test
+  void singleSelectedTransformNameIgnoresZeroOrSeveralSelections() {
+    
assertNull(HopGuiDataSetCreateBeforeDialog.singleSelectedTransformName(null));
+
+    PipelineMeta none = new PipelineMeta();
+    none.addTransform(transform("Check", false));
+    
assertNull(HopGuiDataSetCreateBeforeDialog.singleSelectedTransformName(none));
+
+    PipelineMeta several = new PipelineMeta();
+    several.addTransform(transform("One", true));
+    several.addTransform(transform("Two", true));
+    
assertNull(HopGuiDataSetCreateBeforeDialog.singleSelectedTransformName(several));
+  }
+
+  private static TransformMeta transform(String name, boolean selected) {
+    TransformMeta transformMeta = new TransformMeta(name, null);
+    if (selected) {
+      transformMeta.flipSelected();
+    }
+    return transformMeta;
+  }
+}

Reply via email to