mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
feat(core): Improvements/overhaul for nodes working with binary data (#7651)
Github issue / Community forum post (link here to close automatically): --------- Co-authored-by: Giulio Andreini <andreini@netseven.it> Co-authored-by: Marcus <marcus@n8n.io>
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"node": "n8n-nodes-base.convertToFile",
|
||||
"nodeVersion": "1.0",
|
||||
"codexVersion": "1.0",
|
||||
"categories": ["Core Nodes"],
|
||||
"resources": {
|
||||
"primaryDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.converttofile/"
|
||||
}
|
||||
]
|
||||
},
|
||||
"alias": [
|
||||
"CSV",
|
||||
"Spreadsheet",
|
||||
"Excel",
|
||||
"xls",
|
||||
"xlsx",
|
||||
"ods",
|
||||
"tabular",
|
||||
"encode",
|
||||
"encoding",
|
||||
"Move Binary Data",
|
||||
"Binary",
|
||||
"File",
|
||||
"JSON",
|
||||
"HTML",
|
||||
"ICS",
|
||||
"RTF",
|
||||
"64"
|
||||
],
|
||||
"subcategories": {
|
||||
"Core Nodes": ["Files", "Data Transformation"]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
import type {
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import * as spreadsheet from './actions/spreadsheet.operation';
|
||||
import * as toBinary from './actions/toBinary.operation';
|
||||
import * as toJson from './actions/toJson.operation';
|
||||
import * as iCall from './actions/iCall.operation';
|
||||
|
||||
export class ConvertToFile implements INodeType {
|
||||
// eslint-disable-next-line n8n-nodes-base/node-class-description-missing-subtitle
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'Convert to File',
|
||||
name: 'convertToFile',
|
||||
icon: 'file:convertToFile.svg',
|
||||
group: ['input'],
|
||||
version: 1,
|
||||
description: 'Convert JSON data to binary data',
|
||||
defaults: {
|
||||
name: 'Convert to File',
|
||||
},
|
||||
inputs: ['main'],
|
||||
outputs: ['main'],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
options: [
|
||||
{
|
||||
name: 'Convert to CSV',
|
||||
value: 'csv',
|
||||
action: 'Convert to CSV',
|
||||
description: 'Transform input data into a CSV file',
|
||||
},
|
||||
{
|
||||
name: 'Convert to HTML',
|
||||
value: 'html',
|
||||
action: 'Convert to HTML',
|
||||
description: 'Transform input data into a table in an HTML file',
|
||||
},
|
||||
{
|
||||
name: 'Convert to iCal',
|
||||
value: 'iCal',
|
||||
action: 'Convert to iCal',
|
||||
description: 'Converts each input item to an ICS event file',
|
||||
},
|
||||
{
|
||||
name: 'Convert to JSON',
|
||||
value: 'toJson',
|
||||
action: 'Convert to JSON',
|
||||
description: 'Transform input data into a single or multiple JSON files',
|
||||
},
|
||||
{
|
||||
name: 'Convert to ODS',
|
||||
value: 'ods',
|
||||
action: 'Convert to ODS',
|
||||
description: 'Transform input data into an ODS file',
|
||||
},
|
||||
{
|
||||
name: 'Convert to RTF',
|
||||
value: 'rtf',
|
||||
action: 'Convert to RTF',
|
||||
description: 'Transform input data into a table in an RTF file',
|
||||
},
|
||||
{
|
||||
name: 'Convert to XLS',
|
||||
value: 'xls',
|
||||
action: 'Convert to XLS',
|
||||
description: 'Transform input data into an Excel file',
|
||||
},
|
||||
{
|
||||
name: 'Convert to XLSX',
|
||||
value: 'xlsx',
|
||||
action: 'Convert to XLSX',
|
||||
description: 'Transform input data into an Excel file',
|
||||
},
|
||||
{
|
||||
name: 'Move Base64 String to File',
|
||||
value: 'toBinary',
|
||||
action: 'Move base64 string to file',
|
||||
description: 'Convert a base64-encoded string into its original file format',
|
||||
},
|
||||
],
|
||||
default: 'csv',
|
||||
},
|
||||
...spreadsheet.description,
|
||||
...toBinary.description,
|
||||
...toJson.description,
|
||||
...iCall.description,
|
||||
],
|
||||
};
|
||||
|
||||
async execute(this: IExecuteFunctions) {
|
||||
const items = this.getInputData();
|
||||
const operation = this.getNodeParameter('operation', 0);
|
||||
let returnData: INodeExecutionData[] = [];
|
||||
|
||||
if (spreadsheet.operations.includes(operation)) {
|
||||
returnData = await spreadsheet.execute.call(this, items, operation);
|
||||
}
|
||||
|
||||
if (operation === 'toJson') {
|
||||
returnData = await toJson.execute.call(this, items);
|
||||
}
|
||||
|
||||
if (operation === 'toBinary') {
|
||||
returnData = await toBinary.execute.call(this, items);
|
||||
}
|
||||
|
||||
if (operation === 'iCal') {
|
||||
returnData = await iCall.execute.call(this, items);
|
||||
}
|
||||
|
||||
return [returnData];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import type { IExecuteFunctions, INodeExecutionData, INodeProperties } from 'n8n-workflow';
|
||||
|
||||
import * as createEvent from '../../../ICalendar/createEvent.operation';
|
||||
|
||||
import { updateDisplayOptions } from '@utils/utilities';
|
||||
|
||||
export const description: INodeProperties[] = updateDisplayOptions(
|
||||
{
|
||||
show: {
|
||||
operation: ['iCal'],
|
||||
},
|
||||
},
|
||||
createEvent.description,
|
||||
);
|
||||
|
||||
export async function execute(this: IExecuteFunctions, items: INodeExecutionData[]) {
|
||||
const returnData = await createEvent.execute.call(this, items);
|
||||
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
import {
|
||||
NodeOperationError,
|
||||
type IExecuteFunctions,
|
||||
type INodeExecutionData,
|
||||
type INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { generatePairedItemData, updateDisplayOptions } from '@utils/utilities';
|
||||
import type { JsonToSpreadsheetBinaryOptions, JsonToSpreadsheetBinaryFormat } from '@utils/binary';
|
||||
|
||||
import { convertJsonToSpreadsheetBinary } from '@utils/binary';
|
||||
|
||||
export const operations = ['csv', 'html', 'rtf', 'ods', 'xls', 'xlsx'];
|
||||
|
||||
export const properties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Put Output File in Field',
|
||||
name: 'binaryPropertyName',
|
||||
type: 'string',
|
||||
default: 'data',
|
||||
required: true,
|
||||
placeholder: 'e.g data',
|
||||
hint: 'The name of the output binary field to put the file in',
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Option',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Compression',
|
||||
name: 'compression',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/operation': ['xlsx', 'ods'],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'Whether to reduce the output file size',
|
||||
},
|
||||
{
|
||||
displayName: 'File Name',
|
||||
name: 'fileName',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Name of the output file',
|
||||
},
|
||||
{
|
||||
displayName: 'Header Row',
|
||||
name: 'headerRow',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description: 'Whether the first row of the file contains the header names',
|
||||
},
|
||||
{
|
||||
displayName: 'Sheet Name',
|
||||
name: 'sheetName',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/operation': ['ods', 'xls', 'xlsx'],
|
||||
},
|
||||
},
|
||||
default: 'Sheet',
|
||||
description: 'Name of the sheet to create in the spreadsheet',
|
||||
placeholder: 'e.g. mySheet',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
operation: operations,
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
items: INodeExecutionData[],
|
||||
operation: string,
|
||||
) {
|
||||
let returnData: INodeExecutionData[] = [];
|
||||
|
||||
const pairedItem = generatePairedItemData(items.length);
|
||||
try {
|
||||
const options = this.getNodeParameter('options', 0, {}) as JsonToSpreadsheetBinaryOptions;
|
||||
const binaryPropertyName = this.getNodeParameter('binaryPropertyName', 0, 'data');
|
||||
|
||||
const binaryData = await convertJsonToSpreadsheetBinary.call(
|
||||
this,
|
||||
items,
|
||||
operation as JsonToSpreadsheetBinaryFormat,
|
||||
options,
|
||||
'File',
|
||||
);
|
||||
|
||||
const newItem: INodeExecutionData = {
|
||||
json: {},
|
||||
binary: {
|
||||
[binaryPropertyName]: binaryData,
|
||||
},
|
||||
pairedItem,
|
||||
};
|
||||
|
||||
returnData = [newItem];
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
returnData.push({
|
||||
json: {
|
||||
error: error.message,
|
||||
},
|
||||
pairedItem,
|
||||
});
|
||||
} else {
|
||||
throw new NodeOperationError(this.getNode(), error);
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
import type { IExecuteFunctions, INodeExecutionData, INodeProperties } from 'n8n-workflow';
|
||||
|
||||
import { NodeOperationError } from 'n8n-workflow';
|
||||
|
||||
import type { JsonToBinaryOptions } from '@utils/binary';
|
||||
import { createBinaryFromJson } from '@utils/binary';
|
||||
import { encodeDecodeOptions } from '@utils/descriptions';
|
||||
import { updateDisplayOptions } from '@utils/utilities';
|
||||
|
||||
export const properties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Base64 Input Field',
|
||||
name: 'sourceProperty',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
placeholder: 'e.g data',
|
||||
requiresDataPath: 'single',
|
||||
description:
|
||||
"The name of the input field that contains the base64 string to convert to a file. Use dot-notation for deep fields (e.g. 'level1.level2.currentKey').",
|
||||
},
|
||||
{
|
||||
displayName: 'Put Output File in Field',
|
||||
name: 'binaryPropertyName',
|
||||
type: 'string',
|
||||
default: 'data',
|
||||
required: true,
|
||||
placeholder: 'e.g data',
|
||||
hint: 'The name of the output binary field to put the file in',
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Option',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Add Byte Order Mark (BOM)',
|
||||
description:
|
||||
'Whether to add special marker at the start of your text file. This marker helps some programs understand how to read the file correctly.',
|
||||
name: 'addBOM',
|
||||
displayOptions: {
|
||||
show: {
|
||||
encoding: ['utf8', 'cesu8', 'ucs2'],
|
||||
},
|
||||
},
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
displayName: 'Data Is Base64',
|
||||
name: 'dataIsBase64',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description: 'Whether the data is already base64 encoded',
|
||||
},
|
||||
{
|
||||
displayName: 'Encoding',
|
||||
name: 'encoding',
|
||||
type: 'options',
|
||||
options: encodeDecodeOptions,
|
||||
default: 'utf8',
|
||||
description: 'Choose the character set to use to encode the data',
|
||||
displayOptions: {
|
||||
hide: {
|
||||
dataIsBase64: [true],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'File Name',
|
||||
name: 'fileName',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'e.g. myFile',
|
||||
description: 'Name of the output file',
|
||||
},
|
||||
{
|
||||
displayName: 'MIME Type',
|
||||
name: 'mimeType',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'e.g text/plain',
|
||||
description:
|
||||
'The MIME type of the output file. <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types" target="_blank">Common MIME types</a>.',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
operation: ['toBinary'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(this: IExecuteFunctions, items: INodeExecutionData[]) {
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
try {
|
||||
const options = this.getNodeParameter('options', i, {});
|
||||
const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i, 'data');
|
||||
const sourceProperty = this.getNodeParameter('sourceProperty', i) as string;
|
||||
|
||||
const jsonToBinaryOptions: JsonToBinaryOptions = {
|
||||
sourceKey: sourceProperty,
|
||||
fileName: options.fileName as string,
|
||||
mimeType: options.mimeType as string,
|
||||
dataIsBase64: options.dataIsBase64 !== false,
|
||||
encoding: options.encoding as string,
|
||||
addBOM: options.addBOM as boolean,
|
||||
itemIndex: i,
|
||||
};
|
||||
|
||||
const binaryData = await createBinaryFromJson.call(this, items[i].json, jsonToBinaryOptions);
|
||||
|
||||
const newItem: INodeExecutionData = {
|
||||
json: {},
|
||||
binary: {
|
||||
[binaryPropertyName]: binaryData,
|
||||
},
|
||||
pairedItem: { item: i },
|
||||
};
|
||||
|
||||
returnData.push(newItem);
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
returnData.push({
|
||||
json: {
|
||||
error: error.message,
|
||||
},
|
||||
pairedItem: {
|
||||
item: i,
|
||||
},
|
||||
});
|
||||
continue;
|
||||
}
|
||||
throw new NodeOperationError(this.getNode(), error, { itemIndex: i });
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
import type { IExecuteFunctions, INodeExecutionData, INodeProperties } from 'n8n-workflow';
|
||||
import { NodeOperationError } from 'n8n-workflow';
|
||||
|
||||
import { generatePairedItemData, updateDisplayOptions } from '@utils/utilities';
|
||||
import { createBinaryFromJson } from '@utils/binary';
|
||||
import { encodeDecodeOptions } from '@utils/descriptions';
|
||||
|
||||
export const properties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Mode',
|
||||
name: 'mode',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
options: [
|
||||
{
|
||||
name: 'All Items to One File',
|
||||
value: 'once',
|
||||
},
|
||||
{
|
||||
name: 'Each Item to Separate File',
|
||||
value: 'each',
|
||||
},
|
||||
],
|
||||
default: 'once',
|
||||
},
|
||||
{
|
||||
displayName: 'Put Output File in Field',
|
||||
name: 'binaryPropertyName',
|
||||
type: 'string',
|
||||
default: 'data',
|
||||
required: true,
|
||||
placeholder: 'e.g data',
|
||||
hint: 'The name of the output binary field to put the file in',
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Option',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Add Byte Order Mark (BOM)',
|
||||
name: 'addBOM',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description:
|
||||
'Whether to add special marker at the start of your text file. This marker helps some programs understand how to read the file correctly.',
|
||||
displayOptions: {
|
||||
show: {
|
||||
encoding: ['utf8', 'cesu8', 'ucs2'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Encoding',
|
||||
name: 'encoding',
|
||||
type: 'options',
|
||||
options: encodeDecodeOptions,
|
||||
default: 'utf8',
|
||||
description: 'Choose the character set to use to encode the data',
|
||||
},
|
||||
{
|
||||
displayName: 'File Name',
|
||||
name: 'fileName',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'e.g. myFile.json',
|
||||
description: 'Name of the output file',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
operation: ['toJson'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(this: IExecuteFunctions, items: INodeExecutionData[]) {
|
||||
let returnData: INodeExecutionData[] = [];
|
||||
|
||||
const mode = this.getNodeParameter('mode', 0, 'once') as string;
|
||||
if (mode === 'once') {
|
||||
const pairedItem = generatePairedItemData(items.length);
|
||||
try {
|
||||
const options = this.getNodeParameter('options', 0, {});
|
||||
const binaryPropertyName = this.getNodeParameter('binaryPropertyName', 0, 'data');
|
||||
|
||||
const binaryData = await createBinaryFromJson.call(
|
||||
this,
|
||||
items.map((item) => item.json),
|
||||
{
|
||||
fileName: options.fileName as string,
|
||||
mimeType: 'application/json',
|
||||
encoding: options.encoding as string,
|
||||
addBOM: options.addBOM as boolean,
|
||||
},
|
||||
);
|
||||
|
||||
const newItem: INodeExecutionData = {
|
||||
json: {},
|
||||
binary: {
|
||||
[binaryPropertyName]: binaryData,
|
||||
},
|
||||
pairedItem,
|
||||
};
|
||||
|
||||
returnData = [newItem];
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
returnData.push({
|
||||
json: {
|
||||
error: error.message,
|
||||
},
|
||||
pairedItem,
|
||||
});
|
||||
}
|
||||
throw new NodeOperationError(this.getNode(), error);
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
try {
|
||||
const options = this.getNodeParameter('options', i, {});
|
||||
const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i, 'data');
|
||||
|
||||
const binaryData = await createBinaryFromJson.call(this, items[i].json, {
|
||||
fileName: options.fileName as string,
|
||||
encoding: options.encoding as string,
|
||||
addBOM: options.addBOM as boolean,
|
||||
mimeType: 'application/json',
|
||||
itemIndex: i,
|
||||
});
|
||||
|
||||
const newItem: INodeExecutionData = {
|
||||
json: {},
|
||||
binary: {
|
||||
[binaryPropertyName]: binaryData,
|
||||
},
|
||||
pairedItem: { item: i },
|
||||
};
|
||||
|
||||
returnData.push(newItem);
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
returnData.push({
|
||||
json: {
|
||||
error: error.message,
|
||||
},
|
||||
pairedItem: {
|
||||
item: i,
|
||||
},
|
||||
});
|
||||
continue;
|
||||
}
|
||||
throw new NodeOperationError(this.getNode(), error, { itemIndex: i });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1147_463)">
|
||||
<path d="M170 39.8081C170 33.2867 175.287 28 181.808 28H338.41V189.59C338.41 196.218 343.782 201.59 350.41 201.59H512L512 472.893C512 479.415 506.713 484.701 500.192 484.701H181.808C177.972 484.701 174.564 482.873 172.407 480.039C175.619 477.958 178.665 475.515 181.488 472.708L271.488 383.208C282.057 372.697 288 358.406 288 343.5C288 328.594 282.057 314.303 271.488 303.792L181.488 214.292C177.969 210.793 174.103 207.858 170 205.487V39.8081Z" fill="#2244FF"/>
|
||||
<path d="M369.898 34C369.898 30.6863 372.584 28 375.898 28H378.564C381.7 28 384.708 29.2479 386.923 31.4684L508.551 153.386C510.76 155.6 512 158.599 512 161.726L512 164.102C512 167.416 509.314 170.102 506 170.102H375.898C372.584 170.102 369.898 167.416 369.898 164.102V34Z" fill="#2244FF"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M125.077 415.982C115.678 425.329 115.636 440.525 124.982 449.923C134.329 459.322 149.525 459.364 158.923 450.018L248.923 360.518C253.453 356.013 256 349.888 256 343.5C256 337.112 253.453 330.987 248.923 326.482L158.923 236.982C149.525 227.636 134.329 227.678 124.982 237.077C115.636 246.475 115.678 261.671 125.077 271.018L173.327 319L12 319C5.37257 319 -4.12516e-06 324.373 -4.41485e-06 331L-5.46392e-06 355C-5.75362e-06 361.627 5.37258 367 12 367L174.333 367L125.077 415.982Z" fill="#2244FF"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1147_463">
|
||||
<rect width="512" height="512" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
Reference in New Issue
Block a user