Copy data on execution only if needed

This commit is contained in:
Jan Oberhauser 2019-08-01 22:55:33 +02:00
parent a8f1f9c0ba
commit 1b59d7b886
20 changed files with 182 additions and 65 deletions

View file

@ -39,6 +39,7 @@
"dependencies": { "dependencies": {
"crypto-js": "^3.1.9-1", "crypto-js": "^3.1.9-1",
"lodash.get": "^4.4.2", "lodash.get": "^4.4.2",
"lodash.merge": "^4.6.2",
"mmmagic": "^0.5.2", "mmmagic": "^0.5.2",
"n8n-workflow": "^0.7.0", "n8n-workflow": "^0.7.0",
"request-promise-native": "^1.0.7" "request-promise-native": "^1.0.7"

View file

@ -20,6 +20,7 @@ import {
NodeExecuteFunctions, NodeExecuteFunctions,
} from './'; } from './';
import { merge } from 'lodash';
export class WorkflowExecute { export class WorkflowExecute {
private additionalData: IWorkflowExecuteAdditionalData; private additionalData: IWorkflowExecuteAdditionalData;
@ -600,7 +601,7 @@ export class WorkflowExecute {
} }
runExecutionData.resultData.lastNodeExecuted = executionData.node.name; runExecutionData.resultData.lastNodeExecuted = executionData.node.name;
nodeSuccessData = await workflow.runNode(executionData.node, JSON.parse(JSON.stringify(executionData.data)), runExecutionData, runIndex, this.additionalData, NodeExecuteFunctions, this.mode); nodeSuccessData = await workflow.runNode(executionData.node, executionData.data, runExecutionData, runIndex, this.additionalData, NodeExecuteFunctions, this.mode);
if (nodeSuccessData === null) { if (nodeSuccessData === null) {
// If null gets returned it means that the node did succeed // If null gets returned it means that the node did succeed
@ -638,7 +639,7 @@ export class WorkflowExecute {
// Simply get the input data of the node if it has any and pass it through // Simply get the input data of the node if it has any and pass it through
// to the next node // to the next node
if (executionData.data.main[0] !== null) { if (executionData.data.main[0] !== null) {
nodeSuccessData = [(JSON.parse(JSON.stringify(executionData.data.main[0])) as INodeExecutionData[])]; nodeSuccessData = [executionData.data.main[0] as INodeExecutionData[]];
} }
} }
} else { } else {

View file

@ -621,8 +621,11 @@ export class Chargebee implements INodeType {
returnData.push(data.invoice as IDataObject); returnData.push(data.invoice as IDataObject);
}); });
} else if (resource === 'invoice' && operation === 'pdfUrl') { } else if (resource === 'invoice' && operation === 'pdfUrl') {
item.json.pdfUrl = responseData.download.download_url; const data: IDataObject = {};
returnData.push(item.json); Object.assign(data, items[i].json);
data.pdfUrl = responseData.download.download_url;
returnData.push(data);
} else { } else {
returnData.push(responseData); returnData.push(responseData);
} }

View file

@ -627,10 +627,21 @@ export class Dropbox implements INodeType {
} }
if (resource === 'file' && operation === 'download') { if (resource === 'file' && operation === 'download') {
// TODO: Has to check if it already exists and only add if not
if (items[i].binary === undefined) { const newItem: INodeExecutionData = {
items[i].binary = {}; json: items[i].json,
binary: {},
};
if (items[i].binary !== undefined) {
// Create a shallow copy of the binary data so that the old
// data references which do not get changed still stay behind
// but the incoming data does not get changed.
Object.assign(newItem.binary, items[i].binary);
} }
items[i] = newItem;
const dataPropertyNameDownload = this.getNodeParameter('binaryPropertyName', i) as string; const dataPropertyNameDownload = this.getNodeParameter('binaryPropertyName', i) as string;
const filePathDownload = this.getNodeParameter('path', i) as string; const filePathDownload = this.getNodeParameter('path', i) as string;

View file

@ -538,6 +538,18 @@ export class EditImage implements INodeType {
throw new Error(`The operation "${operation}" is not supported!`); throw new Error(`The operation "${operation}" is not supported!`);
} }
const newItem: INodeExecutionData = {
json: item.json,
binary: {},
};
if (item.binary !== undefined) {
// Create a shallow copy of the binary data so that the old
// data references which do not get changed still stay behind
// but the incoming data does not get changed.
Object.assign(newItem.binary, item.binary);
}
return new Promise<INodeExecutionData>((resolve, reject) => { return new Promise<INodeExecutionData>((resolve, reject) => {
gmInstance gmInstance
.toBuffer((error: Error, buffer: Buffer) => { .toBuffer((error: Error, buffer: Buffer) => {
@ -545,9 +557,9 @@ export class EditImage implements INodeType {
return reject(error); return reject(error);
} }
item.binary![dataPropertyName as string].data = buffer.toString(BINARY_ENCODING); newItem.binary![dataPropertyName as string].data = buffer.toString(BINARY_ENCODING);
return resolve(item); return resolve(newItem);
}); });
}); });
} }

