feat(Convert to File Node): Operation to convert a string in a plain text file, option to format JSON when creating file (#8620)

This commit is contained in:
Michael Kret 2024-02-13 16:52:37 +02:00 committed by GitHub
parent e597fbc78f
commit d18cba37a4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 530 additions and 3 deletions

View file

@ -7,6 +7,7 @@ import type {
import * as spreadsheet from './actions/spreadsheet.operation';
import * as toBinary from './actions/toBinary.operation';
import * as toText from './actions/toText.operation';
import * as toJson from './actions/toJson.operation';
import * as iCall from './actions/iCall.operation';
@ -17,7 +18,7 @@ export class ConvertToFile implements INodeType {
name: 'convertToFile',
icon: 'file:convertToFile.svg',
group: ['input'],
version: 1,
version: [1, 1.1],
description: 'Convert JSON data to binary data',
defaults: {
name: 'Convert to File',
@ -67,6 +68,12 @@ export class ConvertToFile implements INodeType {
action: 'Convert to RTF',
description: 'Transform input data into a table in an RTF file',
},
{
name: 'Convert to Text File',
value: 'toText',
action: 'Convert to text file',
description: 'Transform input data string into a file',
},
{
name: 'Convert to XLS',
value: 'xls',
@ -90,6 +97,7 @@ export class ConvertToFile implements INodeType {
},
...spreadsheet.description,
...toBinary.description,
...toText.description,
...toJson.description,
...iCall.description,
],
@ -112,6 +120,10 @@ export class ConvertToFile implements INodeType {
returnData = await toBinary.execute.call(this, items);
}
if (operation === 'toText') {
returnData = await toText.execute.call(this, items);
}
if (operation === 'iCal') {
returnData = await iCall.execute.call(this, items);
}

View file

@ -54,6 +54,11 @@ export const properties: INodeProperties[] = [
type: 'boolean',
default: true,
description: 'Whether the data is already base64 encoded',
displayOptions: {
show: {
'@version': [1],
},
},
},
{
displayName: 'Encoding',
@ -65,6 +70,7 @@ export const properties: INodeProperties[] = [
displayOptions: {
hide: {
dataIsBase64: [true],
'@version': [{ _cnd: { gt: 1 } }],
},
},
},
@ -100,17 +106,24 @@ export const description = updateDisplayOptions(displayOptions, properties);
export async function execute(this: IExecuteFunctions, items: INodeExecutionData[]) {
const returnData: INodeExecutionData[] = [];
const nodeVersion = this.getNode().typeVersion;
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;
let dataIsBase64 = true;
if (nodeVersion === 1) {
dataIsBase64 = options.dataIsBase64 !== false;
}
const jsonToBinaryOptions: JsonToBinaryOptions = {
sourceKey: sourceProperty,
fileName: options.fileName as string,
mimeType: options.mimeType as string,
dataIsBase64: options.dataIsBase64 !== false,
dataIsBase64,
encoding: options.encoding as string,
addBOM: options.addBOM as boolean,
itemIndex: i,

View file

@ -52,6 +52,13 @@ export const properties: INodeProperties[] = [
},
},
},
{
displayName: 'Format',
name: 'format',
type: 'boolean',
default: false,
description: 'Whether to format the JSON data for easier reading',
},
{
displayName: 'Encoding',
name: 'encoding',
@ -98,6 +105,7 @@ export async function execute(this: IExecuteFunctions, items: INodeExecutionData
mimeType: 'application/json',
encoding: options.encoding as string,
addBOM: options.addBOM as boolean,
format: options.format as boolean,
},
);
@ -131,6 +139,7 @@ export async function execute(this: IExecuteFunctions, items: INodeExecutionData
fileName: options.fileName as string,
encoding: options.encoding as string,
addBOM: options.addBOM as boolean,
format: options.format as boolean,
mimeType: 'application/json',
itemIndex: i,
});

View file

@ -0,0 +1,126 @@
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: 'Text Input Field',
name: 'sourceProperty',
type: 'string',
default: '',
required: true,
placeholder: 'e.g data',
requiresDataPath: 'single',
description:
"The name of the input field that contains a 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: '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',
description: 'Name of the output file',
},
],
},
];
const displayOptions = {
show: {
operation: ['toText'],
},
};
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) || 'file.txt',
mimeType: 'text/plain',
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;
}

