mirror of
				https://github.com/node-red/node-red.git
				synced 2025-03-01 10:36:34 +00:00 
			
		
		
		
	Improve background conflict handling
This commit is contained in:
		| @@ -59,18 +59,53 @@ RED.history = (function() { | ||||
|                         t: 'replace', | ||||
|                         config: RED.nodes.createCompleteNodeSet(), | ||||
|                         changed: {}, | ||||
|                         rev: RED.nodes.version() | ||||
|                         moved: {}, | ||||
|                         complete: true, | ||||
|                         rev: RED.nodes.version(), | ||||
|                         dirty: RED.nodes.dirty() | ||||
|                     }; | ||||
|                     var selectedTab = RED.workspaces.active(); | ||||
|                     inverseEv.config.forEach(n => { | ||||
|                         const node = RED.nodes.node(n.id) | ||||
|                         if (node) { | ||||
|                             inverseEv.changed[n.id] = node.changed | ||||
|                             inverseEv.moved[n.id] = node.moved | ||||
|                         } | ||||
|                     }) | ||||
|                     RED.nodes.clear(); | ||||
|                     var imported = RED.nodes.import(ev.config); | ||||
|                     // Clear all change flags from the import | ||||
|                     RED.nodes.dirty(false); | ||||
|  | ||||
|                     const flowsToLock = new Set() | ||||
|                     function ensureUnlocked(id) { | ||||
|                         const flow = id && (RED.nodes.workspace(id) || RED.nodes.subflow(id) || null); | ||||
|                         const isLocked = flow ? flow.locked : false; | ||||
|                         if (flow && isLocked) { | ||||
|                             flow.locked = false; | ||||
|                             flowsToLock.add(flow) | ||||
|                         } | ||||
|                     } | ||||
|                     imported.nodes.forEach(function(n) { | ||||
|                         if (ev.changed[n.id]) { | ||||
|                             ensureUnlocked(n.z) | ||||
|                             n.changed = true; | ||||
|                             inverseEv.changed[n.id] = true; | ||||
|                         } | ||||
|                         if (ev.moved[n.id]) { | ||||
|                             ensureUnlocked(n.z) | ||||
|                             n.moved = true; | ||||
|                         } | ||||
|                     }) | ||||
|                     flowsToLock.forEach(flow => { | ||||
|                         flow.locked = true | ||||
|                     }) | ||||
|  | ||||
|                     RED.nodes.version(ev.rev); | ||||
|                     RED.view.redraw(true); | ||||
|                     RED.palette.refresh(); | ||||
|                     RED.workspaces.refresh(); | ||||
|                     RED.workspaces.show(selectedTab, true); | ||||
|                     RED.sidebar.config.refresh(); | ||||
|                 } else { | ||||
|                     var importMap = {}; | ||||
|                     ev.config.forEach(function(n) { | ||||
|   | ||||
| @@ -34,6 +34,8 @@ RED.deploy = (function() { | ||||
|  | ||||
|     var currentDiff = null; | ||||
|  | ||||
|     var activeBackgroundDeployNotification; | ||||
|  | ||||
|     function changeDeploymentType(type) { | ||||
|         deploymentType = type; | ||||
|         $("#red-ui-header-button-deploy-icon").attr("src",deploymentTypes[type].img); | ||||
| @@ -133,37 +135,38 @@ RED.deploy = (function() { | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         var activeNotifyMessage; | ||||
|         RED.comms.subscribe("notification/runtime-deploy",function(topic,msg) { | ||||
|             if (!activeNotifyMessage) { | ||||
|                 var currentRev = RED.nodes.version(); | ||||
|                 if (currentRev === null || deployInflight || currentRev === msg.revision) { | ||||
|                     return; | ||||
|                 } | ||||
|                 var message = $('<p>').text(RED._('deploy.confirm.backgroundUpdate')); | ||||
|                 activeNotifyMessage = RED.notify(message,{ | ||||
|                     modal: true, | ||||
|                     fixed: true, | ||||
|                     buttons: [ | ||||
|                         { | ||||
|                             text: RED._('deploy.confirm.button.ignore'), | ||||
|                             click: function() { | ||||
|                                 activeNotifyMessage.close(); | ||||
|                                 activeNotifyMessage = null; | ||||
|                             } | ||||
|                         }, | ||||
|                         { | ||||
|                             text: RED._('deploy.confirm.button.review'), | ||||
|                             class: "primary", | ||||
|                             click: function() { | ||||
|                                 activeNotifyMessage.close(); | ||||
|                                 var nns = RED.nodes.createCompleteNodeSet(); | ||||
|                                 resolveConflict(nns,false); | ||||
|                                 activeNotifyMessage = null; | ||||
|                             } | ||||
|             var currentRev = RED.nodes.version(); | ||||
|             if (currentRev === null || deployInflight || currentRev === msg.revision) { | ||||
|                 return; | ||||
|             } | ||||
|             if (activeBackgroundDeployNotification?.hidden) { | ||||
|                 activeBackgroundDeployNotification.showNotification() | ||||
|                 return | ||||
|             } | ||||
|             const message = $('<p>').text(RED._('deploy.confirm.backgroundUpdate')); | ||||
|             const options = { | ||||
|                 id: 'background-update', | ||||
|                 type: 'compact', | ||||
|                 modal: false, | ||||
|                 fixed: true, | ||||
|                 timeout: 10000, | ||||
|                 buttons: [ | ||||
|                     { | ||||
|                         text: RED._('deploy.confirm.button.review'), | ||||
|                         class: "primary", | ||||
|                         click: function() { | ||||
|                             activeBackgroundDeployNotification.hideNotification(); | ||||
|                             var nns = RED.nodes.createCompleteNodeSet(); | ||||
|                             resolveConflict(nns,false); | ||||
|                         } | ||||
|                     ] | ||||
|                 }); | ||||
|                     } | ||||
|                 ] | ||||
|             } | ||||
|             if (!activeBackgroundDeployNotification || activeBackgroundDeployNotification.closed) { | ||||
|                 activeBackgroundDeployNotification = RED.notify(message, options) | ||||
|             } else { | ||||
|                 activeBackgroundDeployNotification.update(message, options) | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| @@ -220,7 +223,11 @@ RED.deploy = (function() { | ||||
|                 class: "primary disabled", | ||||
|                 click: function() { | ||||
|                     if (!$("#red-ui-deploy-dialog-confirm-deploy-review").hasClass('disabled')) { | ||||
|                         RED.diff.showRemoteDiff(); | ||||
|                         RED.diff.showRemoteDiff(null, { | ||||
|                             onmerge: function () { | ||||
|                                 activeBackgroundDeployNotification.close() | ||||
|                             } | ||||
|                         }); | ||||
|                         conflictNotification.close(); | ||||
|                     } | ||||
|                 } | ||||
| @@ -233,6 +240,7 @@ RED.deploy = (function() { | ||||
|                     if (!$("#red-ui-deploy-dialog-confirm-deploy-merge").hasClass('disabled')) { | ||||
|                         RED.diff.mergeDiff(currentDiff); | ||||
|                         conflictNotification.close(); | ||||
|                         activeBackgroundDeployNotification.close() | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| @@ -245,6 +253,7 @@ RED.deploy = (function() { | ||||
|                 click: function() { | ||||
|                     save(true,activeDeploy); | ||||
|                     conflictNotification.close(); | ||||
|                     activeBackgroundDeployNotification.close() | ||||
|                 } | ||||
|             }) | ||||
|         } | ||||
| @@ -255,21 +264,17 @@ RED.deploy = (function() { | ||||
|             buttons: buttons | ||||
|         }); | ||||
|  | ||||
|         var now = Date.now(); | ||||
|         RED.diff.getRemoteDiff(function(diff) { | ||||
|             var ellapsed = Math.max(1000 - (Date.now()-now), 0); | ||||
|             currentDiff = diff; | ||||
|             setTimeout(function() { | ||||
|                 conflictCheck.hide(); | ||||
|                 var d = Object.keys(diff.conflicts); | ||||
|                 if (d.length === 0) { | ||||
|                     conflictAutoMerge.show(); | ||||
|                     $("#red-ui-deploy-dialog-confirm-deploy-merge").removeClass('disabled') | ||||
|                 } else { | ||||
|                     conflictManualMerge.show(); | ||||
|                 } | ||||
|                 $("#red-ui-deploy-dialog-confirm-deploy-review").removeClass('disabled') | ||||
|             },ellapsed); | ||||
|             conflictCheck.hide(); | ||||
|             var d = Object.keys(diff.conflicts); | ||||
|             if (d.length === 0) { | ||||
|                 conflictAutoMerge.show(); | ||||
|                 $("#red-ui-deploy-dialog-confirm-deploy-merge").removeClass('disabled') | ||||
|             } else { | ||||
|                 conflictManualMerge.show(); | ||||
|             } | ||||
|             $("#red-ui-deploy-dialog-confirm-deploy-review").removeClass('disabled') | ||||
|         }) | ||||
|     } | ||||
|     function cropList(list) { | ||||
|   | ||||
| @@ -1099,11 +1099,11 @@ RED.diff = (function() { | ||||
|     //     var diff = generateDiff(originalFlow,nns); | ||||
|     //     showDiff(diff); | ||||
|     // } | ||||
|     function showRemoteDiff(diff) { | ||||
|         if (diff === undefined) { | ||||
|             getRemoteDiff(showRemoteDiff); | ||||
|     function showRemoteDiff(diff, options = {}) { | ||||
|         if (!diff) { | ||||
|             getRemoteDiff((remoteDiff) => showRemoteDiff(remoteDiff, options)); | ||||
|         } else { | ||||
|             showDiff(diff,{mode:'merge'}); | ||||
|             showDiff(diff,{...options, mode:'merge'}); | ||||
|         } | ||||
|     } | ||||
|     function parseNodes(nodeList) { | ||||
| @@ -1240,7 +1240,7 @@ RED.diff = (function() { | ||||
|         return diff; | ||||
|     } | ||||
|  | ||||
|     function showDiff(diff,options) { | ||||
|     function showDiff(diff, options) { | ||||
|         if (diffVisible) { | ||||
|             return; | ||||
|         } | ||||
| @@ -1315,6 +1315,9 @@ RED.diff = (function() { | ||||
|                         if (!$("#red-ui-diff-view-diff-merge").hasClass('disabled')) { | ||||
|                             refreshConflictHeader(diff); | ||||
|                             mergeDiff(diff); | ||||
|                             if (options.onmerge) { | ||||
|                                 options.onmerge() | ||||
|                             } | ||||
|                             RED.tray.close(); | ||||
|                         } | ||||
|                     } | ||||
| @@ -1345,6 +1348,7 @@ RED.diff = (function() { | ||||
|         var newConfig = []; | ||||
|         var node; | ||||
|         var nodeChangedStates = {}; | ||||
|         var nodeMovedStates = {}; | ||||
|         var localChangedStates = {}; | ||||
|         for (id in localDiff.newConfig.all) { | ||||
|             if (localDiff.newConfig.all.hasOwnProperty(id)) { | ||||
| @@ -1352,12 +1356,14 @@ RED.diff = (function() { | ||||
|                 if (resolutions[id] === 'local') { | ||||
|                     if (node) { | ||||
|                         nodeChangedStates[id] = node.changed; | ||||
|                         nodeMovedStates[id] = node.moved; | ||||
|                     } | ||||
|                     newConfig.push(localDiff.newConfig.all[id]); | ||||
|                 } else if (resolutions[id] === 'remote') { | ||||
|                     if (!remoteDiff.deleted[id] && remoteDiff.newConfig.all.hasOwnProperty(id)) { | ||||
|                         if (node) { | ||||
|                             nodeChangedStates[id] = node.changed; | ||||
|                             nodeMovedStates[id] = node.moved; | ||||
|                         } | ||||
|                         localChangedStates[id] = 1; | ||||
|                         newConfig.push(remoteDiff.newConfig.all[id]); | ||||
| @@ -1381,8 +1387,9 @@ RED.diff = (function() { | ||||
|         } | ||||
|         return { | ||||
|             config: newConfig, | ||||
|             nodeChangedStates: nodeChangedStates, | ||||
|             localChangedStates: localChangedStates | ||||
|             nodeChangedStates, | ||||
|             nodeMovedStates, | ||||
|             localChangedStates | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -1393,6 +1400,7 @@ RED.diff = (function() { | ||||
|  | ||||
|         var newConfig = appliedDiff.config; | ||||
|         var nodeChangedStates = appliedDiff.nodeChangedStates; | ||||
|         var nodeMovedStates = appliedDiff.nodeMovedStates; | ||||
|         var localChangedStates = appliedDiff.localChangedStates; | ||||
|  | ||||
|         var isDirty = RED.nodes.dirty(); | ||||
| @@ -1401,33 +1409,56 @@ RED.diff = (function() { | ||||
|             t:"replace", | ||||
|             config: RED.nodes.createCompleteNodeSet(), | ||||
|             changed: nodeChangedStates, | ||||
|             moved: nodeMovedStates, | ||||
|             complete: true, | ||||
|             dirty: isDirty, | ||||
|             rev: RED.nodes.version() | ||||
|         } | ||||
|  | ||||
|         RED.history.push(historyEvent); | ||||
|  | ||||
|         var originalFlow = RED.nodes.originalFlow(); | ||||
|         // originalFlow is what the editor things it loaded | ||||
|         //  - add any newly added nodes from remote diff as they are now part of the record | ||||
|         for (var id in diff.remoteDiff.added) { | ||||
|             if (diff.remoteDiff.added.hasOwnProperty(id)) { | ||||
|                 if (diff.remoteDiff.newConfig.all.hasOwnProperty(id)) { | ||||
|                     originalFlow.push(JSON.parse(JSON.stringify(diff.remoteDiff.newConfig.all[id]))); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         // var originalFlow = RED.nodes.originalFlow(); | ||||
|         // // originalFlow is what the editor thinks it loaded | ||||
|         // //  - add any newly added nodes from remote diff as they are now part of the record | ||||
|         // for (var id in diff.remoteDiff.added) { | ||||
|         //     if (diff.remoteDiff.added.hasOwnProperty(id)) { | ||||
|         //         if (diff.remoteDiff.newConfig.all.hasOwnProperty(id)) { | ||||
|         //             originalFlow.push(JSON.parse(JSON.stringify(diff.remoteDiff.newConfig.all[id]))); | ||||
|         //         } | ||||
|         //     } | ||||
|         // } | ||||
|  | ||||
|         RED.nodes.clear(); | ||||
|         var imported = RED.nodes.import(newConfig); | ||||
|  | ||||
|         // Restore the original flow so subsequent merge resolutions can properly | ||||
|         // identify new-vs-old | ||||
|         RED.nodes.originalFlow(originalFlow); | ||||
|         // // Restore the original flow so subsequent merge resolutions can properly | ||||
|         // // identify new-vs-old | ||||
|         // RED.nodes.originalFlow(originalFlow); | ||||
|  | ||||
|         // Clear all change flags from the import | ||||
|         RED.nodes.dirty(false); | ||||
|          | ||||
|         const flowsToLock = new Set() | ||||
|         function ensureUnlocked(id) { | ||||
|             const flow = id && (RED.nodes.workspace(id) || RED.nodes.subflow(id) || null); | ||||
|             const isLocked = flow ? flow.locked : false; | ||||
|             if (flow && isLocked) { | ||||
|                 flow.locked = false; | ||||
|                 flowsToLock.add(flow) | ||||
|             } | ||||
|         } | ||||
|         imported.nodes.forEach(function(n) { | ||||
|             if (nodeChangedStates[n.id] || localChangedStates[n.id]) { | ||||
|             if (nodeChangedStates[n.id]) { | ||||
|                 ensureUnlocked(n.z) | ||||
|                 n.changed = true; | ||||
|             } | ||||
|             if (nodeMovedStates[n.id]) { | ||||
|                 ensureUnlocked(n.z) | ||||
|                 n.moved = true; | ||||
|             } | ||||
|         }) | ||||
|         flowsToLock.forEach(flow => { | ||||
|             flow.locked = true | ||||
|         }) | ||||
|  | ||||
|         RED.nodes.version(diff.remoteDiff.rev); | ||||
|   | ||||
| @@ -221,12 +221,12 @@ RED.notifications = (function() { | ||||
|                     if (newType) { | ||||
|                         n.className = "red-ui-notification red-ui-notification-"+newType; | ||||
|                     } | ||||
|  | ||||
|                     newTimeout = newOptions.hasOwnProperty('timeout')?newOptions.timeout:timeout | ||||
|                     if (!fixed || newOptions.fixed === false) { | ||||
|                         newTimeout = (newOptions.hasOwnProperty('timeout')?newOptions.timeout:timeout)||5000; | ||||
|                         newTimeout === newTimeout || 5000 | ||||
|                     } | ||||
|                     if (newOptions.buttons) { | ||||
|                         var buttonSet = $('<div style="margin-top: 20px;" class="ui-dialog-buttonset"></div>').appendTo(nn) | ||||
|                         var buttonSet = $('<div class="ui-dialog-buttonset"></div>').appendTo(nn) | ||||
|                         newOptions.buttons.forEach(function(buttonDef) { | ||||
|                             var b = $('<button>').text(buttonDef.text).on("click", buttonDef.click).appendTo(buttonSet); | ||||
|                             if (buttonDef.id) { | ||||
| @@ -272,6 +272,15 @@ RED.notifications = (function() { | ||||
|                 }; | ||||
|             })()); | ||||
|             n.timeoutid = window.setTimeout(n.close,timeout||5000); | ||||
|         } else if (timeout) { | ||||
|             $(n).on("click.red-ui-notification-close", (function() { | ||||
|                 var nn = n; | ||||
|                 return function() { | ||||
|                     nn.hideNotification(); | ||||
|                     window.clearTimeout(nn.timeoutid); | ||||
|                 }; | ||||
|             })()); | ||||
|             n.timeoutid = window.setTimeout(n.hideNotification,timeout||5000); | ||||
|         } | ||||
|         currentNotifications.push(n); | ||||
|         if (options.id) { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user