View file

@ -40,6 +40,9 @@ export class Function implements INodeType {
// const item = this.getInputData(); // const item = this.getInputData();
let items = this.getInputData(); let items = this.getInputData();
// Copy the items as they may get changed in the functions
items = JSON.parse(JSON.stringify(items));
// Define the global objects for the custom function // Define the global objects for the custom function
const sandbox = { const sandbox = {
getNodeParameter: this.getNodeParameter, getNodeParameter: this.getNodeParameter,

View file

@ -39,7 +39,10 @@ export class FunctionItem implements INodeType {
}; };
async executeSingle(this: IExecuteSingleFunctions): Promise<INodeExecutionData> { async executeSingle(this: IExecuteSingleFunctions): Promise<INodeExecutionData> {
const item = this.getInputData(); let item = this.getInputData();
// Copy the items as they may get changed in the functions
item = JSON.parse(JSON.stringify(item));
// Define the global objects for the custom function // Define the global objects for the custom function
const sandbox = { const sandbox = {

View file

@ -1366,10 +1366,23 @@ export class Github implements INodeType {
if (asBinaryProperty === true) { if (asBinaryProperty === true) {
// Add the returned data to the item as binary property // Add the returned data to the item as binary property
const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i) as string; const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i) as string;
if (items[i].binary === undefined) {
items[i].binary = {}; const newItem: INodeExecutionData = {
json: items[i].json,
binary: {},
};
if (items[i].binary !== undefined) {
// Create a shallow copy of the binary data so that the old
// data references which do not get changed still stay behind
// but the incoming data does not get changed.
Object.assign(newItem.binary, items[i].binary);
} }
items[i].binary![binaryPropertyName] = await this.helpers.prepareBinaryData(Buffer.from(responseData.content, 'base64'), responseData.path);
newItem.binary![binaryPropertyName] = await this.helpers.prepareBinaryData(Buffer.from(responseData.content, 'base64'), responseData.path);
items[i] = newItem;
return this.prepareOutputData(items); return this.prepareOutputData(items);
} }
} }

View file

@ -332,7 +332,6 @@ export class HttpRequest implements INodeType {
const httpBasicAuth = this.getCredentials('httpBasicAuth'); const httpBasicAuth = this.getCredentials('httpBasicAuth');
const httpHeaderAuth = this.getCredentials('httpHeaderAuth'); const httpHeaderAuth = this.getCredentials('httpHeaderAuth');
let item: INodeExecutionData;
let url: string, responseFormat: string; let url: string, responseFormat: string;
let requestOptions: OptionsWithUri; let requestOptions: OptionsWithUri;
let setUiParameter: IDataObject; let setUiParameter: IDataObject;
@ -360,8 +359,6 @@ export class HttpRequest implements INodeType {
const returnItems: INodeExecutionData[] = []; const returnItems: INodeExecutionData[] = [];
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) { for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
item = items[itemIndex];
url = this.getNodeParameter('url', itemIndex) as string; url = this.getNodeParameter('url', itemIndex) as string;
responseFormat = this.getNodeParameter('responseFormat', itemIndex) as string; responseFormat = this.getNodeParameter('responseFormat', itemIndex) as string;

View file

@ -313,11 +313,20 @@ export class LinkFish implements INodeType {
if (operation === 'screenshot') { if (operation === 'screenshot') {
const item = this.getInputData(); const item = this.getInputData();
const newItem: INodeExecutionData = {
json: item.json,
binary: {},
};
if (item.binary !== undefined) {
// Create a shallow copy of the binary data so that the old
// data references which do not get changed still stay behind
// but the incoming data does not get changed.
Object.assign(newItem.binary, item.binary);
}
// Add the returned data to the item as binary property // Add the returned data to the item as binary property
const binaryPropertyName = this.getNodeParameter('binaryPropertyName') as string; const binaryPropertyName = this.getNodeParameter('binaryPropertyName') as string;
if (item.binary === undefined) {
item.binary = {};
}
let fileExtension = 'png'; let fileExtension = 'png';
let mimeType = 'image/png'; let mimeType = 'image/png';
@ -326,9 +335,9 @@ export class LinkFish implements INodeType {
mimeType = 'image/jpeg'; mimeType = 'image/jpeg';
} }
item.binary![binaryPropertyName] = await this.helpers.prepareBinaryData(responseData, `screenshot.${fileExtension}`, mimeType); newItem.binary![binaryPropertyName] = await this.helpers.prepareBinaryData(responseData, `screenshot.${fileExtension}`, mimeType);
return item; return newItem;
} }
return { return {

View file

@ -178,14 +178,19 @@ export class Merge implements INodeType {
if (['null', 'undefined'].includes(typeof referenceValue)) { if (['null', 'undefined'].includes(typeof referenceValue)) {
continue; continue;
} }
// Copy the entry as the data gets changed
entry = JSON.parse(JSON.stringify(entry));
for (key of Object.keys(copyData[referenceValue as string].json)) { for (key of Object.keys(copyData[referenceValue as string].json)) {
// TODO: Currently only copies json data and no binary one // TODO: Currently only copies json data and no binary one
entry.json[key] = copyData[referenceValue as string].json[key]; entry.json[key] = copyData[referenceValue as string].json[key];
} }
} }
returnData.push(entry);
} }
return [dataInput1]; return [returnData];
} else if (mode === 'passThrough') { } else if (mode === 'passThrough') {
const output = this.getNodeParameter('output', 0) as string; const output = this.getNodeParameter('output', 0) as string;

View file

@ -443,7 +443,7 @@ export class NextCloud implements INodeType {
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> { async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData(); const items = this.getInputData().slice();
const returnData: IDataObject[] = []; const returnData: IDataObject[] = [];
const credentials = this.getCredentials('nextCloudApi'); const credentials = this.getCredentials('nextCloudApi');
@ -583,10 +583,21 @@ export class NextCloud implements INodeType {
const responseData = await this.helpers.request(options); const responseData = await this.helpers.request(options);
if (resource === 'file' && operation === 'download') { if (resource === 'file' && operation === 'download') {
// TODO: Has to check if it already exists and only add if not
if (items[i].binary === undefined) { const newItem: INodeExecutionData = {
items[i].binary = {}; json: items[i].json,
binary: {},
};
if (items[i].binary !== undefined) {
// Create a shallow copy of the binary data so that the old
// data references which do not get changed still stay behind
// but the incoming data does not get changed.
Object.assign(newItem.binary, items[i].binary);
} }
items[i] = newItem;
const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i) as string; const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i) as string;
items[i].binary![binaryPropertyName] = await this.helpers.prepareBinaryData(responseData, endpoint); items[i].binary![binaryPropertyName] = await this.helpers.prepareBinaryData(responseData, endpoint);

View file

@ -55,10 +55,6 @@ export class ReadBinaryFile implements INodeType {
const dataPropertyName = this.getNodeParameter('dataPropertyName') as string; const dataPropertyName = this.getNodeParameter('dataPropertyName') as string;
const filePath = this.getNodeParameter('filePath') as string; const filePath = this.getNodeParameter('filePath') as string;
if (item.binary === undefined) {
item.binary = {};
}
let data; let data;
try { try {
data = await fsReadFileAsync(filePath) as Buffer; data = await fsReadFileAsync(filePath) as Buffer;
@ -69,9 +65,22 @@ export class ReadBinaryFile implements INodeType {
throw error; throw error;
} }
item.binary[dataPropertyName] = await this.helpers.prepareBinaryData(data, filePath);
return item; const newItem: INodeExecutionData = {
json: item.json,
binary: {},
};
if (item.binary !== undefined) {
// Create a shallow copy of the binary data so that the old
// data references which do not get changed still stay behind
// but the incoming data does not get changed.
Object.assign(newItem.binary, item.binary);
}
newItem.binary![dataPropertyName] = await this.helpers.prepareBinaryData(data, filePath);
return newItem;
} }
} }

View file

@ -69,12 +69,20 @@ export class ReadFileFromUrl implements INodeType {
// Get the current item and add the binary data // Get the current item and add the binary data
const item = this.getInputData(); const item = this.getInputData();
if (!item.binary) { const newItem: INodeExecutionData = {
item.binary = {}; json: item.json,
binary: {},
};
if (item.binary !== undefined) {
// Create a shallow copy of the binary data so that the old
// data references which do not get changed still stay behind
// but the incoming data does not get changed.
Object.assign(newItem.binary, item.binary);
} }
item.binary[dataPropertyName as string] = await this.helpers.prepareBinaryData(data, fileName); newItem.binary![dataPropertyName as string] = await this.helpers.prepareBinaryData(data, fileName);
return item; return newItem;
} }
} }

View file

@ -395,13 +395,9 @@ export class Redis implements INodeType {
} else if (['delete', 'get', 'keys', 'set'].includes(operation)) { } else if (['delete', 'get', 'keys', 'set'].includes(operation)) {
const items = this.getInputData(); const items = this.getInputData();
if (items.length === 0) {
items.push({ json: {} });
}
let item: INodeExecutionData; let item: INodeExecutionData;
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) { for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
item = items[itemIndex]; item = { json: {} };
if (operation === 'delete') { if (operation === 'delete') {
const keyDelete = this.getNodeParameter('key', itemIndex) as string; const keyDelete = this.getNodeParameter('key', itemIndex) as string;
@ -409,8 +405,6 @@ export class Redis implements INodeType {
const clientDel = util.promisify(client.del).bind(client); const clientDel = util.promisify(client.del).bind(client);
// @ts-ignore // @ts-ignore
await clientDel(keyDelete); await clientDel(keyDelete);
resolve(this.prepareOutputData(items));
} else if (operation === 'get') { } else if (operation === 'get') {
const propertyName = this.getNodeParameter('propertyName', itemIndex) as string; const propertyName = this.getNodeParameter('propertyName', itemIndex) as string;
const keyGet = this.getNodeParameter('key', itemIndex) as string; const keyGet = this.getNodeParameter('key', itemIndex) as string;
@ -418,8 +412,6 @@ export class Redis implements INodeType {
const value = await getValue(client, keyGet, keyType); const value = await getValue(client, keyGet, keyType);
set(item.json, propertyName, value); set(item.json, propertyName, value);
resolve(this.prepareOutputData(items));
} else if (operation === 'keys') { } else if (operation === 'keys') {
const keyPattern = this.getNodeParameter('keyPattern', itemIndex) as string; const keyPattern = this.getNodeParameter('keyPattern', itemIndex) as string;
@ -439,18 +431,16 @@ export class Redis implements INodeType {
for (const keyName of keys) { for (const keyName of keys) {
set(item.json, keyName, await promises[keyName]); set(item.json, keyName, await promises[keyName]);
} }
resolve(this.prepareOutputData(items));
} else if (operation === 'set') { } else if (operation === 'set') {
const keySet = this.getNodeParameter('key', itemIndex) as string; const keySet = this.getNodeParameter('key', itemIndex) as string;
const value = this.getNodeParameter('value', itemIndex) as string; const value = this.getNodeParameter('value', itemIndex) as string;
const keyType = this.getNodeParameter('keyType', itemIndex) as string; const keyType = this.getNodeParameter('keyType', itemIndex) as string;
await setValue(client, keySet, value, keyType); await setValue(client, keySet, value, keyType);
resolve(this.prepareOutputData(items));
} }
} }
resolve(this.prepareOutputData(items));
} }
}); });
}); });

