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

mattcasters pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git


The following commit(s) were added to refs/heads/main by this push:
     new 5f05555662 Fixes #3814: set a run configuration on imported Pipeline 
and Workflow actions (#8641)
5f05555662 is described below

commit 5f0555566294e6d2d15ff8fac2d07ad7d3a8b79d
Author: Bart Maertens <[email protected]>
AuthorDate: Tue Sep 29 11:33:15 2026 +0200

    Fixes #3814: set a run configuration on imported Pipeline and Workflow 
actions (#8641)
    
    PDI only began writing the run_configuration element for Job and
    Transformation entries in version 8, writes it empty when one was never
    selected, and older files have no element at all. The importer replaced the
    element only where it already held a name, so those actions came out of the
    import with none and aborted with "You need to specify a workflow run
    configuration". The only way out was to open every action in the Hop GUI
    and press OK.
    
    Treat an absent element and an empty one the same way: neither carries a
    name to preserve, so both get the run configuration given on the command
    line or in the import dialog, falling back to the 'local' run configuration
    every Hop project is created with. An action that already names one keeps
    that name, as of #8516. Mapping and Metadata Injection transforms are left
    alone because they fall back to a local engine on their own.
    
    Covered by unit tests in KettleImportTest and by integration tests 0004 and
    0005 in integration-tests/import, the second of which runs the imported
    workflow to prove all three actions execute.
---
 .gitignore                                         |   2 +
 .../main/java/org/apache/hop/imp/HopImport.java    |   3 +-
 integration-tests/import/README.md                 |   7 +
 ...0004-import-adds-missing-run-configurations.hwf | 174 +++++++++++++++++++
 ...flow-runs-without-source-run-configurations.hwf | 187 +++++++++++++++++++++
 .../pdi-without-run-configuration/parent.kjb       |  83 +++++++++
 .../pdi-without-run-configuration/sub/child.ktr    |  35 ++++
 .../pdi-without-run-configuration/sub/nested.kjb   |  35 ++++
 .../apache/hop/imports/kettle/KettleImport.java    |  47 +++++-
 .../hop/imports/kettle/KettleImportTest.java       | 100 +++++++++++
 10 files changed, 670 insertions(+), 3 deletions(-)

diff --git a/.gitignore b/.gitignore
index bc10e7338b..7e674ee2ea 100644
--- a/.gitignore
+++ b/.gitignore
@@ -73,6 +73,8 @@ integration-tests/sftp/output/
 # The PGP tests build a throwaway keyring and the files they sign under output/
 integration-tests/pgp/output/
 integration-tests/lint/output/
+# hop-import writes each test's imported project, and 0005 its hop-run log, 
under output/
+integration-tests/import/output/
 integration-tests/spreadsheet/files/sample-file-append.xlsx
 integration-tests/spreadsheet/files/sample-file-append-test.xlsx
 integration-tests/spark-native/output
diff --git a/engine/src/main/java/org/apache/hop/imp/HopImport.java 
b/engine/src/main/java/org/apache/hop/imp/HopImport.java
index 5978cf4b62..ec1e9dda9c 100644
--- a/engine/src/main/java/org/apache/hop/imp/HopImport.java
+++ b/engine/src/main/java/org/apache/hop/imp/HopImport.java
@@ -313,7 +313,8 @@ public class HopImport implements Runnable, 
IHasHopMetadataProvider, IHopCommand
               + subject
               + " run configuration was specified. Imported "
               + subject
-              + "s keep the run configuration named in the source file, which 
can be empty.");
+              + "s keep the run configuration named in the source file. 
Pipeline and Workflow"
+              + " actions that name none fall back to 'local'.");
     }
   }
 
