mirror of
				https://github.com/node-red/node-red.git
				synced 2025-03-01 10:36:34 +00:00 
			
		
		
		
	Compare commits
	
		
			64 Commits
		
	
	
		
			wiring-twe
			...
			3.1.0-beta
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 4d3e3a73fd | ||
|  | 1ffef393c2 | ||
|  | 8b7b3e22d7 | ||
|  | 877aa75e4e | ||
|  | d21c0758b1 | ||
|  | 21be329008 | ||
|  | e7b27ce7fb | ||
|  | fb2c0e5441 | ||
|  | 29898ea68f | ||
|  | 9dcb8a729c | ||
|  | b66237efa8 | ||
|  | 6d2f855227 | ||
|  | 638aa0372b | ||
|  | d5baa402c8 | ||
|  | ec7e594ec1 | ||
|  | 3fad690d1e | ||
|  | 15973768e2 | ||
|  | 0d73a4b013 | ||
|  | 50f11faf1f | ||
|  | cfb7406fb8 | ||
|  | 9008f063c3 | ||
|  | 368ac4ed5c | ||
|  | 6ac2e703a6 | ||
|  | cd9a5f112a | ||
|  | f3847a17f3 | ||
|  | 1fa4aaf706 | ||
|  | db108a37cf | ||
|  | a3e41d4f35 | ||
|  | 80e33489d9 | ||
|  | 271b1327c7 | ||
|  | 5d990ff4c5 | ||
|  | 69aacc6256 | ||
|  | a5066d529f | ||
|  | 5bf034f3c1 | ||
|  | 0743ff371c | ||
|  | 7481b78b16 | ||
|  | bd589aa140 | ||
|  | 8fa1d4def5 | ||
|  | fe3626035c | ||
|  | c4019bd91d | ||
|  | 18e1b670ca | ||
|  | cd76c934b6 | ||
|  | db98641a32 | ||
|  | 59745fec0b | ||
|  | 8bcaea7830 | ||
|  | 3209777aba | ||
|  | f196493402 | ||
|  | 1c5fdb6ab6 | ||
|  | 3ed530ed9e | ||
|  | 8db2972288 | ||
|  | 51a0b68d8e | ||
|  | 2fdbe12a7f | ||
|  | d8f4f92e1d | ||
|  | 610cb170e7 | ||
|  | 234e92db12 | ||
|  | 1ad67d5c73 | ||
|  | 3b38669c04 | ||
|  | 74ab03288b | ||
|  | c1ea3380eb | ||
|  | 694fdebc71 | ||
|  | 1cbd910e5d | ||
|  | b102ef512e | ||
|  | 4047612b96 | ||
|  | 940512fb2c | 
							
								
								
									
										30
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										30
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -1,3 +1,33 @@ | ||||
| #### 3.1.0-beta.4: Beta Release | ||||
|  | ||||
|  Editor | ||||
|  | ||||
|  - Add Japanese translation for 3.1.0 (#4252) @kazuhitoyokoi | ||||
|  - Improve Catalogue visibility (#4248) @Steve-Mcl | ||||
|  - Add support for wiring and moving junctions on touch device (#4244) @Steve-Mcl | ||||
|  - Show errors and statuses of config nodes in the sidebar when no catch node is available (#4231) @bvmensvoort | ||||
|  - Improve wiring for horizontally aligned nodes (#4232) @knolleary | ||||
|  - French translation of Welcome Tours (#4200) @GogoVega | ||||
|  - French translation of v3.1.0-beta.3 changes (#4199) @GogoVega | ||||
|  - add Japanese message for 3.1.0 beta 3 (#4209) @HiroyasuNishiyama | ||||
|  - Dont clone the group nodes `node` array when saving edits (#4208) @Steve-Mcl | ||||
|  | ||||
|  Runtime | ||||
|  | ||||
|  - Add NR_SUBFLOW_NAME/ID/PATH env vars (#4250) @knolleary | ||||
|  - Evaluate all env vars as part of async flow start (#4230) @knolleary | ||||
|  - Add support for httpStatic middleware (#4229) @knolleary | ||||
|  | ||||
|  Nodes | ||||
|  | ||||
|  - Fix JSONata in file nodes (#4246) @kazuhitoyokoi | ||||
|  - Fix timeout icon in function and link call nodes (#4253) @kazuhitoyokoi | ||||
|  - Fix connection keep-alive in http request node (#4228) @knolleary | ||||
|  - adding timeout attribute to function node (#4177) @k1ln | ||||
|  - Fix manual mode join when multiple sequences being handled (#4143) @BitCaesar | ||||
|  - Fix delay node flush issue (#4203) @dceejay | ||||
|  - Update status and catch node labels in group mode (#4207) @Steve-Mcl | ||||
|  | ||||
| #### 3.1.0-beta.3: Beta Release | ||||
|  | ||||
| Editor | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "node-red", | ||||
|     "version": "3.1.0-beta.3", | ||||
|     "version": "3.1.0-beta.4", | ||||
|     "description": "Low-code programming for event-driven applications", | ||||
|     "homepage": "http://nodered.org", | ||||
|     "license": "Apache-2.0", | ||||
| @@ -112,7 +112,7 @@ | ||||
|         "mermaid": "^9.4.3", | ||||
|         "minami": "1.2.3", | ||||
|         "mocha": "9.2.2", | ||||
|         "node-red-node-test-helper": "^0.3.1", | ||||
|         "node-red-node-test-helper": "^0.3.2", | ||||
|         "nodemon": "2.0.20", | ||||
|         "proxy": "^1.0.2", | ||||
|         "sass": "1.62.1", | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "@node-red/editor-api", | ||||
|     "version": "3.1.0-beta.3", | ||||
|     "version": "3.1.0-beta.4", | ||||
|     "license": "Apache-2.0", | ||||
|     "main": "./lib/index.js", | ||||
|     "repository": { | ||||
| @@ -16,8 +16,8 @@ | ||||
|         } | ||||
|     ], | ||||
|     "dependencies": { | ||||
|         "@node-red/util": "3.1.0-beta.3", | ||||
|         "@node-red/editor-client": "3.1.0-beta.3", | ||||
|         "@node-red/util": "3.1.0-beta.4", | ||||
|         "@node-red/editor-client": "3.1.0-beta.4", | ||||
|         "bcryptjs": "2.4.3", | ||||
|         "body-parser": "1.20.2", | ||||
|         "clone": "2.1.2", | ||||
|   | ||||
| @@ -416,6 +416,7 @@ | ||||
|         }, | ||||
|         "errors": { | ||||
|             "noNodesSelected": "<strong>Cannot create subflow</strong>: no nodes selected", | ||||
|             "acrossMultipleGroups": "Cannot create subflow across multiple groups", | ||||
|             "multipleInputsToSelection": "<strong>Cannot create subflow</strong>: multiple inputs to selection" | ||||
|         } | ||||
|     }, | ||||
| @@ -586,6 +587,7 @@ | ||||
|         "editor": { | ||||
|             "title": "Manage palette", | ||||
|             "palette": "Palette", | ||||
|             "allCatalogs": "All Catalogs", | ||||
|             "times": { | ||||
|                 "seconds": "seconds ago", | ||||
|                 "minutes": "minutes ago", | ||||
|   | ||||
| @@ -416,6 +416,7 @@ | ||||
|         }, | ||||
|         "errors": { | ||||
|             "noNodesSelected": "<strong>サブフローを作成できません</strong>: ノードが選択されていません", | ||||
|             "acrossMultipleGroups": "複数のグループをまたがるサブフローは作成できません", | ||||
|             "multipleInputsToSelection": "<strong>サブフローを作成できません</strong>: 複数の入力が選択されています" | ||||
|         } | ||||
|     }, | ||||
| @@ -586,6 +587,7 @@ | ||||
|         "editor": { | ||||
|             "title": "パレットの管理", | ||||
|             "palette": "パレット", | ||||
|             "allCatalogs": "全カタログ", | ||||
|             "times": { | ||||
|                 "seconds": "数秒前", | ||||
|                 "minutes": "数分前", | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "@node-red/editor-client", | ||||
|     "version": "3.1.0-beta.3", | ||||
|     "version": "3.1.0-beta.4", | ||||
|     "license": "Apache-2.0", | ||||
|     "repository": { | ||||
|         "type": "git", | ||||
|   | ||||
| @@ -249,7 +249,10 @@ RED.keyboard = (function() { | ||||
|         // One exception is shortcuts that include both Cmd and Ctrl. We don't | ||||
|         // support them - but we need to make sure we don't block browser-specific | ||||
|         // shortcuts (such as Cmd-Ctrl-F for fullscreen). | ||||
|         if ((evt.ctrlKey || evt.metaKey) && (evt.ctrlKey !== evt.metaKey)) { | ||||
|         if (evt.ctrlKey && evt.metaKey) { | ||||
|             return null; // dont handle both cmd+ctrl - let browser handle this | ||||
|         } | ||||
|         if (evt.ctrlKey || evt.metaKey) { | ||||
|             slot = slot.ctrl; | ||||
|         } | ||||
|         if (slot && evt.shiftKey) { | ||||
|   | ||||
| @@ -16,15 +16,17 @@ | ||||
| RED.palette.editor = (function() { | ||||
|  | ||||
|     var disabled = false; | ||||
|  | ||||
|     let catalogues = [] | ||||
|     const loadedCatalogs = [] | ||||
|     var editorTabs; | ||||
|     var filterInput; | ||||
|     var searchInput; | ||||
|     var nodeList; | ||||
|     var packageList; | ||||
|     var loadedList = []; | ||||
|     var filteredList = []; | ||||
|     var loadedIndex = {}; | ||||
|     let filterInput; | ||||
|     let searchInput; | ||||
|     let nodeList; | ||||
|     let packageList; | ||||
|     let fullList = [] | ||||
|     let loadedList = []; | ||||
|     let filteredList = []; | ||||
|     let loadedIndex = {}; | ||||
|  | ||||
|     var typesInUse = {}; | ||||
|     var nodeEntries = {}; | ||||
| @@ -162,7 +164,6 @@ RED.palette.editor = (function() { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     function getContrastingBorder(rgbColor){ | ||||
|         var parts = /^rgba?\(\s*(\d+),\s*(\d+),\s*(\d+)[,)]/.exec(rgbColor); | ||||
|         if (parts) { | ||||
| @@ -369,10 +370,10 @@ RED.palette.editor = (function() { | ||||
|     var activeSort = sortModulesRelevance; | ||||
|  | ||||
|     function handleCatalogResponse(err,catalog,index,v) { | ||||
|         const url = catalog.url | ||||
|         catalogueLoadStatus.push(err||v); | ||||
|         if (!err) { | ||||
|             if (v.modules) { | ||||
|                 var a = false; | ||||
|                 v.modules = v.modules.filter(function(m) { | ||||
|                     if (RED.utils.checkModuleAllowed(m.id,m.version,installAllowList,installDenyList)) { | ||||
|                         loadedIndex[m.id] = m; | ||||
| @@ -389,13 +390,14 @@ RED.palette.editor = (function() { | ||||
|                             m.timestamp = 0; | ||||
|                         } | ||||
|                         m.index = m.index.join(",").toLowerCase(); | ||||
|                         m.catalog = catalog; | ||||
|                         m.catalogIndex = index; | ||||
|                         return true; | ||||
|                     } | ||||
|                     return false; | ||||
|                 }) | ||||
|                 loadedList = loadedList.concat(v.modules); | ||||
|             } | ||||
|             searchInput.searchBox('count',loadedList.length); | ||||
|         } else { | ||||
|             catalogueLoadErrors = true; | ||||
|         } | ||||
| @@ -404,7 +406,7 @@ RED.palette.editor = (function() { | ||||
|         } | ||||
|         if (catalogueLoadStatus.length === catalogueCount) { | ||||
|             if (catalogueLoadErrors) { | ||||
|                 RED.notify(RED._('palette.editor.errors.catalogLoadFailed',{url: catalog}),"error",false,8000); | ||||
|                 RED.notify(RED._('palette.editor.errors.catalogLoadFailed',{url: url}),"error",false,8000); | ||||
|             } | ||||
|             var delta = 250-(Date.now() - catalogueLoadStart); | ||||
|             setTimeout(function() { | ||||
| @@ -416,12 +418,13 @@ RED.palette.editor = (function() { | ||||
|  | ||||
|     function initInstallTab() { | ||||
|         if (loadedList.length === 0) { | ||||
|             fullList = []; | ||||
|             loadedList = []; | ||||
|             loadedIndex = {}; | ||||
|             packageList.editableList('empty'); | ||||
|  | ||||
|             $(".red-ui-palette-module-shade-status").text(RED._('palette.editor.loading')); | ||||
|             var catalogues = RED.settings.theme('palette.catalogues')||['https://catalogue.nodered.org/catalogue.json']; | ||||
|  | ||||
|             catalogueLoadStatus = []; | ||||
|             catalogueLoadErrors = false; | ||||
|             catalogueCount = catalogues.length; | ||||
| @@ -431,23 +434,92 @@ RED.palette.editor = (function() { | ||||
|             $("#red-ui-palette-module-install-shade").show(); | ||||
|             catalogueLoadStart = Date.now(); | ||||
|             var handled = 0; | ||||
|             catalogues.forEach(function(catalog,index) { | ||||
|                 $.getJSON(catalog, {_: new Date().getTime()},function(v) { | ||||
|                     handleCatalogResponse(null,catalog,index,v); | ||||
|             loadedCatalogs.length = 0; // clear the loadedCatalogs array | ||||
|             for (let index = 0; index < catalogues.length; index++) { | ||||
|                 const url = catalogues[index]; | ||||
|                 $.getJSON(url, {_: new Date().getTime()},function(v) { | ||||
|                     loadedCatalogs.push({ index: index, url: url, name: v.name, updated_at: v.updated_at, modules_count: (v.modules || []).length }) | ||||
|                     handleCatalogResponse(null,{ url: url, name: v.name},index,v); | ||||
|                     refreshNodeModuleList(); | ||||
|                 }).fail(function(jqxhr, textStatus, error) { | ||||
|                     console.warn("Error loading catalog",catalog,":",error); | ||||
|                     handleCatalogResponse(jqxhr,catalog,index); | ||||
|                     console.warn("Error loading catalog",url,":",error); | ||||
|                     handleCatalogResponse(jqxhr,url,index); | ||||
|                 }).always(function() { | ||||
|                     handled++; | ||||
|                     if (handled === catalogueCount) { | ||||
|                         searchInput.searchBox('change'); | ||||
|                         //sort loadedCatalogs by e.index ascending | ||||
|                         loadedCatalogs.sort((a, b) => a.index - b.index) | ||||
|                         updateCatalogFilter(loadedCatalogs) | ||||
|                     } | ||||
|                 }) | ||||
|             }); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Refreshes the catalog filter dropdown and updates local variables | ||||
|      * @param {[{url:String, name:String, updated_at:String, modules_count:Number}]} catalogEntries | ||||
|      */ | ||||
|     function updateCatalogFilter(catalogEntries, maxRetry = 3) { | ||||
|         // clean up existing filters | ||||
|         const catalogSelection = $('#red-catalogue-filter-select') | ||||
|         if (catalogSelection.length === 0) { | ||||
|             // sidebar not yet loaded (red-catalogue-filter-select is not in dom) | ||||
|             if (maxRetry > 0) { | ||||
|                // console.log("updateCatalogFilter: sidebar not yet loaded, retrying in 100ms") | ||||
|                 // try again in 100ms | ||||
|                 setTimeout(() => { | ||||
|                     updateCatalogFilter(catalogEntries, maxRetry - 1) | ||||
|                 }, 100); | ||||
|                 return; | ||||
|             }  | ||||
|             return; // give up | ||||
|         } | ||||
|         catalogSelection.off("change") // remove any existing event handlers | ||||
|         catalogSelection.attr('disabled', 'disabled') | ||||
|         catalogSelection.empty() | ||||
|         catalogSelection.append($('<option>', { value: "loading", text: RED._('palette.editor.loading'), disabled: true, selected: true })); | ||||
|          | ||||
|         fullList = loadedList.slice() | ||||
|         catalogSelection.empty() // clear the select list | ||||
|  | ||||
|         // loop through catalogTypes, and an option entry per catalog | ||||
|         for (let index = 0; index < catalogEntries.length; index++) { | ||||
|             const catalog = catalogEntries[index]; | ||||
|             catalogSelection.append(`<option value="${catalog.name}">${catalog.name}</option>`) | ||||
|         } | ||||
|         // select the 1st option in the select list | ||||
|         catalogSelection.val(catalogSelection.find('option:first').val()) | ||||
|          | ||||
|         // if there is only 1 catalog, hide the select | ||||
|         if (catalogEntries.length > 1) { | ||||
|             catalogSelection.prepend(`<option value="all">${RED._('palette.editor.allCatalogs')}</option>`) | ||||
|             catalogSelection.removeAttr('disabled') // permit the user to select a catalog | ||||
|         } | ||||
|         // refresh the searchInput counter and trigger a change | ||||
|         filterByCatalog(catalogSelection.val()) | ||||
|         searchInput.searchBox('change'); | ||||
|  | ||||
|         // hook up the change event handler | ||||
|         catalogSelection.on("change", function() { | ||||
|             const selectedCatalog = $(this).val(); | ||||
|             filterByCatalog(selectedCatalog); | ||||
|             searchInput.searchBox('change'); | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     function filterByCatalog(selectedCatalog) { | ||||
|         if (loadedCatalogs.length <= 1 || selectedCatalog === "all") { | ||||
|             loadedList = fullList.slice(); | ||||
|         } else { | ||||
|             loadedList = fullList.filter(function(m) { | ||||
|                 return (m.catalog.name === selectedCatalog); | ||||
|             }) | ||||
|         } | ||||
|         refreshFilteredItems(); | ||||
|         searchInput.searchBox('count',filteredList.length+" / "+loadedList.length); | ||||
|     } | ||||
|  | ||||
|     function refreshFilteredItems() { | ||||
|         packageList.editableList('empty'); | ||||
|         var currentFilter = searchInput.searchBox('value').trim(); | ||||
| @@ -462,7 +534,6 @@ RED.palette.editor = (function() { | ||||
|         if (filteredList.length === 0) { | ||||
|             packageList.editableList('addItem',{}); | ||||
|         } | ||||
|  | ||||
|         if (filteredList.length > 10) { | ||||
|             packageList.editableList('addItem',{start:10,more:filteredList.length-10}) | ||||
|         } | ||||
| @@ -492,6 +563,7 @@ RED.palette.editor = (function() { | ||||
|     var updateDenyList = []; | ||||
|  | ||||
|     function init() { | ||||
|         catalogues = RED.settings.theme('palette.catalogues')||['https://catalogue.nodered.org/catalogue.json'] | ||||
|         if (RED.settings.get('externalModules.palette.allowInstall', true) === false) { | ||||
|             return; | ||||
|         } | ||||
| @@ -669,7 +741,8 @@ RED.palette.editor = (function() { | ||||
|             }); | ||||
|  | ||||
|  | ||||
|         nodeList = $('<ol>',{id:"red-ui-palette-module-list", style:"position: absolute;top: 35px;bottom: 0;left: 0;right: 0px;"}).appendTo(modulesTab).editableList({ | ||||
|         nodeList = $('<ol>',{id:"red-ui-palette-module-list"}).appendTo(modulesTab).editableList({ | ||||
|             class: "scrollable", | ||||
|             addButton: false, | ||||
|             scrollOnAdd: false, | ||||
|             sort: function(A,B) { | ||||
| @@ -800,21 +873,20 @@ RED.palette.editor = (function() { | ||||
|                     $('<div>',{class:"red-ui-search-empty"}).text(RED._('search.empty')).appendTo(container); | ||||
|                 } | ||||
|             } | ||||
|         }); | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     function createInstallTab(content) { | ||||
|         var installTab = $('<div>',{class:"red-ui-palette-editor-tab hide"}).appendTo(content); | ||||
|  | ||||
|         const installTab = $('<div>',{class:"red-ui-palette-editor-tab", style: "display: none;"}).appendTo(content); | ||||
|         editorTabs.addTab({ | ||||
|             id: 'install', | ||||
|             label: RED._('palette.editor.tab-install'), | ||||
|             content: installTab | ||||
|         }) | ||||
|  | ||||
|         var toolBar = $('<div>',{class:"red-ui-palette-editor-toolbar"}).appendTo(installTab); | ||||
|  | ||||
|         var searchDiv = $('<div>',{class:"red-ui-palette-search"}).appendTo(installTab); | ||||
|         const toolBar = $('<div>',{class:"red-ui-palette-editor-toolbar"}).appendTo(installTab); | ||||
|          | ||||
|         const searchDiv = $('<div>',{class:"red-ui-palette-search"}).appendTo(installTab); | ||||
|         searchInput = $('<input type="text" data-i18n="[placeholder]palette.search"></input>') | ||||
|             .appendTo(searchDiv) | ||||
|             .searchBox({ | ||||
| @@ -831,19 +903,25 @@ RED.palette.editor = (function() { | ||||
|                         searchInput.searchBox('count',loadedList.length); | ||||
|                         packageList.editableList('empty'); | ||||
|                         packageList.editableList('addItem',{count:loadedList.length}); | ||||
|  | ||||
|                     } | ||||
|                 } | ||||
|             }); | ||||
|  | ||||
|         $('<span>').text(RED._("palette.editor.sort")+' ').appendTo(toolBar); | ||||
|         var sortGroup = $('<span class="button-group"></span>').appendTo(toolBar); | ||||
|         var sortRelevance = $('<a href="#" class="red-ui-palette-editor-install-sort-option red-ui-sidebar-header-button-toggle selected"><i class="fa fa-sort-amount-desc"></i></a>').appendTo(sortGroup); | ||||
|         var sortAZ = $('<a href="#" class="red-ui-palette-editor-install-sort-option red-ui-sidebar-header-button-toggle" data-i18n="palette.editor.sortAZ"></a>').appendTo(sortGroup); | ||||
|         var sortRecent = $('<a href="#" class="red-ui-palette-editor-install-sort-option red-ui-sidebar-header-button-toggle" data-i18n="palette.editor.sortRecent"></a>').appendTo(sortGroup); | ||||
|         const catalogSelection = $('<select id="red-catalogue-filter-select">').appendTo(toolBar); | ||||
|         catalogSelection.addClass('red-ui-palette-editor-catalogue-filter'); | ||||
|   | ||||
|         const toolBarActions = $('<div>',{class:"red-ui-palette-editor-toolbar-actions"}).appendTo(toolBar); | ||||
|  | ||||
|         $('<span>').text(RED._("palette.editor.sort")+' ').appendTo(toolBarActions); | ||||
|         const sortGroup = $('<span class="button-group"></span>').appendTo(toolBarActions); | ||||
|         const sortRelevance = $('<a href="#" class="red-ui-palette-editor-install-sort-option red-ui-sidebar-header-button-toggle selected"><i class="fa fa-sort-amount-desc"></i></a>').appendTo(sortGroup); | ||||
|         const sortAZ = $('<a href="#" class="red-ui-palette-editor-install-sort-option red-ui-sidebar-header-button-toggle"><i class="fa fa-sort-alpha-asc"></i></a>').appendTo(sortGroup); | ||||
|         const sortRecent = $('<a href="#" class="red-ui-palette-editor-install-sort-option red-ui-sidebar-header-button-toggle"><i class="fa fa-calendar"></i></a>').appendTo(sortGroup); | ||||
|         RED.popover.tooltip(sortAZ,RED._("palette.editor.sortAZ")); | ||||
|         RED.popover.tooltip(sortRecent,RED._("palette.editor.sortRecent")); | ||||
|  | ||||
|  | ||||
|         var sortOpts = [ | ||||
|         const sortOpts = [ | ||||
|             {button: sortRelevance, func: sortModulesRelevance}, | ||||
|             {button: sortAZ, func: sortModulesAZ}, | ||||
|             {button: sortRecent, func: sortModulesRecent} | ||||
| @@ -861,7 +939,7 @@ RED.palette.editor = (function() { | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         var refreshSpan = $('<span>').appendTo(toolBar); | ||||
|         var refreshSpan = $('<span>').appendTo(toolBarActions); | ||||
|         var refreshButton = $('<a href="#" class="red-ui-sidebar-header-button"><i class="fa fa-refresh"></i></a>').appendTo(refreshSpan); | ||||
|         refreshButton.on("click", function(e) { | ||||
|             e.preventDefault(); | ||||
| @@ -871,7 +949,8 @@ RED.palette.editor = (function() { | ||||
|         }) | ||||
|         RED.popover.tooltip(refreshButton,RED._("palette.editor.refresh")); | ||||
|  | ||||
|         packageList = $('<ol>',{style:"position: absolute;top: 79px;bottom: 0;left: 0;right: 0px;"}).appendTo(installTab).editableList({ | ||||
|         packageList = $('<ol>').appendTo(installTab).editableList({ | ||||
|             class: "scrollable", | ||||
|             addButton: false, | ||||
|             scrollOnAdd: false, | ||||
|             addItem: function(container,i,object) { | ||||
| @@ -906,6 +985,9 @@ RED.palette.editor = (function() { | ||||
|                     var metaRow = $('<div class="red-ui-palette-module-meta"></div>').appendTo(headerRow); | ||||
|                     $('<span class="red-ui-palette-module-version"><i class="fa fa-tag"></i> '+entry.version+'</span>').appendTo(metaRow); | ||||
|                     $('<span class="red-ui-palette-module-updated"><i class="fa fa-calendar"></i> '+formatUpdatedAt(entry.updated_at)+'</span>').appendTo(metaRow); | ||||
|                     if (loadedCatalogs.length > 1) { | ||||
|                         $('<span class="red-ui-palette-module-updated"><i class="fa fa-cubes"></i>' + (entry.catalog.name || entry.catalog.url) + '</span>').appendTo(metaRow); | ||||
|                     } | ||||
|  | ||||
|                     var duplicateType = false; | ||||
|                     if (entry.types && entry.types.length > 0) { | ||||
| @@ -952,9 +1034,10 @@ RED.palette.editor = (function() { | ||||
|                 } | ||||
|             } | ||||
|         }); | ||||
|          | ||||
|  | ||||
|         if (RED.settings.get('externalModules.palette.allowUpload', true) !== false) { | ||||
|             var uploadSpan = $('<span class="button-group">').prependTo(toolBar); | ||||
|             var uploadSpan = $('<span class="button-group">').prependTo(toolBarActions); | ||||
|             var uploadButton = $('<button type="button" class="red-ui-sidebar-header-button red-ui-palette-editor-upload-button"><label><i class="fa fa-upload"></i><form id="red-ui-palette-editor-upload-form" enctype="multipart/form-data"><input name="tarball" type="file" accept=".tgz"></label></button>').appendTo(uploadSpan); | ||||
|  | ||||
|             var uploadInput = uploadButton.find('input[type="file"]'); | ||||
|   | ||||
| @@ -667,7 +667,7 @@ RED.subflow = (function() { | ||||
|         for (i=0; i<nodeList.length;i++) { | ||||
|             if (nodeList[i].g && !includedGroups.has(nodeList[i].g)) { | ||||
|                 if (containingGroup !== nodeList[i].g) { | ||||
|                     RED.notify("Cannot create subflow across multiple groups","error"); | ||||
|                     RED.notify(RED._("subflow.errors.acrossMultipleGroups"), "error"); | ||||
|                     return; | ||||
|                 } | ||||
|             } | ||||
|   | ||||
| @@ -1305,6 +1305,39 @@ RED.view.tools = (function() { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Determine if a point is within a node | ||||
|      * @param {*} node - A Node or Junction node | ||||
|      * @param {[Number,Number]} mouse_position The x,y position of the mouse | ||||
|      * @param {Number} [marginX=0] - A margin to add or deduct from the x position (to increase the hit area) | ||||
|      * @param {Number} [marginY=0] - A margin to add or deduct from the y position (to increase the hit area) | ||||
|      * @returns  | ||||
|      */ | ||||
|     function isPointInNode (node, [x, y], marginX, marginY) { | ||||
|         marginX = marginX || 0 | ||||
|         marginY = marginY || 0 | ||||
|  | ||||
|         let w = node.w || 10 // junctions dont have any w or h value | ||||
|         let h = node.h || 10 | ||||
|         let x1, x2, y1, y2 | ||||
|          | ||||
|         if (node.type === "junction" || node.type === "group") { | ||||
|             // x/y is the top left of the node | ||||
|             x1 = node.x | ||||
|             y1 = node.y | ||||
|             x2 = node.x + w | ||||
|             y2 = node.y + h | ||||
|         } else { | ||||
|             // x/y is the center of the node | ||||
|             const [xMid, yMid] =  [w/2, h/2] | ||||
|             x1 = node.x - xMid | ||||
|             y1 = node.y - yMid | ||||
|             x2 = node.x + xMid | ||||
|             y2 = node.y + yMid | ||||
|         } | ||||
|         return (x >= (x1 - marginX) && x <= (x2 + marginX) && y >= (y1 - marginY) && y <= (y2 + marginY)) | ||||
|     } | ||||
|  | ||||
|     return { | ||||
|         init: function() { | ||||
|             RED.actions.add("core:show-selected-node-labels", function() { setSelectedNodeLabelState(true); }) | ||||
| @@ -1387,7 +1420,8 @@ RED.view.tools = (function() { | ||||
|          * @param  {Number} dy | ||||
|          */ | ||||
|         moveSelection: moveSelection, | ||||
|         calculateGridSnapOffsets: calculateGridSnapOffsets | ||||
|         calculateGridSnapOffsets: calculateGridSnapOffsets, | ||||
|         isPointInNode: isPointInNode | ||||
|     } | ||||
|  | ||||
| })(); | ||||
|   | ||||
| @@ -101,7 +101,7 @@ RED.view = (function() { | ||||
|  | ||||
|     // Note: these are the permitted status colour aliases. The actual RGB values | ||||
|     //       are set in the CSS - flow.scss/colors.scss | ||||
|     var status_colours = { | ||||
|     const status_colours = { | ||||
|         "red":    "#c00", | ||||
|         "green":  "#5a8", | ||||
|         "yellow": "#F9DF31", | ||||
| @@ -110,19 +110,32 @@ RED.view = (function() { | ||||
|         "gray":   "#d3d3d3" | ||||
|     } | ||||
|  | ||||
|     var PORT_TYPE_INPUT = 1; | ||||
|     var PORT_TYPE_OUTPUT = 0; | ||||
|     const PORT_TYPE_INPUT = 1; | ||||
|     const PORT_TYPE_OUTPUT = 0; | ||||
|  | ||||
|     var chart; | ||||
|     var outer; | ||||
|     /** | ||||
|      * The jQuery object for the workspace chart `#red-ui-workspace-chart` div element | ||||
|      * @type {JQuery<HTMLElement>} #red-ui-workspace-chart HTML Element  | ||||
|      */  | ||||
|     let chart; | ||||
|     /** | ||||
|      * The d3 object `#red-ui-workspace-chart` svg element | ||||
|      * @type {d3.Selection<HTMLElement, Any, Any, Any>} | ||||
|      */  | ||||
|     let outer; | ||||
|     /**  | ||||
|      * The d3 object `#red-ui-workspace-chart` svg element (specifically for events) | ||||
|      * @type {d3.Selection<d3.BaseType, any, any, any>} | ||||
|      */ | ||||
|     var eventLayer; | ||||
|     var gridLayer; | ||||
|     var linkLayer; | ||||
|     var junctionLayer; | ||||
|     var dragGroupLayer; | ||||
|     var groupSelectLayer; | ||||
|     var nodeLayer; | ||||
|     var groupLayer; | ||||
|  | ||||
|     /** @type {SVGGElement} */ let gridLayer; | ||||
|     /** @type {SVGGElement} */ let linkLayer; | ||||
|     /** @type {SVGGElement} */ let junctionLayer; | ||||
|     /** @type {SVGGElement} */ let dragGroupLayer; | ||||
|     /** @type {SVGGElement} */ let groupSelectLayer; | ||||
|     /** @type {SVGGElement} */ let nodeLayer; | ||||
|     /** @type {SVGGElement} */ let groupLayer; | ||||
|     var drag_lines; | ||||
|  | ||||
|     const movingSet = (function() { | ||||
| @@ -391,16 +404,6 @@ RED.view = (function() { | ||||
|                     touchStartTime = setTimeout(function() { | ||||
|                         touchStartTime = null; | ||||
|                         showTouchMenu(obj,pos); | ||||
|                         //lasso = eventLayer.append("rect") | ||||
|                         //    .attr("ox",point[0]) | ||||
|                         //    .attr("oy",point[1]) | ||||
|                         //    .attr("rx",2) | ||||
|                         //    .attr("ry",2) | ||||
|                         //    .attr("x",point[0]) | ||||
|                         //    .attr("y",point[1]) | ||||
|                         //    .attr("width",0) | ||||
|                         //    .attr("height",0) | ||||
|                         //    .attr("class","nr-ui-view-lasso"); | ||||
|                     },touchLongPressTimeout); | ||||
|                 } | ||||
|                 d3.event.preventDefault(); | ||||
| @@ -3094,22 +3097,38 @@ RED.view = (function() { | ||||
|             } | ||||
|         } | ||||
|         document.body.style.cursor = ""; | ||||
|  | ||||
|         if (mouse_mode == RED.state.JOINING || mouse_mode == RED.state.QUICK_JOINING) { | ||||
|             if (typeof TouchEvent != "undefined" && evt instanceof TouchEvent) { | ||||
|                 var found = false; | ||||
|                 RED.nodes.eachNode(function(n) { | ||||
|                     if (n.z == RED.workspaces.active()) { | ||||
|                         var hw = n.w/2; | ||||
|                         var hh = n.h/2; | ||||
|                         if (n.x-hw<mouse_position[0] && n.x+hw> mouse_position[0] && | ||||
|                             n.y-hh<mouse_position[1] && n.y+hh>mouse_position[1]) { | ||||
|                                 found = true; | ||||
|                                 mouseup_node = n; | ||||
|                                 portType = mouseup_node.inputs>0?PORT_TYPE_INPUT:PORT_TYPE_OUTPUT; | ||||
|                                 portIndex = 0; | ||||
|                 if (RED.view.DEBUG) { console.warn("portMouseUp: TouchEvent", mouse_mode,d,portType,portIndex); } | ||||
|                 const direction = drag_lines[0].portType === PORT_TYPE_INPUT ? PORT_TYPE_OUTPUT : PORT_TYPE_INPUT | ||||
|                 let found = false; | ||||
|                 for (let nodeIdx = 0; nodeIdx < activeNodes.length; nodeIdx++) { | ||||
|                     const n = activeNodes[nodeIdx]; | ||||
|                     if (RED.view.tools.isPointInNode(n, mouse_position)) { | ||||
|                         found = true; | ||||
|                         mouseup_node = n; | ||||
|                         // portType = mouseup_node.inputs > 0 ? PORT_TYPE_INPUT : PORT_TYPE_OUTPUT; | ||||
|                         portType = direction; | ||||
|                         portIndex = 0; | ||||
|                         break | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 if (!found && drag_lines.length > 0 && !drag_lines[0].virtualLink) { | ||||
|                     for (let juncIdx = 0; juncIdx < activeJunctions.length; juncIdx++) { | ||||
|                         // NOTE: a junction is 10px x 10px but the target area is expanded to 30wx20h by adding padding to the bounding box | ||||
|                         const jNode = activeJunctions[juncIdx]; | ||||
|                         if (RED.view.tools.isPointInNode(jNode, mouse_position, 20, 10)) { | ||||
|                             found = true; | ||||
|                             mouseup_node = jNode; | ||||
|                             portType = direction; | ||||
|                             portIndex = 0; | ||||
|                             break | ||||
|                         } | ||||
|                     } | ||||
|                 }); | ||||
|                 } | ||||
|  | ||||
|                 if (!found && activeSubflow) { | ||||
|                     var subflowPorts = []; | ||||
|                     if (activeSubflow.status) { | ||||
| @@ -3121,16 +3140,13 @@ RED.view = (function() { | ||||
|                     if (activeSubflow.out) { | ||||
|                         subflowPorts = subflowPorts.concat(activeSubflow.out) | ||||
|                     } | ||||
|                     for (var i=0;i<subflowPorts.length;i++) { | ||||
|                         var n = subflowPorts[i]; | ||||
|                         var hw = n.w/2; | ||||
|                         var hh = n.h/2; | ||||
|                         if (n.x-hw<mouse_position[0] && n.x+hw> mouse_position[0] && | ||||
|                             n.y-hh<mouse_position[1] && n.y+hh>mouse_position[1]) { | ||||
|                                 found = true; | ||||
|                                 mouseup_node = n; | ||||
|                                 portType = mouseup_node.direction === "in"?PORT_TYPE_OUTPUT:PORT_TYPE_INPUT; | ||||
|                                 portIndex = 0; | ||||
|                     for (var i = 0; i < subflowPorts.length; i++) { | ||||
|                         const sf = subflowPorts[i]; | ||||
|                         if (RED.view.tools.isPointInNode(sf, mouse_position)) { | ||||
|                             found = true; | ||||
|                             mouseup_node = sf; | ||||
|                             portType = mouseup_node.direction === "in" ? PORT_TYPE_OUTPUT : PORT_TYPE_INPUT; | ||||
|                             portIndex = 0; | ||||
|                             break; | ||||
|                         } | ||||
|                     } | ||||
| @@ -5015,16 +5031,25 @@ RED.view = (function() { | ||||
|                 contents.appendChild(junctionOutput); | ||||
|                 junctionOutput.addEventListener("mouseup", portMouseUpProxy); | ||||
|                 junctionOutput.addEventListener("mousedown", portMouseDownProxy); | ||||
|  | ||||
|                 junctionOutput.addEventListener("mouseover", junctionMouseOverProxy); | ||||
|                 junctionOutput.addEventListener("mouseout", junctionMouseOutProxy); | ||||
|                 junctionOutput.addEventListener("touchmove", junctionMouseOverProxy); | ||||
|                 junctionOutput.addEventListener("touchend", portMouseUpProxy); | ||||
|                 junctionOutput.addEventListener("touchstart", portMouseDownProxy); | ||||
|  | ||||
|                 junctionInput.addEventListener("mouseover", junctionMouseOverProxy); | ||||
|                 junctionInput.addEventListener("mouseout", junctionMouseOutProxy); | ||||
|                 junctionInput.addEventListener("touchmove", junctionMouseOverProxy); | ||||
|                 junctionInput.addEventListener("touchend", portMouseUpProxy); | ||||
|                 junctionInput.addEventListener("touchstart", portMouseDownProxy); | ||||
|  | ||||
|                 junctionBack.addEventListener("mouseover", junctionMouseOverProxy); | ||||
|                 junctionBack.addEventListener("mouseout", junctionMouseOutProxy); | ||||
|                 junctionBack.addEventListener("touchmove", junctionMouseOverProxy); | ||||
|  | ||||
|                 // These handlers expect to be registered as d3 events | ||||
|                 d3.select(junctionBack).on("mousedown", nodeMouseDown).on("mouseup", nodeMouseUp); | ||||
|                 d3.select(junctionBack).on("touchstart", nodeMouseDown).on("touchend", nodeMouseUp); | ||||
|  | ||||
|                 junction[0][0].appendChild(contents); | ||||
|             }) | ||||
|   | ||||
| @@ -14,7 +14,7 @@ | ||||
|  * limitations under the License. | ||||
|  **/ | ||||
|  | ||||
| #red-ui-settings-tab-palette { | ||||
|  #red-ui-settings-tab-palette { | ||||
|     height: 100%; | ||||
| } | ||||
|  | ||||
| @@ -28,7 +28,17 @@ | ||||
|     padding: 0; | ||||
|     box-sizing:border-box; | ||||
|     background: var(--red-ui-secondary-background); | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|  | ||||
|     .red-ui-tabs { | ||||
|         flex-shrink: 0; | ||||
|         margin-bottom: 0; | ||||
|     } | ||||
|  | ||||
|     .red-ui-editableList.scrollable { | ||||
|         overflow-y: auto; | ||||
|     } | ||||
|     .red-ui-editableList-container { | ||||
|         border: none; | ||||
|         border-radius: 0; | ||||
| @@ -72,11 +82,9 @@ | ||||
|  | ||||
|     } | ||||
|     .red-ui-palette-editor-tab { | ||||
|         position:absolute; | ||||
|         top:35px; | ||||
|         left:0; | ||||
|         right:0; | ||||
|         bottom:0 | ||||
|         display: flex; | ||||
|         flex-direction: column; | ||||
|         min-height: 0; | ||||
|     } | ||||
|     .red-ui-palette-editor-toolbar { | ||||
|         background: var(--red-ui-primary-background); | ||||
| @@ -84,6 +92,24 @@ | ||||
|         padding: 8px 10px; | ||||
|         border-bottom: 1px solid var(--red-ui-primary-border-color); | ||||
|         text-align: right; | ||||
|         display: flex; | ||||
|         justify-content: space-between; | ||||
|         align-items: center; | ||||
|         flex-wrap: wrap; | ||||
|         gap: 3px 12px; | ||||
|         .red-ui-palette-editor-toolbar-actions { | ||||
|             flex-shrink: 0; | ||||
|             flex-grow: 1; | ||||
|         } | ||||
|         .red-ui-palette-editor-catalogue-filter { | ||||
|             width: unset; | ||||
|             margin: 0; | ||||
|             flex-shrink: 1; | ||||
|             flex-grow: 1; | ||||
|             font-size: 12px; | ||||
|             height: 26px; | ||||
|             padding: 1px; | ||||
|         } | ||||
|     } | ||||
|     .red-ui-palette-module-shade-status { | ||||
|         color: var(--red-ui-secondary-text-color); | ||||
|   | ||||
| @@ -54,7 +54,7 @@ | ||||
| } | ||||
| .red-ui-palette-search { | ||||
|     position: relative; | ||||
|     overflow: hidden; | ||||
|     // overflow: hidden; | ||||
|     background: var(--red-ui-form-input-background); | ||||
|     text-align: center; | ||||
|     height: 35px; | ||||
|   | ||||
| @@ -1,17 +1,17 @@ | ||||
| export default { | ||||
|     version: "3.1.0-beta.3", | ||||
|     version: "3.1.0-beta.4", | ||||
|     steps: [ | ||||
|         { | ||||
|             titleIcon: "fa fa-map-o", | ||||
|             title: { | ||||
|                 "en-US": "Welcome to Node-RED 3.1 Beta 3!", | ||||
|                 "en-US": "Welcome to Node-RED 3.1 Beta 4!", | ||||
|                 "ja": "Node-RED 3.1 ベータ3へようこそ!", | ||||
|                 "fr": "Bienvenue dans Node-RED 3.1 Bêta 3 !" | ||||
|                 "fr": "Bienvenue dans Node-RED 3.1 Bêta 4 !" | ||||
|             }, | ||||
|             description: { | ||||
|                 "en-US": "<p>This is the third beta release for 3.1.0. This is mostly a bug fix release, so you can skip this tour if you've tried the other betas.</p><p>If not, stick around to see what's new in Node-RED 3.1.</p>", | ||||
|                 "ja": "<p>これは3.1.0の3回目のベータリリースです。不具合修正のリリースのため、もし他のベータ版を試したことがある場合は、このツアーを読み飛ばしてもかまいません。</p><p>そうでない場合は、Node-RED 3.1の新機能を確認してください。</p>", | ||||
|                 "fr": "<p>Il s'agit de la troisième bêta de la version 3.1.0. Cette version apporte principalement la correction de bugs, vous pouvez donc ignorer cette visite guidée si vous avez essayé les autres versions bêta.</p><p>Si ce n'est pas le cas, restez dans les parages pour voir les nouveautés de Node-RED 3.1.</p>" | ||||
|                 "en-US": "<p>This is the fourth beta release for 3.1.0. This is mostly a bug fix release, so you can skip this tour if you've tried the other betas.</p><p>If not, stick around to see what's new in Node-RED 3.1.</p>", | ||||
|                 "ja": "<p>これは3.1.0の4回目のベータリリースです。不具合修正のリリースのため、もし他のベータ版を試したことがある場合は、このツアーを読み飛ばしてもかまいません。</p><p>そうでない場合は、Node-RED 3.1の新機能を確認してください。</p>", | ||||
|                 "fr": "<p>Il s'agit de la quatrième bêta de la version 3.1.0. Cette version apporte principalement la correction de bugs, vous pouvez donc ignorer cette visite guidée si vous avez essayé les autres versions bêta.</p><p>Si ce n'est pas le cas, restez dans les parages pour voir les nouveautés de Node-RED 3.1.</p>" | ||||
|             } | ||||
|         }, | ||||
|         { | ||||
|   | ||||
| @@ -281,21 +281,4 @@ declare class env { | ||||
|      * ```const flowName = env.get("NR_FLOW_NAME");``` | ||||
|      */ | ||||
|     static get(name:string) :any; | ||||
|     /**  | ||||
|      * Get an environment variable value (asynchronous). | ||||
|      *  | ||||
|      * Predefined node-red variables...   | ||||
|      *   * `NR_NODE_ID` - the ID of the node | ||||
|      *   * `NR_NODE_NAME` - the Name of the node | ||||
|      *   * `NR_NODE_PATH` - the Path of the node | ||||
|      *   * `NR_GROUP_ID` - the ID of the containing group | ||||
|      *   * `NR_GROUP_NAME` - the Name of the containing group | ||||
|      *   * `NR_FLOW_ID` - the ID of the flow the node is on | ||||
|      *   * `NR_FLOW_NAME` - the Name of the flow the node is on | ||||
|      * @param name Name of the environment variable to get | ||||
|      * @param callback Callback function (`(err,value) => {}`) | ||||
|      * @example  | ||||
|      * ```const flowName = env.get("NR_FLOW_NAME");``` | ||||
|      */ | ||||
|     static get(name:string, callback: Function) :void; | ||||
| } | ||||
|   | ||||
| @@ -28,7 +28,7 @@ | ||||
|         <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name"> | ||||
|     </div> | ||||
|     <div class="form-row"> | ||||
|         <label for="node-input-timeout"><span data-i18n="exec.label.timeout"></span></label> | ||||
|         <label for="node-input-timeout"><i class="fa fa-clock-o"></i> <span data-i18n="exec.label.timeout"></span></label> | ||||
|         <input type="text" id="node-input-timeout" placeholder="30" style="width: 70px; margin-right: 5px;"><span data-i18n="inject.seconds"></span> | ||||
|     </div> | ||||
|     <div class="form-row"> | ||||
|   | ||||
| @@ -83,7 +83,7 @@ | ||||
|             </div> | ||||
|  | ||||
|             <div class="form-row"> | ||||
|                 <label for="node-input-timeout"><i class="fa fa-clock"></i> <span data-i18n="function.label.timeout"></span></label> | ||||
|                 <label for="node-input-timeout"><i class="fa fa-clock-o"></i> <span data-i18n="function.label.timeout"></span></label> | ||||
|                 <input id="node-input-timeout" style="width: 60px;" data-i18n="[placeholder]join.seconds"> | ||||
|             </div> | ||||
|  | ||||
|   | ||||
| @@ -242,8 +242,8 @@ module.exports = function(RED) { | ||||
|                 } | ||||
|             }, | ||||
|             env: { | ||||
|                 get: function(envVar, callback) { | ||||
|                     return RED.util.getSetting(node, envVar, node._flow, callback); | ||||
|                 get: function(envVar) { | ||||
|                     return RED.util.getSetting(node, envVar); | ||||
|                 } | ||||
|             }, | ||||
|             setTimeout: function () { | ||||
|   | ||||
| @@ -103,6 +103,7 @@ | ||||
|             } else if (type === "istype") { | ||||
|                 r.v = rule.find(".node-input-rule-type-value").typedInput('type'); | ||||
|                 r.vt = rule.find(".node-input-rule-type-value").typedInput('type'); | ||||
|                 r.vt = (r.vt === "number") ? "num" : "str"; | ||||
|             } else if (type === "jsonata_exp") { | ||||
|                 r.v = rule.find(".node-input-rule-exp-value").typedInput('value'); | ||||
|                 r.vt = rule.find(".node-input-rule-exp-value").typedInput('type'); | ||||
|   | ||||
| @@ -93,7 +93,7 @@ | ||||
|  | ||||
|     <div class="form-row"> | ||||
|         <input type="checkbox" id="node-input-insecureHTTPParser" style="display: inline-block; width: auto; vertical-align: top;"> | ||||
|         <label for="node-input-insecureHTTPParser", style="width: auto;" data-i18n="httpin.insecureHTTPParser"></label> | ||||
|         <label for="node-input-insecureHTTPParser" style="width: auto;" data-i18n="httpin.insecureHTTPParser"></label> | ||||
|     </div> | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -23,6 +23,8 @@ module.exports = async function(RED) { | ||||
|     const { v4: uuid } = require('uuid'); | ||||
|     const crypto = require('crypto'); | ||||
|     const URL = require("url").URL | ||||
|     const http = require("http") | ||||
|     const https = require("https") | ||||
|     var mustache = require("mustache"); | ||||
|     var querystring = require("querystring"); | ||||
|     var cookie = require("cookie"); | ||||
| @@ -65,16 +67,27 @@ in your Node-RED user directory (${RED.settings.userDir}). | ||||
|     function HTTPRequest(n) { | ||||
|         RED.nodes.createNode(this,n); | ||||
|         checkNodeAgentPatch(); | ||||
|         var node = this; | ||||
|         var nodeUrl = n.url; | ||||
|         var isTemplatedUrl = (nodeUrl||"").indexOf("{{") != -1; | ||||
|         var nodeMethod = n.method || "GET"; | ||||
|         var paytoqs = false; | ||||
|         var paytobody = false; | ||||
|         var redirectList = []; | ||||
|         var sendErrorsToCatch = n.senderr; | ||||
|         const node = this; | ||||
|         const nodeUrl = n.url; | ||||
|         const isTemplatedUrl = (nodeUrl||"").indexOf("{{") != -1; | ||||
|         const nodeMethod = n.method || "GET"; | ||||
|         let paytoqs = false; | ||||
|         let paytobody = false; | ||||
|         let redirectList = []; | ||||
|         const sendErrorsToCatch = n.senderr; | ||||
|         node.headers = n.headers || []; | ||||
|         var nodeHTTPPersistent = n["persist"]; | ||||
|         const useKeepAlive = n["persist"]; | ||||
|         let agents = null | ||||
|         if (useKeepAlive) { | ||||
|             agents = { | ||||
|                 http: new http.Agent({ keepAlive: true }), | ||||
|                 https: new https.Agent({ keepAlive: true }) | ||||
|             } | ||||
|             node.on('close', function () { | ||||
|                 agents.http.destroy() | ||||
|                 agents.https.destroy() | ||||
|             }) | ||||
|         } | ||||
|         if (n.tls) { | ||||
|             var tlsNode = RED.nodes.getNode(n.tls); | ||||
|         } | ||||
| @@ -560,12 +573,14 @@ in your Node-RED user directory (${RED.settings.userDir}). | ||||
|                     opts.agent = { | ||||
|                         http: new HttpProxyAgent(proxyOptions), | ||||
|                         https: new HttpsProxyAgent(proxyOptions) | ||||
|                     }; | ||||
|  | ||||
|                     } | ||||
|                 } else { | ||||
|                     node.warn("Bad proxy url: "+ prox); | ||||
|                 } | ||||
|             } | ||||
|             if (useKeepAlive && !opts.agent) { | ||||
|                 opts.agent = agents | ||||
|             } | ||||
|             if (tlsNode) { | ||||
|                 opts.https = {}; | ||||
|                 tlsNode.addTLSOptions(opts.https); | ||||
|   | ||||
| @@ -68,9 +68,12 @@ module.exports = function(RED) { | ||||
|                     node.error(err,msg); | ||||
|                     return done(); | ||||
|                 } else { | ||||
|                     filename = value; | ||||
|                     processMsg2(msg,nodeSend,value,done); | ||||
|                 } | ||||
|             }); | ||||
|        } | ||||
|  | ||||
|        function processMsg2(msg,nodeSend,filename,done) { | ||||
|             filename = filename || ""; | ||||
|             msg.filename = filename; | ||||
|             var fullFilename = filename; | ||||
| @@ -311,9 +314,12 @@ module.exports = function(RED) { | ||||
|                     node.error(err,msg); | ||||
|                     return done(); | ||||
|                 } else { | ||||
|                     filename = (value || "").replace(/\t|\r|\n/g,''); | ||||
|                     processMsg2(msg, nodeSend, (value || "").replace(/\t|\r|\n/g,''), nodeDone); | ||||
|                 } | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         function processMsg2(msg, nodeSend, filename, nodeDone) { | ||||
|             filename = filename || ""; | ||||
|             var fullFilename = filename; | ||||
|             if (filename && RED.settings.fileWorkingDirectory && !path.isAbsolute(filename)) { | ||||
| @@ -434,7 +440,8 @@ module.exports = function(RED) { | ||||
|                         nodeDone(); | ||||
|                     }); | ||||
|             } | ||||
|         }); | ||||
|         } | ||||
|  | ||||
|         this.on('close', function() { | ||||
|             node.status({}); | ||||
|         }); | ||||
|   | ||||
| @@ -36,5 +36,5 @@ greater than one day you should consider using a scheduler node that can cope wi | ||||
| <p><b>Note</b>: The <i>"Interval between times"</i> and <i>"at a specific time"</i> options use the standard cron system. | ||||
| This means that 20 minutes will be at the next hour, 20 minutes past and 40 minutes past - not in 20 minutes time. | ||||
| If you want every 20 minutes from now - use the <i>"interval"</i> option.</p> | ||||
| <p><b>Note</b>: To include a newline in a string you must use a Function node to create the payload.</p> | ||||
| <p><b>Note</b>: To include a newline in a string you must use the Function or Template node to create the payload.</p> | ||||
| </script> | ||||
|   | ||||
| @@ -30,5 +30,5 @@ | ||||
| <p>また、フロー開始の際に一度だけメッセージを送出させることもできます。</p> | ||||
| <p>「<i>時間間隔</i>」に指定可能な値の最大値は、約596時間(もしくは24日)です。一日より長い間隔を扱いたい場合は、電源停止や再起動にも対応可能なスケジューラノードの利用を検討すると良いでしょう。</p> | ||||
| <p><b>注</b>:「<i>指定した時間間隔、日時</i>」と「<i>指定した日時</i>」オプションは標準的なcronシステムを内部で利用します。したがって「20分」という指定は、その時点から20分後ではなく、毎時きっかり、20分、40分を意味します。現時刻から20分毎を指定するには「<i>指定した時間間隔</i>」オプションを用います。</p> | ||||
| <p><b>注</b>: 文字列に改行を含めたい場合は、functionノードを使ってペイロードを設定してください。</p> | ||||
| <p><b>注</b>: 文字列に改行を含めたい場合は、functionノードまたはtemplateノードを使ってペイロードを設定してください。</p> | ||||
| </script> | ||||
|   | ||||
| @@ -94,6 +94,7 @@ | ||||
|     }, | ||||
|     "catch": { | ||||
|         "catch": "catch: 全て", | ||||
|         "catchGroup": "catch: グループ", | ||||
|         "catchNodes": "catch: __number__", | ||||
|         "catchUncaught": "catch: 未補足", | ||||
|         "label": { | ||||
| @@ -109,6 +110,7 @@ | ||||
|     }, | ||||
|     "status": { | ||||
|         "status": "status: 全て", | ||||
|         "statusGroup": "status: グループ", | ||||
|         "statusNodes": "status: __number__", | ||||
|         "label": { | ||||
|             "source": "ステータス取得元", | ||||
| @@ -250,7 +252,8 @@ | ||||
|             "initialize": "初期化処理", | ||||
|             "finalize": "終了処理", | ||||
|             "outputs": "出力数", | ||||
|             "modules": "モジュール" | ||||
|             "modules": "モジュール", | ||||
|             "timeout": "タイムアウト" | ||||
|         }, | ||||
|         "text": { | ||||
|             "initialize": "// ここに記述したコードは、ノードをデプロイした時に\n// 一度だけ実行されます。\n", | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "@node-red/nodes", | ||||
|     "version": "3.1.0-beta.3", | ||||
|     "version": "3.1.0-beta.4", | ||||
|     "license": "Apache-2.0", | ||||
|     "repository": { | ||||
|         "type": "git", | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "@node-red/registry", | ||||
|     "version": "3.1.0-beta.3", | ||||
|     "version": "3.1.0-beta.4", | ||||
|     "license": "Apache-2.0", | ||||
|     "main": "./lib/index.js", | ||||
|     "repository": { | ||||
| @@ -16,7 +16,7 @@ | ||||
|         } | ||||
|     ], | ||||
|     "dependencies": { | ||||
|         "@node-red/util": "3.1.0-beta.3", | ||||
|         "@node-red/util": "3.1.0-beta.4", | ||||
|         "clone": "2.1.2", | ||||
|         "fs-extra": "11.1.1", | ||||
|         "semver": "7.5.0", | ||||
|   | ||||
| @@ -14,19 +14,20 @@ | ||||
|  * limitations under the License. | ||||
|  **/ | ||||
|  | ||||
| var clone = require("clone"); | ||||
| var redUtil = require("@node-red/util").util; | ||||
| const clone = require("clone"); | ||||
| const redUtil = require("@node-red/util").util; | ||||
| const events = require("@node-red/util").events; | ||||
| var flowUtil = require("./util"); | ||||
| const flowUtil = require("./util"); | ||||
| const context = require('../nodes/context'); | ||||
| const hooks = require("@node-red/util").hooks; | ||||
| const credentials = require("../nodes/credentials"); | ||||
|  | ||||
| var Subflow; | ||||
| var Log; | ||||
| let Subflow; | ||||
| let Log; | ||||
| let Group; | ||||
|  | ||||
| var nodeCloseTimeout = 15000; | ||||
| var asyncMessageDelivery = true; | ||||
| let nodeCloseTimeout = 15000; | ||||
| let asyncMessageDelivery = true; | ||||
|  | ||||
| /** | ||||
|  * This class represents a flow within the runtime. It is responsible for | ||||
| @@ -52,6 +53,8 @@ class Flow { | ||||
|             this.isGlobalFlow = false; | ||||
|         } | ||||
|         this.id = this.flow.id || "global"; | ||||
|         this.groups = {} | ||||
|         this.groupOrder = [] | ||||
|         this.activeNodes = {}; | ||||
|         this.subflowInstanceNodes = {}; | ||||
|         this.catchNodes = []; | ||||
| @@ -59,6 +62,11 @@ class Flow { | ||||
|         this.path = this.id; | ||||
|         // Ensure a context exists for this flow | ||||
|         this.context = context.getFlowContext(this.id,this.parent.id); | ||||
|          | ||||
|         // env is an array of env definitions | ||||
|         // _env is an object for direct lookup of env name -> value | ||||
|         this.env = this.flow.env | ||||
|         this._env = {} | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -136,7 +144,7 @@ class Flow { | ||||
|      * @param  {[type]} msg [description] | ||||
|      * @return {[type]}     [description] | ||||
|      */ | ||||
|     start(diff) { | ||||
|     async start(diff) { | ||||
|         this.trace("start "+this.TYPE+" ["+this.path+"]"); | ||||
|         var node; | ||||
|         var newNode; | ||||
| @@ -145,6 +153,52 @@ class Flow { | ||||
|         this.statusNodes = []; | ||||
|         this.completeNodeMap = {}; | ||||
|  | ||||
|  | ||||
|         if (this.isGlobalFlow) { | ||||
|             // This is the global flow. It needs to go find the `global-config` | ||||
|             // node and extract any env properties from it | ||||
|             const configNodes = Object.keys(this.flow.configs); | ||||
|             for (let i = 0; i < configNodes.length; i++) { | ||||
|                 const node = this.flow.configs[configNodes[i]] | ||||
|                 if (node.type === 'global-config' && node.env) { | ||||
|                     const nodeEnv = await flowUtil.evaluateEnvProperties(this, node.env, credentials.get(node.id)) | ||||
|                     this._env = { ...this._env, ...nodeEnv } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (this.env) { | ||||
|             this._env = { ...this._env, ...await flowUtil.evaluateEnvProperties(this, this.env, credentials.get(this.id)) } | ||||
|         } | ||||
|  | ||||
|         // Initialise the group objects. These must be done in the right order | ||||
|         // starting from outer-most to inner-most so that the parent hierarchy | ||||
|         // is maintained. | ||||
|         this.groups = {} | ||||
|         this.groupOrder = [] | ||||
|         const groupIds = Object.keys(this.flow.groups || {}) | ||||
|         while (groupIds.length > 0) { | ||||
|             const id = groupIds.shift() | ||||
|             const groupDef = this.flow.groups[id] | ||||
|             if (!groupDef.g || this.groups[groupDef.g]) { | ||||
|                 // The parent of this group is available - either another group | ||||
|                 // or the top-level flow (this) | ||||
|                 const parent = this.groups[groupDef.g] || this | ||||
|                 this.groups[groupDef.id] = new Group(parent, groupDef) | ||||
|                 this.groupOrder.push(groupDef.id) | ||||
|             } else { | ||||
|                 // Try again once we've processed the other groups | ||||
|                 groupIds.push(id) | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         for (let i = 0; i < this.groupOrder.length; i++) { | ||||
|             // Start the groups in the right order so they | ||||
|             // can setup their env vars knowning their parent | ||||
|             // will have been started | ||||
|             await this.groups[this.groupOrder[i]].start() | ||||
|         } | ||||
|  | ||||
|         var configNodes = Object.keys(this.flow.configs); | ||||
|         var configNodeAttempts = {}; | ||||
|         while (configNodes.length > 0) { | ||||
| @@ -177,7 +231,7 @@ class Flow { | ||||
|                         } | ||||
|                     } | ||||
|                     if (readyToCreate) { | ||||
|                         newNode = flowUtil.createNode(this,node); | ||||
|                         newNode = await flowUtil.createNode(this,node); | ||||
|                         if (newNode) { | ||||
|                             this.activeNodes[id] = newNode; | ||||
|                         } | ||||
| @@ -203,7 +257,7 @@ class Flow { | ||||
|                 if (node.d !== true) { | ||||
|                     if (!node.subflow) { | ||||
|                         if (!this.activeNodes[id]) { | ||||
|                             newNode = flowUtil.createNode(this,node); | ||||
|                             newNode = await flowUtil.createNode(this,node); | ||||
|                             if (newNode) { | ||||
|                                 this.activeNodes[id] = newNode; | ||||
|                             } | ||||
| @@ -221,7 +275,7 @@ class Flow { | ||||
|                                     node | ||||
|                                 ); | ||||
|                                 this.subflowInstanceNodes[id] = subflow; | ||||
|                                 subflow.start(); | ||||
|                                 await subflow.start(); | ||||
|                                 this.activeNodes[id] = subflow.node; | ||||
|  | ||||
|                                 // this.subflowInstanceNodes[id] = nodes.map(function(n) { return n.id}); | ||||
| @@ -404,8 +458,7 @@ class Flow { | ||||
|      * @return {Node}   group node | ||||
|      */ | ||||
|     getGroupNode(id) { | ||||
|         const groups = this.global.groups; | ||||
|         return groups[id]; | ||||
|         return this.groups[id]; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -416,204 +469,28 @@ class Flow { | ||||
|         return this.activeNodes; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * Group callback signature | ||||
|      * | ||||
|      * @callback GroupEnvCallback | ||||
|      * @param {Error} err The error object (or null) | ||||
|      * @param {[result: {val:Any}, name: String]} result The result of the callback | ||||
|      * @returns {void} | ||||
|      * Get a flow setting value. | ||||
|      * @param  {[type]} key [description] | ||||
|      * @return {[type]}     [description] | ||||
|      */ | ||||
|  | ||||
|     /** | ||||
|     * @function getGroupEnvSetting | ||||
|     * Get a group setting value synchronously. | ||||
|     * This currently automatically defers to the parent | ||||
|     * @overload | ||||
|      * @param {Object} node | ||||
|      * @param {Object} group | ||||
|      * @param {String} name | ||||
|      * @returns {Any} | ||||
|      * | ||||
|      * Get a group setting value asynchronously. | ||||
|      * @overload | ||||
|      * @param {Object} node | ||||
|      * @param {Object} group | ||||
|      * @param {String} name | ||||
|      * @param {GroupEnvCallback} callback | ||||
|      * @returns {void} | ||||
|     */ | ||||
|  | ||||
|     getGroupEnvSetting(node, group, name, callback) { | ||||
|         /** @type {GroupEnvCallback} */ | ||||
|         const returnOrCallback = (err, [result, newName]) => { | ||||
|             if (callback) { | ||||
|                 callback(err, [result, newName]); | ||||
|                 return | ||||
|             } | ||||
|             return [result, newName]; | ||||
|         } | ||||
|         if (group) { | ||||
|             if (name === "NR_GROUP_NAME") { | ||||
|                 return returnOrCallback(null, [{ val: group.name }, null]); | ||||
|             } | ||||
|             if (name === "NR_GROUP_ID") { | ||||
|                 return returnOrCallback(null, [{ val: group.id }, null]); | ||||
|             } | ||||
|  | ||||
|             if (group.credentials === undefined) { | ||||
|                 group.credentials = credentials.get(group.id) || {}; | ||||
|             } | ||||
|             if (!name.startsWith("$parent.")) { | ||||
|                 if (group.env) { | ||||
|                     if (!group._env) { | ||||
|                         const envs = group.env; | ||||
|                         const entries = envs.map((env) => { | ||||
|                             if (env.type === "cred") { | ||||
|                                 const cred = group.credentials; | ||||
|                                 if (cred.hasOwnProperty(env.name)) { | ||||
|                                     env.value = cred[env.name]; | ||||
|                                 } | ||||
|                             } | ||||
|                             return [env.name, env]; | ||||
|                         }); | ||||
|                         group._env = Object.fromEntries(entries); | ||||
|                     } | ||||
|                     const env = group._env[name]; | ||||
|                     if (env) { | ||||
|                         let value = env.value; | ||||
|                         const type = env.type; | ||||
|                         if ((type !== "env") || (value !== name)) { | ||||
|                             if (type === "env") { | ||||
|                                 value = value.replace(new RegExp("\\${"+name+"}","g"),"${$parent."+name+"}"); | ||||
|                             } else if (type === "bool") { | ||||
|                                 const val = ((value === "true") || (value === true)); | ||||
|                                 return returnOrCallback(null, [{ val: val }, null]) | ||||
|                             } | ||||
|                             if (type === "cred") { | ||||
|                                 return returnOrCallback(null, [{ val: value }, null]) | ||||
|                             } | ||||
|                             try { | ||||
|                                 if (!callback) { | ||||
|                                     var val = redUtil.evaluateNodeProperty(value, type, node, null, null); | ||||
|                                     return [{ val: val }, null]; | ||||
|                                 } else { | ||||
|                                     redUtil.evaluateNodeProperty(value, type, node, null, (err, value) => { | ||||
|                                         return returnOrCallback(err, [{ val: value }, null]) | ||||
|                                     }); | ||||
|                                     return | ||||
|                                 } | ||||
|                             } | ||||
|                             catch (e) { | ||||
|                                 if (!callback) { | ||||
|                                     this.error(e); | ||||
|                                 } | ||||
|                                 return returnOrCallback(e, null); | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             else { | ||||
|                 name = name.substring(8); | ||||
|             } | ||||
|             if (group.g) { | ||||
|                 const parent = this.getGroupNode(group.g); | ||||
|                 const gVal = this.getGroupEnvSetting(node, parent, name, callback); | ||||
|                 if (callback) { | ||||
|                     return; | ||||
|                 } | ||||
|                 return gVal; | ||||
|             } | ||||
|         } | ||||
|         return returnOrCallback(null, [null, name]); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Settings callback signature | ||||
|      * | ||||
|      * @callback SettingsCallback | ||||
|      * @param {Error} err The error object (or null) | ||||
|      * @param {Any} result The result of the callback | ||||
|      * @returns {void} | ||||
|      */ | ||||
|     /** | ||||
|      * Get a flow setting value. This currently automatically defers to the parent | ||||
|      * flow which, as defined in ./index.js returns `process.env[key]`. | ||||
|      * This lays the groundwork for Subflow to have instance-specific settings | ||||
|      * @param  {String} key The settings key | ||||
|      * @param  {SettingsCallback} callback Optional callback function | ||||
|      * @return {Any} | ||||
|      */ | ||||
|     getSetting(key, callback) { | ||||
|         /** @type {SettingsCallback} */ | ||||
|         const returnOrCallback = (err, result) => { | ||||
|             if (callback) { | ||||
|                 callback(err, result); | ||||
|                 return | ||||
|             } | ||||
|             return result; | ||||
|         } | ||||
|     getSetting(key) { | ||||
|         const flow = this.flow; | ||||
|         if (key === "NR_FLOW_NAME") { | ||||
|             return returnOrCallback(null, flow.label); | ||||
|             return flow.label; | ||||
|         } | ||||
|         if (key === "NR_FLOW_ID") { | ||||
|             return returnOrCallback(null, flow.id); | ||||
|             return flow.id; | ||||
|         } | ||||
|         if (flow.credentials === undefined) { | ||||
|             flow.credentials = credentials.get(flow.id) || {}; | ||||
|         } | ||||
|         if (flow.env) { | ||||
|             if (!key.startsWith("$parent.")) { | ||||
|                 if (!flow._env) { | ||||
|                     const envs = flow.env; | ||||
|                     const entries = envs.map((env) => { | ||||
|                         if (env.type === "cred") { | ||||
|                             const cred = flow.credentials; | ||||
|                             if (cred.hasOwnProperty(env.name)) { | ||||
|                                 env.value = cred[env.name]; | ||||
|                             } | ||||
|                         } | ||||
|                         return [env.name, env] | ||||
|                     }); | ||||
|                     flow._env = Object.fromEntries(entries); | ||||
|                 } | ||||
|                 const env = flow._env[key]; | ||||
|                 if (env) { | ||||
|                     let value = env.value; | ||||
|                     const type = env.type; | ||||
|                     if ((type !== "env") || (value !== key)) { | ||||
|                         if (type === "env") { | ||||
|                             value = value.replace(new RegExp("\\${"+key+"}","g"),"${$parent."+key+"}"); | ||||
|                         } | ||||
|                         try { | ||||
|                             if (type === "bool") { | ||||
|                                 const val = ((value === "true") || (value === true)); | ||||
|                                 return returnOrCallback(null, val); | ||||
|                             } | ||||
|                             if (type === "cred") { | ||||
|                                 return returnOrCallback(null, value); | ||||
|                             } | ||||
|                             var val = redUtil.evaluateNodeProperty(value, type, null, null, null); | ||||
|                             return returnOrCallback(null, val); | ||||
|                         } | ||||
|                         catch (e) { | ||||
|                             this.error(e); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|         if (!key.startsWith("$parent.")) { | ||||
|             if (this._env.hasOwnProperty(key)) { | ||||
|                 return this._env[key] | ||||
|             } | ||||
|             else { | ||||
|         } else { | ||||
|                 key = key.substring(8); | ||||
|             } | ||||
|         } | ||||
|         const pVal = this.parent.getSetting(key, callback); | ||||
|         if (callback) { | ||||
|             return; | ||||
|         } | ||||
|         return pVal; | ||||
|         // Delegate to the parent flow. | ||||
|         return this.parent.getSetting(key); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -650,10 +527,9 @@ class Flow { | ||||
|             // Delegate status to any nodes using this config node | ||||
|             for (let userNode in node.users) { | ||||
|                 if (node.users.hasOwnProperty(userNode)) { | ||||
|                     node.users[userNode]._flow.handleStatus(node,statusMessage,node.users[userNode],true); | ||||
|                     handled = node.users[userNode]._flow.handleStatus(node,statusMessage,node.users[userNode],true) || handled; | ||||
|                 } | ||||
|             } | ||||
|             handled = true; | ||||
|         } else { | ||||
|             const candidateNodes = []; | ||||
|             this.statusNodes.forEach(targetStatusNode => { | ||||
| @@ -667,10 +543,10 @@ class Flow { | ||||
|                 let distance = 0 | ||||
|                 if (reportingNode.g) { | ||||
|                     // Reporting node inside a group. Calculate the distance between it and the status node | ||||
|                     let containingGroup = this.global.groups[reportingNode.g] | ||||
|                     let containingGroup = this.groups[reportingNode.g] | ||||
|                     while (containingGroup && containingGroup.id !== targetStatusNode.g) { | ||||
|                         distance++ | ||||
|                         containingGroup = this.global.groups[containingGroup.g] | ||||
|                         containingGroup = this.groups[containingGroup.g] | ||||
|                     } | ||||
|                     if (!containingGroup && targetStatusNode.g && targetStatusNode.scope === 'group') { | ||||
|                         // This status node is in a group, but not in the same hierachy | ||||
| @@ -737,10 +613,9 @@ class Flow { | ||||
|             // Delegate status to any nodes using this config node | ||||
|             for (let userNode in node.users) { | ||||
|                 if (node.users.hasOwnProperty(userNode)) { | ||||
|                     node.users[userNode]._flow.handleError(node,logMessage,msg,node.users[userNode]); | ||||
|                     handled = node.users[userNode]._flow.handleError(node,logMessage,msg,node.users[userNode]) || handled; | ||||
|                 } | ||||
|             } | ||||
|             handled = true; | ||||
|         } else { | ||||
|             const candidateNodes = []; | ||||
|             this.catchNodes.forEach(targetCatchNode => { | ||||
| @@ -755,10 +630,10 @@ class Flow { | ||||
|                 let distance = 0 | ||||
|                 if (reportingNode.g) { | ||||
|                     // Reporting node inside a group. Calculate the distance between it and the catch node | ||||
|                     let containingGroup = this.global.groups[reportingNode.g] | ||||
|                     let containingGroup = this.groups[reportingNode.g] | ||||
|                     while (containingGroup && containingGroup.id !== targetCatchNode.g) { | ||||
|                         distance++ | ||||
|                         containingGroup = this.global.groups[containingGroup.g] | ||||
|                         containingGroup = this.groups[containingGroup.g] | ||||
|                     } | ||||
|                     if (!containingGroup && targetCatchNode.g && targetCatchNode.scope === 'group') { | ||||
|                         // This catch node is in a group, but not in the same hierachy | ||||
| @@ -908,7 +783,7 @@ function handlePreRoute(flow, sendEvent, reportError) { | ||||
|             return; | ||||
|         } else if (err !== false) { | ||||
|             sendEvent.destination.node = flow.getNode(sendEvent.destination.id); | ||||
|             if (sendEvent.destination.node) { | ||||
|             if (sendEvent.destination.node && typeof sendEvent.destination.node === 'object') { | ||||
|                 if (sendEvent.cloneMessage) { | ||||
|                     sendEvent.msg = redUtil.cloneMessage(sendEvent.msg); | ||||
|                 } | ||||
| @@ -958,9 +833,10 @@ module.exports = { | ||||
|         asyncMessageDelivery = !runtime.settings.runtimeSyncDelivery | ||||
|         Log = runtime.log; | ||||
|         Subflow = require("./Subflow"); | ||||
|         Group = require("./Group").Group | ||||
|     }, | ||||
|     create: function(parent,global,conf) { | ||||
|         return new Flow(parent,global,conf); | ||||
|         return new Flow(parent,global,conf) | ||||
|     }, | ||||
|     Flow: Flow | ||||
| } | ||||
|   | ||||
							
								
								
									
										55
									
								
								packages/node_modules/@node-red/runtime/lib/flows/Group.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								packages/node_modules/@node-red/runtime/lib/flows/Group.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | ||||
| const flowUtil = require("./util"); | ||||
| const credentials = require("../nodes/credentials"); | ||||
|  | ||||
| /** | ||||
|  * This class represents a group within the runtime. | ||||
|  */ | ||||
| class Group { | ||||
|  | ||||
|     /** | ||||
|      * Create a Group object. | ||||
|      * @param {[type]} parent     The parent flow/group | ||||
|      * @param {[type]} groupDef   This group's definition | ||||
|      */ | ||||
|     constructor(parent, groupDef) { | ||||
|         this.TYPE = 'group' | ||||
|         this.name = groupDef.name | ||||
|         this.parent = parent | ||||
|         this.group = groupDef | ||||
|         this.id = this.group.id | ||||
|         this.g = this.group.g | ||||
|         this.env = this.group.env | ||||
|         this._env = {} | ||||
|     } | ||||
|  | ||||
|     async start() { | ||||
|         if (this.env) { | ||||
|             this._env = await flowUtil.evaluateEnvProperties(this, this.env, credentials.get(this.id)) | ||||
|         } | ||||
|     } | ||||
|     /** | ||||
|      * Get a group setting value. | ||||
|      * @param  {[type]} key [description] | ||||
|      * @return {[type]}     [description] | ||||
|      */ | ||||
|     getSetting(key) { | ||||
|         if (key === "NR_GROUP_NAME") { | ||||
|             return this.name; | ||||
|         } | ||||
|         if (key === "NR_GROUP_ID") { | ||||
|             return this.id; | ||||
|         } | ||||
|         if (!key.startsWith("$parent.")) { | ||||
|             if (this._env.hasOwnProperty(key)) { | ||||
|                 return this._env[key] | ||||
|             } | ||||
|         } else { | ||||
|             key = key.substring(8); | ||||
|         } | ||||
|         return this.parent.getSetting(key); | ||||
|     } | ||||
| } | ||||
|  | ||||
| module.exports = { | ||||
|     Group | ||||
| } | ||||
| @@ -119,7 +119,7 @@ class Subflow extends Flow { | ||||
|         this.templateCredentials = credentials.get(subflowDef.id) || {}; | ||||
|         this.instanceCredentials = credentials.get(id) || {}; | ||||
|  | ||||
|         var env = []; | ||||
|         var env = {}; | ||||
|         if (this.subflowDef.env) { | ||||
|             this.subflowDef.env.forEach(e => { | ||||
|                 env[e.name] = e; | ||||
| @@ -145,7 +145,7 @@ class Subflow extends Flow { | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|         this.env = env; | ||||
|         this.env = Object.values(env); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -156,7 +156,7 @@ class Subflow extends Flow { | ||||
|      * @param  {[type]} diff [description] | ||||
|      * @return {[type]}      [description] | ||||
|      */ | ||||
|     start(diff) { | ||||
|     async start(diff) { | ||||
|         var self = this; | ||||
|         // Create a subflow node to accept inbound messages and route appropriately | ||||
|         var Node = require("../nodes/Node"); | ||||
| @@ -310,7 +310,7 @@ class Subflow extends Flow { | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         super.start(diff); | ||||
|         return super.start(diff); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -335,68 +335,35 @@ class Subflow extends Flow { | ||||
|     } | ||||
|     /** | ||||
|      * Get environment variable of subflow | ||||
|      * @param {String}   name   name of env var | ||||
|      * @param {String}   key   name of env var | ||||
|      * @return {Object}  val    value of env var | ||||
|      */ | ||||
|     getSetting(name) { | ||||
|         if (!/^\$parent\./.test(name)) { | ||||
|             var env = this.env; | ||||
|             if (env && env.hasOwnProperty(name)) { | ||||
|                 var val = env[name]; | ||||
|                 // If this is an env type property we need to be careful not | ||||
|                 // to get into lookup loops. | ||||
|                 // 1. if the value to lookup is the same as this one, go straight to parent | ||||
|                 // 2. otherwise, check if it is a compound env var ("foo $(bar)") | ||||
|                 //    and if so, substitute any instances of `name` with $parent.name | ||||
|                 // See https://github.com/node-red/node-red/issues/2099 | ||||
|                 if (val.type !== 'env' || val.value !== name) { | ||||
|                     let value = val.value; | ||||
|                     var type = val.type; | ||||
|                     if (type === 'env') { | ||||
|                         value = value.replace(new RegExp("\\${"+name+"}","g"),"${$parent."+name+"}"); | ||||
|                     } | ||||
|                     try { | ||||
|                         return evaluateInputValue(value, type, this.node); | ||||
|                     } | ||||
|                     catch (e) { | ||||
|                         this.error(e); | ||||
|                         return undefined; | ||||
|                     } | ||||
|                 } else { | ||||
|                     // This _is_ an env property pointing at itself - go to parent | ||||
|                 } | ||||
|             } | ||||
|         } else { | ||||
|             // name starts $parent. ... so delegate to parent automatically | ||||
|             name = name.substring(8); | ||||
|         } | ||||
|     getSetting(key) { | ||||
|         const node = this.subflowInstance; | ||||
|         if (node) { | ||||
|             if (name === "NR_NODE_NAME") { | ||||
|             if (key === "NR_NODE_NAME" || key === "NR_SUBFLOW_NAME") { | ||||
|                 return node.name; | ||||
|             } | ||||
|             if (name === "NR_NODE_ID") { | ||||
|             if (key === "NR_NODE_ID" || key === "NR_SUBFLOW_ID") { | ||||
|                 return node.id; | ||||
|             } | ||||
|             if (name === "NR_NODE_PATH") { | ||||
|             if (key === "NR_NODE_PATH" || key === "NR_SUBFLOW_PATH") { | ||||
|                 return node._path; | ||||
|             } | ||||
|         } | ||||
|         if (node.g) { | ||||
|             const group = this.getGroupNode(node.g); | ||||
|             const [result, newName] = this.getGroupEnvSetting(node, group, name); | ||||
|             if (result) { | ||||
|                 return result.val; | ||||
|         if (!key.startsWith("$parent.")) { | ||||
|             if (this._env.hasOwnProperty(key)) { | ||||
|                 return this._env[key] | ||||
|             } | ||||
|             name = newName; | ||||
|         } else { | ||||
|             key = key.substring(8); | ||||
|         } | ||||
|  | ||||
|         var parent = this.parent; | ||||
|         if (parent) { | ||||
|             var val = parent.getSetting(name); | ||||
|             return val; | ||||
|         // Push the request up to the parent. | ||||
|         // Unlike a Flow, the parent of a Subflow could be a Group | ||||
|         if (node.g) { | ||||
|             return this.parent.getGroupNode(node.g).getSetting(key) | ||||
|         } | ||||
|         return undefined; | ||||
|         return this.parent.getSetting(key) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|   | ||||
| @@ -271,6 +271,10 @@ function getFlows() { | ||||
|  | ||||
| async function start(type,diff,muteLog,isDeploy) { | ||||
|     type = type || "full"; | ||||
|     if (diff && diff.globalConfigChanged) { | ||||
|         type = 'full' | ||||
|     } | ||||
|  | ||||
|     started = true; | ||||
|     state = 'start' | ||||
|     var i; | ||||
| @@ -359,7 +363,7 @@ async function start(type,diff,muteLog,isDeploy) { | ||||
|             if (activeFlowConfig.flows.hasOwnProperty(id)) { | ||||
|                 if (!activeFlowConfig.flows[id].disabled && !activeFlows[id]) { | ||||
|                     // This flow is not disabled, nor is it currently active, so create it | ||||
|                     activeFlows[id] = Flow.create(flowAPI,activeFlowConfig,activeFlowConfig.flows[id]); | ||||
|                     activeFlows[id] = Flow.create(activeFlows['global'],activeFlowConfig,activeFlowConfig.flows[id]); | ||||
|                     log.debug("red/nodes/flows.start : starting flow : "+id); | ||||
|                 } else { | ||||
|                     log.debug("red/nodes/flows.start : not starting disabled flow : "+id); | ||||
| @@ -379,7 +383,7 @@ async function start(type,diff,muteLog,isDeploy) { | ||||
|                         activeFlows[id].update(activeFlowConfig,activeFlowConfig.flows[id]); | ||||
|                     } else { | ||||
|                         // This flow didn't previously exist, so create it | ||||
|                         activeFlows[id] = Flow.create(flowAPI,activeFlowConfig,activeFlowConfig.flows[id]); | ||||
|                         activeFlows[id] = Flow.create(activeFlows['global'],activeFlowConfig,activeFlowConfig.flows[id]); | ||||
|                         log.debug("red/nodes/flows.start : starting flow : "+id); | ||||
|                     } | ||||
|                 } else { | ||||
| @@ -391,7 +395,7 @@ async function start(type,diff,muteLog,isDeploy) { | ||||
|     for (id in activeFlows) { | ||||
|         if (activeFlows.hasOwnProperty(id)) { | ||||
|             try { | ||||
|                 activeFlows[id].start(diff); | ||||
|                 await activeFlows[id].start(diff); | ||||
|                 // Create a map of node id to flow id and also a subflowInstance lookup map | ||||
|                 var activeNodes = activeFlows[id].getActiveNodes(); | ||||
|                 Object.keys(activeNodes).forEach(function(nid) { | ||||
| @@ -432,7 +436,8 @@ function stop(type,diff,muteLog,isDeploy) { | ||||
|         changed:[], | ||||
|         removed:[], | ||||
|         rewired:[], | ||||
|         linked:[] | ||||
|         linked:[], | ||||
|         flowChanged:[] | ||||
|     }; | ||||
|     if (!muteLog) { | ||||
|         if (type !== "full") { | ||||
| @@ -441,6 +446,9 @@ function stop(type,diff,muteLog,isDeploy) { | ||||
|             log.info(log._("nodes.flows.stopping-flows")); | ||||
|         } | ||||
|     } | ||||
|     if (diff.globalConfigChanged) { | ||||
|         type = 'full' | ||||
|     } | ||||
|     started = false; | ||||
|     state = 'stop' | ||||
|     var promises = []; | ||||
| @@ -464,7 +472,7 @@ function stop(type,diff,muteLog,isDeploy) { | ||||
|  | ||||
|     activeFlowIds.forEach(id => { | ||||
|         if (activeFlows.hasOwnProperty(id)) { | ||||
|             var flowStateChanged = diff && (diff.added.indexOf(id) !== -1 || diff.removed.indexOf(id) !== -1); | ||||
|             var flowStateChanged = diff && (diff.flowChanged.indexOf(id) !== -1 || diff.added.indexOf(id) !== -1 || diff.removed.indexOf(id) !== -1); | ||||
|             log.debug("red/nodes/flows.stop : stopping flow : "+id); | ||||
|             promises.push(activeFlows[id].stop(flowStateChanged?null:stopList,removedList)); | ||||
|             if (type === "full" || flowStateChanged || diff.removed.indexOf(id)!==-1) { | ||||
| @@ -780,21 +788,10 @@ const flowAPI = { | ||||
|     getNode: getNode, | ||||
|     handleError: () => false, | ||||
|     handleStatus: () => false, | ||||
|     getSetting: (k, callback) => flowUtil.getEnvVar(k, callback), | ||||
|     getSetting: k => flowUtil.getEnvVar(k), | ||||
|     log: m => log.log(m) | ||||
| } | ||||
|  | ||||
|  | ||||
| function getGlobalConfig() { | ||||
|     let gconf = null; | ||||
|     eachNode((n) => { | ||||
|         if (n.type === "global-config") { | ||||
|             gconf = n; | ||||
|         } | ||||
|     }); | ||||
|     return gconf; | ||||
| } | ||||
|  | ||||
| module.exports = { | ||||
|     init: init, | ||||
|  | ||||
| @@ -807,10 +804,7 @@ module.exports = { | ||||
|  | ||||
|     get:getNode, | ||||
|     eachNode: eachNode, | ||||
|  | ||||
|  | ||||
|     getGlobalConfig: getGlobalConfig, | ||||
|      | ||||
|    | ||||
|     /** | ||||
|      * Gets the current flow configuration | ||||
|      */ | ||||
|   | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -205,7 +205,6 @@ module.exports = { | ||||
|     getNode: flows.get, | ||||
|     eachNode: flows.eachNode, | ||||
|     getContext: context.get, | ||||
|     getGlobalConfig: flows.getGlobalConfig, | ||||
|  | ||||
|     clearContext: context.clear, | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "@node-red/runtime", | ||||
|     "version": "3.1.0-beta.3", | ||||
|     "version": "3.1.0-beta.4", | ||||
|     "license": "Apache-2.0", | ||||
|     "main": "./lib/index.js", | ||||
|     "repository": { | ||||
| @@ -16,8 +16,8 @@ | ||||
|         } | ||||
|     ], | ||||
|     "dependencies": { | ||||
|         "@node-red/registry": "3.1.0-beta.3", | ||||
|         "@node-red/util": "3.1.0-beta.3", | ||||
|         "@node-red/registry": "3.1.0-beta.4", | ||||
|         "@node-red/util": "3.1.0-beta.4", | ||||
|         "async-mutex": "0.4.0", | ||||
|         "clone": "2.1.2", | ||||
|         "express": "4.18.2", | ||||
|   | ||||
							
								
								
									
										79
									
								
								packages/node_modules/@node-red/util/lib/util.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										79
									
								
								packages/node_modules/@node-red/util/lib/util.js
									
									
									
									
										vendored
									
									
								
							| @@ -18,7 +18,6 @@ | ||||
| /** | ||||
|  * @mixin @node-red/util_util | ||||
|  */ | ||||
| /** @typedef {import('../../runtime/lib/flows/Flow.js').Flow} RuntimeLibFlowsFlow */ | ||||
|  | ||||
| const clonedeep = require("lodash.clonedeep"); | ||||
| const jsonata = require("jsonata"); | ||||
| @@ -531,64 +530,31 @@ function setObjectProperty(msg,prop,value,createMissing) { | ||||
|  * Get value of environment variable. | ||||
|  * @param {Node} node - accessing node | ||||
|  * @param {String} name - name of variable | ||||
|  * @param {RuntimeLibFlowsFlow} flow_ - (optional) flow to check for setting | ||||
|  * @param  {(err: Error, result: Any) => void} callback - (optional) called when the property is evaluated | ||||
|  * @return {String} value of env var | ||||
|  */ | ||||
| function getSetting(node, name, flow_, callback) { | ||||
|     const returnOrCallback = (err, result) => { | ||||
|         if (callback) { | ||||
|             callback(err, result); | ||||
|             return | ||||
|         } | ||||
|         return result; | ||||
|     } | ||||
| function getSetting(node, name, flow_) { | ||||
|     if (node) { | ||||
|         if (name === "NR_NODE_NAME") { | ||||
|             return returnOrCallback(null, node.name); | ||||
|             return node.name; | ||||
|         } | ||||
|         if (name === "NR_NODE_ID") { | ||||
|             return returnOrCallback(null, node.id); | ||||
|             return node.id; | ||||
|         } | ||||
|         if (name === "NR_NODE_PATH") { | ||||
|             return returnOrCallback(null, node._path); | ||||
|             return node._path; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** @type {RuntimeLibFlowsFlow} */ | ||||
|     var flow = (flow_ ? flow_ : (node ? node._flow : null)); | ||||
|     if (flow) { | ||||
|         if (node && node.g) { | ||||
|             const group = flow.getGroupNode(node.g); | ||||
|             if (callback) { | ||||
|                 flow.getGroupEnvSetting(node, group, name, (e, [result, newName]) => { | ||||
|                     if (e) { | ||||
|                         callback(e); | ||||
|                         return | ||||
|                     } | ||||
|                     if (result) { | ||||
|                         callback(null, result.val); | ||||
|                         return | ||||
|                     } | ||||
|                     name = newName; | ||||
|                     flow.getSetting(name, callback); | ||||
|                 }); | ||||
|                 return | ||||
|             } else { | ||||
|                 const [result, newName] = flow.getGroupEnvSetting(node, group, name); | ||||
|                 if (result) { | ||||
|                     return result.val; | ||||
|                 } | ||||
|                 name = newName; | ||||
|             if (group) { | ||||
|                 return group.getSetting(name) | ||||
|             } | ||||
|         } | ||||
|         const fVal = flow.getSetting(name, callback) | ||||
|         if (callback) { | ||||
|             return | ||||
|         } | ||||
|         return fVal; | ||||
|         return flow.getSetting(name); | ||||
|     } | ||||
|     return returnOrCallback(null, process.env[name]); | ||||
|     return process.env[name]; | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -600,34 +566,19 @@ function getSetting(node, name, flow_, callback) { | ||||
|  * will return `Hello Joe!`. | ||||
|  * @param  {String} value - the string to parse | ||||
|  * @param  {Node} node - the node evaluating the property | ||||
|  * @param  {(err: Error, result: Any) => void} callback - (optional) called when the property is evaluated | ||||
|  * @return {String} The parsed string | ||||
|  * @memberof @node-red/util_util | ||||
|  */ | ||||
| function evaluateEnvProperty(value, node, callback) { | ||||
|     const returnOrCallback = (err, result) => { | ||||
|         if (callback) { | ||||
|             callback(err, result); | ||||
|             return | ||||
|         } | ||||
|         return result; | ||||
|     } | ||||
|     /** @type {RuntimeLibFlowsFlow}  */ | ||||
| function evaluateEnvProperty(value, node) { | ||||
|     var flow = (node && hasOwnProperty.call(node, "_flow")) ? node._flow : null; | ||||
|     var result; | ||||
|     if (/^\${[^}]+}$/.test(value)) { | ||||
|         // ${ENV_VAR} | ||||
|         var name = value.substring(2,value.length-1); | ||||
|         result = getSetting(node, name, flow, callback); | ||||
|         if (callback) { | ||||
|             return | ||||
|         } | ||||
|         result = getSetting(node, name, flow); | ||||
|     } else if (!/\${\S+}/.test(value)) { | ||||
|         // ENV_VAR | ||||
|         result = getSetting(node, value, flow, callback); | ||||
|         if (callback) { | ||||
|             return | ||||
|         } | ||||
|         result = getSetting(node, value, flow); | ||||
|     } else { | ||||
|         // FOO${ENV_VAR}BAR | ||||
|         return value.replace(/\${([^}]+)}/g, function(match, name) { | ||||
| @@ -635,7 +586,8 @@ function evaluateEnvProperty(value, node, callback) { | ||||
|             return (val === undefined)?"":val; | ||||
|         }); | ||||
|     } | ||||
|     return returnOrCallback(null, (result === undefined)?"":result); | ||||
|     return (result === undefined)?"":result; | ||||
|  | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -723,10 +675,7 @@ function evaluateNodeProperty(value, type, node, msg, callback) { | ||||
|             return | ||||
|         } | ||||
|     } else if (type === 'env') { | ||||
|         result = evaluateEnvProperty(value, node, callback); | ||||
|         if (callback) { | ||||
|             return | ||||
|         } | ||||
|         result = evaluateEnvProperty(value, node); | ||||
|     } | ||||
|     if (callback) { | ||||
|         callback(null,result); | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "@node-red/util", | ||||
|     "version": "3.1.0-beta.3", | ||||
|     "version": "3.1.0-beta.4", | ||||
|     "license": "Apache-2.0", | ||||
|     "repository": { | ||||
|         "type": "git", | ||||
|   | ||||
							
								
								
									
										10
									
								
								packages/node_modules/node-red/package.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								packages/node_modules/node-red/package.json
									
									
									
									
										vendored
									
									
								
							| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "node-red", | ||||
|     "version": "3.1.0-beta.3", | ||||
|     "version": "3.1.0-beta.4", | ||||
|     "description": "Low-code programming for event-driven applications", | ||||
|     "homepage": "http://nodered.org", | ||||
|     "license": "Apache-2.0", | ||||
| @@ -31,10 +31,10 @@ | ||||
|         "flow" | ||||
|     ], | ||||
|     "dependencies": { | ||||
|         "@node-red/editor-api": "3.1.0-beta.3", | ||||
|         "@node-red/runtime": "3.1.0-beta.3", | ||||
|         "@node-red/util": "3.1.0-beta.3", | ||||
|         "@node-red/nodes": "3.1.0-beta.3", | ||||
|         "@node-red/editor-api": "3.1.0-beta.4", | ||||
|         "@node-red/runtime": "3.1.0-beta.4", | ||||
|         "@node-red/util": "3.1.0-beta.4", | ||||
|         "@node-red/nodes": "3.1.0-beta.4", | ||||
|         "basic-auth": "2.0.1", | ||||
|         "bcryptjs": "2.4.3", | ||||
|         "express": "4.18.2", | ||||
|   | ||||
							
								
								
									
										6
									
								
								test/node_modules/nr-test-utils/index.js
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								test/node_modules/nr-test-utils/index.js
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -16,7 +16,6 @@ | ||||
|  | ||||
|  | ||||
| const path = require("path"); | ||||
| const fs = require("fs"); | ||||
|  | ||||
| const PACKAGE_ROOT = "../../../packages/node_modules"; | ||||
|  | ||||
| @@ -27,5 +26,10 @@ module.exports = { | ||||
|     }, | ||||
|     resolve: function(file) { | ||||
|         return path.resolve(path.join(__dirname,PACKAGE_ROOT,file)); | ||||
|     }, | ||||
|     sleep: async (time) => { | ||||
|         return new Promise(resolve => { | ||||
|             setTimeout(resolve, time) | ||||
|         }) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -22,7 +22,9 @@ var helper = require("node-red-node-test-helper"); | ||||
| describe('inject node', function() { | ||||
|  | ||||
|     beforeEach(function(done) { | ||||
|         helper.startServer(done); | ||||
|         helper.startServer(() => { | ||||
|             done() | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     function initContext(done) { | ||||
| @@ -41,7 +43,7 @@ describe('inject node', function() { | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     afterEach(function(done) { | ||||
|     afterEach(async function() { | ||||
|         helper.unload().then(function () { | ||||
|             return Context.clean({allNodes: {}}); | ||||
|         }).then(function () { | ||||
| @@ -53,8 +55,11 @@ describe('inject node', function() { | ||||
|  | ||||
|     function basicTest(type, val, rval) { | ||||
|         it('inject value ('+type+')', function (done) { | ||||
|             var flow = [{id: "n1", type: "inject", topic: "t1", payload: val, payloadType: type, wires: [["n2"]], z: "flow"}, | ||||
|             {id: "n2", type: "helper"}]; | ||||
|             var flow = [ | ||||
|                 {id:'flow', type:'tab'}, | ||||
|                 {id: "n1", type: "inject", topic: "t1", payload: val, payloadType: type, wires: [["n2"]], z: "flow"}, | ||||
|                 {id: "n2", type: "helper", z:'flow'} | ||||
|             ]; | ||||
|             helper.load(injectNode, flow, function () { | ||||
|                 var n1 = helper.getNode("n1"); | ||||
|                 var n2 = helper.getNode("n2"); | ||||
| @@ -93,6 +98,7 @@ describe('inject node', function() { | ||||
|             var n1 = helper.getNode("n1"); | ||||
|             var n2 = helper.getNode("n2"); | ||||
|             n2.on("input", function (msg) { | ||||
|                 delete process.env.NR_TEST | ||||
|                 try { | ||||
|                     msg.should.have.property("topic", "t1"); | ||||
|                     msg.should.have.property("payload", "foo"); | ||||
| @@ -101,13 +107,13 @@ describe('inject node', function() { | ||||
|                     done(err); | ||||
|                 } | ||||
|             }); | ||||
| 	    process.env.NR_TEST = 'foo'; | ||||
|             process.env.NR_TEST = 'foo'; | ||||
|             n1.receive({}); | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     it('inject name of node as environment variable ', function (done) { | ||||
|         var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "NR_NODE_NAME", payloadType: "env", wires: [["n2"]], z: "flow"}, | ||||
|         var flow = [{id: "n1", type: "inject", name: "NAME", topic: "t1", payload: "NR_NODE_NAME", payloadType: "env", wires: [["n2"]], z: "flow"}, | ||||
|                     {id: "n2", type: "helper"}]; | ||||
|         helper.load(injectNode, flow, function () { | ||||
|             var n1 = helper.getNode("n1"); | ||||
| @@ -125,7 +131,7 @@ describe('inject node', function() { | ||||
|     }); | ||||
|  | ||||
|     it('inject id of node as environment variable ', function (done) { | ||||
|         var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "NR_NODE_ID", payloadType: "env", wires: [["n2"]], z: "flow"}, | ||||
|         var flow = [{id: "n1", type: "inject", name: "NAME", topic: "t1", payload: "NR_NODE_ID", payloadType: "env", wires: [["n2"]], z: "flow"}, | ||||
|                     {id: "n2", type: "helper"}]; | ||||
|         helper.load(injectNode, flow, function () { | ||||
|             var n1 = helper.getNode("n1"); | ||||
| @@ -143,7 +149,7 @@ describe('inject node', function() { | ||||
|     }); | ||||
|  | ||||
|     it('inject path of node as environment variable ', function (done) { | ||||
|         var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "NR_NODE_PATH", payloadType: "env", wires: [["n2"]], z: "flow"}, | ||||
|         var flow = [{id: "n1", type: "inject", name: "NAME", topic: "t1", payload: "NR_NODE_PATH", payloadType: "env", wires: [["n2"]], z: "flow"}, | ||||
|                     {id: "n2", type: "helper"}]; | ||||
|         helper.load(injectNode, flow, function () { | ||||
|             var n1 = helper.getNode("n1"); | ||||
| @@ -162,7 +168,7 @@ describe('inject node', function() { | ||||
|  | ||||
|  | ||||
|     it('inject name of flow as environment variable ', function (done) { | ||||
|         var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "NR_FLOW_NAME", payloadType: "env", wires: [["n2"]], z: "flow"}, | ||||
|         var flow = [{id: "n1", type: "inject", name: "NAME", topic: "t1", payload: "NR_FLOW_NAME", payloadType: "env", wires: [["n2"]], z: "flow"}, | ||||
|                     {id: "n2", type: "helper"}, | ||||
|                     {id: "flow", type: "tab", label: "FLOW" }, | ||||
|                    ]; | ||||
| @@ -182,7 +188,7 @@ describe('inject node', function() { | ||||
|     }); | ||||
|  | ||||
|     it('inject id of flow as environment variable ', function (done) { | ||||
|         var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "NR_FLOW_ID", payloadType: "env", wires: [["n2"]], z: "flow"}, | ||||
|         var flow = [{id: "n1", type: "inject", name: "NAME", topic: "t1", payload: "NR_FLOW_ID", payloadType: "env", wires: [["n2"]], z: "flow"}, | ||||
|                     {id: "n2", type: "helper"}, | ||||
|                     {id: "flow", type: "tab", name: "FLOW" }, | ||||
|                    ]; | ||||
| @@ -202,9 +208,10 @@ describe('inject node', function() { | ||||
|     }); | ||||
|  | ||||
|     it('inject name of group as environment variable ', function (done) { | ||||
|         var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "NR_GROUP_NAME", payloadType: "env", wires: [["n2"]], z: "flow", g: "g0"}, | ||||
|                     {id: "n2", type: "helper"}, | ||||
|                     {id: "g0", type: "group", name: "GROUP" }, | ||||
|         var flow = [{id: "flow", type: "tab" }, | ||||
|                     {id: "n1", type: "inject", name: "NAME", topic: "t1", payload: "NR_GROUP_NAME", payloadType: "env", wires: [["n2"]], z: "flow", g: "g0"}, | ||||
|                     {id: "n2", type: "helper", z: "flow"}, | ||||
|                     {id: "g0", type: "group", name: "GROUP", z: "flow" }, | ||||
|                    ]; | ||||
|         helper.load(injectNode, flow, function () { | ||||
|             var n1 = helper.getNode("n1"); | ||||
| @@ -222,9 +229,10 @@ describe('inject node', function() { | ||||
|     }); | ||||
|  | ||||
|     it('inject id of group as environment variable ', function (done) { | ||||
|         var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "NR_GROUP_ID", payloadType: "env", wires: [["n2"]], z: "flow", g: "g0"}, | ||||
|                     {id: "n2", type: "helper"}, | ||||
|                     {id: "g0", type: "group", name: "GROUP" }, | ||||
|         var flow = [{id: "flow", type: "tab" }, | ||||
|                     {id: "n1", type: "inject", name: "NAME", topic: "t1", payload: "NR_GROUP_ID", payloadType: "env", wires: [["n2"]], z: "flow", g: "g0"}, | ||||
|                     {id: "n2", type: "helper", z: "flow"}, | ||||
|                     {id: "g0", type: "group", name: "GROUP", z: "flow" }, | ||||
|                    ]; | ||||
|         helper.load(injectNode, flow, function () { | ||||
|             var n1 = helper.getNode("n1"); | ||||
| @@ -243,8 +251,9 @@ describe('inject node', function() { | ||||
|  | ||||
|  | ||||
|     it('inject name of node as environment variable by substitution ', function (done) { | ||||
|         var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "${NR_NODE_NAME}", payloadType: "str", wires: [["n2"]], z: "flow"}, | ||||
|                     {id: "n2", type: "helper"}]; | ||||
|         var flow = [{id: "flow", type: "tab" }, | ||||
|                     {id: "n1", type: "inject", name: "NAME", topic: "t1", payload: "${NR_NODE_NAME}", payloadType: "str", wires: [["n2"]], z: "flow"}, | ||||
|                     {id: "n2", type: "helper", z: "flow"}]; | ||||
|         helper.load(injectNode, flow, function () { | ||||
|             var n1 = helper.getNode("n1"); | ||||
|             var n2 = helper.getNode("n2"); | ||||
| @@ -261,7 +270,7 @@ describe('inject node', function() { | ||||
|     }); | ||||
|  | ||||
|     it('inject id of node as environment variable by substitution ', function (done) { | ||||
|         var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "${NR_NODE_ID}", payloadType: "str", wires: [["n2"]], z: "flow"}, | ||||
|         var flow = [{id: "n1", type: "inject", name: "NAME", topic: "t1", payload: "${NR_NODE_ID}", payloadType: "str", wires: [["n2"]], z: "flow"}, | ||||
|                     {id: "n2", type: "helper"}]; | ||||
|         helper.load(injectNode, flow, function () { | ||||
|             var n1 = helper.getNode("n1"); | ||||
| @@ -279,7 +288,7 @@ describe('inject node', function() { | ||||
|     }); | ||||
|  | ||||
|     it('inject path of node as environment variable by substitution ', function (done) { | ||||
|         var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "${NR_NODE_PATH}", payloadType: "str", wires: [["n2"]], z: "flow"}, | ||||
|         var flow = [{id: "n1", type: "inject", name: "NAME", topic: "t1", payload: "${NR_NODE_PATH}", payloadType: "str", wires: [["n2"]], z: "flow"}, | ||||
|                     {id: "n2", type: "helper"}]; | ||||
|         helper.load(injectNode, flow, function () { | ||||
|             var n1 = helper.getNode("n1"); | ||||
| @@ -298,7 +307,7 @@ describe('inject node', function() { | ||||
|  | ||||
|  | ||||
|     it('inject name of flow as environment variable by substitution ', function (done) { | ||||
|         var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "${NR_FLOW_NAME}", payloadType: "str", wires: [["n2"]], z: "flow"}, | ||||
|         var flow = [{id: "n1", type: "inject", name: "NAME", topic: "t1", payload: "${NR_FLOW_NAME}", payloadType: "str", wires: [["n2"]], z: "flow"}, | ||||
|                     {id: "n2", type: "helper"}, | ||||
|                     {id: "flow", type: "tab", label: "FLOW" }, | ||||
|                    ]; | ||||
| @@ -318,7 +327,7 @@ describe('inject node', function() { | ||||
|     }); | ||||
|  | ||||
|     it('inject id of flow as environment variable ', function (done) { | ||||
|         var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "${NR_FLOW_ID}", payloadType: "str", wires: [["n2"]], z: "flow"}, | ||||
|         var flow = [{id: "n1", type: "inject", name: "NAME", topic: "t1", payload: "${NR_FLOW_ID}", payloadType: "str", wires: [["n2"]], z: "flow"}, | ||||
|                     {id: "n2", type: "helper"}, | ||||
|                     {id: "flow", type: "tab", name: "FLOW" }, | ||||
|                    ]; | ||||
| @@ -338,9 +347,10 @@ describe('inject node', function() { | ||||
|     }); | ||||
|  | ||||
|     it('inject name of group as environment variable by substitution ', function (done) { | ||||
|         var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "${NR_GROUP_NAME}", payloadType: "str", wires: [["n2"]], z: "flow", g: "g0"}, | ||||
|                     {id: "n2", type: "helper"}, | ||||
|                     {id: "g0", type: "group", name: "GROUP" }, | ||||
|         var flow = [{id: "flow", type: "tab" }, | ||||
|                     {id: "n1", type: "inject", name: "NAME", topic: "t1", payload: "${NR_GROUP_NAME}", payloadType: "str", wires: [["n2"]], z: "flow", g: "g0"}, | ||||
|                     {id: "n2", type: "helper", z: "flow"}, | ||||
|                     {id: "g0", type: "group", name: "GROUP", z: "flow" }, | ||||
|                    ]; | ||||
|         helper.load(injectNode, flow, function () { | ||||
|             var n1 = helper.getNode("n1"); | ||||
| @@ -358,9 +368,10 @@ describe('inject node', function() { | ||||
|     }); | ||||
|  | ||||
|     it('inject id of group as environment variable by substitution ', function (done) { | ||||
|         var flow = [{id: "n1", type: "inject", name: "NAME", topnic: "t1", payload: "${NR_GROUP_ID}", payloadType: "str", wires: [["n2"]], z: "flow", g: "g0"}, | ||||
|                     {id: "n2", type: "helper"}, | ||||
|                     {id: "g0", type: "group", name: "GROUP" }, | ||||
|         var flow = [{id: "flow", type: "tab" }, | ||||
|                     {id: "n1", type: "inject", name: "NAME", topic: "t1", payload: "${NR_GROUP_ID}", payloadType: "str", wires: [["n2"]], z: "flow", g: "g0"}, | ||||
|                     {id: "n2", type: "helper", z: "flow"}, | ||||
|                     {id: "g0", type: "group", name: "GROUP", z: "flow" }, | ||||
|                    ]; | ||||
|         helper.load(injectNode, flow, function () { | ||||
|             var n1 = helper.getNode("n1"); | ||||
|   | ||||
| @@ -3,7 +3,7 @@ var config = require("nr-test-utils").require("@node-red/nodes/core/common/91-gl | ||||
| var inject = require("nr-test-utils").require("@node-red/nodes/core/common/20-inject.js"); | ||||
| var helper = require("node-red-node-test-helper"); | ||||
|  | ||||
| describe('unknown Node', function() { | ||||
| describe('Global Config Node', function() { | ||||
|  | ||||
|     afterEach(function() { | ||||
|         helper.unload(); | ||||
|   | ||||
| @@ -568,11 +568,12 @@ describe('change Node', function() { | ||||
|  | ||||
|             it('sets the value using env property from group', function(done) { | ||||
|                 var flow = [ | ||||
|                     {"id": "flow", type:"tab"}, | ||||
|                     {"id":"group1","type":"group","env":[ | ||||
|                         {"name":"NR_TEST_A", "value":"bar", "type": "str"} | ||||
|                     ]}, | ||||
|                     {"id":"changeNode1","type":"change","g":"group1",rules:[{"t":"set","p":"payload","pt":"msg","to":"NR_TEST_A","tot":"env"}],"name":"changeNode","wires":[["helperNode1"]]}, | ||||
|                     {id:"helperNode1", type:"helper", wires:[]} | ||||
|                     ], z: "flow"}, | ||||
|                     {"id":"changeNode1","type":"change","g":"group1",rules:[{"t":"set","p":"payload","pt":"msg","to":"NR_TEST_A","tot":"env"}],"name":"changeNode","wires":[["helperNode1"]], z: "flow"}, | ||||
|                     {id:"helperNode1", type:"helper", wires:[], z: "flow"} | ||||
|                 ]; | ||||
|                 helper.load(changeNode, flow, function() { | ||||
|                     var changeNode1 = helper.getNode("changeNode1"); | ||||
| @@ -591,12 +592,13 @@ describe('change Node', function() { | ||||
|  | ||||
|             it('sets the value using env property from nested group', function(done) { | ||||
|                 var flow = [ | ||||
|                     {"id": "flow", type:"tab"}, | ||||
|                     {"id":"group1","type":"group","env":[ | ||||
|                         {"name":"NR_TEST_A", "value":"bar", "type": "str"} | ||||
|                     ]}, | ||||
|                     {"id":"group2","type":"group","g":"group1","env":[]}, | ||||
|                     {"id":"changeNode1","type":"change","g":"group2",rules:[{"t":"set","p":"payload","pt":"msg","to":"NR_TEST_A","tot":"env"}],"name":"changeNode","wires":[["helperNode1"]]}, | ||||
|                     {id:"helperNode1", type:"helper", wires:[]} | ||||
|                     ], z: "flow"}, | ||||
|                     {"id":"group2","type":"group","g":"group1","env":[], z: "flow"}, | ||||
|                     {"id":"changeNode1","type":"change","g":"group2",rules:[{"t":"set","p":"payload","pt":"msg","to":"NR_TEST_A","tot":"env"}],"name":"changeNode","wires":[["helperNode1"]], z: "flow"}, | ||||
|                     {id:"helperNode1", type:"helper", wires:[], z: "flow"} | ||||
|                 ]; | ||||
|                 helper.load(changeNode, flow, function() { | ||||
|                     var changeNode1 = helper.getNode("changeNode1"); | ||||
|   | ||||
| @@ -98,6 +98,30 @@ describe('file Nodes', function() { | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         it('should write to a file using JSONata', function(done) { | ||||
|             var fileToTest4jsonata = "'" + resourcesDir + "/'&(20+30)&'-file-test-file.txt'"; | ||||
|             var flow = [{id:"fileNode1", type:"file", name: "fileNode", "filename": fileToTest4jsonata, "filenameType": "jsonata", "appendNewline":false, "overwriteFile":true, wires: [["helperNode1"]]}, | ||||
|                         {id:"helperNode1", type:"helper"}]; | ||||
|             helper.load(fileNode, flow, function() { | ||||
|                 var n1 = helper.getNode("fileNode1"); | ||||
|                 var n2 = helper.getNode("helperNode1"); | ||||
|                 n2.on("input", function(msg) { | ||||
|                     try { | ||||
|                         var f = fs.readFileSync(fileToTest); | ||||
|                         f.should.have.length(4); | ||||
|                         fs.unlinkSync(fileToTest); | ||||
|                         msg.should.have.property("payload", "test"); | ||||
|                         done(); | ||||
|                     } | ||||
|                     catch (e) { | ||||
|                         done(e); | ||||
|                     } | ||||
|                 }); | ||||
|                 n1.receive({payload:"test"}); | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|  | ||||
|         it('should write to a file using RED.settings.fileWorkingDirectory', function(done) { | ||||
|             var flow = [{id:"fileNode1", type:"file", name: "fileNode", "filename":relativePathToFile, "appendNewline":false, "overwriteFile":true, wires: [["helperNode1"]]}, | ||||
|                         {id:"helperNode1", type:"helper"}]; | ||||
| @@ -1237,6 +1261,27 @@ describe('file Nodes', function() { | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         it('should read in a file using JSONata and output a utf8 string', function(done) { | ||||
|             var fileToTest4jsonata = "'" + resourcesDir + "/'&(20+30)&'-file-test-file.txt'"; | ||||
|             var flow = [{id:"fileInNode1", type:"file in", name: "fileInNode", "filename":fileToTest4jsonata, "filenameType": "jsonata", "format":"utf8", wires:[["n2"]]}, | ||||
|                         {id:"n2", type:"helper"}]; | ||||
|             helper.load(fileNode, flow, function() { | ||||
|                 var n1 = helper.getNode("fileInNode1"); | ||||
|                 var n2 = helper.getNode("n2"); | ||||
|                 n2.on("input", function(msg) { | ||||
|                     try { | ||||
|                         msg.should.have.property('payload'); | ||||
|                         msg.payload.should.be.a.String(); | ||||
|                         msg.payload.should.have.length(40) | ||||
|                         msg.payload.should.equal("File message line 1\nFile message line 2\n"); | ||||
|                         done(); | ||||
|                     } catch(err) { | ||||
|                         done(err); | ||||
|                     } | ||||
|                 }); | ||||
|                 n1.receive({payload:""}); | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         it('should read in a file using fileWorkingDirectory to set cwd', function(done) { | ||||
|             var flow = [{id:"fileInNode1", type:"file in", name: "fileInNode", "filename":relativePathToFile, "format":"utf8", wires:[["n2"]]}, | ||||
|   | ||||
| @@ -253,35 +253,32 @@ describe('subflow', function() { | ||||
|  | ||||
|     it('should access typed value of env var', function(done) { | ||||
|         var flow = [ | ||||
|             {id:"t0", type:"tab", label:"", disabled:false, info:""}, | ||||
|             {id:"n1", x:10, y:10, z:"t0", type:"subflow:s1", | ||||
|              env: [ | ||||
|                  {name: "KN", type: "num", value: "100"}, | ||||
|                  {name: "KB", type: "bool", value: "true"}, | ||||
|                  {name: "KJ", type: "json", value: "[1,2,3]"}, | ||||
|                  {name: "Kb", type: "bin", value: "[65,65]"}, | ||||
|                  {name: "Ke", type: "env", value: "KS"}, | ||||
|                  {name: "Kj", type: "jsonata", value: "1+2"}, | ||||
|              ], | ||||
|              wires:[["n2"]]}, | ||||
|             {id:"n2", x:10, y:10, z:"t0", type:"helper", wires:[]}, | ||||
|             // Subflow | ||||
|             {id:"s1", type:"subflow", name:"Subflow", info:"", | ||||
|              in:[{ | ||||
|                  x:10, y:10, | ||||
|                  wires:[ {id:"s1-n1"} ] | ||||
|              }], | ||||
|              out:[{ | ||||
|                  x:10, y:10, | ||||
|                  wires:[ {id:"s1-n1", port:0} ] | ||||
|              }], | ||||
|              env: [ | ||||
|                  {name: "KS", type: "str", value: "STR"} | ||||
|              ] | ||||
|             { id: "t0", type: "tab", label: "", disabled: false, info: "" }, | ||||
|             { | ||||
|                 id: "n1", x: 10, y: 10, z: "t0", type: "subflow:s1", | ||||
|                 env: [ | ||||
|                     { name: "KN", type: "num", value: "100" }, | ||||
|                     { name: "KB", type: "bool", value: "true" }, | ||||
|                     { name: "KJ", type: "json", value: "[1,2,3]" }, | ||||
|                     { name: "Kb", type: "bin", value: "[65,65]" }, | ||||
|                     { name: "Ke", type: "env", value: "KS" }, | ||||
|                     { name: "Kj", type: "jsonata", value: "1+2" }, | ||||
|                 ], | ||||
|                 wires: [["n2"]] | ||||
|             }, | ||||
|             {id:"s1-n1", x:10, y:10, z:"s1", type:"function", | ||||
|              func:"msg.VE = env.get('Ke'); msg.VS = env.get('KS'); msg.VN = env.get('KN'); msg.VB = env.get('KB'); msg.VJ = env.get('KJ'); msg.Vb = env.get('Kb'); msg.Vj = env.get('Kj'); return msg;", | ||||
|              wires:[]} | ||||
|             { id: "n2", x: 10, y: 10, z: "t0", type: "helper", wires: [] }, | ||||
|             // Subflow | ||||
|             { | ||||
|                 id: "s1", type: "subflow", name: "Subflow", info: "", | ||||
|                 in: [{ x: 10, y: 10, wires: [{ id: "s1-n1" }] }], | ||||
|                 out: [{ x: 10, y: 10, wires: [{ id: "s1-n1", port: 0 }] }], | ||||
|                 env: [{ name: "KS", type: "str", value: "STR" }] | ||||
|             }, | ||||
|             { | ||||
|                 id: "s1-n1", x: 10, y: 10, z: "s1", type: "function", | ||||
|                 func: "msg.VE = env.get('Ke'); msg.VS = env.get('KS'); msg.VN = env.get('KN'); msg.VB = env.get('KB'); msg.VJ = env.get('KJ'); msg.Vb = env.get('Kb'); msg.Vj = env.get('Kj'); return msg;", | ||||
|                 wires: [] | ||||
|             } | ||||
|         ]; | ||||
|         helper.load(functionNode, flow, function() { | ||||
|             var n1 = helper.getNode("n1"); | ||||
|   | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										48
									
								
								test/unit/@node-red/runtime/lib/flows/Group_spec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								test/unit/@node-red/runtime/lib/flows/Group_spec.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | ||||
| const should = require("should"); | ||||
| const NR_TEST_UTILS = require("nr-test-utils"); | ||||
| const { Group } = NR_TEST_UTILS.require("@node-red/runtime/lib/flows/Group"); | ||||
|  | ||||
| describe('Group', function () { | ||||
|     describe('getSetting', function () { | ||||
|         it("returns group name/id", async function () { | ||||
|             const group = new Group({ | ||||
|                 getSetting: v => v+v | ||||
|             }, { | ||||
|                 name: "g1", | ||||
|                 id: "group1" | ||||
|             }) | ||||
|             await group.start() | ||||
|  | ||||
|             group.getSetting("NR_GROUP_NAME").should.equal("g1") | ||||
|             group.getSetting("NR_GROUP_ID").should.equal("group1") | ||||
|         }) | ||||
|         it("delegates to parent if not found", async function () { | ||||
|             const group = new Group({ | ||||
|                 getSetting: v => v+v | ||||
|             }, { | ||||
|                 name: "g1", | ||||
|                 id: "group1" | ||||
|             }) | ||||
|             await group.start() | ||||
|  | ||||
|             group.getSetting("123").should.equal("123123") | ||||
|         }) | ||||
|         it("delegates to parent if explicit requested", async function () { | ||||
|             const parentGroup = new Group({ | ||||
|                 getSetting: v => v+v | ||||
|             }, { | ||||
|                 name: "g0", | ||||
|                 id: "group0" | ||||
|             }) | ||||
|             const group = new Group(parentGroup, { | ||||
|                 name: "g1", | ||||
|                 id: "group1" | ||||
|             }) | ||||
|             await parentGroup.start() | ||||
|             await group.start() | ||||
|  | ||||
|             group.getSetting("$parent.NR_GROUP_NAME").should.equal("g0") | ||||
|             group.getSetting("$parent.NR_GROUP_ID").should.equal("group0") | ||||
|         }) | ||||
|     }) | ||||
| }) | ||||
| @@ -68,11 +68,13 @@ describe('Subflow', function() { | ||||
|         this.handled = 0; | ||||
|         this.stopped = false; | ||||
|         this.received = null; | ||||
|         this.receivedEnv = null; | ||||
|         currentNodes[node.id] = node; | ||||
|         this.on('input',function(msg) { | ||||
|             // console.log(this.id,msg.payload); | ||||
|             node.handled++; | ||||
|             node.received = msg.payload; | ||||
|             node.receivedEnv = msg.receivedEnv; | ||||
|             node.send(msg); | ||||
|         }); | ||||
|         this.on('close',function() { | ||||
| @@ -185,7 +187,15 @@ describe('Subflow', function() { | ||||
|             var flow = node._flow; | ||||
|             var val = flow.getSetting("__KEY__"); | ||||
|             node.received = val; | ||||
|             node.send({payload: val}); | ||||
|             const receivedEnv = {} | ||||
|             try { | ||||
|                 ['__KEY__','__KEY1__','__KEY2__','__KEY3__','__KEY4__'].forEach(k => { | ||||
|                     receivedEnv[k] = flow.getSetting(k) | ||||
|                 }) | ||||
|             } catch (err) { | ||||
|                 console.log(err) | ||||
|             } | ||||
|             node.send({payload: val, receivedEnv}); | ||||
|         }); | ||||
|         this.on('close',function() { | ||||
|             node.stopped = true; | ||||
| @@ -282,7 +292,7 @@ describe('Subflow', function() { | ||||
|         getType.restore(); | ||||
|     }); | ||||
|     describe('#start',function() { | ||||
|         it("instantiates a subflow and stops it",function(done) { | ||||
|         it("instantiates a subflow and stops it", async function() { | ||||
|             var config = flowUtils.parseConfig([ | ||||
|                 {id:"t1",type:"tab"}, | ||||
|                 {id:"1",x:10,y:10,z:"t1",type:"test",foo:"a",wires:["2"]}, | ||||
| @@ -297,7 +307,7 @@ describe('Subflow', function() { | ||||
|             ]); | ||||
|             var flow = Flow.create({handleError: (a,b,c) => { console.log(a,b,c); }},config,config.flows["t1"]); | ||||
|  | ||||
|             flow.start(); | ||||
|             await flow.start(); | ||||
|  | ||||
|             var activeNodes = flow.getActiveNodes(); | ||||
|             Object.keys(activeNodes).should.have.length(4); | ||||
| @@ -332,37 +342,21 @@ describe('Subflow', function() { | ||||
|             // currentNodes[sfInstanceId2].should.have.a.property("handled",0); | ||||
|  | ||||
|             currentNodes["1"].receive({payload:"test"}); | ||||
|             | ||||
|             await NR_TEST_UTILS.sleep(150) | ||||
|  | ||||
|             setTimeout(function() { | ||||
|                 currentNodes["1"].should.have.a.property("handled",1); | ||||
|                 // currentNodes[sfInstanceId].should.have.a.property("handled",1); | ||||
|                 // currentNodes[sfInstanceId2].should.have.a.property("handled",1); | ||||
|                 currentNodes["3"].should.have.a.property("handled",1); | ||||
|                 currentNodes["4"].should.have.a.property("handled",1); | ||||
|             currentNodes["1"].should.have.a.property("handled",1); | ||||
|             // currentNodes[sfInstanceId].should.have.a.property("handled",1); | ||||
|             // currentNodes[sfInstanceId2].should.have.a.property("handled",1); | ||||
|             currentNodes["3"].should.have.a.property("handled",1); | ||||
|             currentNodes["4"].should.have.a.property("handled",1); | ||||
|  | ||||
|  | ||||
|  | ||||
|                 flow.stop().then(function() { | ||||
|                     Object.keys(currentNodes).should.have.length(0); | ||||
|                     Object.keys(stoppedNodes).should.have.length(6); | ||||
|  | ||||
|                     // currentNodes.should.not.have.a.property("1"); | ||||
|                     // currentNodes.should.not.have.a.property("3"); | ||||
|                     // currentNodes.should.not.have.a.property("4"); | ||||
|                     // // currentNodes.should.not.have.a.property(sfInstanceId); | ||||
|                     // // currentNodes.should.not.have.a.property(sfInstanceId2); | ||||
|                     // // currentNodes.should.not.have.a.property(sfConfigId); | ||||
|                     // stoppedNodes.should.have.a.property("1"); | ||||
|                     // stoppedNodes.should.have.a.property("3"); | ||||
|                     // stoppedNodes.should.have.a.property("4"); | ||||
|                     // // stoppedNodes.should.have.a.property(sfInstanceId); | ||||
|                     // // stoppedNodes.should.have.a.property(sfInstanceId2); | ||||
|                     // // stoppedNodes.should.have.a.property(sfConfigId); | ||||
|                     done(); | ||||
|                 }); | ||||
|             },150); | ||||
|             await flow.stop() | ||||
|             Object.keys(currentNodes).should.have.length(0); | ||||
|             Object.keys(stoppedNodes).should.have.length(6); | ||||
|         }); | ||||
|         it("instantiates a subflow inside a subflow and stops it",function(done) { | ||||
|  | ||||
|         it("instantiates a subflow inside a subflow and stops it", async function() { | ||||
|             var config = flowUtils.parseConfig([ | ||||
|                 {id:"t1",type:"tab"}, | ||||
|                 {id:"1",x:10,y:10,z:"t1",type:"test",foo:"a",wires:["2"]}, | ||||
| @@ -379,24 +373,20 @@ describe('Subflow', function() { | ||||
|             ]); | ||||
|             var flow = Flow.create({},config,config.flows["t1"]); | ||||
|  | ||||
|             flow.start(); | ||||
|             await flow.start(); | ||||
|  | ||||
|             currentNodes["1"].should.have.a.property("handled",0); | ||||
|             currentNodes["3"].should.have.a.property("handled",0); | ||||
|  | ||||
|             currentNodes["1"].receive({payload:"test"}); | ||||
|  | ||||
|             setTimeout(function() { | ||||
|                 currentNodes["1"].should.have.a.property("handled",1); | ||||
|                 currentNodes["3"].should.have.a.property("handled",1); | ||||
|                 flow.stop().then(function() { | ||||
|                     Object.keys(currentNodes).should.have.length(0); | ||||
|                     done(); | ||||
|                 }); | ||||
|             },150); | ||||
|             await NR_TEST_UTILS.sleep(150) | ||||
|             currentNodes["1"].should.have.a.property("handled",1); | ||||
|             currentNodes["3"].should.have.a.property("handled",1); | ||||
|             await flow.stop() | ||||
|             Object.keys(currentNodes).should.have.length(0); | ||||
|         }); | ||||
|         it("rewires a subflow node on update/start",function(done){ | ||||
|  | ||||
|         it("rewires a subflow node on update/start", async function(){ | ||||
|             var rawConfig = [ | ||||
|                 {id:"t1",type:"tab"}, | ||||
|                 {id:"1",x:10,y:10,z:"t1",type:"test",foo:"a",wires:["2"]}, | ||||
| @@ -417,7 +407,7 @@ describe('Subflow', function() { | ||||
|             var diff = flowUtils.diffConfigs(config,newConfig); | ||||
|             var flow = Flow.create({},config,config.flows["t1"]); | ||||
|  | ||||
|             flow.start(); | ||||
|             await flow.start(); | ||||
|  | ||||
|             var activeNodes = flow.getActiveNodes(); | ||||
|             Object.keys(activeNodes).should.have.length(4); | ||||
| @@ -429,36 +419,28 @@ describe('Subflow', function() { | ||||
|             currentNodes["4"].should.have.a.property("handled",0); | ||||
|  | ||||
|             currentNodes["1"].receive({payload:"test"}); | ||||
|             await NR_TEST_UTILS.sleep(150) | ||||
|             currentNodes["1"].should.have.a.property("handled",1); | ||||
|             // currentNodes[sfInstanceId].should.have.a.property("handled",1); | ||||
|             // currentNodes[sfInstanceId2].should.have.a.property("handled",1); | ||||
|             currentNodes["3"].should.have.a.property("handled",1); | ||||
|             currentNodes["4"].should.have.a.property("handled",0); | ||||
|  | ||||
|             setTimeout(function() { | ||||
|                 currentNodes["1"].should.have.a.property("handled",1); | ||||
|                 // currentNodes[sfInstanceId].should.have.a.property("handled",1); | ||||
|                 // currentNodes[sfInstanceId2].should.have.a.property("handled",1); | ||||
|                 currentNodes["3"].should.have.a.property("handled",1); | ||||
|                 currentNodes["4"].should.have.a.property("handled",0); | ||||
|             flow.update(newConfig,newConfig.flows["t1"]); | ||||
|             await flow.start(diff) | ||||
|             currentNodes["1"].receive({payload:"test2"}); | ||||
|             await NR_TEST_UTILS.sleep(150) | ||||
|             currentNodes["1"].should.have.a.property("handled",2); | ||||
|             // currentNodes[sfInstanceId].should.have.a.property("handled",2); | ||||
|             // currentNodes[sfInstanceId2].should.have.a.property("handled",2); | ||||
|             currentNodes["3"].should.have.a.property("handled",1); | ||||
|             currentNodes["4"].should.have.a.property("handled",1); | ||||
|  | ||||
|                 flow.update(newConfig,newConfig.flows["t1"]); | ||||
|                 flow.start(diff) | ||||
|  | ||||
|                 currentNodes["1"].receive({payload:"test2"}); | ||||
|                 setTimeout(function() { | ||||
|  | ||||
|                     currentNodes["1"].should.have.a.property("handled",2); | ||||
|                     // currentNodes[sfInstanceId].should.have.a.property("handled",2); | ||||
|                     // currentNodes[sfInstanceId2].should.have.a.property("handled",2); | ||||
|                     currentNodes["3"].should.have.a.property("handled",1); | ||||
|                     currentNodes["4"].should.have.a.property("handled",1); | ||||
|  | ||||
|  | ||||
|                     flow.stop().then(function() { | ||||
|                         done(); | ||||
|                     }); | ||||
|                 },150); | ||||
|             },150); | ||||
|             await flow.stop() | ||||
|         }); | ||||
|     }); | ||||
|     describe('#stop', function() { | ||||
|         it("stops subflow instance nodes",function(done) { | ||||
|         it("stops subflow instance nodes", async function() { | ||||
|             var config = flowUtils.parseConfig([ | ||||
|                 {id:"t1",type:"tab"}, | ||||
|                 {id:"1",x:10,y:10,z:"t1",type:"test",foo:"a",wires:["2"]}, | ||||
| @@ -470,20 +452,18 @@ describe('Subflow', function() { | ||||
|             ]); | ||||
|             var flow = Flow.create({},config,config.flows["t1"]); | ||||
|  | ||||
|             flow.start(); | ||||
|             await flow.start(); | ||||
|  | ||||
|             var activeNodes = flow.getActiveNodes(); | ||||
|             Object.keys(activeNodes).should.have.length(3); | ||||
|             Object.keys(stoppedNodes).should.have.length(0); | ||||
|             flow.stop(["2"]).then(function() { | ||||
|                 Object.keys(currentNodes).should.have.length(2); | ||||
|                 Object.keys(stoppedNodes).should.have.length(1); | ||||
|                 done(); | ||||
|             }).catch(done); | ||||
|             await flow.stop(["2"]) | ||||
|             Object.keys(currentNodes).should.have.length(2); | ||||
|             Object.keys(stoppedNodes).should.have.length(1); | ||||
|         }); | ||||
|     }); | ||||
|     describe("#handleStatus",function() { | ||||
|         it("passes a status event to the subflow's parent tab status node - all scope",function(done) { | ||||
|         it("passes a status event to the subflow's parent tab status node - all scope", async function() { | ||||
|             var config = flowUtils.parseConfig([ | ||||
|                 {id:"t1",type:"tab"}, | ||||
|                 {id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]}, | ||||
| @@ -496,27 +476,24 @@ describe('Subflow', function() { | ||||
|             ]); | ||||
|             var flow = Flow.create({},config,config.flows["t1"]); | ||||
|  | ||||
|             flow.start(); | ||||
|             await flow.start(); | ||||
|  | ||||
|             var activeNodes = flow.getActiveNodes(); | ||||
|  | ||||
|             activeNodes["1"].receive({payload:"test"}); | ||||
|             setTimeout(function() { | ||||
|                 currentNodes["sn"].should.have.a.property("handled",1); | ||||
|                 var statusMessage = currentNodes["sn"].messages[0]; | ||||
|             await NR_TEST_UTILS.sleep(150) | ||||
|             currentNodes["sn"].should.have.a.property("handled",1); | ||||
|             var statusMessage = currentNodes["sn"].messages[0]; | ||||
|  | ||||
|                 statusMessage.should.have.a.property("status"); | ||||
|                 statusMessage.status.should.have.a.property("text","test status"); | ||||
|                 statusMessage.status.should.have.a.property("source"); | ||||
|                 statusMessage.status.source.should.have.a.property("type","testStatus"); | ||||
|                 statusMessage.status.source.should.have.a.property("name","test-status-node"); | ||||
|             statusMessage.should.have.a.property("status"); | ||||
|             statusMessage.status.should.have.a.property("text","test status"); | ||||
|             statusMessage.status.should.have.a.property("source"); | ||||
|             statusMessage.status.source.should.have.a.property("type","testStatus"); | ||||
|             statusMessage.status.source.should.have.a.property("name","test-status-node"); | ||||
|  | ||||
|                 flow.stop().then(function() { | ||||
|                     done(); | ||||
|                 }); | ||||
|             },150); | ||||
|             await flow.stop() | ||||
|         }); | ||||
|         it("passes a status event to the subflow's parent tab status node - targetted scope",function(done) { | ||||
|         it("passes a status event to the subflow's parent tab status node - targetted scope", async function() { | ||||
|             var config = flowUtils.parseConfig([ | ||||
|                 {id:"t1",type:"tab"}, | ||||
|                 {id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]}, | ||||
| @@ -531,34 +508,30 @@ describe('Subflow', function() { | ||||
|  | ||||
|             var flow = Flow.create({handleStatus:() => { parentFlowStatusCalled = true} },config,config.flows["t1"]); | ||||
|  | ||||
|             flow.start(); | ||||
|             await flow.start(); | ||||
|  | ||||
|             var activeNodes = flow.getActiveNodes(); | ||||
|  | ||||
|             activeNodes["1"].receive({payload:"test"}); | ||||
|  | ||||
|             setTimeout(function() { | ||||
|                 parentFlowStatusCalled.should.be.false(); | ||||
|             await NR_TEST_UTILS.sleep(150) | ||||
|             parentFlowStatusCalled.should.be.false(); | ||||
|  | ||||
|                 currentNodes["sn"].should.have.a.property("handled",1); | ||||
|                 var statusMessage = currentNodes["sn"].messages[0]; | ||||
|             currentNodes["sn"].should.have.a.property("handled",1); | ||||
|             var statusMessage = currentNodes["sn"].messages[0]; | ||||
|  | ||||
|                 statusMessage.should.have.a.property("status"); | ||||
|                 statusMessage.status.should.have.a.property("text","test status"); | ||||
|                 statusMessage.status.should.have.a.property("source"); | ||||
|                 statusMessage.status.source.should.have.a.property("type","testStatus"); | ||||
|                 statusMessage.status.source.should.have.a.property("name","test-status-node"); | ||||
|             statusMessage.should.have.a.property("status"); | ||||
|             statusMessage.status.should.have.a.property("text","test status"); | ||||
|             statusMessage.status.should.have.a.property("source"); | ||||
|             statusMessage.status.source.should.have.a.property("type","testStatus"); | ||||
|             statusMessage.status.source.should.have.a.property("name","test-status-node"); | ||||
|  | ||||
|                 flow.stop().then(function() { | ||||
|  | ||||
|                     done(); | ||||
|                 }); | ||||
|             },150); | ||||
|             await flow.stop() | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     describe("status node", function() { | ||||
|         it("emits a status event when a message is passed to a subflow-status node - msg.payload as string", function(done) { | ||||
|         it("emits a status event when a message is passed to a subflow-status node - msg.payload as string", async function() { | ||||
|             var config = flowUtils.parseConfig([ | ||||
|                 {id:"t1",type:"tab"}, | ||||
|                 {id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]}, | ||||
| @@ -578,29 +551,24 @@ describe('Subflow', function() { | ||||
|             ]); | ||||
|             var flow = Flow.create({},config,config.flows["t1"]); | ||||
|  | ||||
|             flow.start(); | ||||
|             await flow.start(); | ||||
|  | ||||
|             var activeNodes = flow.getActiveNodes(); | ||||
|  | ||||
|             activeNodes["1"].receive({payload:"test-payload"}); | ||||
|             await NR_TEST_UTILS.sleep(150) | ||||
|  | ||||
|             setTimeout(function() { | ||||
|                 currentNodes["sn"].should.have.a.property("handled",1); | ||||
|                 var statusMessage = currentNodes["sn"].messages[0]; | ||||
|             currentNodes["sn"].should.have.a.property("handled",1); | ||||
|             var statusMessage = currentNodes["sn"].messages[0]; | ||||
|  | ||||
|                 statusMessage.should.have.a.property("status"); | ||||
|                 statusMessage.status.should.have.a.property("text","test-payload"); | ||||
|                 statusMessage.status.should.have.a.property("source"); | ||||
|                 statusMessage.status.source.should.have.a.property("id","2"); | ||||
|                 statusMessage.status.source.should.have.a.property("type","subflow:sf1"); | ||||
|  | ||||
|                 flow.stop().then(function() { | ||||
|  | ||||
|                     done(); | ||||
|                 }); | ||||
|             },150); | ||||
|             statusMessage.should.have.a.property("status"); | ||||
|             statusMessage.status.should.have.a.property("text","test-payload"); | ||||
|             statusMessage.status.should.have.a.property("source"); | ||||
|             statusMessage.status.source.should.have.a.property("id","2"); | ||||
|             statusMessage.status.source.should.have.a.property("type","subflow:sf1"); | ||||
|             await flow.stop() | ||||
|         }); | ||||
|         it("emits a status event when a message is passed to a subflow-status node - msg.payload as status obj", function(done) { | ||||
|         it("emits a status event when a message is passed to a subflow-status node - msg.payload as status obj", async function() { | ||||
|             var config = flowUtils.parseConfig([ | ||||
|                 {id:"t1",type:"tab"}, | ||||
|                 {id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]}, | ||||
| @@ -620,29 +588,26 @@ describe('Subflow', function() { | ||||
|             ]); | ||||
|             var flow = Flow.create({},config,config.flows["t1"]); | ||||
|  | ||||
|             flow.start(); | ||||
|             await flow.start(); | ||||
|  | ||||
|             var activeNodes = flow.getActiveNodes(); | ||||
|  | ||||
|             activeNodes["1"].receive({payload:{text:"payload-obj"}}); | ||||
|  | ||||
|             setTimeout(function() { | ||||
|                 currentNodes["sn"].should.have.a.property("handled",1); | ||||
|                 var statusMessage = currentNodes["sn"].messages[0]; | ||||
|             await NR_TEST_UTILS.sleep(150) | ||||
|  | ||||
|                 statusMessage.should.have.a.property("status"); | ||||
|                 statusMessage.status.should.have.a.property("text","payload-obj"); | ||||
|                 statusMessage.status.should.have.a.property("source"); | ||||
|                 statusMessage.status.source.should.have.a.property("id","2"); | ||||
|                 statusMessage.status.source.should.have.a.property("type","subflow:sf1"); | ||||
|             currentNodes["sn"].should.have.a.property("handled",1); | ||||
|             var statusMessage = currentNodes["sn"].messages[0]; | ||||
|  | ||||
|                 flow.stop().then(function() { | ||||
|             statusMessage.should.have.a.property("status"); | ||||
|             statusMessage.status.should.have.a.property("text","payload-obj"); | ||||
|             statusMessage.status.should.have.a.property("source"); | ||||
|             statusMessage.status.source.should.have.a.property("id","2"); | ||||
|             statusMessage.status.source.should.have.a.property("type","subflow:sf1"); | ||||
|  | ||||
|                     done(); | ||||
|                 }); | ||||
|             },150); | ||||
|             await flow.stop() | ||||
|         }); | ||||
|         it("emits a status event when a message is passed to a subflow-status node - msg.status", function(done) { | ||||
|         it("emits a status event when a message is passed to a subflow-status node - msg.status", async function() { | ||||
|             var config = flowUtils.parseConfig([ | ||||
|                 {id:"t1",type:"tab"}, | ||||
|                 {id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]}, | ||||
| @@ -662,29 +627,26 @@ describe('Subflow', function() { | ||||
|             ]); | ||||
|             var flow = Flow.create({},config,config.flows["t1"]); | ||||
|  | ||||
|             flow.start(); | ||||
|             await flow.start(); | ||||
|  | ||||
|             var activeNodes = flow.getActiveNodes(); | ||||
|  | ||||
|             activeNodes["1"].receive({status:{text:"status-obj"}}); | ||||
|  | ||||
|             setTimeout(function() { | ||||
|                 currentNodes["sn"].should.have.a.property("handled",1); | ||||
|                 var statusMessage = currentNodes["sn"].messages[0]; | ||||
|             await NR_TEST_UTILS.sleep(150) | ||||
|  | ||||
|                 statusMessage.should.have.a.property("status"); | ||||
|                 statusMessage.status.should.have.a.property("text","status-obj"); | ||||
|                 statusMessage.status.should.have.a.property("source"); | ||||
|                 statusMessage.status.source.should.have.a.property("id","2"); | ||||
|                 statusMessage.status.source.should.have.a.property("type","subflow:sf1"); | ||||
|             currentNodes["sn"].should.have.a.property("handled",1); | ||||
|             var statusMessage = currentNodes["sn"].messages[0]; | ||||
|  | ||||
|                 flow.stop().then(function() { | ||||
|             statusMessage.should.have.a.property("status"); | ||||
|             statusMessage.status.should.have.a.property("text","status-obj"); | ||||
|             statusMessage.status.should.have.a.property("source"); | ||||
|             statusMessage.status.source.should.have.a.property("id","2"); | ||||
|             statusMessage.status.source.should.have.a.property("type","subflow:sf1"); | ||||
|  | ||||
|                     done(); | ||||
|                 }); | ||||
|             },150); | ||||
|             flow.stop() | ||||
|         }); | ||||
|         it("does not emit a regular status event if it contains a subflow-status node", function(done) { | ||||
|         it("does not emit a regular status event if it contains a subflow-status node", async function() { | ||||
|             var config = flowUtils.parseConfig([ | ||||
|                 {id:"t1",type:"tab"}, | ||||
|                 {id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]}, | ||||
| @@ -704,7 +666,7 @@ describe('Subflow', function() { | ||||
|             ]); | ||||
|             var flow = Flow.create({},config,config.flows["t1"]); | ||||
|  | ||||
|             flow.start(); | ||||
|             await flow.start(); | ||||
|  | ||||
|             var activeNodes = flow.getActiveNodes(); | ||||
|  | ||||
| @@ -712,15 +674,12 @@ describe('Subflow', function() { | ||||
|  | ||||
|             currentNodes["sn"].should.have.a.property("handled",0); | ||||
|  | ||||
|             flow.stop().then(function() { | ||||
|  | ||||
|                 done(); | ||||
|             }); | ||||
|             await flow.stop() | ||||
|         }); | ||||
|     }) | ||||
|  | ||||
|     describe("#handleError",function() { | ||||
|         it("passes an error event to the subflow's parent tab catch node - all scope",function(done) { | ||||
|         it("passes an error event to the subflow's parent tab catch node - all scope",async function() { | ||||
|             var config = flowUtils.parseConfig([ | ||||
|                 {id:"t1",type:"tab"}, | ||||
|                 {id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]}, | ||||
| @@ -733,28 +692,26 @@ describe('Subflow', function() { | ||||
|             ]); | ||||
|             var flow = Flow.create({},config,config.flows["t1"]); | ||||
|  | ||||
|             flow.start(); | ||||
|             await flow.start(); | ||||
|  | ||||
|             var activeNodes = flow.getActiveNodes(); | ||||
|  | ||||
|             activeNodes["1"].receive({payload:"test"}); | ||||
|              | ||||
|             await NR_TEST_UTILS.sleep(150) | ||||
|  | ||||
|             setTimeout(function() { | ||||
|                 currentNodes["sn"].should.have.a.property("handled",1); | ||||
|                 var statusMessage = currentNodes["sn"].messages[0]; | ||||
|             currentNodes["sn"].should.have.a.property("handled",1); | ||||
|             var statusMessage = currentNodes["sn"].messages[0]; | ||||
|  | ||||
|                 statusMessage.should.have.a.property("error"); | ||||
|                 statusMessage.error.should.have.a.property("message","test error"); | ||||
|                 statusMessage.error.should.have.a.property("source"); | ||||
|                 statusMessage.error.source.should.have.a.property("type","testError"); | ||||
|                 statusMessage.error.source.should.have.a.property("name","test-error-node"); | ||||
|             statusMessage.should.have.a.property("error"); | ||||
|             statusMessage.error.should.have.a.property("message","test error"); | ||||
|             statusMessage.error.should.have.a.property("source"); | ||||
|             statusMessage.error.source.should.have.a.property("type","testError"); | ||||
|             statusMessage.error.source.should.have.a.property("name","test-error-node"); | ||||
|  | ||||
|                 flow.stop().then(function() { | ||||
|                     done(); | ||||
|                 }); | ||||
|             },150); | ||||
|             await flow.stop() | ||||
|         }); | ||||
|         it("passes an error event to the subflow's parent tab catch node - targetted scope",function(done) { | ||||
|         it("passes an error event to the subflow's parent tab catch node - targetted scope", async function() { | ||||
|             var config = flowUtils.parseConfig([ | ||||
|                 {id:"t1",type:"tab"}, | ||||
|                 {id:"1",x:10,y:10,z:"t1",type:"test",name:"a",wires:["2"]}, | ||||
| @@ -768,50 +725,31 @@ describe('Subflow', function() { | ||||
|             var parentFlowErrorCalled = false; | ||||
|             var flow = Flow.create({handleError:() => { parentFlowErrorCalled = true} },config,config.flows["t1"]); | ||||
|  | ||||
|             flow.start(); | ||||
|             await flow.start(); | ||||
|  | ||||
|             var activeNodes = flow.getActiveNodes(); | ||||
|  | ||||
|             activeNodes["1"].receive({payload:"test"}); | ||||
|  | ||||
|             setTimeout(function() { | ||||
|                 parentFlowErrorCalled.should.be.false(); | ||||
|             await NR_TEST_UTILS.sleep(150) | ||||
|              | ||||
|             parentFlowErrorCalled.should.be.false(); | ||||
|  | ||||
|                 currentNodes["sn"].should.have.a.property("handled",1); | ||||
|                 var statusMessage = currentNodes["sn"].messages[0]; | ||||
|             currentNodes["sn"].should.have.a.property("handled",1); | ||||
|             var statusMessage = currentNodes["sn"].messages[0]; | ||||
|  | ||||
|                 statusMessage.should.have.a.property("error"); | ||||
|                 statusMessage.error.should.have.a.property("message","test error"); | ||||
|                 statusMessage.error.should.have.a.property("source"); | ||||
|                 statusMessage.error.source.should.have.a.property("type","testError"); | ||||
|                 statusMessage.error.source.should.have.a.property("name","test-error-node"); | ||||
|  | ||||
|                 flow.stop().then(function() { | ||||
|                     done(); | ||||
|                 }); | ||||
|             },150); | ||||
|             statusMessage.should.have.a.property("error"); | ||||
|             statusMessage.error.should.have.a.property("message","test error"); | ||||
|             statusMessage.error.should.have.a.property("source"); | ||||
|             statusMessage.error.source.should.have.a.property("type","testError"); | ||||
|             statusMessage.error.source.should.have.a.property("name","test-error-node"); | ||||
|  | ||||
|             await flow.stop() | ||||
|         }); | ||||
|     }); | ||||
|  | ||||
|     describe("#env var", function() { | ||||
|         // should be changed according to internal env var representation | ||||
|         function setEnv(node, key, val) { | ||||
|             var flow = node._flow; | ||||
|             if (flow) { | ||||
|                 var env = flow.env; | ||||
|                 if (!env) { | ||||
|                     env = flow.env = {}; | ||||
|                 } | ||||
|                 env[key] = { | ||||
|                         name: key, | ||||
|                         type: "str", | ||||
|                         value: val | ||||
|                 }; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         it("can access process env var", function(done) { | ||||
|         it("can access process env var", async function() { | ||||
|             var config = flowUtils.parseConfig([ | ||||
|                 {id:"t1",type:"tab"}, | ||||
|                 {id:"1",x:10,y:10,z:"t1",type:"test",foo:"t1.1",wires:["2"]}, | ||||
| @@ -828,29 +766,25 @@ describe('Subflow', function() { | ||||
|                 handleError: (a,b,c) => { console.log(a,b,c); } | ||||
|             },config,config.flows["t1"]); | ||||
|  | ||||
|             flow.start(); | ||||
|             await flow.start(); | ||||
|  | ||||
|             process.env["__KEY__"] = "__VAL__"; | ||||
|  | ||||
|             currentNodes["1"].receive({payload: "test"}); | ||||
|             setTimeout(function() { | ||||
|                 currentNodes["3"].should.have.a.property("received", "__VAL__"); | ||||
|  | ||||
|                 flow.stop().then(function() { | ||||
|                     done(); | ||||
|                 }); | ||||
|             },150); | ||||
|             await NR_TEST_UTILS.sleep(150) | ||||
|             currentNodes["3"].should.have.a.property("received", "__VAL__"); | ||||
|             await flow.stop() | ||||
|         }); | ||||
|  | ||||
|         it("can access subflow env var", function(done) { | ||||
|         it("can access subflow env var", async function() { | ||||
|             var config = flowUtils.parseConfig([ | ||||
|                 {id:"t1",type:"tab"}, | ||||
|                 {id:"1",x:10,y:10,z:"t1",type:"test",foo:"t1.1",wires:["2"]}, | ||||
|                 {id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3"]}, | ||||
|                 {id:"3",x:10,y:10,z:"t1",type:"test",foo:"t1.3",wires:[]}, | ||||
|                 {id:"sf1",type:"subflow",name:"Subflow 2",info:"", | ||||
|                  "in":[ {wires:[{id:"sf1-1"}]} ], | ||||
|                  "out":[ {wires:[{id:"sf1-2",port:0}]} ]}, | ||||
|                 {id:"sf1",type:"subflow",name:"Subflow 2",info:"",env: [{name: '__KEY__', value: '__VAL1__', type: 'str'}], | ||||
|                     "in":[ {wires:[{id:"sf1-1"}]} ], | ||||
|                     "out":[ {wires:[{id:"sf1-2",port:0}]} ]}, | ||||
|                 {id:"sf1-1",type:"test",z:"sf1",foo:"sf1.1",x:166,y:99,wires:[["sf1-2"]]}, | ||||
|                 {id:"sf1-2",type:"testEnv",z:"sf1",foo:"sf1.2",x:166,y:99,wires:[[]]} | ||||
|             ]); | ||||
| @@ -859,7 +793,7 @@ describe('Subflow', function() { | ||||
|                 handleError: (a,b,c) => { console.log(a,b,c); } | ||||
|             },config,config.flows["t1"]); | ||||
|  | ||||
|             flow.start(); | ||||
|             await flow.start(); | ||||
|  | ||||
|             var testenv_node = null; | ||||
|             for (var n in currentNodes) { | ||||
| @@ -870,32 +804,30 @@ describe('Subflow', function() { | ||||
|                 } | ||||
|             } | ||||
|             process.env["__KEY__"] = "__VAL0__"; | ||||
|             setEnv(testenv_node, "__KEY__", "__VAL1__"); | ||||
|  | ||||
|             currentNodes["1"].receive({payload: "test"}); | ||||
|             setTimeout(function() { | ||||
|                 currentNodes["3"].should.have.a.property("received", "__VAL1__"); | ||||
|             await NR_TEST_UTILS.sleep(150) | ||||
|  | ||||
|                 flow.stop().then(function() { | ||||
|                     done(); | ||||
|                 }); | ||||
|             },150); | ||||
|             currentNodes["3"].should.have.a.property("received", "__VAL1__"); | ||||
|             await flow.stop() | ||||
|         }); | ||||
|  | ||||
|         it("can access nested subflow env var", function(done) { | ||||
|         it("can access nested subflow env var", async function() { | ||||
|             var config = flowUtils.parseConfig([ | ||||
|                 {id:"t1",type:"tab"}, | ||||
|                 {id:"t1",type:"tab", env: [{name: '__KEY1__', value: 't1', type: 'str'}]}, | ||||
|                 {id:"1",x:10,y:10,z:"t1",type:"test",foo:"t1.1",wires:["2"]}, | ||||
|                 {id:"2",x:10,y:10,z:"t1",type:"subflow:sf1",wires:["3"]}, | ||||
|                 {id:"3",x:10,y:10,z:"t1",type:"test",foo:"t1.3",wires:[]}, | ||||
|                 {id:"sf1",type:"subflow",name:"Subflow 1",info:"", | ||||
|                  in:[{wires:[{id:"sf1-1"}]}], | ||||
|                  out:[{wires:[{id:"sf1-2",port:0}]}]}, | ||||
|                     env: [{name: '__KEY2__', value: 'sf1', type: 'str'}], | ||||
|                     in:[{wires:[{id:"sf1-1"}]}], | ||||
|                     out:[{wires:[{id:"sf1-2",port:0}]}]}, | ||||
|                 {id:"sf2",type:"subflow",name:"Subflow 2",info:"", | ||||
|                  in:[{wires:[{id:"sf2-1"}]}], | ||||
|                  out:[{wires:[{id:"sf2-2",port:0}]}]}, | ||||
|                     env: [{name: '__KEY3__', value: 'sf2', type: 'str'}], | ||||
|                     in:[{wires:[{id:"sf2-1"}]}], | ||||
|                     out:[{wires:[{id:"sf2-2",port:0}]}]}, | ||||
|                 {id:"sf1-1",type:"test",z:"sf1",foo:"sf1.1",x:166,y:99,wires:[["sf1-2"]]}, | ||||
|                 {id:"sf1-2",type:"subflow:sf2",z:"sf1",x:166,y:99,wires:[[]]}, | ||||
|                 {id:"sf1-2",type:"subflow:sf2",z:"sf1",x:166,y:99,wires:[[]], env: [{name: '__KEY4__', value: 'sf1-2', type: 'str'}] }, | ||||
|                 {id:"sf2-1",type:"test",z:"sf2",foo:"sf2.1",x:166,y:99,wires:[["sf2-2"]]}, | ||||
|                 {id:"sf2-2",type:"testEnv",z:"sf2",foo:"sf2.2",x:166,y:99,wires:[[]]}, | ||||
|             ]); | ||||
| @@ -904,45 +836,22 @@ describe('Subflow', function() { | ||||
|                 handleError: (a,b,c) => { console.log(a,b,c); } | ||||
|             },config,config.flows["t1"]); | ||||
|  | ||||
|             flow.start(); | ||||
|  | ||||
|             var node_sf1_1 = null; | ||||
|             var node_sf2_1 = null; | ||||
|             var testenv_node = null; | ||||
|             for (var n in currentNodes) { | ||||
|                 var node = currentNodes[n]; | ||||
|                 if (node.foo === "sf1.1") { | ||||
|                     node_sf1_1 = node; | ||||
|                 } | ||||
|                 if (node.foo === "sf2.1") { | ||||
|                     node_sf2_1 = node; | ||||
|                 } | ||||
|             } | ||||
|             await flow.start(); | ||||
|  | ||||
|             process.env["__KEY__"] = "__VAL0__"; | ||||
|             currentNodes["1"].receive({payload: "test"}); | ||||
|             setTimeout(function() { | ||||
|                 currentNodes["3"].should.have.a.property("received", "__VAL0__"); | ||||
|  | ||||
|                 setEnv(node_sf1_1, "__KEY__", "__VAL1__"); | ||||
|                 currentNodes["1"].receive({payload: "test"}); | ||||
|                 setTimeout(function() { | ||||
|                     currentNodes["3"].should.have.a.property("received", "__VAL1__"); | ||||
|  | ||||
|                     setEnv(node_sf2_1, "__KEY__", "__VAL2__"); | ||||
|                     currentNodes["1"].receive({payload: "test"}); | ||||
|                     setTimeout(function() { | ||||
|                         currentNodes["3"].should.have.a.property("received", "__VAL2__"); | ||||
|  | ||||
|                         flow.stop().then(function() { | ||||
|                             done(); | ||||
|                         }); | ||||
|                     },150); | ||||
|                 },150); | ||||
|             },150); | ||||
|             await NR_TEST_UTILS.sleep(150) | ||||
|             currentNodes["3"].should.have.a.property("receivedEnv"); | ||||
|             currentNodes["3"].receivedEnv.should.have.a.property('__KEY__', '__VAL0__') | ||||
|             currentNodes["3"].receivedEnv.should.have.a.property('__KEY1__', 't1') | ||||
|             currentNodes["3"].receivedEnv.should.have.a.property('__KEY2__', 'sf1') | ||||
|             currentNodes["3"].receivedEnv.should.have.a.property('__KEY3__', 'sf2') | ||||
|             currentNodes["3"].receivedEnv.should.have.a.property('__KEY4__', 'sf1-2') | ||||
|              | ||||
|             await flow.stop() | ||||
|         }); | ||||
|  | ||||
|         it("can access name of subflow as env var", function(done) { | ||||
|         it("can access name of subflow as env var", async function() { | ||||
|             var config = flowUtils.parseConfig([ | ||||
|                 {id:"t1",type:"tab"}, | ||||
|                 {id:"1",x:10,y:10,z:"t1",type:"test",foo:"t1.1",wires:["2"]}, | ||||
| @@ -959,19 +868,15 @@ describe('Subflow', function() { | ||||
|                 handleError: (a,b,c) => { console.log(a,b,c); } | ||||
|             },config,config.flows["t1"]); | ||||
|  | ||||
|             flow.start(); | ||||
|             await flow.start(); | ||||
|  | ||||
|             currentNodes["1"].receive({payload: "test"}); | ||||
|             setTimeout(function() { | ||||
|                 currentNodes["3"].should.have.a.property("received", "SFN"); | ||||
|  | ||||
|                 flow.stop().then(function() { | ||||
|                     done(); | ||||
|                 }); | ||||
|             },150); | ||||
|             await NR_TEST_UTILS.sleep(150) | ||||
|             currentNodes["3"].should.have.a.property("received", "SFN"); | ||||
|             await flow.stop() | ||||
|         }); | ||||
|  | ||||
|         it("can access id of subflow as env var", function(done) { | ||||
|         it("can access id of subflow as env var", async function() { | ||||
|             var config = flowUtils.parseConfig([ | ||||
|                 {id:"t1",type:"tab"}, | ||||
|                 {id:"1",x:10,y:10,z:"t1",type:"test",foo:"t1.1",wires:["2"]}, | ||||
| @@ -988,19 +893,13 @@ describe('Subflow', function() { | ||||
|                 handleError: (a,b,c) => { console.log(a,b,c); } | ||||
|             },config,config.flows["t1"]); | ||||
|  | ||||
|             flow.start(); | ||||
|             await flow.start(); | ||||
|  | ||||
|             currentNodes["1"].receive({payload: "test"}); | ||||
|             setTimeout(function() { | ||||
|                 currentNodes["3"].should.have.a.property("received", "2"); | ||||
|  | ||||
|                 flow.stop().then(function() { | ||||
|                     done(); | ||||
|                 }); | ||||
|             },150); | ||||
|             await NR_TEST_UTILS.sleep(150) | ||||
|             currentNodes["3"].should.have.a.property("received", "2"); | ||||
|             await flow.stop() | ||||
|         }); | ||||
|  | ||||
|  | ||||
|     }); | ||||
|  | ||||
| }); | ||||
|   | ||||
| @@ -93,7 +93,7 @@ describe('flows/index', function() { | ||||
|             flowCreate.flows[id] = { | ||||
|                 flow: flow, | ||||
|                 global: global, | ||||
|                 start: sinon.spy(), | ||||
|                 start: sinon.spy(async() => {}), | ||||
|                 update: sinon.spy(), | ||||
|                 stop: sinon.spy(), | ||||
|                 getActiveNodes: function() { | ||||
| @@ -221,13 +221,18 @@ describe('flows/index', function() { | ||||
|                 return Promise.resolve({flows:originalConfig}); | ||||
|             } | ||||
|             events.once('flows:started',function() { | ||||
|                 flows.setFlows(newConfig,"nodes").then(function() { | ||||
|                     flows.getFlows().flows.should.eql(newConfig); | ||||
|                     flowCreate.flows['t1'].update.called.should.be.true(); | ||||
|                     flowCreate.flows['t2'].start.called.should.be.true(); | ||||
|                     flowCreate.flows['_GLOBAL_'].update.called.should.be.true(); | ||||
|                     done(); | ||||
|                 events.once('flows:started', function() { | ||||
|                     try { | ||||
|                         flows.getFlows().flows.should.eql(newConfig); | ||||
|                         flowCreate.flows['t1'].update.called.should.be.true(); | ||||
|                         flowCreate.flows['t2'].start.called.should.be.true(); | ||||
|                         flowCreate.flows['_GLOBAL_'].update.called.should.be.true(); | ||||
|                         done(); | ||||
|                     } catch(err) { | ||||
|                         done(err) | ||||
|                     } | ||||
|                 }) | ||||
|                 flows.setFlows(newConfig,"nodes") | ||||
|             }); | ||||
|  | ||||
|             flows.init({log:mockLog, settings:{},storage:storage}); | ||||
| @@ -250,13 +255,14 @@ describe('flows/index', function() { | ||||
|             } | ||||
|  | ||||
|             events.once('flows:started',function() { | ||||
|                 flows.setFlows(newConfig,"nodes").then(function() { | ||||
|                 events.once('flows:started',function() { | ||||
|                     flows.getFlows().flows.should.eql(newConfig); | ||||
|                     flowCreate.flows['t1'].update.called.should.be.true(); | ||||
|                     flowCreate.flows['t2'].start.called.should.be.true(); | ||||
|                     flowCreate.flows['_GLOBAL_'].update.called.should.be.true(); | ||||
|                     flows.stopFlows().then(done); | ||||
|                 }) | ||||
|                 flows.setFlows(newConfig,"nodes") | ||||
|             }); | ||||
|  | ||||
|             flows.init({log:mockLog, settings:{},storage:storage}); | ||||
|   | ||||
| @@ -149,7 +149,7 @@ describe('flows/util', function() { | ||||
|                 {id:"t1",type:"tab"} | ||||
|             ]; | ||||
|             var parsedConfig = flowUtil.parseConfig(originalConfig); | ||||
|             var expectedConfig = {"allNodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]},"t1":{"id":"t1","type":"tab"}},"subflows":{},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]}}}},"groups":{},"missingTypes":[]}; | ||||
|             var expectedConfig = {"allNodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]},"t1":{"id":"t1","type":"tab"}},"subflows":{},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"groups":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]}}}},"missingTypes":[]}; | ||||
|             parsedConfig.should.eql(expectedConfig); | ||||
|         }); | ||||
|  | ||||
| @@ -160,7 +160,7 @@ describe('flows/util', function() { | ||||
|                 {id:"t1",type:"tab"} | ||||
|             ]; | ||||
|             var parsedConfig = flowUtil.parseConfig(originalConfig); | ||||
|             var expectedConfig = {"allNodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","foo":"cn","wires":[]},"cn":{"id":"cn","type":"test"},"t1":{"id":"t1","type":"tab"}},"subflows":{},"configs":{"cn":{"id":"cn","type":"test","_users":["t1-1"]}},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","foo":"cn","wires":[]}}}},"groups":{},"missingTypes":[]}; | ||||
|             var expectedConfig = {"allNodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","foo":"cn","wires":[]},"cn":{"id":"cn","type":"test"},"t1":{"id":"t1","type":"tab"}},"subflows":{},"configs":{"cn":{"id":"cn","type":"test","_users":["t1-1"]}},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"groups":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","foo":"cn","wires":[]}}}},"missingTypes":[]}; | ||||
|             parsedConfig.should.eql(expectedConfig); | ||||
|         }); | ||||
|  | ||||
| @@ -172,7 +172,7 @@ describe('flows/util', function() { | ||||
|                 {id:"t2-1",x:10,y:10,z:"t2",type:"test",wires:[]} | ||||
|             ]; | ||||
|             var parsedConfig = flowUtil.parseConfig(originalConfig); | ||||
|             var expectedConfig = {"allNodes":{"t1":{"id":"t1","type":"tab"},"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]},"t2":{"id":"t2","type":"tab"},"t2-1":{"id":"t2-1","x":10,"y":10,"z":"t2","type":"test","wires":[]}},"subflows":{},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]}}},"t2":{"id":"t2","type":"tab","subflows":{},"configs":{},"nodes":{"t2-1":{"id":"t2-1","x":10,"y":10,"z":"t2","type":"test","wires":[]}}}},"groups":{},"missingTypes":[]}; | ||||
|             var expectedConfig = {"allNodes":{"t1":{"id":"t1","type":"tab"},"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]},"t2":{"id":"t2","type":"tab"},"t2-1":{"id":"t2-1","x":10,"y":10,"z":"t2","type":"test","wires":[]}},"subflows":{},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"groups":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]}}},"t2":{"id":"t2","type":"tab","subflows":{},"configs":{},"groups":{},"nodes":{"t2-1":{"id":"t2-1","x":10,"y":10,"z":"t2","type":"test","wires":[]}}}},"missingTypes":[]}; | ||||
|             parsedConfig.should.eql(expectedConfig); | ||||
|         }); | ||||
|  | ||||
| @@ -184,7 +184,7 @@ describe('flows/util', function() { | ||||
|                 {id:"sf1-1",x:10,y:10,z:"sf1",type:"test",wires:[]} | ||||
|             ]; | ||||
|             var parsedConfig = flowUtil.parseConfig(originalConfig); | ||||
|             var expectedConfig = {"allNodes":{"t1":{"id":"t1","type":"tab"},"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"subflow:sf1","wires":[]},"sf1":{"id":"sf1","type":"subflow"},"sf1-1":{"id":"sf1-1","x":10,"y":10,"z":"sf1","type":"test","wires":[]}},"subflows":{"sf1":{"id":"sf1","type":"subflow","configs":{},"nodes":{"sf1-1":{"id":"sf1-1","x":10,"y":10,"z":"sf1","type":"test","wires":[]}},"instances":[{"id":"t1-1","x":10,"y":10,"z":"t1","type":"subflow:sf1","wires":[],"subflow":"sf1"}]}},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"subflow:sf1","wires":[],"subflow":"sf1"}}}},"groups":{},"missingTypes":[]}; | ||||
|             var expectedConfig = {"allNodes":{"t1":{"id":"t1","type":"tab"},"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"subflow:sf1","wires":[]},"sf1":{"id":"sf1","type":"subflow"},"sf1-1":{"id":"sf1-1","x":10,"y":10,"z":"sf1","type":"test","wires":[]}},"subflows":{"sf1":{"id":"sf1","type":"subflow","configs":{},"groups":{},"nodes":{"sf1-1":{"id":"sf1-1","x":10,"y":10,"z":"sf1","type":"test","wires":[]}},"instances":[{"id":"t1-1","x":10,"y":10,"z":"t1","type":"subflow:sf1","wires":[],"subflow":"sf1"}]}},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"groups":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"subflow:sf1","wires":[],"subflow":"sf1"}}}},"missingTypes":[]}; | ||||
|             parsedConfig.should.eql(expectedConfig); | ||||
|         }); | ||||
|  | ||||
| @@ -196,7 +196,7 @@ describe('flows/util', function() { | ||||
|             ]; | ||||
|             var parsedConfig = flowUtil.parseConfig(originalConfig); | ||||
|             parsedConfig.missingTypes.should.eql(['missing']); | ||||
|             var expectedConfig = {"allNodes":{"t1":{"id":"t1","type":"tab"},"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"sf1","wires":[]},"t1-2":{"id":"t1-2","x":10,"y":10,"z":"t1","type":"missing","wires":[]}},"subflows":{},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"sf1","wires":[]},'t1-2': { id: 't1-2', x: 10, y: 10, z: 't1', type: 'missing', wires: [] }}}},"groups":{},"missingTypes":["missing"]}; | ||||
|             var expectedConfig = {"allNodes":{"t1":{"id":"t1","type":"tab"},"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"sf1","wires":[]},"t1-2":{"id":"t1-2","x":10,"y":10,"z":"t1","type":"missing","wires":[]}},"subflows":{},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"groups":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"sf1","wires":[]},'t1-2': { id: 't1-2', x: 10, y: 10, z: 't1', type: 'missing', wires: [] }}}},"missingTypes":["missing"]}; | ||||
|             redUtil.compareObjects(parsedConfig,expectedConfig).should.be.true(); | ||||
|         }); | ||||
|  | ||||
| @@ -206,7 +206,7 @@ describe('flows/util', function() { | ||||
|                 {id:"cn",type:"test"}, | ||||
|             ]; | ||||
|             var parsedConfig = flowUtil.parseConfig(originalConfig); | ||||
|             var expectedConfig = {"allNodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","foo":"cn","wires":[]},"cn":{"id":"cn","type":"test"}},"subflows":{},"configs":{"cn":{"id":"cn","type":"test","_users":["t1-1"]}},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","foo":"cn","wires":[]}}}},"groups":{},"missingTypes":[]}; | ||||
|             var expectedConfig = {"allNodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","foo":"cn","wires":[]},"cn":{"id":"cn","type":"test"}},"subflows":{},"configs":{"cn":{"id":"cn","type":"test","_users":["t1-1"]}},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","foo":"cn","wires":[]}}}},"missingTypes":[]}; | ||||
|             parsedConfig.should.eql(expectedConfig); | ||||
|         }); | ||||
|  | ||||
| @@ -217,7 +217,7 @@ describe('flows/util', function() { | ||||
|                 {id:"g1",type:"group",z:"t1"} | ||||
|             ]; | ||||
|             var parsedConfig = flowUtil.parseConfig(originalConfig); | ||||
|             var expectedConfig = {"allNodes":{"t1":{"id":"t1","type":"tab"},"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]},"g1":{"id":"g1","type":"group","z":"t1"}},"subflows":{},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]}}}},"groups":{"g1":{"id":"g1","type":"group","z":"t1"}},"missingTypes":[]} | ||||
|             var expectedConfig = {"allNodes":{"t1":{"id":"t1","type":"tab"},"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]},"g1":{"id":"g1","type":"group","z":"t1"}},"subflows":{},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"groups":{"g1":{"id":"g1","type":"group","z":"t1"}},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]}}}},"missingTypes":[]} | ||||
|             parsedConfig.should.eql(expectedConfig); | ||||
|         }); | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user