View file

@ -74,13 +74,27 @@ export class RenameKeys implements INodeType {
const items = this.getInputData(); const items = this.getInputData();
const returnData: INodeExecutionData[] = [];
let item: INodeExecutionData; let item: INodeExecutionData;
let newItem: INodeExecutionData;
let renameKeys: IRenameKey[]; let renameKeys: IRenameKey[];
let value: any; // tslint:disable-line:no-any let value: any; // tslint:disable-line:no-any
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) { for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
renameKeys = this.getNodeParameter('keys.key', itemIndex, []) as IRenameKey[]; renameKeys = this.getNodeParameter('keys.key', itemIndex, []) as IRenameKey[];
item = items[itemIndex]; item = items[itemIndex];
// Copy the whole JSON data as data on any level can be renamed
newItem = {
json: JSON.parse(JSON.stringify(item.json)),
};
if (item.binary !== undefined) {
// Reference binary data if any exists. We can reference it
// as this nodes does not change it
newItem.binary = item.binary;
}
renameKeys.forEach((renameKey) => { renameKeys.forEach((renameKey) => {
if (renameKey.currentKey === '' || renameKey.newKey === '') { if (renameKey.currentKey === '' || renameKey.newKey === '') {
// Ignore all which do not have all the values set // Ignore all which do not have all the values set
@ -90,12 +104,14 @@ export class RenameKeys implements INodeType {
if (value === undefined) { if (value === undefined) {
return; return;
} }
set(item.json, renameKey.newKey, value); set(newItem.json, renameKey.newKey, value);
unset(item.json, renameKey.currentKey as string); unset(newItem.json, renameKey.currentKey as string);
}); });
returnData.push(newItem);
} }
return this.prepareOutputData(items); return [returnData];
} }
} }

