mirror of
https://github.com/n8n-io/n8n.git
synced 2025-02-02 07:01:30 -08:00
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:
parent
e597fbc78f
commit
d18cba37a4
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
import { testWorkflows, getWorkflowFilenames } from '@test/nodes/Helpers';
|
||||
|
||||
const workflows = getWorkflowFilenames(__dirname);
|
||||
|
||||
describe('Test ConvertToFile Node', () => testWorkflows(workflows));
|
|
@ -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": []
|
||||
}
|
|
@ -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', {
|
||||
|
|
Loading…
Reference in a new issue