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 '<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.