View file

@ -115,32 +115,48 @@ export class Set implements INodeType {
items.push({json: {}}); items.push({json: {}});
} }
const returnData: INodeExecutionData[] = [];
let item: INodeExecutionData; let item: INodeExecutionData;
let keepOnlySet: boolean; let keepOnlySet: boolean;
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) { for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
keepOnlySet = this.getNodeParameter('keepOnlySet', itemIndex, []) as boolean; keepOnlySet = this.getNodeParameter('keepOnlySet', itemIndex, []) as boolean;
item = items[itemIndex]; item = items[itemIndex];
if (keepOnlySet === true) { const newItem: INodeExecutionData = {
item.json = {}; json: {},
};
if (keepOnlySet !== true) {
if (item.binary !== undefined) {
// Create a shallow copy of the binary data so that the old
// data references which do not get changed still stay behind
// but the incoming data does not get changed.
newItem.binary = {};
Object.assign(newItem.binary, item.binary);
}
newItem.json = JSON.parse(JSON.stringify(item.json));
} }
// Add boolean values // Add boolean values
(this.getNodeParameter('values.boolean', itemIndex, []) as INodeParameters[]).forEach((setItem) => { (this.getNodeParameter('values.boolean', itemIndex, []) as INodeParameters[]).forEach((setItem) => {
set(item.json, setItem.name as string, !!setItem.value); set(newItem.json, setItem.name as string, !!setItem.value);
}); });
// Add number values // Add number values
(this.getNodeParameter('values.number', itemIndex, []) as INodeParameters[]).forEach((setItem) => { (this.getNodeParameter('values.number', itemIndex, []) as INodeParameters[]).forEach((setItem) => {
set(item.json, setItem.name as string, setItem.value); set(newItem.json, setItem.name as string, setItem.value);
}); });
// Add string values // Add string values
(this.getNodeParameter('values.string', itemIndex, []) as INodeParameters[]).forEach((setItem) => { (this.getNodeParameter('values.string', itemIndex, []) as INodeParameters[]).forEach((setItem) => {
set(item.json, setItem.name as string, setItem.value); set(newItem.json, setItem.name as string, setItem.value);
}); });
returnData.push(newItem);
} }
return this.prepareOutputData(items); return this.prepareOutputData(returnData);
} }
} }