View file

@ -0,0 +1,5 @@
import { testWorkflows, getWorkflowFilenames } from '@test/nodes/Helpers';
const workflows = getWorkflowFilenames(__dirname);
describe('Test ConvertToFile Node', () => testWorkflows(workflows));

View file

@ -0,0 +1,357 @@
{
"name": "My workflow 2",
"nodes": [
{
"parameters": {},
"id": "59f5ae0f-52f7-4bc8-b325-29d2b0d810f8",
"name": "When clicking \"Test workflow\"",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
460,
500
]
},
{
"parameters": {
"operation": "toText",
"sourceProperty": "notes",
"options": {}
},
"id": "add99ca3-7bd3-4561-a654-fac4b8ded285",
"name": "Convert to File",
"type": "n8n-nodes-base.convertToFile",
"typeVersion": 1,
"position": [
940,
400
]
},
{
"parameters": {
"operation": "toJson",
"options": {
"format": true
}
},
"id": "89498c96-f1a0-49ec-890d-79f12c5554e6",
"name": "Convert to File1",
"type": "n8n-nodes-base.convertToFile",
"typeVersion": 1.1,
"position": [
940,
580
]
},
{
"parameters": {
"operation": "toBinary",
"sourceProperty": "base64",
"options": {}
},
"id": "ae06c883-f2af-4d25-bc64-4f0ecad53c85",
"name": "Convert to File2",
"type": "n8n-nodes-base.convertToFile",
"typeVersion": 1.1,
"position": [
940,
200
]
},
{
"parameters": {
"jsCode": "return {\n \"id\": \"23423532\",\n \"name\": \"Jay Gatsby\",\n \"email\": \"gatsby@west-egg.com\",\n \"notes\": \"Keeps asking about a green light??\",\n \"country\": \"US\",\n \"created\": \"1925-04-10\",\n \"base64\": \"VGhpcyBpcyBzb21lIHRleHQgZW5jb2RlZCBhcyBiYXNlNjQ=\"\n }"
},
"id": "b14b18b0-6570-4376-85cf-f3cc74835e58",
"name": "Code",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
680,
500
]
},
{
"parameters": {
"operation": "text",
"options": {}
},
"id": "76373e3f-e103-465a-8b15-dd643915c532",
"name": "Extract From File",
"type": "n8n-nodes-base.extractFromFile",
"typeVersion": 1,
"position": [
1160,
200
]
},
{
"parameters": {
"operation": "text",
"options": {}
},
"id": "d8ba3980-873d-47d7-ad88-f5ff6c66774c",
"name": "Extract From File1",
"type": "n8n-nodes-base.extractFromFile",
"typeVersion": 1,
"position": [
1160,
400
]
},
{
"parameters": {
"operation": "text",
"options": {}
},
"id": "34838f1e-aee5-4b17-a9ec-bd9e09789045",
"name": "Extract From File2",
"type": "n8n-nodes-base.extractFromFile",
"typeVersion": 1,
"position": [
1160,
580
]
},
{
"parameters": {
"operation": "toJson",
"options": {}
},
"id": "a6617075-83f4-4157-9d07-7e5df0cbd9b6",
"name": "Convert to File3",
"type": "n8n-nodes-base.convertToFile",
"typeVersion": 1.1,
"position": [
960,
780
]
},
{
"parameters": {
"operation": "text",
"options": {}
},
"id": "b2deb5a4-0f7a-4a1f-858f-17235db0b94e",
"name": "Extract From File3",
"type": "n8n-nodes-base.extractFromFile",
"typeVersion": 1,
"position": [
1160,
780
]
},
{
"parameters": {},
"id": "11022c53-136b-44a0-af32-faac16e2fa89",
"name": "No Operation, do nothing",
"type": "n8n-nodes-base.noOp",
"typeVersion": 1,
"position": [
1380,
200
]
},
{
"parameters": {},
"id": "f5ec42e5-8088-4a93-91ea-3a1cb4997eee",
"name": "No Operation, do nothing1",
"type": "n8n-nodes-base.noOp",
"typeVersion": 1,
"position": [
1380,
400
]
},
{
"parameters": {},
"id": "d7106de2-455f-428f-bdfa-fe701136bdfa",
"name": "No Operation, do nothing2",
"type": "n8n-nodes-base.noOp",
"typeVersion": 1,
"position": [
1380,
580
]
},
{
"parameters": {},
"id": "14dbc74c-fc0b-4339-88ea-76b3e9534de0",
"name": "No Operation, do nothing3",
"type": "n8n-nodes-base.noOp",
"typeVersion": 1,
"position": [
1380,
780
]
}
],
"pinData": {
"No Operation, do nothing": [
{
"json": {
"data": "This is some text encoded as base64"
}
}
],
"No Operation, do nothing1": [
{
"json": {
"data": "Keeps asking about a green light??"
}
}
],
"No Operation, do nothing2": [
{
"json": {
"data": "[\n {\n \"id\": \"23423532\",\n \"name\": \"Jay Gatsby\",\n \"email\": \"gatsby@west-egg.com\",\n \"notes\": \"Keeps asking about a green light??\",\n \"country\": \"US\",\n \"created\": \"1925-04-10\",\n \"base64\": \"VGhpcyBpcyBzb21lIHRleHQgZW5jb2RlZCBhcyBiYXNlNjQ=\"\n }\n]"
}
}
],
"No Operation, do nothing3": [
{
"json": {
"data": "[{\"id\":\"23423532\",\"name\":\"Jay Gatsby\",\"email\":\"gatsby@west-egg.com\",\"notes\":\"Keeps asking about a green light??\",\"country\":\"US\",\"created\":\"1925-04-10\",\"base64\":\"VGhpcyBpcyBzb21lIHRleHQgZW5jb2RlZCBhcyBiYXNlNjQ=\"}]"
}
}
]
},
"connections": {
"When clicking \"Test workflow\"": {
"main": [
[
{
"node": "Code",
"type": "main",
"index": 0
}
]
]
},
"Code": {
"main": [
[
{
"node": "Convert to File2",
"type": "main",
"index": 0
},
{
"node": "Convert to File",
"type": "main",
"index": 0
},
{
"node": "Convert to File1",
"type": "main",
"index": 0
},
{
"node": "Convert to File3",
"type": "main",
"index": 0
}
]
]
},
"Convert to File2": {
"main": [
[
{
"node": "Extract From File",
"type": "main",
"index": 0
}
]
]
},
"Convert to File": {
"main": [
[
{
"node": "Extract From File1",
"type": "main",
"index": 0
}
]
]
},
"Convert to File1": {
"main": [
[
{
"node": "Extract From File2",
"type": "main",
"index": 0
}
]
]
},
"Convert to File3": {
"main": [
[
{
"node": "Extract From File3",
"type": "main",
"index": 0
}
]
]
},
"Extract From File": {
"main": [
[
{
"node": "No Operation, do nothing",
"type": "main",
"index": 0
}
]
]
},
"Extract From File3": {
"main": [
[
{
"node": "No Operation, do nothing3",
"type": "main",
"index": 0
}
]
]
},
"Extract From File2": {
"main": [
[
{
"node": "No Operation, do nothing2",
"type": "main",
"index": 0
}
]
]
},
"Extract From File1": {
"main": [
[
{
"node": "No Operation, do nothing1",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "6555e00a-2c20-46f2-9a2c-fb368d557035",
"meta": {
"templateCredsSetupCompleted": true,
"instanceId": "be251a83c052a9862eeac953816fbb1464f89dfbf79d7ac490a8e336a8cc8bfd"
},
"id": "RSOCg1c3be66ZqVH",
"tags": []
}

View file

@ -26,6 +26,7 @@ export type JsonToBinaryOptions = {
mimeType?: string;
dataIsBase64?: boolean;
itemIndex?: number;
format?: boolean;
};
type PdfDocument = Awaited<ReturnType<Awaited<typeof readPDF>>['promise']>;
@ -102,7 +103,11 @@ export async function createBinaryFromJson(
if (typeof value === 'object') {
options.mimeType = 'application/json';
valueAsString = JSON.stringify(value);
if (options.format) {
valueAsString = JSON.stringify(value, null, 2);
} else {
valueAsString = JSON.stringify(value);
}
}
buffer = iconv.encode(valueAsString, options.encoding || 'utf8', {