diff --git a/packages/editor-ui/src/main.ts b/packages/editor-ui/src/main.ts index 1784368ded..9cfa172e33 100644 --- a/packages/editor-ui/src/main.ts +++ b/packages/editor-ui/src/main.ts @@ -40,6 +40,7 @@ import { faEye, faExclamationTriangle, faExternalLinkAlt, + faExchangeAlt, faFile, faFileCode, faFileDownload, @@ -111,6 +112,7 @@ library.add(faEnvelope); library.add(faEye); library.add(faExclamationTriangle); library.add(faExternalLinkAlt); +library.add(faExchangeAlt); library.add(faFile); library.add(faFileCode); library.add(faFileDownload); diff --git a/packages/nodes-base/nodes/MoveBinaryData.node.ts b/packages/nodes-base/nodes/MoveBinaryData.node.ts new file mode 100644 index 0000000000..3fb9c936d8 --- /dev/null +++ b/packages/nodes-base/nodes/MoveBinaryData.node.ts @@ -0,0 +1,320 @@ +import { + get, + set, + unset, +} from 'lodash'; + +import { IExecuteFunctions } from 'n8n-core'; +import { + IDataObject, + INodeExecutionData, + INodeType, + INodeTypeDescription, +} from 'n8n-workflow'; + + +export class MoveBinaryData implements INodeType { + description: INodeTypeDescription = { + displayName: 'Move Binary Data', + name: 'moveBinaryData', + icon: 'fa:exchange-alt', + group: ['transform'], + version: 1, + subtitle: '={{$parameter["mode"]==="binaryToJson" ? "Binary to JSON" : "JSON to Binary"}}', + description: 'Move data between binary and JSON properties.', + defaults: { + name: 'Move Binary Data', + color: '#7722CC', + }, + inputs: ['main'], + outputs: ['main'], + properties: [ + { + displayName: 'Mode', + name: 'mode', + type: 'options', + options: [ + { + name: 'Binary to JSON', + value: 'binaryToJson', + description: 'Move data from Binary to JSON', + }, + { + name: 'JSON to Binary', + value: 'jsonToBinary', + description: 'Move data from JSON to Binary.', + }, + ], + default: 'binaryToJson', + description: 'From and to where data should be moved.', + }, + + + // ---------------------------------- + // binaryToJson + // ---------------------------------- + { + displayName: 'Set all Data', + name: 'setAllData', + type: 'boolean', + displayOptions: { + show: { + mode: [ + 'binaryToJson', + ], + }, + }, + default: true, + description: 'If all JSON data should be replaced with the data retrieved
from binary key. Else the data will be written to a single key.', + }, + { + displayName: 'Source Key', + name: 'sourceKey', + type: 'string', + displayOptions: { + show: { + mode: [ + 'binaryToJson', + ], + }, + }, + default: 'data', + required: true, + placeholder: 'data', + description: 'The name of the binary key to get data from.
It is also possible to define deep keys by using dot-notation like for example:
"level1.level2.currentKey"', + }, + { + displayName: 'Destination Key', + name: 'destinationKey', + type: 'string', + displayOptions: { + show: { + mode: [ + 'binaryToJson', + ], + setAllData: [ + false, + ], + }, + }, + default: 'data', + required: true, + placeholder: '', + description: 'The name the JSON key to copy data to. It is also possible
to define deep keys by using dot-notation like for example:
"level1.level2.newKey"', + }, + + // ---------------------------------- + // jsonToBinary + // ---------------------------------- + { + displayName: 'Convert all Data', + name: 'convertAllData', + type: 'boolean', + displayOptions: { + show: { + mode: [ + 'jsonToBinary', + ], + }, + }, + default: true, + description: 'If all JSON data should be converted to binary.
Else only the data of one key will be converted.', + }, + { + displayName: 'Source Key', + name: 'sourceKey', + type: 'string', + displayOptions: { + show: { + convertAllData: [ + false, + ], + mode: [ + 'jsonToBinary', + ], + }, + }, + default: 'data', + required: true, + placeholder: 'data', + description: 'The name of the JSON key to get data from. It is also possible
to define deep keys by using dot-notation like for example:
"level1.level2.currentKey"', + }, + { + displayName: 'Destination Key', + name: 'destinationKey', + type: 'string', + displayOptions: { + show: { + mode: [ + 'jsonToBinary', + ], + }, + }, + default: 'data', + required: true, + placeholder: 'data', + description: 'The name the binary key to copy data to. It is also possible
to define deep keys by using dot-notation like for example:
"level1.level2.newKey"', + }, + + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + options: [ + { + displayName: 'Keep Source', + name: 'keepSource', + type: 'boolean', + default: false, + description: 'If the source key should be kept. By default does it get deleted.', + }, + { + displayName: 'Mime Type', + name: 'mimeType', + type: 'string', + displayOptions: { + show: { + '/mode': [ + 'jsonToBinary', + ], + }, + }, + default: 'application/json', + placeholder: 'application/json', + description: 'The mime-type to set. By default will the mime-type for JSON be set.', + }, + { + displayName: 'Use Raw Data', + name: 'useRawData', + type: 'boolean', + displayOptions: { + show: { + '/mode': [ + 'jsonToBinary', + ], + }, + }, + default: false, + description: 'Use data as is and do not JSON.stringify it.', + }, + ], + } + ], + }; + + async execute(this: IExecuteFunctions): Promise { + + const items = this.getInputData(); + + const mode = this.getNodeParameter('mode', 0) as string; + + const returnData: INodeExecutionData[] = []; + + let item: INodeExecutionData; + let newItem: INodeExecutionData; + let options: IDataObject; + for (let itemIndex = 0; itemIndex < items.length; itemIndex++) { + item = items[itemIndex]; + options = this.getNodeParameter('options', 0, {}) as IDataObject; + + // Copy the whole JSON data as data on any level can be renamed + newItem = { + json: {}, + }; + + if (mode === 'binaryToJson') { + const setAllData = this.getNodeParameter('setAllData', itemIndex) as boolean; + const sourceKey = this.getNodeParameter('sourceKey', itemIndex) as string; + + const value = get(item.binary, sourceKey); + + if (value === undefined) { + // No data found so skip + continue; + } + + const convertedValue = JSON.parse(new Buffer(value.data, 'base64').toString('ascii')); + + if (setAllData === true) { + // Set the full data + newItem.json = convertedValue; + } else { + // Does get added to existing data so copy it first + newItem.json = JSON.parse(JSON.stringify(item.json)); + + const destinationKey = this.getNodeParameter('destinationKey', itemIndex, '') as string; + set(newItem.json, destinationKey, convertedValue); + } + + if (options.keepSource === true) { + // Binary data does not get touched so simply reference it + newItem.binary = item.binary; + } else { + // Binary data will change so copy it + newItem.binary = JSON.parse(JSON.stringify(item.binary)); + unset(newItem.binary, sourceKey); + } + + } else if (mode === 'jsonToBinary') { + const convertAllData = this.getNodeParameter('convertAllData', itemIndex) as boolean; + const destinationKey = this.getNodeParameter('destinationKey', itemIndex) as string; + + let value: IDataObject | string = item.json; + if (convertAllData === false) { + const sourceKey = this.getNodeParameter('sourceKey', itemIndex) as string; + value = get(item.json, sourceKey) as IDataObject; + } + + if (value === undefined) { + // No data found so skip + continue; + } + + if (item.binary !== undefined) { + // Item already has binary data so copy it + newItem.binary = JSON.parse(JSON.stringify(item.binary)); + } else { + // Item does not have binary data yet so initialize empty + newItem.binary = {}; + } + + if (options.useRawData !== true) { + value = JSON.stringify(value); + } + + const convertedValue = { + data: new Buffer(value as string).toString('base64'), + mimeType: options.mimeType || 'application/json', + }; + set(newItem.binary!, destinationKey, convertedValue); + + if (options.keepSource === true) { + // JSON data does not get touched so simply reference it + newItem.json = item.json; + } else { + // JSON data will change so copy it + + if (convertAllData === true) { + // Data should not be kept and all data got converted. So simply set new as empty + newItem.json = {}; + } else { + // Data should not be kept and only one key has to get removed. So copy all + // data and then remove the not needed one + newItem.json = JSON.parse(JSON.stringify(item.json)); + const sourceKey = this.getNodeParameter('sourceKey', itemIndex) as string; + + unset(newItem.json, sourceKey); + } + } + } else { + throw new Error(`The operation "${mode}" is not known!`); + } + + returnData.push(newItem); + } + + return [returnData]; + } +} diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 4adb05ac46..3ab0854948 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -89,6 +89,7 @@ "dist/nodes/Mailgun/Mailgun.node.js", "dist/nodes/Mattermost/Mattermost.node.js", "dist/nodes/Merge.node.js", + "dist/nodes/MoveBinaryData.node", "dist/nodes/MongoDb/MongoDb.node.js", "dist/nodes/NextCloud/NextCloud.node.js", "dist/nodes/NoOp.node.js",