View file

@ -35,7 +35,9 @@ export class SplitInBatches implements INodeType {
}; };
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][] | null> { async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][] | null> {
const items = this.getInputData(); // Get the input data and create a new array so that we can remove
// items without a problem
const items = this.getInputData().slice();
const nodeContext = this.getContext('node'); const nodeContext = this.getContext('node');

View file

@ -70,13 +70,22 @@ export class WriteBinaryFile implements INodeType {
// Write the file to disk // Write the file to disk
await fsWriteFileAsync(fileName, Buffer.from(item.binary[dataPropertyName].data, BINARY_ENCODING), 'binary'); await fsWriteFileAsync(fileName, Buffer.from(item.binary[dataPropertyName].data, BINARY_ENCODING), 'binary');
if (item.json === undefined) { const newItem: INodeExecutionData = {
item.json = {}; json: {},
};
Object.assign(newItem.json, item.json);
if (item.binary !== undefined) {
// Create a shallow copy of the binary data so that the old
// data references which do not get changed still stay behind
// but the incoming data does not get changed.
newItem.binary = {};
Object.assign(newItem.binary, item.binary);
} }
// Add the file name to data // Add the file name to data
(item.json as IDataObject).fileName = fileName; (newItem.json as IDataObject).fileName = fileName;
return item; return newItem;
} }
} }

View file

@ -1045,8 +1045,6 @@ export class Workflow {
const returnPromises: Array<Promise<INodeExecutionData>> = []; const returnPromises: Array<Promise<INodeExecutionData>> = [];
for (let itemIndex = 0; itemIndex < connectionInputData.length; itemIndex++) { for (let itemIndex = 0; itemIndex < connectionInputData.length; itemIndex++) {
// executionData = connectionInputData[itemIndex];
// const thisArgs = NodeExecuteFunctions.getExecuteSingleFunctions(this, runData, connectionInputData, inputData, node, itemIndex);
const thisArgs = nodeExecuteFunctions.getExecuteSingleFunctions(this, runExecutionData, runIndex, connectionInputData, inputData, node, itemIndex, additionalData, mode); const thisArgs = nodeExecuteFunctions.getExecuteSingleFunctions(this, runExecutionData, runIndex, connectionInputData, inputData, node, itemIndex, additionalData, mode);
returnPromises.push(nodeType.executeSingle!.call(thisArgs)); returnPromises.push(nodeType.executeSingle!.call(thisArgs));