diff --git a/packages/node_modules/@node-red/nodes/core/common/25-catch.html b/packages/node_modules/@node-red/nodes/core/common/25-catch.html index 4b92c9758..2b2f43ca6 100644 --- a/packages/node_modules/@node-red/nodes/core/common/25-catch.html +++ b/packages/node_modules/@node-red/nodes/core/common/25-catch.html @@ -12,6 +12,14 @@ +
+ + +
+
+ + +
@@ -30,9 +38,11 @@ category: 'common', color:"#e49191", defaults: { - name: {value:""}, - scope: {value:null, type:"*[]"}, - uncaught: {value:false} + name: { value: "" }, + scope: { value: null, type: "*[]" }, + uncaught: { value: false }, + anyError: { value: false }, + includeConfig: { value: false } }, inputs:0, outputs:1, @@ -80,29 +90,64 @@ } } }); + const sidebar = $("#red-ui-sidebar-config-category-global"); var dirList = $("#node-input-catch-target-container-div").css({width: "100%", height: "100%"}) .treeList({multi:true}).on("treelistitemmouseover", function(e, item) { - item.node.highlighted = true; - item.node.dirty = true; - RED.view.redraw(); + if (item.node) { + if (!item.node.x && !item.node.y) { + sidebar.find(".red-ui-palette-node_id_" + item.node.id).children("div").addClass("highlighted"); + } else { + item.node.highlighted = true; + item.node.dirty = true; + RED.view.redraw(); + } + } }).on("treelistitemmouseout", function(e, item) { - item.node.highlighted = false; - item.node.dirty = true; - RED.view.redraw(); + if (item.node) { + if (!item.node.x && !item.node.y) { + sidebar.find(".red-ui-palette-node_id_" + item.node.id).children("div").removeClass("highlighted"); + } else { + item.node.highlighted = false; + item.node.dirty = true; + RED.view.redraw(); + } + } }) - var candidateNodes = RED.nodes.filterNodes({z:node.z}); - var allChecked = true; - var items = []; - var nodeItemMap = {}; + + const flowItems = []; + const flowItemMap = {}; + let activeWorkspace = RED.nodes.workspace(node.z); + activeWorkspace = activeWorkspace || RED.nodes.subflow(node.z); + flowItemMap[activeWorkspace.id] = { + id: activeWorkspace.id, + class: "red-ui-palette-header", + label: activeWorkspace.label || activeWorkspace.name || activeWorkspace.id, + expanded: true, + children: [] + }; + flowItemMap.global = { + id: "global", + class: "red-ui-palette-header", + label: this._("catch.label.globalConfig"), + expanded: true, + children: [] + }; + + const nodeItemMap = {}; + const configNodeSet = new Set(); + const candidateNodes = RED.nodes.filterNodes({ z: node.z }); + RED.nodes.eachConfig((cn) => { + // Add config nodes with the same or global scope + if (cn.z === node.z || !cn.z) { + configNodeSet.add(cn); + candidateNodes.push(cn); + } + }); candidateNodes.forEach(function(n) { - if (n.id === node.id) { + if (n.id === node.id || n.type === "global-config") { return; } - var isChecked = scope.indexOf(n.id) !== -1; - - allChecked = allChecked && isChecked; - var nodeDef = RED.nodes.getType(n.type); var label; var sublabel; @@ -119,21 +164,42 @@ if (!nodeDef || !label) { label = n.type; } + const isChecked = scope.indexOf(n.id) !== -1; + const isConfigNode = (nodeDef && nodeDef.category === "config") || (!nodeDef && !n.x && !n.y); nodeItemMap[n.id] = { node: n, label: label, sublabel: sublabel, selected: isChecked, - checkbox: true + checkbox: true, + icon: isConfigNode ? "fa fa-cog" : "" }; - items.push(nodeItemMap[n.id]); + if (!n.z && isConfigNode) { + flowItemMap.global.children.push(nodeItemMap[n.id]); + } else { + flowItemMap[activeWorkspace.id].children.push(nodeItemMap[n.id]); + } + }); + + flowItems.push(flowItemMap[activeWorkspace.id], flowItemMap.global); + + $("#node-input-includeConfig").on("change", function () { + const checked = $(this).prop("checked"); + if (checked) { + dirList.treeList('data', flowItems); + } else { + // Remove config nodes from the list + const filteredItems = flowItemMap[activeWorkspace.id].children + .filter((e) => !configNodeSet.has(e.node)); + dirList.treeList('data', filteredItems); + } }); - dirList.treeList('data',items); $("#node-input-catch-target-select").on("click", function(e) { e.preventDefault(); var preselected = dirList.treeList('selected').map(function(n) {return n.node.id}); RED.tray.hide(); + // TODO: extend selection to config nodes too RED.view.selectNodes({ selected: preselected, onselect: function(selection) { @@ -169,6 +235,13 @@ $(".node-input-target-row").hide(); $(".node-input-uncaught-row").show(); } + if (scope === "group") { + $(".node-input-anyError-row").hide(); + $(".node-input-includeConfig-row").hide(); + } else { + $(".node-input-anyError-row").show(); + $(".node-input-includeConfig-row").show(); + } node._resize(); }); if (this.scope === null) { diff --git a/packages/node_modules/@node-red/nodes/core/common/25-catch.js b/packages/node_modules/@node-red/nodes/core/common/25-catch.js index 5ed525c36..a343451b9 100644 --- a/packages/node_modules/@node-red/nodes/core/common/25-catch.js +++ b/packages/node_modules/@node-red/nodes/core/common/25-catch.js @@ -14,19 +14,20 @@ * limitations under the License. **/ -module.exports = function(RED) { +module.exports = function (RED) { "use strict"; function CatchNode(n) { - RED.nodes.createNode(this,n); - var node = this; + RED.nodes.createNode(this, n); this.scope = n.scope; this.uncaught = n.uncaught; - this.on("input",function(msg, send, done) { + this.anyError = n.anyError; + this.includeConfig = n.includeConfig; + this.on("input", function (msg, send, done) { send(msg); done(); }); } - RED.nodes.registerType("catch",CatchNode); + RED.nodes.registerType("catch", CatchNode); } diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json index cb9badab9..975759be6 100644 --- a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json @@ -100,7 +100,10 @@ "label": { "source": "Catch errors from", "selectAll": "select all", - "uncaught": "Ignore errors handled by other Catch nodes" + "uncaught": "Ignore errors handled by other Catch nodes", + "anyError": "Allow any error other than message processing", + "includeConfig": "Catch errors from config nodes too", + "globalConfig": "Global config nodes" }, "scope": { "all": "all nodes", diff --git a/packages/node_modules/@node-red/nodes/locales/fr/messages.json b/packages/node_modules/@node-red/nodes/locales/fr/messages.json index 4c71f5a83..8b24bb25f 100644 --- a/packages/node_modules/@node-red/nodes/locales/fr/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/fr/messages.json @@ -98,14 +98,17 @@ "catchNodes": "catch : __number__", "catchUncaught": "catch : non capturé", "label": { - "source": "Détecter les erreurs de", + "source": "Capturer les erreurs ", "selectAll": "Tout sélectionner", - "uncaught": "Ignorer les erreurs gérées par les autres noeuds Catch" + "uncaught": "Ignorer les erreurs gérées par les autres noeuds Catch", + "anyError": "Autoriser toute erreur autre que le traitement des messages", + "includeConfig": "Capturer également les erreurs des noeuds de configuration", + "globalConfig": "Noeuds de configuration globale" }, "scope": { - "all": "tous les noeuds", + "all": "de tous les noeuds", "group": "dans le même groupe", - "selected": "noeuds sélectionnés" + "selected": "des noeuds sélectionnés" } }, "status": { diff --git a/packages/node_modules/@node-red/runtime/lib/flows/Flow.js b/packages/node_modules/@node-red/runtime/lib/flows/Flow.js index f96a67780..c0da0e90b 100644 --- a/packages/node_modules/@node-red/runtime/lib/flows/Flow.js +++ b/packages/node_modules/@node-red/runtime/lib/flows/Flow.js @@ -26,6 +26,9 @@ let Subflow; let Log; let Group; +/** @type {Record} */ +let activeFlows; + let nodeCloseTimeout = 15000; let asyncMessageDelivery = true; @@ -620,9 +623,25 @@ class Flow { handled = node.users[userNode]._flow.handleError(node,logMessage,msg,node.users[userNode]) || handled; } } + } else if (this.id === 'global') { + for (const flow of Object.values(activeFlows)) { + if (flow.id === 'global') { + continue; + } + handled = flow.handleError(node, logMessage, msg) || handled; + } } else { const candidateNodes = []; + const isConfigNode = !!node._flow?.flow.configs[node.id]; this.catchNodes.forEach(targetCatchNode => { + if (msg === null && !targetCatchNode.anyError) { + // Skip if the catch node only accepts errors produced by message processing + return; + } + if (isConfigNode && !targetCatchNode.includeConfig) { + // Skip if the catch node only accepts errors produced by non config nodes + return; + } if (targetCatchNode.g && targetCatchNode.scope === 'group' && !reportingNode.g) { // Catch node inside a group, reporting node not in a group - skip it return @@ -860,6 +879,9 @@ module.exports = { Subflow = require("./Subflow"); Group = require("./Group").Group }, + setActiveFlows: function (flows) { + activeFlows = flows; + }, create: function(parent,global,conf) { return new Flow(parent,global,conf) }, diff --git a/packages/node_modules/@node-red/runtime/lib/flows/index.js b/packages/node_modules/@node-red/runtime/lib/flows/index.js index f21bd56f9..28a4c03ed 100644 --- a/packages/node_modules/@node-red/runtime/lib/flows/index.js +++ b/packages/node_modules/@node-red/runtime/lib/flows/index.js @@ -70,6 +70,7 @@ function init(runtime) { typeEventRegistered = true; } Flow.init(runtime); + Flow.setActiveFlows(activeFlows); flowUtil.init(runtime); } diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/Node.js b/packages/node_modules/@node-red/runtime/lib/nodes/Node.js index f31504214..592aec686 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/Node.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/Node.js @@ -566,9 +566,10 @@ Node.prototype.error = function(logMessage,msg) { if (typeof logMessage != 'boolean') { logMessage = logMessage || ""; } - var handled = false; - if (this._flow && msg && typeof msg === 'object') { - handled = this._flow.handleError(this,logMessage,msg); + let handled = false; + const shouldHandle = (msg && typeof msg === 'object') || msg === undefined; + if (this._flow && shouldHandle) { + handled = this._flow.handleError(this, logMessage, msg || null); } if (!handled) { log_helper(this, Log.ERROR, logMessage); diff --git a/test/nodes/core/function/10-function_spec.js b/test/nodes/core/function/10-function_spec.js index 6a04547f4..46159d545 100644 --- a/test/nodes/core/function/10-function_spec.js +++ b/test/nodes/core/function/10-function_spec.js @@ -69,7 +69,7 @@ describe('function node', function() { }); it('should do something with the catch node', function(done) { - var flow = [{"id":"funcNode","type":"function","wires":[["goodNode"]],"func":"node.error('This is an error', msg);"},{"id":"goodNode","type":"helper"},{"id":"badNode","type":"helper"},{"id":"catchNode","type":"catch","scope":null,"uncaught":false,"wires":[["badNode"]]}]; + var flow = [{"id":"flow","type":"tab"},{"id":"funcNode","type":"function","wires":[["goodNode"]],"func":"node.error('This is an error', msg);","x":0,"y":0,"z":"flow"},{"id":"goodNode","type":"helper","z":"flow"},{"id":"badNode","type":"helper","z":"flow"},{"id":"catchNode","type":"catch","scope":null,"uncaught":false,"wires":[["badNode"]],"z":"flow"}]; var catchNodeModule = require("nr-test-utils").require("@node-red/nodes/core/common/25-catch.js") helper.load([catchNodeModule, functionNode], flow, function() { var funcNode = helper.getNode("funcNode");