diff --git a/integration-tests/import/README.md 
b/integration-tests/import/README.md
index 15e24d4a3f..5508bd7f78 100644
--- a/integration-tests/import/README.md
+++ b/integration-tests/import/README.md
@@ -10,9 +10,16 @@ front-end parity work in [#8516]; the import dialog half is 
covered by
 | `main-0001-import-preserves-run-configurations` | Without 
`--pipeline-run-configuration` / `--workflow-run-configuration`, the run 
configuration names in the PDI source survive the import instead of being 
blanked ([#3814], [#8516]). Also checks that sub-folders are imported by 
default. |
 | `main-0002-import-applies-default-run-configurations` | With both options, 
every imported pipeline and workflow gets the given run configuration. |
 | `main-0003-import-registers-a-project` | `--project` registers the target 
folder as a Hop project, and a second run resolves that project's home folder 
without a `-o`. An import that fails on its options registers nothing. |
+| `main-0004-import-adds-missing-run-configurations` | A PDI entry whose 
`run_configuration` element is absent, or present but empty, gets the run 
configuration given on the command line ([#3814]). |
+| `main-0005-imported-workflow-runs-without-source-run-configurations` | The 
same source without defaults falls back to `local`, and the imported workflow 
is then executed with `hop-run` to prove all three actions run ([#3814]). |
 
 `subject/pdi` holds the PDI job, transformation and nested job the tests 
import. Both entries of
 `parent.kjb` name a run configuration, which is what tests 0001 and 0002 
assert on.
+`subject/pdi-without-run-configuration` is the same job with three entries 
that name no run
+configuration: two with the `run_configuration` element left out, the way PDI 
wrote job entries
+before version 8, and one with an empty element, the way PDI 8 and later write 
it when none was
+ever selected. Neither carries a name to preserve, so both get filled. Tests 
0004 and 0005 import
+that one.
 
 Each test imports into its own folder under `output/`, so they can run in any 
order. Test 0003
 points `HOP_CONFIG_FOLDER` at a throwaway folder as well: registering a 
project writes
diff --git 
a/integration-tests/import/main-0004-import-adds-missing-run-configurations.hwf 
b/integration-tests/import/main-0004-import-adds-missing-run-configurations.hwf
new file mode 100644
index 0000000000..bb5adaf16c
--- /dev/null
+++ 
b/integration-tests/import/main-0004-import-adds-missing-run-configurations.hwf
@@ -0,0 +1,174 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+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.
+
+-->
+<workflow>
+  <name>0004-import-adds-missing-run-configurations</name>
+  <name_sync_with_filename>Y</name_sync_with_filename>
+  <description>hop-import gives a Pipeline or Workflow action the default run 
configuration when the PDI source named none</description>
+  <extended_description/>
+  <workflow_version/>
+  <created_user>-</created_user>
+  <created_date>2026/09/26 16:00:00.000</created_date>
+  <modified_user>-</modified_user>
+  <modified_date>2026/09/26 16:00:00.000</modified_date>
+  <parameters>
+    </parameters>
+  <actions>
+    <action>
+      <name>Start</name>
+      <description/>
+      <type>SPECIAL</type>
+      <attributes/>
+      <DayOfMonth>1</DayOfMonth>
+      <hour>12</hour>
+      <intervalMinutes>60</intervalMinutes>
+      <intervalSeconds>0</intervalSeconds>
+      <minutes>0</minutes>
+      <repeat>N</repeat>
+      <schedulerType>0</schedulerType>
+      <weekDay>1</weekDay>
+      <parallel>N</parallel>
+      <xloc>112</xloc>
+      <yloc>96</yloc>
+      <attributes_hac/>
+    </action>
+    <action>
+      <name>Import with run configurations</name>
+      <description/>
+      <type>SHELL</type>
+      <attributes/>
+      <filename/>
+      <work_directory/>
+      <arg_from_previous>N</arg_from_previous>
+      <exec_per_row>N</exec_per_row>
+      <set_logfile>N</set_logfile>
+      <logfile/>
+      <set_append_logfile>N</set_append_logfile>
+      <logext/>
+      <add_date>N</add_date>
+      <add_time>N</add_time>
+      <insertScript>Y</insertScript>
+      <script>set -e
+SRC="${PROJECT_HOME}/subject/pdi-without-run-configuration"
+OUT="${PROJECT_HOME}/output/0004-added"
+
+rm -rf "$OUT"
+mkdir -p "$OUT"
+
+sh hop-import.sh -i "$SRC" -o "$OUT" \
+  --pipeline-run-configuration 'Imported pipelines' \
+  --workflow-run-configuration 'Imported workflows'
+
+# Two of the three PDI entries carry no run_configuration element at all, so 
the importer has to
+# add one. It only replaced elements that were already there, which left those 
actions unable to
+# run (#3814). The third entry's element is present but empty; a default was 
always applied over
+# that one, so main-0005 is what covers it, on the path where no default is 
given.
+test "$(grep -c '&lt;run_configuration&gt;Imported 
pipelines&lt;/run_configuration&gt;' "$OUT/parent.hwf")" = 2
+test "$(grep -c '&lt;run_configuration&gt;Imported 
workflows&lt;/run_configuration&gt;' "$OUT/parent.hwf")" = 1
+
+# Filling the empty element must not leave a second one behind.
+test "$(grep -c '&lt;run_configuration&gt;' "$OUT/parent.hwf")" = 3</script>
+      <loglevel>Basic</loglevel>
+      <parallel>N</parallel>
+      <xloc>288</xloc>
+      <yloc>96</yloc>
+      <attributes_hac/>
+    </action>
+    <action>
+      <name>Success</name>
+      <description/>
+      <type>SUCCESS</type>
+      <attributes/>
+      <parallel>N</parallel>
+      <xloc>464</xloc>
+      <yloc>96</yloc>
+      <attributes_hac/>
+    </action>
+    <action>
+      <name>Failure: the default run configurations were not added</name>
+      <description/>
+      <type>ABORT</type>
+      <attributes/>
+      <always_log_rows>N</always_log_rows>
+      <message>Failure: the default run configurations were not added</message>
+      <parallel>N</parallel>
+      <xloc>464</xloc>
+      <yloc>192</yloc>
+      <attributes_hac/>
+    </action>
+  </actions>
+  <hops>
+    <hop>
+      <from>Start</from>
+      <to>Import with run configurations</to>
+      <enabled>Y</enabled>
+      <evaluation>Y</evaluation>
+      <unconditional>Y</unconditional>
+    </hop>
+    <hop>
+      <from>Import with run configurations</from>
+      <to>Success</to>
+      <enabled>Y</enabled>
+      <evaluation>Y</evaluation>
+      <unconditional>N</unconditional>
+    </hop>
+    <hop>
+      <from>Import with run configurations</from>
+      <to>Failure: the default run configurations were not added</to>
+      <enabled>Y</enabled>
+      <evaluation>N</evaluation>
+      <unconditional>N</unconditional>
+    </hop>
+  </hops>
+  <notepads>
+    <notepad>
+      <backgroundcolorblue>210</backgroundcolorblue>
+      <backgroundcolorgreen>136</backgroundcolorgreen>
+      <backgroundcolorred>15</backgroundcolorred>
+      <bordercolorblue>250</bordercolorblue>
+      <bordercolorgreen>231</bordercolorgreen>
+      <bordercolorred>200</bordercolorred>
+      <fontbold>N</fontbold>
+      <fontcolorblue>250</fontcolorblue>
+      <fontcolorgreen>231</fontcolorgreen>
+      <fontcolorred>200</fontcolorred>
+      <fontitalic>N</fontitalic>
+      <fontname>Noto Sans</fontname>
+      <fontsize>10</fontsize>
+      <height>132</height>
+      <xloc>80</xloc>
+      <yloc>256</yloc>
+      <note>The PDI job under subject/pdi-without-run-configuration has three 
entries that name no
+run configuration. Two carry no run_configuration element at all, the way PDI 
wrote job entries
+before version 8, and the third carries an empty one, the way PDI 8 and later 
write it when none
+was ever selected. Neither carries a name to preserve.
+
+hop-import is given both --pipeline-run-configuration and 
--workflow-run-configuration here, so
+all three actions have to come out of the import naming them, and the empty 
element has to be
+filled rather than duplicated. The importer used to add nothing where the 
element was absent, so
+those actions failed at execution time with
+"You need to specify a workflow run configuration to execute this workflow" 
(issue #3814).
+
+The empty element was already overwritten whenever a default was given, so the 
case where nothing
+fills it is the one without defaults: main-0005 covers that.</note>
+      <width>620</width>
+    </notepad>
+  </notepads>
+  <attributes/>
+</workflow>
diff --git 
a/integration-tests/import/main-0005-imported-workflow-runs-without-source-run-configurations.hwf
 
b/integration-tests/import/main-0005-imported-workflow-runs-without-source-run-configurations.hwf
new file mode 100644
index 0000000000..cd3ccf6b34
--- /dev/null
+++ 
b/integration-tests/import/main-0005-imported-workflow-runs-without-source-run-configurations.hwf
@@ -0,0 +1,187 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+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.
+
+-->
+<workflow>
+  <name>0005-imported-workflow-runs-without-source-run-configurations</name>
+  <name_sync_with_filename>Y</name_sync_with_filename>
+  <description>A PDI job whose entries name no run configuration is imported 
and then actually runs</description>
+  <extended_description/>
+  <workflow_version/>
+  <created_user>-</created_user>
+  <created_date>2026/09/26 16:00:00.000</created_date>
+  <modified_user>-</modified_user>
+  <modified_date>2026/09/26 16:00:00.000</modified_date>
+  <parameters>
+    </parameters>
+  <actions>
+    <action>
+      <name>Start</name>
+      <description/>
+      <type>SPECIAL</type>
+      <attributes/>
+      <DayOfMonth>1</DayOfMonth>
+      <hour>12</hour>
+      <intervalMinutes>60</intervalMinutes>
+      <intervalSeconds>0</intervalSeconds>
+      <minutes>0</minutes>
+      <repeat>N</repeat>
+      <schedulerType>0</schedulerType>
+      <weekDay>1</weekDay>
+      <parallel>N</parallel>
+      <xloc>112</xloc>
+      <yloc>96</yloc>
+      <attributes_hac/>
+    </action>
+    <action>
+      <name>Import and run the workflow</name>
+      <description/>
+      <type>SHELL</type>
+      <attributes/>
+      <filename/>
+      <work_directory/>
+      <arg_from_previous>N</arg_from_previous>
+      <exec_per_row>N</exec_per_row>
+      <set_logfile>N</set_logfile>
+      <logfile/>
+      <set_append_logfile>N</set_append_logfile>
+      <logext/>
+      <add_date>N</add_date>
+      <add_time>N</add_time>
+      <insertScript>Y</insertScript>
+      <script>set -e
+SRC="${PROJECT_HOME}/subject/pdi-without-run-configuration"
+OUT="${PROJECT_HOME}/output/0005-local-fallback"
+LOG="${PROJECT_HOME}/output/0005-local-fallback.log"
+
+rm -rf "$OUT" "$LOG"
+mkdir -p "$OUT"
+
+# No --pipeline-run-configuration / --workflow-run-configuration: there is 
nothing in the source
+# to fall back on either, so the importer has to write the 'local' run 
configuration every Hop
+# project is created with. An empty one leaves the actions unable to run.
+sh hop-import.sh -i "$SRC" -o "$OUT"
+
+test "$(grep -c '&lt;run_configuration&gt;local&lt;/run_configuration&gt;' 
"$OUT/parent.hwf")" = 3
+
+# One of the three source entries already had an empty run_configuration 
element. Filling it must
+# not leave a second one behind.
+test "$(grep -c '&lt;run_configuration&gt;' "$OUT/parent.hwf")" = 3
+
+# The symptom from issue #3814: running the imported workflow aborted on every 
Pipeline and
+# Workflow action. Run it for real and require both actions to execute.
+if ! sh hop-run.sh -j 'default' -r 'local' -f "$OUT/parent.hwf" -l Basic &gt; 
"$LOG" 2&gt;&amp;1; then
+  echo "The imported workflow failed to run:"
+  cat "$LOG"
+  exit 1
+fi
+if grep -q 'need to specify a .* run configuration' "$LOG"; then
+  echo "An action of the imported workflow has no run configuration:"
+  cat "$LOG"
+  exit 1
+fi
+grep -q 'Run the child transformation' "$LOG"
+grep -q 'Run the nested job' "$LOG"
+grep -q 'Run the child transformation again' "$LOG"</script>
+      <loglevel>Basic</loglevel>
+      <parallel>N</parallel>
+      <xloc>288</xloc>
+      <yloc>96</yloc>
+      <attributes_hac/>
+    </action>
+    <action>
+      <name>Success</name>
+      <description/>
+      <type>SUCCESS</type>
+      <attributes/>
+      <parallel>N</parallel>
+      <xloc>464</xloc>
+      <yloc>96</yloc>
+      <attributes_hac/>
+    </action>
+    <action>
+      <name>Failure: the imported workflow could not run</name>
+      <description/>
+      <type>ABORT</type>
+      <attributes/>
+      <always_log_rows>N</always_log_rows>
+      <message>Failure: the imported workflow could not run</message>
+      <parallel>N</parallel>
+      <xloc>464</xloc>
+      <yloc>192</yloc>
+      <attributes_hac/>
+    </action>
+  </actions>
+  <hops>
+    <hop>
+      <from>Start</from>
+      <to>Import and run the workflow</to>
+      <enabled>Y</enabled>
+      <evaluation>Y</evaluation>
+      <unconditional>Y</unconditional>
+    </hop>
+    <hop>
+      <from>Import and run the workflow</from>
+      <to>Success</to>
+      <enabled>Y</enabled>
+      <evaluation>Y</evaluation>
+      <unconditional>N</unconditional>
+    </hop>
+    <hop>
+      <from>Import and run the workflow</from>
+      <to>Failure: the imported workflow could not run</to>
+      <enabled>Y</enabled>
+      <evaluation>N</evaluation>
+      <unconditional>N</unconditional>
+    </hop>
+  </hops>
+  <notepads>
+    <notepad>
+      <backgroundcolorblue>210</backgroundcolorblue>
+      <backgroundcolorgreen>136</backgroundcolorgreen>
+      <backgroundcolorred>15</backgroundcolorred>
+      <bordercolorblue>250</bordercolorblue>
+      <bordercolorgreen>231</bordercolorgreen>
+      <bordercolorred>200</bordercolorred>
+      <fontbold>N</fontbold>
+      <fontcolorblue>250</fontcolorblue>
+      <fontcolorgreen>231</fontcolorgreen>
+      <fontcolorred>200</fontcolorred>
+      <fontitalic>N</fontitalic>
+      <fontname>Noto Sans</fontname>
+      <fontsize>10</fontsize>
+      <height>148</height>
+      <xloc>80</xloc>
+      <yloc>256</yloc>
+      <note>This is issue #3814 end to end. The PDI job has three entries that 
name no run
+configuration - two with no run_configuration element at all, one with an 
empty element - and
+hop-import is run without defaults.
+
+There is nothing to carry over, so the importer writes the 'local' run 
configuration that every
+Hop project ships with. The test then runs the imported workflow with hop-run 
and requires all
+three actions to execute. Before the fix they aborted with
+"You need to specify a workflow run configuration to execute this workflow",
+and the only way out was to open every action in the Hop GUI and press OK.
+
+An entry that already names a run configuration keeps that name instead: see
+main-0001-import-preserves-run-configurations.</note>
+      <width>620</width>
+    </notepad>
+  </notepads>
+  <attributes/>
+</workflow>
diff --git 
a/integration-tests/import/subject/pdi-without-run-configuration/parent.kjb 
b/integration-tests/import/subject/pdi-without-run-configuration/parent.kjb
new file mode 100644
index 0000000000..9d9ae671f3
--- /dev/null
+++ b/integration-tests/import/subject/pdi-without-run-configuration/parent.kjb
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+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.
+
+-->
+<job>
+  <name>parent</name>
+  <description/>
+  <job_version/>
+  <parameters/>
+  <entries>
+    <entry>
+      <name>START</name>
+      <type>SPECIAL</type>
+      <start>Y</start>
+      <dummy>N</dummy>
+      <repeat>N</repeat>
+      <xloc>96</xloc>
+      <yloc>96</yloc>
+    </entry>
+    <entry>
+      <name>Run the child transformation</name>
+      <type>TRANS</type>
+      <specification_method>filename</specification_method>
+      <filename>${Internal.Entry.Current.Directory}/sub/child.ktr</filename>
+      <xloc>240</xloc>
+      <yloc>96</yloc>
+    </entry>
+    <entry>
+      <name>Run the nested job</name>
+      <type>JOB</type>
+      <specification_method>filename</specification_method>
+      <filename>${Internal.Entry.Current.Directory}/sub/nested.kjb</filename>
+      <xloc>400</xloc>
+      <yloc>96</yloc>
+    </entry>
+    <entry>
+      <name>Run the child transformation again</name>
+      <type>TRANS</type>
+      <specification_method>filename</specification_method>
+      <filename>${Internal.Entry.Current.Directory}/sub/child.ktr</filename>
+      <run_configuration/>
+      <xloc>560</xloc>
+      <yloc>96</yloc>
+    </entry>
+  </entries>
+  <hops>
+    <hop>
+      <from>START</from>
+      <to>Run the child transformation</to>
+      <enabled>Y</enabled>
+      <unconditional>Y</unconditional>
+    </hop>
+    <hop>
+      <from>Run the child transformation</from>
+      <to>Run the nested job</to>
+      <enabled>Y</enabled>
+      <unconditional>N</unconditional>
+      <evaluation>Y</evaluation>
+    </hop>
+    <hop>
+      <from>Run the nested job</from>
+      <to>Run the child transformation again</to>
+      <enabled>Y</enabled>
+      <unconditional>N</unconditional>
+      <evaluation>Y</evaluation>
+    </hop>
+  </hops>
+</job>
diff --git 
a/integration-tests/import/subject/pdi-without-run-configuration/sub/child.ktr 
b/integration-tests/import/subject/pdi-without-run-configuration/sub/child.ktr
new file mode 100644
index 0000000000..3e2e55c64d
--- /dev/null
+++ 
b/integration-tests/import/subject/pdi-without-run-configuration/sub/child.ktr
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+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.
+
+-->
+<transformation>
+  <info>
+    <name>child</name>
+    <description/>
+  </info>
+  <step>
+    <name>Generate rows</name>
+    <type>RowGenerator</type>
+    <limit>1</limit>
+    <fields/>
+    <GUI>
+      <xloc>96</xloc>
+      <yloc>96</yloc>
+    </GUI>
+  </step>
+</transformation>
diff --git 
a/integration-tests/import/subject/pdi-without-run-configuration/sub/nested.kjb 
b/integration-tests/import/subject/pdi-without-run-configuration/sub/nested.kjb
new file mode 100644
index 0000000000..09be1de993
--- /dev/null
+++ 
b/integration-tests/import/subject/pdi-without-run-configuration/sub/nested.kjb
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+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.
+
+-->
+<job>
+  <name>nested</name>
+  <description/>
+  <entries>
+    <entry>
+      <name>START</name>
+      <type>SPECIAL</type>
+      <start>Y</start>
+      <dummy>N</dummy>
+      <repeat>N</repeat>
+      <xloc>96</xloc>
+      <yloc>96</yloc>
+    </entry>
+  </entries>
+  <hops/>
+</job>
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 ae4610a96e..a7e3dc7984 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
@@ -87,6 +87,9 @@ public class KettleImport extends HopImportBase implements 
IHopImport {
   private static final String TRANS_EXECUTOR_TYPE = "TransExecutor";
   private static final String SFTP_CONNECTION_METADATA_KEY = "sftp-connection";
 
+  /** The run configuration every Hop project is created with. */
+  private static final String DEFAULT_RUN_CONFIGURATION = "local";
+
   /** The elements of a Kettle SFTPPut step which describe the server, not the 
upload itself. */
   private static final List<String> SFTP_CONNECTION_TAGS =
       List.of(
@@ -787,6 +790,46 @@ public class KettleImport extends HopImportBase implements 
IHopImport {
     child.setTextContent(value);
   }
 
+  /**
+   * A Pipeline or Workflow action without a run configuration name refuses to 
run: both actions
+   * throw "You need to specify a run configuration" when the name is empty, 
and neither falls back
+   * to the parent's engine the way a Mapping transform does. Kettle only 
started writing the {@code
+   * run_configuration} element in PDI 8, writes it empty when one was never 
selected, and later
+   * versions still leave it out entirely, so fill it in at import time 
(#3814).
+   *
+   * <p>An element that is absent and one that is present but empty are the 
same thing here: neither
+   * carries a name to preserve, so both get the default. An element that 
names a run configuration
+   * is left to the main loop in {@link #processNode}, which keeps that name 
unless a default was
+   * configured.
+   *
+   * @param entryNode the Kettle {@code entry} node being imported
+   * @param entryType the type of the entry, only JOB and TRANS are handled
+   */
+  private void addMissingRunConfiguration(Document doc, Node entryNode, 
EntryType entryType) {
+    if (entryType != EntryType.JOB && entryType != EntryType.TRANS) {
+      return;
+    }
+    if (StringUtils.isNotBlank(getChildText(entryNode, "run_configuration"))) {
+      return;
+    }
+    // Reuses the element when the source wrote an empty one, so this never 
adds a second.
+    setChildElement(doc, entryNode, "run_configuration", 
defaultActionRunConfiguration(entryType));
+  }
+
+  /**
+   * The run configuration to give a Pipeline or Workflow action that has 
none. Falls back to the
+   * {@code local} run configuration every Hop project is created with, 
because an empty name leaves
+   * the action unable to execute. An action that already names one keeps that 
name when no default
+   * is configured, so this fallback only applies to a missing or empty 
element.
+   */
+  private String defaultActionRunConfiguration(EntryType entryType) {
+    String runConfiguration =
+        entryType == EntryType.JOB
+            ? defaultWorkflowRunConfiguration
+            : defaultPipelineRunConfiguration;
+    return StringUtils.isNotEmpty(runConfiguration) ? runConfiguration : 
DEFAULT_RUN_CONFIGURATION;
+  }
+
   private void processNode(Document doc, Node node, EntryType entryType, int 
depth) {
     Node nodeToProcess = node;
     NodeList nodeList = nodeToProcess.getChildNodes();
@@ -850,6 +893,7 @@ public class KettleImport extends HopImportBase implements 
IHopImport {
               }
             }
           }
+          addMissingRunConfiguration(doc, currentNode, entryType);
         }
 
         if (currentNode.getNodeName().equals("step")) {
@@ -1175,9 +1219,8 @@ public class KettleImport extends HopImportBase 
implements IHopImport {
         filenameNode = childNode;
       }
 
-      // hard coded local run configuration for now
       if (childNode.getNodeName().equals("run_configuration")) {
-        childNode.setTextContent("local");
+        childNode.setTextContent(DEFAULT_RUN_CONFIGURATION);
       }
       if (childNode.getNodeName().equals("jobname")
           || childNode.getNodeName().equals("transname")) {
diff --git 
a/plugins/misc/import/src/test/java/org/apache/hop/imports/kettle/KettleImportTest.java
 
b/plugins/misc/import/src/test/java/org/apache/hop/imports/kettle/KettleImportTest.java
index 4bd2b3e321..351ccd9702 100644
--- 
a/plugins/misc/import/src/test/java/org/apache/hop/imports/kettle/KettleImportTest.java
+++ 
b/plugins/misc/import/src/test/java/org/apache/hop/imports/kettle/KettleImportTest.java
@@ -166,6 +166,106 @@ class KettleImportTest {
     assertEquals("Target workflow RC", runConfigurationAt(doc, 1));
   }
 
+  /**
+   * Kettle only started writing a {@code run_configuration} element for Job 
and Transformation
+   * entries in PDI 8, and later versions still leave it out when it was never 
set. The importer has
+   * to add one, or the imported action throws "You need to specify a run 
configuration" (#3814).
+   */
+  @Test
+  void missingRunConfigurationGetsTheDefault() throws Exception {
+    Document doc = parse(entriesWithoutRunConfiguration());
+
+    KettleImport kettleImport = new KettleImport();
+    kettleImport.setDefaultPipelineRunConfiguration("Target pipeline RC");
+    kettleImport.setDefaultWorkflowRunConfiguration("Target workflow RC");
+    invokeProcessNode(kettleImport, doc);
+
+    assertEquals(2, doc.getElementsByTagName("run_configuration").getLength());
+    assertEquals("Target pipeline RC", runConfigurationAt(doc, 0));
+    assertEquals("Target workflow RC", runConfigurationAt(doc, 1));
+  }
+
+  /**
+   * An empty run configuration is always broken, so a missing element falls 
back to {@code local}
+   * rather than staying out. That is different from an entry that names one: 
see {@link
+   * #runConfigurationOfSourceSurvivesWithoutADefault()}.
+   */
+  @Test
+  void missingRunConfigurationFallsBackToLocalWithoutADefault() throws 
Exception {
+    Document doc = parse(entriesWithoutRunConfiguration());
+
+    invokeProcessNode(new KettleImport(), doc);
+
+    assertEquals(2, doc.getElementsByTagName("run_configuration").getLength());
+    assertEquals("local", runConfigurationAt(doc, 0));
+    assertEquals("local", runConfigurationAt(doc, 1));
+  }
+
+  /**
+   * PDI 8 and later write the element out empty when no run configuration was 
ever selected. That
+   * carries no name to preserve, and an empty name leaves the action unable 
to run, so it is filled
+   * in exactly like a missing one. The two spellings of an empty element 
parse identically, so both
+   * are covered here.
+   */
+  @Test
+  void emptyRunConfigurationElementGetsTheDefault() throws Exception {
+    Document doc = parse(entriesWithEmptyRunConfiguration());
+
+    KettleImport kettleImport = new KettleImport();
+    kettleImport.setDefaultPipelineRunConfiguration("Target pipeline RC");
+    kettleImport.setDefaultWorkflowRunConfiguration("Target workflow RC");
+    invokeProcessNode(kettleImport, doc);
+
+    // Still three elements: the existing empty ones are filled rather than 
duplicated.
+    assertEquals(3, doc.getElementsByTagName("run_configuration").getLength());
+    assertEquals("Target pipeline RC", runConfigurationAt(doc, 0));
+    assertEquals("Target workflow RC", runConfigurationAt(doc, 1));
+    assertEquals("Target workflow RC", runConfigurationAt(doc, 2));
+  }
+
+  @Test
+  void emptyRunConfigurationElementFallsBackToLocalWithoutADefault() throws 
Exception {
+    Document doc = parse(entriesWithEmptyRunConfiguration());
+
+    invokeProcessNode(new KettleImport(), doc);
+
+    assertEquals(3, doc.getElementsByTagName("run_configuration").getLength());
+    assertEquals("local", runConfigurationAt(doc, 0));
+    assertEquals("local", runConfigurationAt(doc, 1));
+    assertEquals("local", runConfigurationAt(doc, 2));
+  }
+
+  /**
+   * Self-closing, explicitly closed and whitespace-only: all three carry no 
run configuration name.
+   * The name and location elements keep each entry from collapsing into a 
single text value.
+   */
+  private static String entriesWithEmptyRunConfiguration() {
+    return "<job>"
+        + 
"<entry><name>a</name><type>TRANS</type><xloc>1</xloc><run_configuration/></entry>"
+        + "<entry><name>b</name><type>JOB</type><xloc>2</xloc>"
+        + "<run_configuration></run_configuration></entry>"
+        + "<entry><name>c</name><type>JOB</type><xloc>3</xloc>"
+        + "<run_configuration>   </run_configuration></entry>"
+        + "</job>";
+  }
+
+  /** An entry that is neither a Job nor a Transformation must not gain a run 
configuration. */
+  @Test
+  void otherEntryTypesGetNoRunConfiguration() throws Exception {
+    Document doc = 
parse("<job><entry><name>Success</name><type>SUCCESS</type></entry></job>");
+
+    invokeProcessNode(new KettleImport(), doc);
+
+    assertEquals(0, doc.getElementsByTagName("run_configuration").getLength());
+  }
+
+  private static String entriesWithoutRunConfiguration() {
+    return "<job>"
+        + "<entry><name>sub 
trans</name><type>TRANS</type><filename>child.ktr</filename></entry>"
+        + "<entry><name>sub 
job</name><type>JOB</type><filename>child.kjb</filename></entry>"
+        + "</job>";
+  }
+
   /**
    * A Simple Mapping step has no run configuration in PDI, so the importer 
appends one. With no
    * default to append it used to add an empty element; leave the transform 
alone instead.

Reply via email to