1
0
mirror of https://github.com/node-red/node-red.git synced 2023-10-10 13:36:53 +02:00

Clear context contents when switching projects

Fixes #3240
This commit is contained in:
Nick O'Leary 2021-11-02 00:12:30 +00:00
parent 30b00741b5
commit 3abef972a7
No known key found for this signature in database
GPG Key ID: 4F2157149161A6C9
4 changed files with 124 additions and 28 deletions

View File

@ -14,30 +14,30 @@
* limitations under the License. * limitations under the License.
**/ **/
var clone = require("clone"); const clone = require("clone");
var log = require("@node-red/util").log; const log = require("@node-red/util").log;
var util = require("@node-red/util").util; const util = require("@node-red/util").util;
var memory = require("./memory"); const memory = require("./memory");
var settings; let settings;
// A map of scope id to context instance // A map of scope id to context instance
var contexts = {}; let contexts = {};
// A map of store name to instance // A map of store name to instance
var stores = {}; let stores = {};
var storeList = []; let storeList = [];
var defaultStore; let defaultStore;
// Whether there context storage has been configured or left as default // Whether there context storage has been configured or left as default
var hasConfiguredStore = false; let hasConfiguredStore = false;
// Unknown Stores // Unknown Stores
var unknownStores = {}; let unknownStores = {};
function logUnknownStore(name) { function logUnknownStore(name) {
if (name) { if (name) {
var count = unknownStores[name] || 0; let count = unknownStores[name] || 0;
if (count == 0) { if (count == 0) {
log.warn(log._("context.unknown-store", {name: name})); log.warn(log._("context.unknown-store", {name: name}));
count++; count++;
@ -52,8 +52,8 @@ function init(_settings) {
stores = {}; stores = {};
storeList = []; storeList = [];
hasConfiguredStore = false; hasConfiguredStore = false;
var seed = settings.functionGlobalContext || {}; initialiseGlobalContext();
contexts['global'] = createContext("global",seed);
// create a default memory store - used by the unit tests that skip the full // create a default memory store - used by the unit tests that skip the full
// `load()` initialisation sequence. // `load()` initialisation sequence.
// If the user has any stores configured, this will be disgarded // If the user has any stores configured, this will be disgarded
@ -61,6 +61,11 @@ function init(_settings) {
defaultStore = "memory"; defaultStore = "memory";
} }
function initialiseGlobalContext() {
const seed = settings.functionGlobalContext || {};
contexts['global'] = createContext("global",seed);
}
function load() { function load() {
return new Promise(function(resolve,reject) { return new Promise(function(resolve,reject) {
// load & init plugins in settings.contextStorage // load & init plugins in settings.contextStorage
@ -233,12 +238,15 @@ function validateContextKey(key) {
function createContext(id,seed,parent) { function createContext(id,seed,parent) {
// Seed is only set for global context - sourced from functionGlobalContext // Seed is only set for global context - sourced from functionGlobalContext
var scope = id; const scope = id;
var obj = seed || {}; const obj = {};
var seedKeys; let seedKeys;
var insertSeedValues; let insertSeedValues;
if (seed) { if (seed) {
seedKeys = Object.keys(seed); seedKeys = Object.keys(seed);
seedKeys.forEach(key => {
obj[key] = seed[key];
})
insertSeedValues = function(keys,values) { insertSeedValues = function(keys,values) {
if (!Array.isArray(keys)) { if (!Array.isArray(keys)) {
if (values[0] === undefined) { if (values[0] === undefined) {
@ -540,8 +548,28 @@ function getContext(nodeId, flowId) {
return newContext; return newContext;
} }
/**
* Delete the context of the given node/flow/global
*
* If the user has configured a context store, this
* will no-op a request to delete node/flow context.
*/
function deleteContext(id,flowId) { function deleteContext(id,flowId) {
if(!hasConfiguredStore){ if (id === "global") {
// 1. delete global from all configured stores
var promises = [];
for(var plugin in stores){
if(stores.hasOwnProperty(plugin)){
promises.push(stores[plugin].delete('global'));
}
}
return Promise.all(promises).then(function() {
// 2. delete global context
delete contexts['global'];
// 3. reinitialise global context
initialiseGlobalContext();
})
} else if (!hasConfiguredStore) {
// only delete context if there's no configured storage. // only delete context if there's no configured storage.
var contextId = id; var contextId = id;
if (flowId) { if (flowId) {
@ -549,12 +577,19 @@ function deleteContext(id,flowId) {
} }
delete contexts[contextId]; delete contexts[contextId];
return stores["_"].delete(contextId); return stores["_"].delete(contextId);
}else{ } else {
return Promise.resolve(); return Promise.resolve();
} }
} }
/**
* Delete any contexts that are no longer in use
* @param flowConfig object includes allNodes as object of id->node
*
* If flowConfig is undefined, all flow/node contexts will be removed
**/
function clean(flowConfig) { function clean(flowConfig) {
flowConfig = flowConfig || { allNodes: {} };
var promises = []; var promises = [];
for(var plugin in stores){ for(var plugin in stores){
if(stores.hasOwnProperty(plugin)){ if(stores.hasOwnProperty(plugin)){
@ -572,6 +607,16 @@ function clean(flowConfig) {
return Promise.all(promises); return Promise.all(promises);
} }
/**
* Deletes all contexts, including global and reinitialises global to
* initial state.
*/
function clear() {
return clean().then(function() {
return deleteContext('global')
})
}
function close() { function close() {
var promises = []; var promises = [];
for(var plugin in stores){ for(var plugin in stores){
@ -594,5 +639,6 @@ module.exports = {
getFlowContext:getFlowContext, getFlowContext:getFlowContext,
delete: deleteContext, delete: deleteContext,
clean: clean, clean: clean,
clear: clear,
close: close close: close
}; };

View File

@ -206,6 +206,7 @@ module.exports = {
eachNode: flows.eachNode, eachNode: flows.eachNode,
getContext: context.get, getContext: context.get,
clearContext: context.clear,
installerEnabled: registry.installerEnabled, installerEnabled: registry.installerEnabled,
installModule: installModule, installModule: installModule,

View File

@ -378,15 +378,23 @@ function getActiveProject(user) {
} }
function reloadActiveProject(action) { function reloadActiveProject(action) {
// Stop the current flows
return runtime.nodes.stopFlows().then(function() { return runtime.nodes.stopFlows().then(function() {
return runtime.nodes.loadFlows(true).then(function() { // Reset context to remove any old values
events.emit("runtime-event",{id:"project-update", payload:{ project: activeProject.name, action:action}}); return runtime.nodes.clearContext().then(function() {
}).catch(function(err) { // Load the new project flows and start them
// We're committed to the project change now, so notify editors return runtime.nodes.loadFlows(true).then(function() {
// that it has changed. events.emit("runtime-event",{id:"project-update", payload:{ project: activeProject.name, action:action}});
events.emit("runtime-event",{id:"project-update", payload:{ project: activeProject.name, action:action}}); }).catch(function(err) {
throw err; // We're committed to the project change now, so notify editors
}); // that it has changed.
events.emit("runtime-event",{id:"project-update", payload:{ project: activeProject.name, action:action}});
throw err;
});
})
}).catch(function(err) {
console.log(err.stack);
throw err;
}); });
} }
function createProject(user, metadata) { function createProject(user, metadata) {

View File

@ -156,6 +156,22 @@ describe('context', function() {
}); });
}); });
it('deletes global context',function() {
Context.init({functionGlobalContext: {foo:"bar"}});
return Context.load().then(function(){
var globalContextA = Context.get("global")
globalContextA.get('foo').should.eql('bar')
globalContextA.set("another","value");
return Context.delete("global").then(function(){
var globalContextB = Context.get("global")
globalContextB.get('foo').should.eql('bar')
should.not.exist(globalContextB.get("another"));
});
});
});
it('enumerates context keys - sync', function() { it('enumerates context keys - sync', function() {
var flowContextA = Context.getFlowContext("flowA") var flowContextA = Context.getFlowContext("flowA")
var context = Context.get("1","flowA"); var context = Context.get("1","flowA");
@ -316,6 +332,31 @@ describe('context', function() {
}); });
}); });
describe("clear", function() {
it('clears all context',function() {
Context.init({functionGlobalContext: {foo:"bar"}});
return Context.load().then(function(){
var globalContextA = Context.get("global")
globalContextA.get('foo').should.eql('bar')
globalContextA.set("another","value");
var flowContextA = Context.getFlowContext("flowA")
flowContextA.set("foo","abc");
flowContextA.get("foo").should.equal("abc");
return Context.clear().then(function(){
var globalContextB = Context.getFlowContext("global")
globalContextB.get('foo').should.eql('bar')
should.not.exist(globalContextB.get("another"));
flowContextA = Context.getFlowContext("flowA")
should.not.exist(flowContextA.get("foo"))
});
});
});
})
}); });
describe('external context storage',function() { describe('external context storage',function() {