mirror of
https://github.com/node-red/node-red.git
synced 2023-10-10 13:36:53 +02:00
Merge pull request #3243 from node-red/delete-global-context
Clear context contents when switching projects
This commit is contained in:
commit
4173625fca
@ -122,6 +122,7 @@ module.exports = {
|
||||
}
|
||||
|
||||
if (req.body.active) {
|
||||
opts.clearContext = req.body.hasOwnProperty('clearContext')?req.body.clearContext:true
|
||||
runtimeAPI.projects.setActiveProject(opts).then(function() {
|
||||
listProjects(req,res);
|
||||
}).catch(function(err) {
|
||||
|
@ -1095,7 +1095,8 @@
|
||||
"not-git": "Not a git repository",
|
||||
"no-resource": "Repository not found",
|
||||
"cant-get-ssh-key-path": "Error! Can't get selected SSH key path.",
|
||||
"unexpected_error": "unexpected_error"
|
||||
"unexpected_error": "unexpected_error",
|
||||
"clearContext": "Clear context when switching projects"
|
||||
},
|
||||
"delete": {
|
||||
"confirm": "Are you sure you want to delete this project?"
|
||||
|
@ -1212,6 +1212,9 @@ RED.projects = (function() {
|
||||
}
|
||||
}).appendTo(row);
|
||||
|
||||
row = $('<div class="form-row red-ui-projects-dialog-screen-create-row red-ui-projects-dialog-screen-create-row-open"></div>').hide().appendTo(container);
|
||||
$('<span style="display: flex; align-items: center;"><input style="padding:0; margin: 0 5px 0 0" checked type="checkbox" id="red-ui-projects-dialog-screen-clear-context"> <label for="red-ui-projects-dialog-screen-clear-context" style="padding:0; margin: 0"> <span data-i18n="projects.create.clearContext"></span></label></span>').appendTo(row).i18n();
|
||||
|
||||
row = $('<div class="form-row red-ui-projects-dialog-screen-create-row red-ui-projects-dialog-screen-create-row-empty red-ui-projects-dialog-screen-create-row-clone"></div>').appendTo(container);
|
||||
$('<label for="red-ui-projects-dialog-screen-create-project-name">'+RED._("projects.create.project-name")+'</label>').appendTo(row);
|
||||
|
||||
@ -1501,7 +1504,8 @@ RED.projects = (function() {
|
||||
};
|
||||
}
|
||||
} else if (projectType === 'open') {
|
||||
return switchProject(selectedProject.name,function(err,data) {
|
||||
var clearContext = $("#red-ui-projects-dialog-screen-clear-context").prop("checked")
|
||||
return switchProject(selectedProject.name, clearContext, function(err,data) {
|
||||
if (err) {
|
||||
if (err.code !== 'credentials_load_failed') {
|
||||
console.log(RED._("projects.create.unexpected_error"),err)
|
||||
@ -1595,7 +1599,7 @@ RED.projects = (function() {
|
||||
}
|
||||
}
|
||||
|
||||
function switchProject(name,done) {
|
||||
function switchProject(name,clearContext,done) {
|
||||
RED.deploy.setDeployInflight(true);
|
||||
RED.projects.settings.switchProject(name);
|
||||
sendRequest({
|
||||
@ -1614,7 +1618,7 @@ RED.projects = (function() {
|
||||
'*': done
|
||||
},
|
||||
}
|
||||
},{active:true}).then(function() {
|
||||
},{active:true, clearContext:clearContext}).then(function() {
|
||||
dialog.dialog( "close" );
|
||||
RED.events.emit("project:change", {name:name});
|
||||
}).always(function() {
|
||||
@ -1687,7 +1691,7 @@ RED.projects = (function() {
|
||||
dialogHeight = 590 - (750 - winHeight);
|
||||
}
|
||||
$(".red-ui-projects-dialog-box").height(dialogHeight);
|
||||
$(".red-ui-projects-dialog-project-list-inner-container").height(Math.max(500,dialogHeight) - 180);
|
||||
$(".red-ui-projects-dialog-project-list-inner-container").height(Math.max(500,dialogHeight) - 210);
|
||||
dialog.dialog('option','title',screen.title||"");
|
||||
dialog.dialog("open");
|
||||
}
|
||||
|
@ -99,6 +99,7 @@ var api = module.exports = {
|
||||
* @param {Object} opts
|
||||
* @param {User} opts.user - the user calling the api
|
||||
* @param {String} opts.id - the id of the project to activate
|
||||
* @param {boolean} opts.clearContext - whether to clear context
|
||||
* @param {Object} opts.req - the request to log (optional)
|
||||
* @return {Promise<Object>} - resolves when complete
|
||||
* @memberof @node-red/runtime_projects
|
||||
@ -107,7 +108,7 @@ var api = module.exports = {
|
||||
var currentProject = runtime.storage.projects.getActiveProject(opts.user);
|
||||
runtime.log.audit({event: "projects.set",id:opts.id}, opts.req);
|
||||
if (!currentProject || opts.id !== currentProject.name) {
|
||||
return runtime.storage.projects.setActiveProject(opts.user, opts.id);
|
||||
return runtime.storage.projects.setActiveProject(opts.user, opts.id, opts.clearContext);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -14,30 +14,30 @@
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
var clone = require("clone");
|
||||
var log = require("@node-red/util").log;
|
||||
var util = require("@node-red/util").util;
|
||||
var memory = require("./memory");
|
||||
const clone = require("clone");
|
||||
const log = require("@node-red/util").log;
|
||||
const util = require("@node-red/util").util;
|
||||
const memory = require("./memory");
|
||||
|
||||
var settings;
|
||||
let settings;
|
||||
|
||||
// A map of scope id to context instance
|
||||
var contexts = {};
|
||||
let contexts = {};
|
||||
|
||||
// A map of store name to instance
|
||||
var stores = {};
|
||||
var storeList = [];
|
||||
var defaultStore;
|
||||
let stores = {};
|
||||
let storeList = [];
|
||||
let defaultStore;
|
||||
|
||||
// Whether there context storage has been configured or left as default
|
||||
var hasConfiguredStore = false;
|
||||
let hasConfiguredStore = false;
|
||||
|
||||
// Unknown Stores
|
||||
var unknownStores = {};
|
||||
let unknownStores = {};
|
||||
|
||||
function logUnknownStore(name) {
|
||||
if (name) {
|
||||
var count = unknownStores[name] || 0;
|
||||
let count = unknownStores[name] || 0;
|
||||
if (count == 0) {
|
||||
log.warn(log._("context.unknown-store", {name: name}));
|
||||
count++;
|
||||
@ -52,8 +52,8 @@ function init(_settings) {
|
||||
stores = {};
|
||||
storeList = [];
|
||||
hasConfiguredStore = false;
|
||||
var seed = settings.functionGlobalContext || {};
|
||||
contexts['global'] = createContext("global",seed);
|
||||
initialiseGlobalContext();
|
||||
|
||||
// create a default memory store - used by the unit tests that skip the full
|
||||
// `load()` initialisation sequence.
|
||||
// If the user has any stores configured, this will be disgarded
|
||||
@ -61,6 +61,11 @@ function init(_settings) {
|
||||
defaultStore = "memory";
|
||||
}
|
||||
|
||||
function initialiseGlobalContext() {
|
||||
const seed = settings.functionGlobalContext || {};
|
||||
contexts['global'] = createContext("global",seed);
|
||||
}
|
||||
|
||||
function load() {
|
||||
return new Promise(function(resolve,reject) {
|
||||
// load & init plugins in settings.contextStorage
|
||||
@ -233,12 +238,15 @@ function validateContextKey(key) {
|
||||
|
||||
function createContext(id,seed,parent) {
|
||||
// Seed is only set for global context - sourced from functionGlobalContext
|
||||
var scope = id;
|
||||
var obj = seed || {};
|
||||
var seedKeys;
|
||||
var insertSeedValues;
|
||||
const scope = id;
|
||||
const obj = {};
|
||||
let seedKeys;
|
||||
let insertSeedValues;
|
||||
if (seed) {
|
||||
seedKeys = Object.keys(seed);
|
||||
seedKeys.forEach(key => {
|
||||
obj[key] = seed[key];
|
||||
})
|
||||
insertSeedValues = function(keys,values) {
|
||||
if (!Array.isArray(keys)) {
|
||||
if (values[0] === undefined) {
|
||||
@ -540,8 +548,28 @@ function getContext(nodeId, flowId) {
|
||||
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) {
|
||||
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.
|
||||
var contextId = id;
|
||||
if (flowId) {
|
||||
@ -554,7 +582,14 @@ function deleteContext(id,flowId) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
flowConfig = flowConfig || { allNodes: {} };
|
||||
var promises = [];
|
||||
for(var plugin in stores){
|
||||
if(stores.hasOwnProperty(plugin)){
|
||||
@ -572,6 +607,16 @@ function clean(flowConfig) {
|
||||
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() {
|
||||
var promises = [];
|
||||
for(var plugin in stores){
|
||||
@ -594,5 +639,6 @@ module.exports = {
|
||||
getFlowContext:getFlowContext,
|
||||
delete: deleteContext,
|
||||
clean: clean,
|
||||
clear: clear,
|
||||
close: close
|
||||
};
|
||||
|
@ -206,6 +206,7 @@ module.exports = {
|
||||
eachNode: flows.eachNode,
|
||||
getContext: context.get,
|
||||
|
||||
clearContext: context.clear,
|
||||
|
||||
installerEnabled: registry.installerEnabled,
|
||||
installModule: installModule,
|
||||
|
@ -377,8 +377,17 @@ function getActiveProject(user) {
|
||||
return activeProject;
|
||||
}
|
||||
|
||||
function reloadActiveProject(action) {
|
||||
function reloadActiveProject(action, clearContext) {
|
||||
// Stop the current flows
|
||||
return runtime.nodes.stopFlows().then(function() {
|
||||
if (clearContext) {
|
||||
// Reset context to remove any old values
|
||||
return runtime.nodes.clearContext()
|
||||
} else {
|
||||
return Promise.resolve()
|
||||
}
|
||||
}).then(function() {
|
||||
// Load the new project flows and start them
|
||||
return runtime.nodes.loadFlows(true).then(function() {
|
||||
events.emit("runtime-event",{id:"project-update", payload:{ project: activeProject.name, action:action}});
|
||||
}).catch(function(err) {
|
||||
@ -387,6 +396,9 @@ function reloadActiveProject(action) {
|
||||
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) {
|
||||
@ -424,7 +436,7 @@ function createProject(user, metadata) {
|
||||
return getProject(user, metadata.name);
|
||||
})
|
||||
}
|
||||
function setActiveProject(user, projectName) {
|
||||
function setActiveProject(user, projectName, clearContext) {
|
||||
return loadProject(projectName).then(function(project) {
|
||||
var globalProjectSettings = settings.get("projects")||{};
|
||||
globalProjectSettings.activeProject = project.name;
|
||||
@ -434,7 +446,7 @@ function setActiveProject(user, projectName) {
|
||||
// console.log("Updated file targets to");
|
||||
// console.log(flowsFullPath)
|
||||
// console.log(credentialsFile)
|
||||
return reloadActiveProject("loaded");
|
||||
return reloadActiveProject("loaded", clearContext);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
@ -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() {
|
||||
var flowContextA = Context.getFlowContext("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() {
|
||||
|
Loading…
Reference in New Issue
Block a user