Load node properties on demand (#1089)

*  Changed rest api endpoint to omit node properties by default.
 Created another endpoint to return all node information based on a list of names

* refactor: changed endpoint to POST instead of GET

* Changed properties to be optional for node description

* Removed eager loading for node properties.

All nodes will have their properties fetched when used in a workflow.
Works when opening an already saved workflow, creating a new one from
scratch or pasting JSON / URLs.

* Removing unnecessary dependency
This commit is contained in:
Omar Ajoue 2020-10-22 12:24:35 -03:00 committed by GitHub
parent 40c2acd77b
commit 37f787d7b2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 50 additions and 2 deletions

View file

@ -715,13 +715,29 @@ class App {
const allNodes = nodeTypes.getAll(); const allNodes = nodeTypes.getAll();
allNodes.forEach((nodeData) => { allNodes.forEach((nodeData) => {
returnData.push(nodeData.description); // Make a copy of the object. If we don't do this, then when
// The method below is called the properties are removed for good
// This happens because nodes are returned as reference.
let nodeInfo: INodeTypeDescription = {...nodeData.description};
if (!['true', '1'].includes(req.query.includeProperties as string)) {
delete nodeInfo.properties;
}
returnData.push(nodeInfo);
}); });
return returnData; return returnData;
})); }));
// Returns node information baesd on namese
this.app.post(`/${this.restEndpoint}/node-types`, ResponseHelper.send(async (req: express.Request, res: express.Response): Promise<INodeTypeDescription[]> => {
const nodeNames = _.get(req, 'body.nodeNames', []);
const nodeTypes = NodeTypes();
const allNodes = nodeTypes.getAll();
return allNodes.filter(node => nodeNames.includes(node.description.name)).map(node => node.description);
}));
// ---------------------------------------- // ----------------------------------------
// Node-Types // Node-Types

View file

@ -126,6 +126,7 @@ export interface IRestApi {
makeRestApiRequest(method: string, endpoint: string, data?: any): Promise<any>; // tslint:disable-line:no-any makeRestApiRequest(method: string, endpoint: string, data?: any): Promise<any>; // tslint:disable-line:no-any
getSettings(): Promise<IN8nUISettings>; getSettings(): Promise<IN8nUISettings>;
getNodeTypes(): Promise<INodeTypeDescription[]>; getNodeTypes(): Promise<INodeTypeDescription[]>;
getNodesInformation(nodeList: string[]): Promise<INodeTypeDescription[]>;
getNodeParameterOptions(nodeType: string, methodName: string, currentNodeParameters: INodeParameters, credentials?: INodeCredentials): Promise<INodePropertyOptions[]>; getNodeParameterOptions(nodeType: string, methodName: string, currentNodeParameters: INodeParameters, credentials?: INodeCredentials): Promise<INodePropertyOptions[]>;
removeTestWebhook(workflowId: string): Promise<boolean>; removeTestWebhook(workflowId: string): Promise<boolean>;
runWorkflow(runData: IStartRunData): Promise<IExecutionPushResponse>; runWorkflow(runData: IStartRunData): Promise<IExecutionPushResponse>;

View file

@ -152,6 +152,10 @@ export const restApi = Vue.extend({
return self.restApi().makeRestApiRequest('GET', `/node-types`); return self.restApi().makeRestApiRequest('GET', `/node-types`);
}, },
getNodesInformation: (nodeList: string[]): Promise<INodeTypeDescription[]> => {
return self.restApi().makeRestApiRequest('POST', `/node-types`, {nodeNames: nodeList});
},
// Returns all the parameter options from the server // Returns all the parameter options from the server
getNodeParameterOptions: (nodeType: string, methodName: string, currentNodeParameters: INodeParameters, credentials?: INodeCredentials): Promise<INodePropertyOptions[]> => { getNodeParameterOptions: (nodeType: string, methodName: string, currentNodeParameters: INodeParameters, credentials?: INodeCredentials): Promise<INodePropertyOptions[]> => {
const sendData = { const sendData = {

View file

@ -562,6 +562,14 @@ export const store = new Vuex.Store({
Vue.set(state.workflow, 'settings', {}); Vue.set(state.workflow, 'settings', {});
} }
}, },
updateNodeTypes (state, nodeTypes: INodeTypeDescription[]) {
const updatedNodeNames = nodeTypes.map(node => node.name) as string[];
const oldNodesNotChanged = state.nodeTypes.filter(node => !updatedNodeNames.includes(node.name));
const updatedNodes = [...oldNodesNotChanged, ...nodeTypes];
Vue.set(state, 'nodeTypes', updatedNodes);
state.nodeTypes = updatedNodes;
},
}, },
getters: { getters: {

View file

@ -1563,6 +1563,11 @@ export default mixins(
return; return;
} }
// Before proceeding we must check if all nodes contain the `properties` attribute.
// Nodes are loaded without this information so we must make sure that all nodes
// being added have this information.
await this.loadNodesProperties(nodes.map(node => node.type));
// Add the node to the node-list // Add the node to the node-list
let nodeType: INodeTypeDescription | null; let nodeType: INodeTypeDescription | null;
let foundNodeIssues: INodeIssues | null; let foundNodeIssues: INodeIssues | null;
@ -1673,6 +1678,9 @@ export default mixins(
let oldName: string; let oldName: string;
let newName: string; let newName: string;
const createNodes: INode[] = []; const createNodes: INode[] = [];
await this.loadNodesProperties(data.nodes.map(node => node.type));
data.nodes.forEach(node => { data.nodes.forEach(node => {
if (nodeTypesCount[node.type] !== undefined) { if (nodeTypesCount[node.type] !== undefined) {
if (nodeTypesCount[node.type].exist >= nodeTypesCount[node.type].max) { if (nodeTypesCount[node.type].exist >= nodeTypesCount[node.type].max) {
@ -1896,6 +1904,17 @@ export default mixins(
const credentials = await this.restApi().getAllCredentials(); const credentials = await this.restApi().getAllCredentials();
this.$store.commit('setCredentials', credentials); this.$store.commit('setCredentials', credentials);
}, },
async loadNodesProperties(nodeNames: string[]): Promise<void> {
const allNodes = this.$store.getters.allNodeTypes;
const nodesToBeFetched = allNodes.filter((node: INodeTypeDescription) => nodeNames.includes(node.name) && !node.hasOwnProperty('properties')).map((node: INodeTypeDescription) => node.name) as string[];
if (nodesToBeFetched.length > 0) {
// Only call API if node information is actually missing
this.startLoading();
const nodeInfo = await this.restApi().getNodesInformation(nodesToBeFetched);
this.$store.commit('updateNodeTypes', nodeInfo);
this.stopLoading();
}
},
}, },
async mounted () { async mounted () {

View file

@ -215,7 +215,7 @@ export class Workflow {
typeUnknown: true, typeUnknown: true,
}; };
} else { } else {
nodeIssues = NodeHelpers.getNodeParametersIssues(nodeType.description.properties, node); nodeIssues = NodeHelpers.getNodeParametersIssues(nodeType.description.properties!, node);
} }
if (nodeIssues !== null) { if (nodeIssues !== null) {