This is an automated email from the ASF dual-hosted git repository.
bamaer pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git
The following commit(s) were added to refs/heads/main by this push:
new bc70157ed5 Fix LoadScriptFromTab swallowing exceptions in the
JavaScript transform (#8322)
bc70157ed5 is described below
commit bc70157ed55a601437ae26909efbef8f1918cc26
Author: Lance <[email protected]>
AuthorDate: Sun Sep 13 15:10:27 2026 +0800
Fix LoadScriptFromTab swallowing exceptions in the JavaScript transform
(#8322)
* Fix LoadScriptFromTab swallowing exceptions in the JavaScript transform
Signed-off-by: lance <[email protected]>
* Fix LoadScriptFromTab swallowing exceptions in the JavaScript transform
Signed-off-by: lance <[email protected]>
---------
Signed-off-by: lance <[email protected]>
---
.../0087-javascript-loadscript-from-tab.hpl | 162 +++++++++++++++++++++
.../golden-javascript-loadscript-from-tab.csv | 4 +
.../transforms/main-0087-javascript.hwf | 9 +-
.../golden-javascript-loadscript-from-tab.json | 24 +++
.../0087-javascript-loadscript-from-tab UNIT.json | 32 ++++
.../javascript/ScriptValuesAddedFunctions.java | 25 +++-
.../transforms/javascript/jsFunctionHelp.xml | 10 +-
...ptValuesAddedFunctionsRhinoIntegrationTest.java | 49 ++++++-
8 files changed, 299 insertions(+), 16 deletions(-)
diff --git
a/integration-tests/transforms/0087-javascript-loadscript-from-tab.hpl
b/integration-tests/transforms/0087-javascript-loadscript-from-tab.hpl
new file mode 100644
index 0000000000..7a43022804
--- /dev/null
+++ b/integration-tests/transforms/0087-javascript-loadscript-from-tab.hpl
@@ -0,0 +1,162 @@
+<?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.
+
+-->
+<pipeline>
+ <info>
+ <name>0087-javascript-loadscript-from-tab</name>
+ <name_sync_with_filename>Y</name_sync_with_filename>
+ <description>LoadScriptFromTab loads a library tab into the Start script
context</description>
+ <extended_description/>
+ <pipeline_version/>
+ <pipeline_type>Normal</pipeline_type>
+ <parameters>
+ </parameters>
+ <capture_transform_performance>N</capture_transform_performance>
+
<transform_performance_capturing_delay>1000</transform_performance_capturing_delay>
+
<transform_performance_capturing_size_limit>100</transform_performance_capturing_size_limit>
+ <created_user>-</created_user>
+ <created_date>2026/09/11 00:00:00.000</created_date>
+ <modified_user>-</modified_user>
+ <modified_date>2026/09/11 00:00:00.000</modified_date>
+ </info>
+ <notepads>
+ </notepads>
+ <order>
+ <hop>
+ <from>Input</from>
+ <to>JavaScript</to>
+ <enabled>Y</enabled>
+ </hop>
+ <hop>
+ <from>JavaScript</from>
+ <to>validate</to>
+ <enabled>Y</enabled>
+ </hop>
+ </order>
+ <transform>
+ <name>Input</name>
+ <type>DataGrid</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <data>
+ <line>
+ <item>1</item>
+ </line>
+ <line>
+ <item>2</item>
+ </line>
+ <line>
+ <item>3</item>
+ </line>
+ </data>
+ <fields>
+ <field>
+ <length>-1</length>
+ <precision>-1</precision>
+ <set_empty_string>N</set_empty_string>
+ <name>value</name>
+ <type>Integer</type>
+ </field>
+ </fields>
+ <attributes/>
+ <GUI>
+ <xloc>96</xloc>
+ <yloc>80</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>JavaScript</name>
+ <type>ScriptValueMod</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <fields>
+ <field>
+ <length>-1</length>
+ <name>result</name>
+ <precision>-1</precision>
+ <rename>result</rename>
+ <replace>N</replace>
+ <type>Integer</type>
+ </field>
+ </fields>
+ <jsScripts>
+ <jsScript>
+ <jsScript_name>Lib</jsScript_name>
+ <jsScript_script>
+function helper(v) {
+ return v * 2;
+}
+</jsScript_script>
+ <jsScript_type>-1</jsScript_type>
+ </jsScript>
+ <jsScript>
+ <jsScript_name>startScript</jsScript_name>
+ <jsScript_script>
+LoadScriptFromTab("Lib");
+</jsScript_script>
+ <jsScript_type>1</jsScript_type>
+ </jsScript>
+ <jsScript>
+ <jsScript_name>transformScript</jsScript_name>
+ <jsScript_script>
+var result = helper(value);
+</jsScript_script>
+ <jsScript_type>0</jsScript_type>
+ </jsScript>
+ </jsScripts>
+ <optimizationLevel>9</optimizationLevel>
+ <attributes/>
+ <GUI>
+ <xloc>288</xloc>
+ <yloc>80</yloc>
+ </GUI>
+ </transform>
+ <transform>
+ <name>validate</name>
+ <type>Dummy</type>
+ <description/>
+ <distribute>Y</distribute>
+ <custom_distribution/>
+ <copies>1</copies>
+ <partitioning>
+ <method>none</method>
+ <schema_name/>
+ </partitioning>
+ <attributes/>
+ <GUI>
+ <xloc>432</xloc>
+ <yloc>80</yloc>
+ </GUI>
+ </transform>
+ <transform_error_handling>
+ </transform_error_handling>
+ <attributes/>
+</pipeline>
diff --git
a/integration-tests/transforms/datasets/golden-javascript-loadscript-from-tab.csv
b/integration-tests/transforms/datasets/golden-javascript-loadscript-from-tab.csv
new file mode 100644
index 0000000000..fde7a9ca3d
--- /dev/null
+++
b/integration-tests/transforms/datasets/golden-javascript-loadscript-from-tab.csv
@@ -0,0 +1,4 @@
+value,result
+1,2
+2,4
+3,6
diff --git a/integration-tests/transforms/main-0087-javascript.hwf
b/integration-tests/transforms/main-0087-javascript.hwf
index 17d6daada1..371fa0fc0a 100644
--- a/integration-tests/transforms/main-0087-javascript.hwf
+++ b/integration-tests/transforms/main-0087-javascript.hwf
@@ -50,7 +50,7 @@ limitations under the License.
<attributes_hac/>
</action>
<action>
- <name>0087-javascript-basics UNIT</name>
+ <name>Run Pipeline Unit Tests</name>
<description/>
<type>RunPipelineTests</type>
<attributes/>
@@ -58,6 +58,9 @@ limitations under the License.
<test_name>
<name>0087-javascript-basics UNIT</name>
</test_name>
+ <test_name>
+ <name>0087-javascript-loadscript-from-tab UNIT</name>
+ </test_name>
</test_names>
<parallel>N</parallel>
<xloc>208</xloc>
@@ -108,13 +111,13 @@ limitations under the License.
<hops>
<hop>
<from>Start</from>
- <to>0087-javascript-basics UNIT</to>
+ <to>Run Pipeline Unit Tests</to>
<enabled>Y</enabled>
<evaluation>Y</evaluation>
<unconditional>Y</unconditional>
</hop>
<hop>
- <from>0087-javascript-basics UNIT</from>
+ <from>Run Pipeline Unit Tests</from>
<to>Simple evaluation</to>
<enabled>Y</enabled>
<evaluation>Y</evaluation>
diff --git
a/integration-tests/transforms/metadata/dataset/golden-javascript-loadscript-from-tab.json
b/integration-tests/transforms/metadata/dataset/golden-javascript-loadscript-from-tab.json
new file mode 100644
index 0000000000..38919923f6
--- /dev/null
+++
b/integration-tests/transforms/metadata/dataset/golden-javascript-loadscript-from-tab.json
@@ -0,0 +1,24 @@
+{
+ "base_filename": "golden-javascript-loadscript-from-tab.csv",
+ "name": "golden-javascript-loadscript-from-tab",
+ "description": "",
+ "dataset_fields": [
+ {
+ "field_comment": "",
+ "field_length": -1,
+ "field_type": 5,
+ "field_precision": 0,
+ "field_name": "value",
+ "field_format": "####0;-####0"
+ },
+ {
+ "field_comment": "",
+ "field_length": -1,
+ "field_type": 5,
+ "field_precision": 0,
+ "field_name": "result",
+ "field_format": "####0;-####0"
+ }
+ ],
+ "folder_name": ""
+}
diff --git
a/integration-tests/transforms/metadata/unit-test/0087-javascript-loadscript-from-tab
UNIT.json
b/integration-tests/transforms/metadata/unit-test/0087-javascript-loadscript-from-tab
UNIT.json
new file mode 100644
index 0000000000..ebaf0eabc9
--- /dev/null
+++
b/integration-tests/transforms/metadata/unit-test/0087-javascript-loadscript-from-tab
UNIT.json
@@ -0,0 +1,32 @@
+{
+ "database_replacements": [],
+ "autoOpening": true,
+ "description": "Start script loads Lib via LoadScriptFromTab; transform uses
helper()",
+ "persist_filename": "",
+ "test_type": "UNIT_TEST",
+ "variableValues": [],
+ "basePath": "${HOP_UNIT_TESTS_FOLDER}",
+ "golden_data_sets": [
+ {
+ "field_mappings": [
+ {
+ "transform_field": "value",
+ "data_set_field": "value"
+ },
+ {
+ "transform_field": "result",
+ "data_set_field": "result"
+ }
+ ],
+ "field_order": [
+ "value"
+ ],
+ "data_set_name": "golden-javascript-loadscript-from-tab",
+ "transform_name": "validate"
+ }
+ ],
+ "input_data_sets": [],
+ "name": "0087-javascript-loadscript-from-tab UNIT",
+ "trans_test_tweaks": [],
+ "pipeline_filename": "./0087-javascript-loadscript-from-tab.hpl"
+}
diff --git
a/plugins/transforms/javascript/src/main/java/org/apache/hop/pipeline/transforms/javascript/ScriptValuesAddedFunctions.java
b/plugins/transforms/javascript/src/main/java/org/apache/hop/pipeline/transforms/javascript/ScriptValuesAddedFunctions.java
index 6d50460095..4eaa1ef245 100644
---
a/plugins/transforms/javascript/src/main/java/org/apache/hop/pipeline/transforms/javascript/ScriptValuesAddedFunctions.java
+++
b/plugins/transforms/javascript/src/main/java/org/apache/hop/pipeline/transforms/javascript/ScriptValuesAddedFunctions.java
@@ -1772,14 +1772,27 @@ public class ScriptValuesAddedFunctions extends
ScriptableObject {
// Adding the ScriptsItemTab to the actual running Context
public static void LoadScriptFromTab(
Context actualContext, Scriptable actualObject, Object[] argList,
Function functionContext) {
- try {
- for (Object o : argList) { // don't worry about "undefined" arguments
- String strToLoad = Context.toString(o);
- String strScript = actualObject.get(strToLoad,
actualObject).toString();
+ if (argList.length == 0) {
+ throw Context.reportRuntimeError(
+ "The function call LoadScriptFromTab requires at least 1 argument.");
+ }
+
+ for (Object o : argList) {
+ String strToLoad = Context.toString(o);
+ Object scriptObj = actualObject.get(strToLoad, actualObject);
+ if (scriptObj == Scriptable.NOT_FOUND
+ || scriptObj == null
+ || scriptObj == Context.getUndefinedValue()) {
+ throw Context.reportRuntimeError("Unable to find script tab \"" +
strToLoad + "\"");
+ }
+
+ try {
+ String strScript = Context.toString(scriptObj);
actualContext.evaluateString(actualObject, strScript, "_" + strToLoad
+ "_", 0, null);
+ } catch (Exception e) {
+ throw Context.reportRuntimeError(
+ "Unable to load script from tab \"" + strToLoad + "\": " +
e.getMessage());
}
- } catch (Exception e) {
- // TODO: DON'T EAT EXCEPTION
}
}
diff --git
a/plugins/transforms/javascript/src/main/resources/org/apache/hop/pipeline/transforms/javascript/jsFunctionHelp.xml
b/plugins/transforms/javascript/src/main/resources/org/apache/hop/pipeline/transforms/javascript/jsFunctionHelp.xml
index cc3ddd0d45..f980875378 100644
---
a/plugins/transforms/javascript/src/main/resources/org/apache/hop/pipeline/transforms/javascript/jsFunctionHelp.xml
+++
b/plugins/transforms/javascript/src/main/resources/org/apache/hop/pipeline/transforms/javascript/jsFunctionHelp.xml
@@ -385,18 +385,16 @@
</Arguments>
</jsFunction>
<jsFunction name="LoadScriptFromTab">
- <help>Adds the content of a JavaScript_mod Tab into the current
Context.</help>
- <sample>// Load's JavaScript Functions from an existing tab
+ <help>Adds the content of a JavaScript Tab into the current Context.
Throws an error if the tab name does not exist or the script cannot be
evaluated.</help>
+ <sample>// Loads JavaScript functions from an existing tab
// into your actual running Context.
- // This function should called from your defined StartScript
+ // Call this from your defined StartScript
// otherwise your functions are loaded on each processing
- // Row.
+ // Row. Missing or invalid tabs fail the transform immediately.
//
// Usage:
// LoadScriptFromTab(var);
//
- // 2006-11-15
- //
var xTab = "Your TabName";
LoadScriptFromTab(xTab);
</sample>
diff --git
a/plugins/transforms/javascript/src/test/java/org/apache/hop/pipeline/transforms/javascript/ScriptValuesAddedFunctionsRhinoIntegrationTest.java
b/plugins/transforms/javascript/src/test/java/org/apache/hop/pipeline/transforms/javascript/ScriptValuesAddedFunctionsRhinoIntegrationTest.java
index 9e58219414..e87365cf59 100644
---
a/plugins/transforms/javascript/src/test/java/org/apache/hop/pipeline/transforms/javascript/ScriptValuesAddedFunctionsRhinoIntegrationTest.java
+++
b/plugins/transforms/javascript/src/test/java/org/apache/hop/pipeline/transforms/javascript/ScriptValuesAddedFunctionsRhinoIntegrationTest.java
@@ -64,9 +64,10 @@ class ScriptValuesAddedFunctionsRhinoIntegrationTest {
@Test
void getDigitsOnly_requiresOneArgument() {
+ Object[] args = args();
assertThrows(
EvaluatorException.class,
- () -> ScriptValuesAddedFunctions.getDigitsOnly(cx, scope, args(),
null));
+ () -> ScriptValuesAddedFunctions.getDigitsOnly(cx, scope, args, null));
}
@Test
@@ -221,4 +222,50 @@ class ScriptValuesAddedFunctionsRhinoIntegrationTest {
assertNotNull(ScriptValuesAddedFunctions.getDayNumber(cx, scope, args(js,
"m"), null));
assertNotNull(ScriptValuesAddedFunctions.getDayNumber(cx, scope, args(js,
"w"), null));
}
+
+ @Test
+ void loadScriptFromTab_loadsFunctionsIntoScope() {
+ // Same shape as ScriptValues: tab name -> script source on the scope.
+ scope.put("Lib", scope, Context.toObject("function helper() { return 42;
}", scope));
+
+ ScriptValuesAddedFunctions.LoadScriptFromTab(cx, scope, args("Lib"), null);
+
+ Object result = cx.evaluateString(scope, "helper()", "script", 1, null);
+ assertEquals(42.0, Context.toNumber(result), 1e-9);
+ }
+
+ @Test
+ void loadScriptFromTab_missingTabThrows() {
+ Object[] doesNotExists = args("DoesNotExist");
+ EvaluatorException ex =
+ assertThrows(
+ EvaluatorException.class,
+ () -> ScriptValuesAddedFunctions.LoadScriptFromTab(cx, scope,
doesNotExists, null));
+ assertTrue(ex.getMessage().contains("Unable to find script tab"));
+ assertTrue(ex.getMessage().contains("DoesNotExist"));
+ }
+
+ @Test
+ void loadScriptFromTab_invalidScriptThrows() {
+ scope.put("Broken", scope, Context.toObject("function {", scope));
+ Object[] broken = args("Broken");
+
+ EvaluatorException ex =
+ assertThrows(
+ EvaluatorException.class,
+ () -> ScriptValuesAddedFunctions.LoadScriptFromTab(cx, scope,
broken, null));
+ assertTrue(ex.getMessage().contains("Unable to load script from tab"));
+ assertTrue(ex.getMessage().contains("Broken"));
+ }
+
+ @Test
+ void loadScriptFromTab_requiresAtLeastOneArgument() {
+ Object[] args = args();
+
+ EvaluatorException ex =
+ assertThrows(
+ EvaluatorException.class,
+ () -> ScriptValuesAddedFunctions.LoadScriptFromTab(cx, scope,
args, null));
+ assertTrue(ex.getMessage().contains("requires at least 1 argument"));
+ }
}