You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hop.apache.org by mc...@apache.org on 2026/09/29 09:33:22 UTC
(hop) branch main updated: Fixes #3814: set a run configuration on imported Pipeline and Workflow actions (#8641)
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 <ba...@know.bi>
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 '<run_configuration>Imported pipelines</run_configuration>' "$OUT/parent.hwf")" = 2
+test "$(grep -c '<run_configuration>Imported workflows</run_configuration>' "$OUT/parent.hwf")" = 1
+
+# Filling the empty element must not leave a second one behind.
+test "$(grep -c '<run_configuration>' "$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 '<run_configuration>local</run_configuration>' "$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 '<run_configuration>' "$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 > "$LOG" 2>&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.