mirror of
				https://github.com/node-red/node-red.git
				synced 2025-03-01 10:36:34 +00:00 
			
		
		
		
	Merge branch 'master' into update-dev
This commit is contained in:
		
							
								
								
									
										2
									
								
								.github/workflows/tests.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/tests.yml
									
									
									
									
										vendored
									
									
								
							@@ -16,7 +16,7 @@ jobs:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    strategy:
 | 
			
		||||
      matrix:
 | 
			
		||||
        node-version: [18, 20, 22]
 | 
			
		||||
        node-version: [18, 20, 22.4.x]
 | 
			
		||||
    steps:
 | 
			
		||||
    - uses: actions/checkout@v4
 | 
			
		||||
    - name: Use Node.js ${{ matrix.node-version }}
 | 
			
		||||
 
 | 
			
		||||
@@ -32,24 +32,28 @@ RED.contextMenu = (function () {
 | 
			
		||||
            const canRemoveFromGroup = hasSelection && !!selection.nodes[0].g
 | 
			
		||||
            let hasGroup, isAllGroups = true, hasDisabledNode, hasEnabledNode, hasLabeledNode, hasUnlabeledNode;
 | 
			
		||||
            if (hasSelection) {
 | 
			
		||||
                selection.nodes.forEach(n => {
 | 
			
		||||
                const nodes = selection.nodes.slice();
 | 
			
		||||
                while (nodes.length) {
 | 
			
		||||
                    const n = nodes.shift();
 | 
			
		||||
                    if (n.type === 'group') {
 | 
			
		||||
                        hasGroup = true;
 | 
			
		||||
                        nodes.push(...n.nodes);
 | 
			
		||||
                    } else {
 | 
			
		||||
                        isAllGroups = false;
 | 
			
		||||
                    }
 | 
			
		||||
                    if (n.d) {
 | 
			
		||||
                        hasDisabledNode = true;
 | 
			
		||||
                    } else {
 | 
			
		||||
                        hasEnabledNode = true;
 | 
			
		||||
                        if (n.d) {
 | 
			
		||||
                            hasDisabledNode = true;
 | 
			
		||||
                        } else {
 | 
			
		||||
                            hasEnabledNode = true;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    if (n.l === undefined || n.l) {
 | 
			
		||||
                        hasLabeledNode = true;
 | 
			
		||||
                    } else {
 | 
			
		||||
                        hasUnlabeledNode = true;
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const offset = $("#red-ui-workspace-chart").offset()
 | 
			
		||||
 | 
			
		||||
            let addX = options.x - offset.left + $("#red-ui-workspace-chart").scrollLeft()
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@
 | 
			
		||||
        <input type="text" id="node-input-name" data-i18n="[placeholder]node-red:common.label.name">
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row">
 | 
			
		||||
        <label for="node-input-property"><i class="fa fa-forward"></i> <span data-i18n="split.split"></span></label>
 | 
			
		||||
        <label for="node-input-property"><i class="fa fa-forward"></i> <span data-i18n="split.splitThe"></span></label>
 | 
			
		||||
        <input type="text" id="node-input-property" style="width:70%;"/>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-row"><span data-i18n="[html]split.strBuff"></span></div>
 | 
			
		||||
 
 | 
			
		||||
@@ -36,6 +36,10 @@
 | 
			
		||||
            <label style="margin-left: 10px; width: 175px;" for="node-input-overlap" data-i18n="batch.count.overlap"></label>
 | 
			
		||||
            <input type="text" id="node-input-overlap" style="width: 50px;">
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="form-row">
 | 
			
		||||
            <input type="checkbox" id="node-input-honourParts" style="margin-left: 10px; margin-right:10px; vertical-align:top; width:auto;">
 | 
			
		||||
            <label for="node-input-honourParts" style="width:auto;" data-i18n="batch.honourParts"></label>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="node-row-msg-interval">
 | 
			
		||||
@@ -45,7 +49,7 @@
 | 
			
		||||
            <span data-i18n="batch.interval.seconds"></span>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="form-row">
 | 
			
		||||
            <input type="checkbox" id="node-input-allowEmptySequence" style="margin-left:20px; margin-right: 10px; vertical-align:top; width:auto;">
 | 
			
		||||
            <input type="checkbox" id="node-input-allowEmptySequence" style="margin-left:20px; margin-right:10px; vertical-align:top; width:auto;">
 | 
			
		||||
            <label for="node-input-allowEmptySequence" style="width:auto;" data-i18n="batch.interval.empty"></label>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
@@ -101,6 +105,7 @@
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            allowEmptySequence: {value:false},
 | 
			
		||||
            honourParts: {value:false},
 | 
			
		||||
            topics: {value:[{topic:""}]}
 | 
			
		||||
        },
 | 
			
		||||
        inputs:1,
 | 
			
		||||
 
 | 
			
		||||
@@ -181,6 +181,8 @@ module.exports = function(RED) {
 | 
			
		||||
        RED.nodes.createNode(this,n);
 | 
			
		||||
        var node = this;
 | 
			
		||||
        var mode = n.mode || "count";
 | 
			
		||||
        var eof = false;
 | 
			
		||||
        node.honourParts = n.honourParts || false;
 | 
			
		||||
 | 
			
		||||
        node.pending_count = 0;
 | 
			
		||||
        if (mode === "count") {
 | 
			
		||||
@@ -201,9 +203,12 @@ module.exports = function(RED) {
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                var queue = node.pending;
 | 
			
		||||
                if (node.honourParts && msg.hasOwnProperty("parts")) {
 | 
			
		||||
                    if (msg.parts.index + 1 === msg.parts.count) { eof = true; }
 | 
			
		||||
                }
 | 
			
		||||
                queue.push({msg, send, done});
 | 
			
		||||
                node.pending_count++;
 | 
			
		||||
                if (queue.length === count) {
 | 
			
		||||
                if (queue.length === count || eof === true) {
 | 
			
		||||
                    send_msgs(node, queue, is_overlap);
 | 
			
		||||
                    for (let i = 0; i < queue.length-overlap; i++) {
 | 
			
		||||
                        queue[i].done();
 | 
			
		||||
@@ -211,6 +216,7 @@ module.exports = function(RED) {
 | 
			
		||||
                    node.pending =
 | 
			
		||||
                        (overlap === 0) ? [] : queue.slice(-overlap);
 | 
			
		||||
                    node.pending_count = 0;
 | 
			
		||||
                    eof = false;
 | 
			
		||||
                }
 | 
			
		||||
                var max_msgs = max_kept_msgs_count(node);
 | 
			
		||||
                if ((max_msgs > 0) && (node.pending_count > max_msgs)) {
 | 
			
		||||
 
 | 
			
		||||
@@ -912,6 +912,7 @@
 | 
			
		||||
        "objectSend": "Sende eine Nachricht für jedes Schlüssel/Wert-Paar",
 | 
			
		||||
        "strBuff": "<b>string</b> / <b>buffer</b>",
 | 
			
		||||
        "array": "<b>array</b>",
 | 
			
		||||
        "splitThe": "Split",
 | 
			
		||||
        "splitUsing": "Aufteilung",
 | 
			
		||||
        "splitLength": "feste Längen von",
 | 
			
		||||
        "stream": "Als Nachrichtenstrom behandeln (Streaming-Modus)",
 | 
			
		||||
 
 | 
			
		||||
@@ -1011,12 +1011,13 @@
 | 
			
		||||
        "tip": "Tip: The filename should be an absolute path, otherwise it will be relative to the working directory of the Node-RED process."
 | 
			
		||||
    },
 | 
			
		||||
    "split": {
 | 
			
		||||
        "split": "Split",
 | 
			
		||||
        "split": "split",
 | 
			
		||||
        "intro": "Split <code>msg.payload</code> based on type:",
 | 
			
		||||
        "object": "<b>Object</b>",
 | 
			
		||||
        "objectSend": "Send a message for each key/value pair",
 | 
			
		||||
        "strBuff": "<b>String</b> / <b>Buffer</b>",
 | 
			
		||||
        "array": "<b>Array</b>",
 | 
			
		||||
        "splitThe": "Split the",
 | 
			
		||||
        "splitUsing": "Split using",
 | 
			
		||||
        "splitLength": "Fixed length of",
 | 
			
		||||
        "stream": "Handle as a stream of messages",
 | 
			
		||||
@@ -1113,6 +1114,7 @@
 | 
			
		||||
        "too-many": "too many pending messages in batch node",
 | 
			
		||||
        "unexpected": "unexpected mode",
 | 
			
		||||
        "no-parts": "no parts property in message",
 | 
			
		||||
        "honourParts": "Allow msg.parts to also complete batch operation.",
 | 
			
		||||
        "error": {
 | 
			
		||||
            "invalid-count": "Invalid count",
 | 
			
		||||
            "invalid-overlap": "Invalid overlap",
 | 
			
		||||
 
 | 
			
		||||
@@ -1017,6 +1017,7 @@
 | 
			
		||||
        "objectSend": "各key/valueペアのメッセージを送信",
 | 
			
		||||
        "strBuff": "<b>文字列</b> / <b>バッファ</b>",
 | 
			
		||||
        "array": "<b>配列</b>",
 | 
			
		||||
        "splitThe": "に基づく",
 | 
			
		||||
        "splitUsing": "分割",
 | 
			
		||||
        "splitLength": "固定長",
 | 
			
		||||
        "stream": "メッセージのストリームとして処理",
 | 
			
		||||
 
 | 
			
		||||
@@ -44,7 +44,7 @@
 | 
			
		||||
             "global": "contexto global",
 | 
			
		||||
             "str": "Cadeia de caracteres",
 | 
			
		||||
             "num": "número",
 | 
			
		||||
             "bool": "booliano",  
 | 
			
		||||
             "bool": "booliano",
 | 
			
		||||
             "json": "objeto",
 | 
			
		||||
             "bin": "Armazenamento temporário",
 | 
			
		||||
             "date": "Carimbo de data/hora",
 | 
			
		||||
@@ -352,8 +352,8 @@
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    "trigger": {
 | 
			
		||||
        "send": "Enviar", 
 | 
			
		||||
        "then": "então", 
 | 
			
		||||
        "send": "Enviar",
 | 
			
		||||
        "then": "então",
 | 
			
		||||
        "then-send": "então enviem",
 | 
			
		||||
        "output": {
 | 
			
		||||
            "string": "a cadeia de caracteres",
 | 
			
		||||
@@ -446,7 +446,7 @@
 | 
			
		||||
            "staticTopic": "Assinar um tópico único",
 | 
			
		||||
            "dynamicTopic": "Assinatura dinâmica",
 | 
			
		||||
            "auto-connect": "Conectar automaticamente",
 | 
			
		||||
            "auto-mode-depreciated": "Esta opção está deprecada. Favor utilizar o novo modo de auto-detecção."            
 | 
			
		||||
            "auto-mode-depreciated": "Esta opção está deprecada. Favor utilizar o novo modo de auto-detecção."
 | 
			
		||||
        },
 | 
			
		||||
        "sections-label": {
 | 
			
		||||
            "birth-message": "Mensagem enviada na conexão (mensagem de nascimento)",
 | 
			
		||||
@@ -466,8 +466,8 @@
 | 
			
		||||
            "close-topic": "Deixe em branco para desativar a mensagem de fechamento"
 | 
			
		||||
        },
 | 
			
		||||
        "state": {
 | 
			
		||||
            "connected": "Conectado ao negociante: _ broker _", 
 | 
			
		||||
            "disconnected": "Desconectado do negociante: _ broker _", 
 | 
			
		||||
            "connected": "Conectado ao negociante: _ broker _",
 | 
			
		||||
            "disconnected": "Desconectado do negociante: _ broker _",
 | 
			
		||||
            "connect-failed": "Falha na conexão com o negociante: __broker__",
 | 
			
		||||
            "broker-disconnected": "Cliente de negociante __broker__ desconectado: __reasonCode__ __reasonString__"
 | 
			
		||||
        },
 | 
			
		||||
@@ -898,7 +898,7 @@
 | 
			
		||||
            "o2j": "Objeto para opções JSON",
 | 
			
		||||
            "pretty": "Formatar cadeia de caracteres JSON",
 | 
			
		||||
            "action": "Ação",
 | 
			
		||||
            "property": "Propriedade",  
 | 
			
		||||
            "property": "Propriedade",
 | 
			
		||||
            "actions": {
 | 
			
		||||
                "toggle": "Converter entre cadeia de caracteres JSON e Objeto",
 | 
			
		||||
                "str": "Sempre converter em cadeia de caracteres JSON",
 | 
			
		||||
@@ -929,7 +929,7 @@
 | 
			
		||||
            "write": "escrever arquivo",
 | 
			
		||||
            "read": "ler arquivo",
 | 
			
		||||
            "filename": "Nome do arquivo",
 | 
			
		||||
            "path": "caminho", 
 | 
			
		||||
            "path": "caminho",
 | 
			
		||||
            "action": "Ação",
 | 
			
		||||
            "addnewline": "Adicionar nova linha (\\n) a cada carga útil?",
 | 
			
		||||
            "createdir": "Criar diretório se não existir?",
 | 
			
		||||
@@ -994,6 +994,7 @@
 | 
			
		||||
         "objectSend": "Envia uma mensagem para cada par chave/valor",
 | 
			
		||||
         "strBuff": "<b>Cadeia de caracteres</b> / <b>Armazenamento Temporário</b>",
 | 
			
		||||
         "array": "<b>Matriz</b>",
 | 
			
		||||
         "splitThe": "Dividir",
 | 
			
		||||
         "splitUsing": "Dividir usando",
 | 
			
		||||
         "splitLength": "Comprimento fixo de",
 | 
			
		||||
         "stream": "Tratar como uma transmissão de mensagens",
 | 
			
		||||
@@ -1066,9 +1067,9 @@
 | 
			
		||||
    "batch" : {
 | 
			
		||||
        "batch": "lote",
 | 
			
		||||
        "mode": {
 | 
			
		||||
            "label": "Modo", 
 | 
			
		||||
            "num-msgs": "Agrupar por número de mensagens", 
 | 
			
		||||
            "interval": "Agrupar por intervalo de tempo", 
 | 
			
		||||
            "label": "Modo",
 | 
			
		||||
            "num-msgs": "Agrupar por número de mensagens",
 | 
			
		||||
            "interval": "Agrupar por intervalo de tempo",
 | 
			
		||||
            "concat": "Concatenar sequências"
 | 
			
		||||
        },
 | 
			
		||||
        "count": {
 | 
			
		||||
 
 | 
			
		||||
@@ -874,6 +874,7 @@
 | 
			
		||||
        "objectSend":"Отправлять сообщение для каждой пары ключ/значение",
 | 
			
		||||
        "strBuff":"<b>Строка</b> / <b>Буфер</b>",
 | 
			
		||||
        "array":"<b>Массив</b>",
 | 
			
		||||
        "splitThe": "Pазделить",
 | 
			
		||||
        "splitUsing":"С помощью",
 | 
			
		||||
        "splitLength":"Фикс. длина",
 | 
			
		||||
        "stream":"Обрабатывать как поток сообщений",
 | 
			
		||||
 
 | 
			
		||||
@@ -997,6 +997,7 @@
 | 
			
		||||
    "objectSend": "每个键值对作为单个消息发送",
 | 
			
		||||
    "strBuff": "<b>字符串</b> / <b>Buffer</b>",
 | 
			
		||||
    "array": "<b>数组</b>",
 | 
			
		||||
    "splitThe": "Split",
 | 
			
		||||
    "splitUsing": "拆分使用",
 | 
			
		||||
    "splitLength": "固定长度",
 | 
			
		||||
    "stream": "作为消息流处理",
 | 
			
		||||
 
 | 
			
		||||
@@ -866,6 +866,7 @@
 | 
			
		||||
        "objectSend": "每個鍵值對作為單個消息發送",
 | 
			
		||||
        "strBuff": "<b>字串</b> / <b>Buffer</b>",
 | 
			
		||||
        "array": "<b>陣列</b>",
 | 
			
		||||
        "splitThe": "Split",
 | 
			
		||||
        "splitUsing": "拆分使用",
 | 
			
		||||
        "splitLength": "固定長度",
 | 
			
		||||
        "stream": "作為消息流處理",
 | 
			
		||||
 
 | 
			
		||||
@@ -98,7 +98,7 @@ describe('BATCH node', function() {
 | 
			
		||||
                var n2 = helper.getNode("n2");
 | 
			
		||||
                check_data(n1, n2, results, done);
 | 
			
		||||
                for(var i = 0; i < 6; i++) {
 | 
			
		||||
                    n1.receive({payload: i});
 | 
			
		||||
                    n1.receive({payload: i, parts: { count:6, index:i }});
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
@@ -168,6 +168,25 @@ describe('BATCH node', function() {
 | 
			
		||||
            check_count(flow, results, done);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should create seq. with count (more sent than count)', function(done) {
 | 
			
		||||
            var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "count", count: 4, overlap: 0, interval: 10, allowEmptySequence: false, topics: [], wires:[["n2"]]},
 | 
			
		||||
                        {id:"n2", type:"helper"}];
 | 
			
		||||
            var results = [
 | 
			
		||||
                [0, 1, 2, 3]
 | 
			
		||||
            ];
 | 
			
		||||
            check_count(flow, results, done);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should create seq. with count and terminate early if parts honoured', function(done) {
 | 
			
		||||
            var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "count", count: 4, overlap: 0, interval: 10, allowEmptySequence:false, honourParts:true, topics: [], wires:[["n2"]]},
 | 
			
		||||
                        {id:"n2", type:"helper"}];
 | 
			
		||||
            var results = [
 | 
			
		||||
                [0, 1, 2, 3],
 | 
			
		||||
                [4, 5]
 | 
			
		||||
            ];
 | 
			
		||||
            check_count(flow, results, done);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        it('should create seq. with count and overlap', function(done) {
 | 
			
		||||
            var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "count", count: 3, overlap: 2, interval: 10, allowEmptySequence: false, topics: [], wires:[["n2"]]},
 | 
			
		||||
                        {id:"n2", type:"helper"}];
 | 
			
		||||
@@ -455,7 +474,7 @@ describe('BATCH node', function() {
 | 
			
		||||
        function mapiDoneTestHelper(done, mode, count, overlap, interval, allowEmptySequence, msgAndTimings) {
 | 
			
		||||
            const completeNode = require("nr-test-utils").require("@node-red/nodes/core/common/24-complete.js");
 | 
			
		||||
            const catchNode = require("nr-test-utils").require("@node-red/nodes/core/common/25-catch.js");
 | 
			
		||||
            const flow = [{id:"batchNode1", type:"batch", name: "BatchNode", mode, count, overlap, interval, 
 | 
			
		||||
            const flow = [{id:"batchNode1", type:"batch", name: "BatchNode", mode, count, overlap, interval,
 | 
			
		||||
                           allowEmptySequence, topics: [{topic: "TA"}], wires:[[]]},
 | 
			
		||||
                          {id:"completeNode1",type:"complete",scope: ["batchNode1"],uncaught:false,wires:[["helperNode1"]]},
 | 
			
		||||
                          {id:"catchNode1", type:"catch",scope: ["batchNode1"],uncaught:false,wires:[["helperNode1"]]},
 | 
			
		||||
@@ -482,13 +501,13 @@ describe('BATCH node', function() {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        it('should call done() when message is sent (mode: count)', function(done) {
 | 
			
		||||
            mapiDoneTestHelper(done, "count", 2, 0, 2, false, [ 
 | 
			
		||||
            mapiDoneTestHelper(done, "count", 2, 0, 2, false, [
 | 
			
		||||
                { msg: {payload: 0}, delay: 0, avr: 0, var: 100},
 | 
			
		||||
                { msg: {payload: 1}, delay: 0, avr: 0, var: 100}
 | 
			
		||||
            ]);
 | 
			
		||||
        });
 | 
			
		||||
        it('should call done() when reset (mode: count)', function(done) {
 | 
			
		||||
            mapiDoneTestHelper(done, "count", 2, 0, 2, false, [ 
 | 
			
		||||
            mapiDoneTestHelper(done, "count", 2, 0, 2, false, [
 | 
			
		||||
                { msg: {payload: 0}, delay: 0, avr: 200, var: 100},
 | 
			
		||||
                { msg: {payload: 1, reset:true}, delay: 200, avr: 200, var: 100}
 | 
			
		||||
            ]);
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user